جشنوارهی فجر مهمترین رویداد سینمای داخلی است. آن هم نه فقط برای فیلمبینها و فیلمسازها، بلکه برای سینماها و بلیتفروشها هم رویداد بسیار مهمی است.
جشنواهی فجر سال ۱۳۹۹ از بسیاری از جهات عجیبترین جشنوارهی فجری بود که در این سالها برگزار شد. از نامشخّص بودن وضعیّت برگزاری آن تا دو هفته مانده به زمان جشنواره تا فروش تمام الکترونیکی بلیتهای آن. فروشی که در چندساعت اوّل عملاً در کار نبود، چون تمام بلیت فروشها از دسترس خارج شدند.
این چندساعت عدم دسترس را بگذارید کنار تعطیلی تقریباً یکسالهی سینماها و نیاز شدید تمام بخشهای سینما به جریان نقدینگیای که جشنوارهی فجر قراربود ایجاد کند. اینطوری شاید متوجّه اهمّیّت این خاموشی چندساعته بشوید.
در این نوشته میخواهیم دلیل فنّی از دسترس خارجشدن سینماتیکت را، به عنوان بزرگترین فروشندهی بلیت، با هم ببینیم.
این جشنوارهی فجر با بقیهی جشنوارهها فرقی مهم داشت. به خاطر شرایط کرونایی قرارشد که بلیتفروشی تنها به صورت اینترنتی انجام شود.
مسئله این بود که تمام پایانههای فروش امسال برای اوّلین بار باید از طریق APIهای «سمفا»، سامانهی مدیریت فروش سازمان سینمایی، بلیتفروشی میکردند. و خب این سازمان هم به همه اطمینان داده بود که ما بررسیهای لازم را انجام دادهایم و مطمئن هستیم که باری که هنگام شروع بلیتفروشی عمومی به روی سایتها میآید برای ما مشکلی ایجاد نمیکند.
در این جشنواره قرار بر این شد که دو روز زودتر از بلیتفروشی عمومی، بلیتفروشی برای افرادی که از طرف خود جشنوارهی فجر و سازمانهای دیگر کد تخفیف دریافت کردهاند شروع شود و پس از آن فروش عمومی آغاز شود.
فروش افراد خاص در آن دو روز انجام شد و به جز یک سری مشکلات کوچک در اعتبارسنجی کدها توسّط سمفا، مشکل دیگری وجود نداشت.
ساعت ۱۳:۵۵ روز شنبه ۱۱ بهمن ۱۳۹۹. تنها پنج دقیقه تا شروع فروش عمومی جشنوارهی فجر باقی مانده بود. هر دقیقه به تعداد افراد آنلاین روی سینماتیکت هزارنفر افزوده میشد. همهچیز خوب پیش میرفت و تمام مانیتورها وضعیّت سرورها را خوب نمایش میدادند.
نه مشکلی در سرعت بارگذاری سایت وجود داشت و نه مشکلی در کارکردهای مختلف آن. همه خوشحال و منتظر ساعاتی پرفروش بودند.
ساعت به ۱۴ رسید و بلیتفروشی عمومی شروع شد. برای یکی دو دقیقهی اوّل همهچیز خوب بود. تا اینکه پیامهای پر از نگرانی تیمهای بازاریابی و مدیریت شروع شد: سایت باز نمیشود.
تا بررسی سایت را شروع کردیم خبر آمد که دو سامانهی فروش دیگر هم از دسترس خارج شدهاند. این خودش قوّت قلب بود.
چیزی که عجیب بود این بود که هنوز وضعیّتی که در سیستم مانیتورینگ از سرورها میدیدیم عادی بود. یعنی منطقاً نباید هیچ مشکلی وجود میداشت.
حالا که رقبا هم غیر قابل دسترس بودند و فشار و استرس کمتر شده بود، یک بار دیگر پیامهای خطا را بررسی کردیم.
قضیه عجیبتر شد. پیامهای خطای رنج ۵۰۰ مستقیماً از طرف Nginx صادر میشدند. اصلاً درخواستها به سرورهای ما نمیرسید.
از آنجایی که تمام Nginxها در پاسخ تمامی درخواستها خطای ۵۰۳ برمیگرداندند، تمامی عملکردهای سایت از کار افتاده بودند و افراد با ورود به سینماتیکت تنها یک صفحهی خالی را مشاهده میکردند. چون که وباپلیکیشن ما امکان دریافت هیچ دادهای را از سرورها نداشت.
خبر جدیدی از تیمهای دیگر به ما رسید: سایتهای دیگر سامانههای فروش باز میشدند. ولی همچنان امکان فروش نداشتند.
اوضاع دوباره برای تیمهای فنّی و زیرساخت پرفشار شده بود. چون تمامی وبسایت ما غیرقابل استفاده بود. برخلاف دیگران که تنها بخش خریدشان از کار افتاده بود.
ما برای حدود ۵۰ دقیقه هیچ فروشی نداشتیم.
اینکه هیچ سامانهی فروشی امکان خرید نداشت ما را تقریباً متقاعد کرد که مشکل به خاطر پایینآمدن سرویسهای سمفا است.
چیزی که با بررسی لاگها تأیید شد. امّا سؤال اصلی این بود که چرا تمام سایت از دسترس خارج شده؟
با کمی بررسی بیشتر علّت مشخّص شد.
تمام سرورهای ما، و در نتیجه تمام Nginxها، برای گرفتن پلان سانسها، لیست سانسها، رزرو صندلیها و تکمیل سفارشات به سمفا درخواست ارسال میکنند.
حالا که هیچکدام از APIهای سمفا پاسخ نمیدهند، سوکتهای ما به سرورهای سمفا باز میمانند و از آنجایی که تعداد درخواستها بسیار زیاد است، تمام سوکتهای تمام Nginxها پر شده اند.
در نتیجه به صورت خودکار هنگامی که یک درخواست به یک Nginx میرسد، خود آن Nginx به صورت خودکار با یک خطای ۵۰۳ آب پاکی را روی دستان فرستنده میریزد.
اول از همه ببنیم که سوکت چیست. سوکت درگاهی است که ارسال و دریافت دادهها از طریق آن انجام میشود. در واقع، سوکت یک ساختار نرمافزاری است که به یک پردازه تعلق دارد. در سیستمعاملهای لینوکسی، مثل سیستمعاملی که ما روی ماشینهایمان از آن استفاده میکنیم، هر سوکت در واقع یک file descriptor است. همانطور که میدانید، ما هم تعداد محدودی file descriptor را میتوانیم به صورت همزمان استفاده کنیم.
برای تغییر این محدودیّتها باید محدودیّت سخت تعداد فایلهای باز را در سطح سیستمعامل تغییر بدهیم. پس از آن محدودیّت نرم آن را هم میتوان از طریق سیستمعامل یا خود تنظیمات Nginx تغییر داد.
اگر میخواهید از میزان این محدودیّت روی سیستمعامل خودتان با خبر شوید، میتوانید از دستور ulimit
استفاده کنید.
مثلاً محدودیّتهای پیشفرض ابونتو این است:
با اینکه ما محدودیّت تعداد سوکتها را با توجّه به نیازمان به صورتی منطقی تنظیم کرده بودیم، ولی از آنجایی که زمان زیادی طول میکشید تا اتّصالهای TCP/IP باز، بسته شوند، تمام ماشینها به سقف ظرفیّت تعریفشده رسیده بوند.
یعنی هنگامی که درخواست جدیدی به Nginx میرسید، از آنجایی که دیگر هیچ file descriptor آزادی وجود نداشت، پذیرش درخواست هم امکانپذیر نبود و به صورت خودکار یک پیام ۵۰۳ به فرستندهی درخواست داده میشد.
اوّلین راه حل برای رفع موقّت مشکل، کاهش زمان timeout درخواستهایی بود که به سمفا ارسال میکردیم.
همزمان با بررسیهای تیم زیرساخت، تیم فنّی میزان timeout را ۳ثانیه کاهش داد. دلیل انتخاب این عدد این بود که باتوجّه به تأخیر پاسخگویی سمفا، بعید بود که با گذاشتن محدودیّتی کمتر از ۳ ثانیه هیچکدام از درخواستها هیچ پاسخی دریافت کنند.
امید ما این بود که با کاهش این عدد، کمی وضعیّت سرویسدهی بهتر شود.
بالأخره ساعت ۱۴:۵۸ ما اوّلین پاسخها را از APIهای سمفا گرفتیم. هرچند هنوز حجم درخواستهایی که منتظر پاسخ میماندند بسیار زیاد بود. ولی شروعشدن فروش کمی از فشارها، چه از روی سرورها و چه از دوش تیمهای فنّی، کم کرد.
مسئله این بود که قراردادن timeout هم به تنهایی باعث رفع مشکل نشد و هنوز برای اکثر افراد سایت غیرقابل دسترس یا بسیار کند بود.
از آنجایی که هنوز کاربران زیادی دچار مشکل بودند، ما شروع به بررسی راه حل هایی کردیم که در دسترسمان بودند.
اوّلین راه حل افزودن ماشینهای جدید بود. مسئله این بود که آمادهکردن ماشین جدید و اتّصالش به بخشهای دیگر کار زمانبری بود.
از طرفی مهمترین ساعات مهمترین رویداد سینمایی کشور در حال تلفشدن بودند و اکثر کاربران امکان خرید بلیت را نداشتند. بلیتهایی که پول آنها برای سینماهایی که یک سال تعطیل بودند و سامانههای فروشی که اکثر سال چیزی نفروخته بودند حیاتی بود.
راه حل دیگری که مطرحشد، نجاتدادن بخشهایی از سینماتیکت بود که با سمفا ارتباطی نداشتند.
ما به این نتیجه رسیدیم که بهترین کار این است که فعلاً تمام کاربران بدون مشکل بتوانند وارد سایت بشوند و تنها در صفحات خرید منتظر دریافت پاسخ از سمفا بمانند. اینطوری هم از نارضایتی آنها کاسته میشد و هم احتمال اینکه منتظر عادی شدن شرایط سمفا بمانند افزایش مییافت. اینطوری کاربر کمتری از دست میرفت.
ما دوباره به یک راه حل موقّت روی آوردیم. با تغییر تنظیمات HAProxy، درخواستهایی که به سمفا میرفتند را به دو ماشین محدود کردیم.
اینطوری فشار سوکتهای بازمانده را از باقی ماشینها کم کردیم. حالا، در ساعت ۱۷، وبسایت سینماتیکت بدون مشکل برای تمام کاربران بالا بود.
هرچند هنوز تعداد زیادی از کاربران موقع خرید دچار مشکل میشدند.
دو ساعت پس از راه حل موقّت ما، بالأخره تعداد درخواستها آنقدری کم شد که سمفا به اکثر درخواستها پاسخ درست میداد.
حالا هم سایت پایدار بود و هم کاربران امکان خرید داشتند. هرچند اکثر آدمهایی که قصد خرید داشتند تا این موقع شب دیگر ناامید شده بودند.
حالا ساعتها پس از شروع بلیتفروشی عمومی، تازه شرایط فروش قابل قبول شده بود و فشارها هم از تیمهای فنّی برداشته شد.
حالا میشد یک نفس راحت کشید و در آرامش یک لیوان چای نوشید. وضعیّت سفید شده بود.
درست است. ما زیر فشار کم نیاورده بودیم و این سمفا بود که از دسترس خارج شده بود. ولی عدم امکان دسترسی به وبسایت ما به خاطر از دسترس خارج شدن یک سرویس خارجی اشتباه ما بود.
اگر بخواهیم به صورت کلّی نگاه کنیم، ما دو اشتباه بزرگ داشتیم:
ما تمام مدّت با یک مثبتاندیشی توأم با سادهانگاری با سرویسهای شخص ثالث برخورد کردیم. همواره فرض ما در پیادهسازی بر این بوده که این سرویسها در دسترس خواهند بود و اکثر اوقات پاسخ ما را میدهند.
هرچند به خاطر فشار بازنویسی سینماتیکت برای ماههای ابتدایی نسخهی جدید این مثبتاندیشی عاقلانه به نظر میرسید. ولی باید تا پیش از شروع جشنواره فجر بخشهایی از نرمافزار را که با این دید جلو رفته بودند تغییر میدادیم.
درسی که از این موضوع گرفتیم این بود که همواره بنا را بر نادرستی سرویسهای شخص ثالث بگذاریم. حتّی اگر deadline آنقدر نزدیک بود که نمیشد برای آن بدبینی چارهای اندیشید.
درست است که اینجا هم میتوان همان بهانهی فشار deadlineها را آورد، ولی ما به اشتباه اولویت پیادهسازی مکانیسمهای جلوگیری از فاجعه را از کارهای دیگر پایینتر درنظر گرفته بودیم.
درسی که ما گرفتیم این بود که مکانیسم جلوگیری از فاجعه درست به اندازهی خود ویژگیای که در حال پیادهسازی آن هستیم اهمّیّت دارد.
در این مورد خاص، کافی بود که ما از الگوی مدارشکن (CircuitBreaker) استفاده میکردیم.
این الگو به زبان ساده و به شکل خلاصه میگوید: اگر به یک مقصد خاص چندبار درخواست زدید و پاسخی نگرفتید یا پاسخی که گرفتید معتبر نبود، برای مدّتی به جای فرستادن درخواست جدید به همان مقصد، پیام خطا را به کسی که آن API را فراخوانی کرده برگردانید.
اینطوری زیرساخت شما اسیر عملکرد نادرست شخص دیگری نمیشود و نرمافزار شما حتّی با وجود از کار افتادن سرویسهای خارجی به زندگیاش ادامه میدهد.
هنگام ساخت یک نرمافزار شما خیلی از چیزهای مهم را درنظر میگیرید. مفاهیم بزرگی که همان بزرگ بودنشان شما را گول میزند تا فکرکنید از باقی چیزها مهمتر هستند.
اگر در این دام گرفتار شوید، چیزهای کوچکی که خیلی مهم هستند را فراموش میکنید. آن وقت میبینید سیستمی که تمام مشکلات بزرگ را به راحتی پشت سر میگذارد، به خاطر پیش پا افتادهترین موضوعات زمینگیر میشود.
یک ترک ریز در بدنهی یک سد از چالهای بزرگ وسط یک خیابان مهمتر است. نباید اجازه بدهیم که بزرگی و کوچکی انتزاعی مفاهیم، ما را از دایرهی تأثیر آنها در دنیای واقعی غافل کند.
این مطلب توسط محمدرضا علی حسینی در بلاگ راهبردهای خلاق آرمان نوشته شده است.