یه developer کاملا معمولی در یک کلودپروایدر معمولی
تحلیل رخداد اختلال در سرویس API محصول CDN آروانکلاد

در تاریخ ۱۲ و ۱۳ مهر، API مربوط به محصول CDN در چند بازه زمانی از دسترس خارج شد. این اختلال منجر به افزایش زمان پاسخدهی درخواستها و در مواقعی عدم پاسخدهی شد. در ابتدا ما مشاهده کردیم که این بازهها تعداد کانکشنهای باز به دیتابیس به اندازه قابل توجهی افزایش یافته و زمان پاسخگویی (Response Time) بالا رفته است. این مسئله در نهایت موجب اشباع شدن ورکرهای وبسرور Apache و در نهایت از دسترس خارج شدن کامل سرویس شد.
در واکنش اولیه، تیم فنی با فرض اینکه ریشه مشکل افزایش ناگهانی ترافیک و تعداد درخواستهای همزمان است، اقدام به افزایش تعداد پادهای سرویس و افزایش مقدار max_connections در کانفیگ دیتابیس سرویس کرد. این تغییرات بهصورت موقت موجب کاهش فشار سیستم شد، اما پس از گذشت چند ساعت، اختلال دوباره تکرار شد. در این مرحله مشخص شد که مشکل ریشهدارتر از صرفاً افزایش بار ترافیکی است و باید بهصورت عمیقتر بررسی شود.
علت فنی اختلال
در بررسیهای بعدی مشخص شد که ریشه اصلی مشکل در شیوه طراحی مدل ذخیرهسازی اطلاعات (Data Model) «لیستها» است. در ساختار اولیه، برای سادگی و با فرض کم بودن درخواستهای همزمان روی یک لیست، تمامی آیتمهای مربوط به آن لیست بهصورت JSON در یک ستون از جدول ذخیره میشدند. به این ترتیب، هر بار که کاربر آیتمی را به لیست اضافه یا از آن حذف میکرد، کل مقدار JSON مجدداً بهروزرسانی (Update) میشد.
اما در دیتابیسهایی که از Transaction Consistency و Isolation Level پشتیبانی میکنند، در زمان اجرای عملیات UPDATE، سطر (Row) مورد نظر قفل (Lock) میشود تا از تداخل همزمانی در تغییر داده جلوگیری شود. در این حالت، اگر چند درخواست همزمان تلاش کنند یک سطر مشابه را تغییر دهند، تنها یک تراکنش میتواند فعال بماند و سایر تراکنشها باید تا پایان عملیات قبلی منتظر بمانند.
چند مورد از لیستهای ایجاد شده توسط کاربران شامل تعداد بالایی آیتم بود و با افزایش تعداد درخواستهای همزمان برای بهروزرسانی آنها، مدت زمان انتظار برای آزاد شدن قفل سطرهای مربوطه افزایش مییافت. همچنین با توجه به اینکه PHP به طور رسمی از Connection Pooling پشتیبانی نمیکند، هر درخواست جدید برای اتصال به دیتابیس یک کانکشن جدید ایجاد میکرد. این امر باعث افزایش تعداد کانکشنهای فعال، اشباع شدن مقدار max_connections در دیتابیس و در نهایت کندی کلی عملکرد دیتابیس شد.

از سوی دیگر، طولانی شدن زمان پاسخ دیتابیس موجب انتظار طولانیتر درخواستها در Apache شد. از آنجا که Apache برای هر درخواست یک Worker مجزا اختصاص میدهد، اشباع شدن ورکرها باعث شد که سرور دیگر نتواند به درخواستهای جدید پاسخ دهد و سرویس عملاً از دسترس خارج شود. بهعبارتی، یک Bottleneck در سطح دیتابیس موجب دومینوی اختلال در لایه وبسرور شد.

اقدام اصلاحی تیم فنی
در صبح روز ۱۳ مهر، برای کنترل فوری وضعیت و جلوگیری از گسترش اختلال، محدودیتهایی بر روی API آپدیت لیست اعمال شد تا از تعداد بالای درخواستهای همزمان برای یک لیست خاص جلوگیری شود. این اقدام باعث شد که بار ترافیکی کاهش یابد و سرویس بهطور موقت پایدار شود. با این حال، مشخص بود که این تنها یک راهحل موقتی است و نمیتواند پاسخگوی نیازهای مقیاسپذیری (Scalability) در آینده باشد.
در ادامه، تیم فنی تصمیم گرفت ساختار دادهای جدول مربوط به لیستها را باز طراحی کند. بهجای ذخیرهسازی تمام آیتمهای یک لیست در قالب یک JSON، دادهها به دو جدول مجزا تقسیم شدند:
جدول اول شامل اطلاعات کلی لیست (List Metadata) مانند نام، مالک و شناسه است.
جدول دوم شامل آیتمهای مربوط به هر لیست (List Items) بود که هر رکورد بهصورت مجزا در دیتابیس ذخیره میشود.
با این تغییر، عملیات افزودن یا حذف آیتمها بهصورت مستقل از یکدیگر و با استفاده از دستورات INSERT و DELETE انجام شد. از آنجا که عملیات درج و حذف در دیتابیسهای تراکنشی معمولاً نیازی به قفل کامل سطرهای اصلی ندارند، کارایی بهطور چشمگیری افزایش مییابد و چیزی در زمان افزودن و یا حذف آیتم جدید به جدول قفل نمیشود.
از آنجایی که سرویس باید با SDKها و پنل فعلی سازگار باقی میماند، این تغییرات بهصورت مرحلهای پیادهسازی شد:
ایجاد جدول جدید و استفاده از آن برای لیستهای تازه ایجاد شده.
حفظ APIهای قدیمی جهت پشتیبانی از لیستهای موجود و جلوگیری از ناسازگاری.
همگامسازی دادهها با Edge Serverها با در نظر گرفتن هر دو مدل ذخیرهسازی قدیم و جدید.
مهاجرت کامل دادهها به ساختار جدید با حفظ سازگاری در سطح API.
اعلام منسوخ شدن APIهای قدیمی و حذف نهایی آنها پس از یک ماه از تاریخ مهاجرت.
رخداد ۱۲ و ۱۳ مهر نمونهای از چالشهای متداول در طراحی سرویسهای دیتابیسمحور با بار همزمان بالا بود. علت اصلی اختلال نه در حجم ترافیک، بلکه در طراحی نامناسب ساختار دادهای بود. با بازطراحی ساختار جداول و اصلاح نحوهی ارتباط API با دیتابیس، تیم فنی توانست مسئله را بهصورت پایدار برطرف کرده و از تکرار آن جلوگیری کند.
مطلبی دیگر از این انتشارات
مدیریت حادثه؛ بخش دوم: آمادگی برای حادثه
مطلبی دیگر از این انتشارات
قهرمان خاموش یک مایگریشن موفق: AM-ROUTE
مطلبی دیگر از این انتشارات
چطور در لاراول به شکلی درست کوئری خود را بر اساس URL Query String فیلتر کنیم