آخرین اخبار
همهٔ اخبار
گزارش‌ها و تحلیل‌ها گزارش بحرانیت ۱/۱۰

کالبدشکافی یک باگ پیچیده در مکانیزم کشینگ MySQL سرویس Hyperdrive

کالبدشکافی یک باگ پیچیده در مکانیزم کشینگ MySQL سرویس Hyperdrive

سرویس Hyperdrive کلودفلر که یک Connection Pooler توزیع‌شده برای اتصال Cloudflare Workers به پایگاه‌های داده SQL است، اکنون پشتیبانی از MySQL را به صورت عمومی (GA) ارائه می‌دهد. در مسیر تبدیل نسخه بتا به نسخه نهایی، تیم مهندسی با چالش‌های فنی متعددی روبرو شد که در نهایت منجر به شناسایی یک باگ نادر و پیچیده در محیط عملیاتی شد.

چالش‌های اولیه: کدگذاری و کاراکترها

برخی از مشکلات شناسایی شده در ابتدا ساده بودند. برای مثال، در نسخه بتا مشاهده شد که ایموجی‌های ذخیره شده در دیتابیس به درستی بازگردان نمی‌شدند. این مشکل به دلیل استفاده از کدگذاری utf8 بود که در واقع یک نام مستعار برای utf8mb3 است و تنها از ۱ تا ۳ بایت برای هر کد یونیکد پشتیبانی می‌کند. برای حل این مشکل، پروتکل دست‌تکانی (Handshake) به utf8mb4 تغییر یافت تا از تمام محدوده UTF-8 پشتیبانی شود.

مشکل دوم زمانی رخ داد که قابلیت پاک‌سازی اتصال از طریق دستور COM_RESET_CONNECTION اضافه شد. مشخص شد که این دستور، وضعیت کدگذاری را به مقدار پیش‌فرض سرور (مثلاً latin1) برمی‌گرداند و تنظیمات utf8mb4 را نادیده می‌گیرد. راهکار نهایی این بود که پس از هر بار Reset اتصال، دستور تنظیم کدگذاری مجدداً اجرا شود.

تحلیل یک باگ سخت‌یافت

یکی از مشتریان با حجم ترافیک بالا گزارش داد که در موارد بسیار نادر (حدود ۰.۰۰۱٪)، پاسخ‌های کوئری به صورت خالی (Empty) بازگردانده می‌شوند، در حالی که انتظار می‌رفت داده‌ای وجود داشته باشد. بررسی‌ها نشان داد که این اتفاق تنها در نمونه‌هایی از Hyperdrive رخ می‌دهد که قابلیت Caching در آن‌ها فعال است.

معماری کشینگ در Hyperdrive

سرویس Hyperdrive از یک معماری دوگانه تشکیل شده است:

  • Client Node: در همان ماشینِ Worker کاربر اجرا می‌شود و کش محلی را مدیریت می‌کند.
  • Endpoint Node: استخر اتصالات (Connection Pool) را نگه می‌دارد و در نزدیک‌ترین دیتاسنتر به دیتابیس اصلی قرار دارد.

هر دو نود از کش CDN محلی دیتاسنتر خود استفاده می‌کنند. اگر هر دو نود در یک دیتاسنتر باشند، یک کش مشترک را به اشتراک می‌گذارند.

تداخل در پروتکل MySQL و علت ریشه‌ای

بررسی‌های عمیق روی پروتکل کلاینت/سرور MySQL نشان داد که مشکل از تفاوت در قابلیت‌های (Capabilities) اعلام شده است. یکی از این قابلیت‌ها CLIENT_DEPRECATE_EOF است که نحوه پایان یافتن مجموعه‌نتایج (Result Sets) را تغییر می‌دهد. در حالت عادی، سرور بسته‌های EOF را ارسال می‌کند، اما در صورت فعال بودن این قابلیت، بسته نهایی EOF با یک بسته OK جایگزین می‌شود.

از آنجایی که Hyperdrive هم به عنوان کلاینت (برای دیتابیس اصلی) و هم به عنوان سرور (برای Worker) عمل می‌کند، باید پاسخ‌ها را بر اساس قابلیت‌های مورد انتظار Worker ترجمه کند. باگ زمانی رخ می‌داد که Client و Endpoint یک کش مشترک داشتند:

  • سناریوی اول: Endpoint پاسخ را با قابلیت‌های داخلی Hyperdrive در کش ذخیره می‌کرد، اما Client آن را بدون ترجمه به قابلیت‌های Worker ارسال می‌کرد.
  • سناریوی دوم: Client پاسخ ترجمه شده برای Worker را در کش ذخیره می‌کرد و Endpoint آن را می‌خواند و مجدداً سعی می‌کرد ترجمه کند که منجر به ارسال زودهنگام EOF یا Hang شدن اتصال می‌شد.

راهکار نهایی

برای رفع این مشکل، تیم مهندسی تصمیم گرفت که فارغ از اینکه کدام نود داده را ذخیره می‌کند، همیشه یک نسخه استاندارد (Canonical) از نتایج کوئری را با استفاده از قابلیت‌های Hyperdrive در کش ذخیره کند. پس از اعمال این تغییر، مشکل پاسخ‌های خالی برطرف شد و نرخ موفقیت اتصالات به ۹۹.۹۹٪ رسید.