چطور در دو ماه برای لود ۱۰ برابری آماده شدیم؟

توی این مقاله تجربه‌ای را به اشتراک می‌گذاریم که در تپسی در مدت دو ماه برای تعدادِ درخواست درحدود ۱۰ برابر حالت عادی (در برخی از بخش‌ها تا ۱۷ برابر) آمادگی کسب کردیم. در این مقاله در ابتدا تعریف صورت مسئله و چالش‌های آن را مرور می‌کنیم، و بعد از آن به نحوه‌ی انجام لودتست و همچنین گرفتن بازخورد در بخش‌های مختلف آن خواهیم پرداخت. این مقاله به جزئیات فنی نمی‌پردازیم و مسیر حل مسئله را با هم مرور می‌کنیم، در آینده در مقاله‌های دیگر به جزئیات بیشتری خواهیم پرداخت. چرا لود ده‌برابری؟داستان از این قرار بود که در یک برنامه‌ی تلویزیونی از بینندگان خواسته می‌شد که اگر همان‌لحظه اپلیکیشن را باز کنند، کد تخفیف می‌گیرند یا در قرعه‌کشی می‌توانند شرکت کنند یا در نقشه به دنبال کد تخفیف بگردند و مواردی شبیه این. تجربه‌ی سال‌های گذشته این برنامه به ما نشون داد که باید برای تعداد درخواست در ثانیه‌ی (TPS) بیشتر از ۷ برابر در باز شدن اپلیکیشن (نسبت به بیشترین حجم درخواستی که در گذشته براش آماده بودیم) و تعداد ثبت‌نام بیشتر از ۱۵ برابر بیشترین ثبت‌نام آماده می‌شدیم.پیچیدگی‌ها؟۱- عدم اطلاع دقیق از تعداد درخواست: ما با توجه به اعداد برنامه در سال‌های گذشته در مورد نرخ درخواست‌ها تخمین داشتیم ولی تخمین بود و اطلاعی از نرخ دقیق نداشتیم و باید برای بیشترین چیزی که تصورش رو می‌کردیم آماده می‌شدیم، برای همین باید به صورت کاملاً بدبینانه (البته خوش‌بینانه از نظر تعداد کاربر) آماده می‌شدیم.۲- تغییر در رفتار کاربر: رفتار کاربر با اپلیکیشن به طور کامل در بازه‌ی برنامه تغییر پیدا می‌کرد، و برای مثال اگر در یک مایکروسرویس درخواست A در گذشته بیشترین تعداد بود، در این برنامه ممکن بود درخواست B که تعدادش کم بوده بیشتر شود. مثلاً وارد کردن کد تخفیف درخواستی‌ست که در زمان‌های پربار سیستم خیلی تحت تأثیر قرار نمی‌گرفت ولی در این برنامه نیاز بود که عملکرد خیلی خوبی داشته باشد. در نتیجه شناخت کامل محصول، رفتار کاربر و رفتار احتمالی کاربر از مواردی بود که پیش از هرکاری لازم بود انجام شود.۳- افزایش ضربه‌ای تعداد درخواست: نرخ درخواست‌ها به صورت ضربه‌ای افزایش پیدا می‌کرد، و ابزارهای scaling که در کوبرنتیس مورد استفاده‌ی ما بودند، به طور خاص در این مورد پاسخگوی نیازهای ما نبودند و باید برای این موضوع هم فکری می‌کردیم.۴- عدم اطلاع از زمان دقیق برنامه: با توجه به تغییرات در برنامه‌ها، زمان دقیق برنامه و افزایش تعداد درخواست مشخص نبود.۵- تلاش برای کاهش هزینه‌ها: یکی از اهداف ما این بود که با اضافه کردن کمترین سخت‌افزار ممکن و با استفاده‌ از منابع قبلی برای این برنامه آماده بشیم.۶- مدیریت پروژه در بین تیم‌های مختلف: در این پروژه تیم‌های مختلفی درگیر بودند، ۴ تیم بکندی، ۲ تیم از وب و تیم زیرساخت، ایجاد هماهنگی بین این تیم‌ها و بررسی خروجی کامل این کارها در کنار یکدیگر هم از چالش‌های این پروژه بود.مراحل انجام کار۱- شناسایی نیازمندی‌ها، محصولات تحت تأثیر و رفتار کاربران:در ابتدا تیم فنی با همکاری تیم محصول، تمامی مسیرهایی که کاربران در طول برنامه به آن با احتمال (conversion) بیشتری مراجعه می‌کردند را استخراج کردند، بعد از تیم فنی APIها و مایکروسرویس‌هایی که در این فانل شناسایی کردند و بررسی دقیق پرفورمنس این سرویس‌ها به تیم‌های مربوطه واگذار شد.اهمیت این شناخت بسیار بالا است، در دو ماه اجرای برنامه به طور کلی ۳ دقیقه داون‌تایم رو تجربه کردیم، دلیل این داون‌تایم فیچری بود که به صورت دقیقه ۹۰ی ۲۴ ساعت قبل از پخش برنامه بدون هماهنگی کامل در برنامه قرار گرفته بود و باعث لود بسیار بالا در بخش نقشه می‌شد، و عدم آمادگی ما برای این فیچر باعث شد که نتوانیم آمادگی کامل را برای این بخش داشته باشیم. ۲- بررسی فیچرهایی که در زمان برنامه مورد نیاز نیست (degraded mode):با هدف کاهش مصرف منابع، در کنار شناسایی فیچرهای پرکاربرد در بازه‌ی زمانی برنامه، در جهت کاهش هزینه و منابع تصمیم گرفتیم تا بخش‌هایی از سیستم که در طول برنامه نیاز به آن وجود ندارد، یا در تجربه‌ی کاربری در آن زمان تأثیر قابل توجهی ندارد خاموش شود و ریسورس‌های این فیچرها در اختیار سناریوهای مهم قرار بگیرد. برای مثال فیچرهایی برای بالا بردن دقت مچینگ (انتخاب راننده‌ی مناسب برای مسافر) وجود دارد که در زمان برنامه با توجه به این کاربران درخواست سفر نمی‌دهند اهمیت بالایی ندارد، و می‌تواند خاموش شود. این فیچرها و میزان مصرف ریسورس در آن‌ها مشخص و اولویت خاموش کردن فیچرها نیز با کمک تیم محصول مشخص شدند. این کار را با استفاده از feature flagها در بخش‌های مختلف سیستم انجام دادیم. فیچر‌فلگ در تپسی مدت‌هاست برای خاموش و روشن کردن و همچنین اجرای‌ A/B Testها مورد استفاده قرار می‌گیرد و فیچرهای مختلف اپلیکیشن از سمت سرور قابل کنترل هستند. این ابزار همچنین برای کنترل فیچرهایی که در اپلیکیشن تأثیر مستقیم ندارند و در بکند تأثیرگذار هستند، نیز استفاده می‌شوند. ۳- بررسی محصولات تحت تأثیر و بهبود نقاط قابل بهبود شناسایی‌شده:پس از انجام مرحله‌ی ۱، تیم‌های مربوطه با بررسی ابزارهای در دسترس، مانیتورینگ‌ها و ابزار profiling به صورت آفلاین تلاش خود را برای بهبود نقاط قابل شناسایی هر مایکروسرویس انجام دادند. در این بررسی‌ها، بهبود الگوریتم، کاهش تعداد i/o، موازی کردن بخش‌هایی از سرویس‌ها و استفاده‌ی بهتر از دیتابیس (ایندکس‌ مناسب، کوئری‌های بهتر و یا بهبود کشینگ) اقداماتی بود که برای بهبود بخش‌های مختلف انجام شد.۴- پیاده‌سازی زیرساختی لودتست (Load test tool):انجام اقدامات بالا به تنهایی به ما اطمینان کامل برای میزان تحمل لود سیستم را نمی‌داد، لازم بود که در تمامی بخش‌های تپسی از بهبود عدد دقیق پاسخگویی سیستم و تحمل آن را داشته باشیم.یکی از مهم‌ترین مراحل انجام کار انجام لودتست برای شناسایی نقاط شکست سیستم و همچنین شناخت بهتر از ظرفیت کلی سیستم بود.انجام لودتست به روش‌های مختلف قابل انجام بود:لودتست هر مایکروسرویس به صورت تنها در محیط تستبا توجه به تفاوت‌های محیط تست و محیط پروداکشن، از نظر میزان ریسورس، حجم دیتا و همچنین اثر مایکروسرویس‌های در مایکروسرویس مورد تست، این نوع تست نمی‌تواند ما را به عدد دقیق tps یک سرویس و ریسپانس‌تایم آن در محیط پروداکشن برساند.بیشترین کاربرد این نوع تست، مقایسه‌ی وضعیت یک مایکروسرویس قبل و بعد از ایجاد تغییرات است. پس از انجام هر تغییر که انتظار افزایش throughput و یا بهبود ریسپانس‌تایم وجود دارد، انجام لودتست به صورت ایزوله می‌تواند از میزان اثرگذاری آن به ما اطمینان دهد. همچنین انجام پروفایلینگ در زمان لودتست ایزوله یک سرویس به ما امکان پیدا کردن باتل‌نک‌های سیستم را می‌داد.لودتست با سناریوی کامل سیستم در محیط تستاین نوع تست، می‌تواند اثر مایکروسرویس‌های دیگر که در روش قبل قابل دسترسی نبود را برطرف کند. همچنین در صورت این که لودتست با لود کاملاً واقعی انجام می‌شود می‌تواند تخمینی از میزان ریسورس مورد نیاز را به ما بدهد. اما به دلیل این که میزان ریسورس مورد استفاده در محیط پروداکشن بسیار بیشتر از محیط تست است، هزینه‌ی انجام این تست بسیار زیادتر است. با توجه به این که ما در زیرساخت از سرویس‌های ابری خارجی استفاده نمی‌کنیم و اضافه کردن سخت‌افزار یا اضافه کردن سرویس‌های ابری هزینه‌ی مالی و زمانی زیادی ایجاد می‌کرد از این نوع تست صرف‌نظر کردیم.لودتست کامل سیستم در محیط پروداکشن [1]همان‌طور که از عنوان این نوع تست مشخص است، قرار هست که لود غیرواقعی‌ای در پروداکشن ایجاد کنیم. انجام چنین کاری ریسک‌های متفاوتی دارد که باید مورد ارزیابی قرار گیرد.۱- ایجاد دیتای غیرواقعی در دیتابیس‌ها و اثرات جانبی این داده‌ها (مثلاً افزایش تعداد درخواست ممکن است در قیمت‌گذاری تأثیرگذار باشد): با لیبل زدن درخواست‌هایی که از طریق لودتست ایجاد می‌شود و مشخص بودن کاربرهای تستی و حذف آن‌ها از الگوریتم‌ها و دیتابیس پس از انجام تست نگرانی این مورد و مورد بعد را حذف کردیم.۲- تحت تأثیر قرار گرفتن داده‌های تحلیلی (analytical) در بازه‌ی انجام تست۳- امکان خرابی سیستم در زمان تست:در عین این که ریسک خرابی سیستم را در این مورد پذیرفتیم و همچنین افراد ذینفع را در مورد این ریسک آگاه کردیم، با افزایش تدریجی لود در پروداکشن این ریسک را تا جای ممکن کاهش دادیم.۴- برخی سناریوها مانند پرداخت قابل تست نیستند: سناریوهای اصلی که مورد نظر ما بودند با پوشش ۸۰درصدی با این روش قابل تست بودند و اطمینان خوبی به ما می‌دادند.در برنامه‌ریزی اولیه، زمان‌های انجام لودتست کامل در محیط پروداکشن مشخص شد و این کار را ۵ بار قبل از رسیدن به ددلاین نهایی انجام دادیم، در هر تست تعدادی باتل‌نک در سرویس‌های مختلف شناسایی شد و با بهبود آن‌ها به تدریج به نتایج دلخواه رسیدیم، در نهایت توانستیم ۳۰ درصد لود بیشتر از لود تخمینی در برنامه را در لودتست تحمل کنیم و این نتیجه برای ما دلچسب بود.همچنین این لودتست به ما کمک کرد که میزان اسکیل هر سرویس در زمان برنامه‌ را به طور کامل آگاهی داشته باشیم.۵- پیاده‌سازی ابزار اسکیلینگ (auto/manual scaling tool):همان‌طور که در ابتدا گفته شد با توجه به ضربه‌ای بودن افزایش لود (۱۰ برابر شدن لود کلی در کمتر از دو دقیقه)، ابزارهای autoscalingی که استفاده می‌کردیم برای ما سرعت پاسخگویی کافی را نداشتند و امکان چند برابر شدن تعداد instanceهای مایکروسرویس‌های مختلف در دو دقیقه وجود نداشت.با توجه به این که برنامه در ساعت نسبتاً مشخصی پخش می‌شد، با ایجاد یک ابزار دستی، هر روز ۱۰ دقیقه قبل از برنامه اسکیل کلیه‌ی مایکروسرویس‌ها که با توجه به خروجی لودتست مشخص شده بود تعیین می‌شد و پس از برنامه به حالت قبلی برمی‌گشت.۶- آمادگی برای شرایط اضطراری:با وجود تمامی اقدامات بالا، با توجه به ناشناخته بودن حجم درخواست و نوع آن در اجراهای ابتدایی، لازم بود خودمون رو برای شرایط خاص آماده کنیم. در هرکدام از بخش‌های سرویس‌ها، مسئول سرویس راه‌حل‌های اضطراری را از دو نوع آماده‌ی اجرا بودند. حذف فیچرهایی که از نظر تجربه‌ی کاربری مورد نیاز بودند و همچنین دراپ کردن درخواست‌های یک سرویس در شرایطی که تعداد درخواست بسیار بیشتر از پیش‌بینی ما بودند. در اجراهای ابتدایی برنامه و در زمان‌هایی که انتظار تغییر رفتار کاربر رو داشتیم تیم آنکال مسئولیت مشاهده و مانیتور سیستم و رفتن به شرایط اضطرار (با کمک فیچرفلگ‌ها) را داشتند.[۲] در نهایت با انجام فرآیند‌های ذکرشده، به صورت متناوب و بازبینی شرایط باعث شد به جز یک مورد که در بالا ذکر شد، کاملاً شرایط تحت کنترل باشد و بتوانیم تعداد درخواست ۱۰ برابری را با موفقیت و با کیفیت سرویس مورد انتظار انجام دهیم. منابع:[1] https://www.artillery.io/blog/load-testing-in-production https://sre.google/sre-book/testing-reliability/[2] https://sre.google/sre-book/reliable-product-launches/ https://sre.google/sre-book/handling-overload/