وقتی تازه کوییز آو کینگز رو شروع کرده بودیم و تعداد کاربران بازی زیر ۱۰۰ هزار نفر بود، همیشه برای شخص خودم سوال بود که اگر تعداد کاربرها زیاد بشه چیکار باید بکنیم، چطور سیستم میتونه این بار رو تحمل بکنه و در عین حال، کاربران هم از بازی رضایت داشته باشند، یعنی مشکلی که باعث کندی در بازی یا مشکل در ذخیرهی اطلاعاتش بشه وجود نداشته باشه.
توی اون مواقع شنیده بودم که مثلا فیسبوک از شاردینگ دیتا برای دیتابیس هاش استفاده می کنه و کم و تا حدودی با مفاهمیمش آشنا شده بودم، اما هیچ وقت فکر نمی کردم که در کمتر از یکسال، ما هم مجبور بشیم چنین راهکارهایی رو پیادهسازی کنیم!
کوییز آو کینگز در کمتر از یکسال شدیدا وایرال شد. به نحوی که سرعت رشد کاربران از سرعت رشد زیرساخت و تکنولوژیهای فنی ما بسیار بیشتر بود.
در ابتدا کوییز آو کینگز کار خود را با ۲ سرور شروع کرده بود که روی یکی از آنها Load Balancer ای قرار داشت که ریکوئستها رو بین هر دو سرور که هر دو Application Server بودند، تقسیم میکرد. روی یکی از سرورها یک Mysql نیز قرار داشت و روی هر دو سرور نیز دو عدد Redis بود.
اندکی بعد با رشد کاربران، ترافیک بازی نیز بالا رفت و با این اتفاق در بازی کندی های غیر قابل تحملی برای کاربران به وجود آمد، به طوری که بازی از ساعت ۹ شب به بعد و با افزایش ترافیک باز نمیشد.
اولین قدم در حل این مسئله زیاد کردن تعداد App Server ها بود.
تعداد App server ها رو افزایش دادیم. افزایش تعداد App serverها باعث یک التیام مقطعی به کندی بازی شد و سبب شد باز هم با استقبال بیشتری مواجه بشیم و این بار با کندیهای جدی تری مواجه شدیم!
اما این کندی ها رو چطور باید حل می کردیم؟!
اولین قدم این بود که بفهمیم اصلا کندی ها از کجاست تا بعد ببینیم برای هر مورد چه باید کرد. مهمترین و اصلیترین مرحلهی کار همین ریشهیابی مشکلات است.
برای ریشهیابی مشکل، قدم اول راه انداختن یه سیستم مانیتورینگ بود، با راه اندازی مانیتورینگ و مانیتور کردن سیستم متوجه شدیم که App Server ها نمیتوانستند بیشتر از یک تعداد کانکشن در لحظه رو پشتیبانی کنند. همین باعث شد متوجه بشیم که جدا از توسعهی تعداد App Server ها، Tune کردن متغیرهای سیستم عاملی هم مهمه.
نه فقط برای یک App Server بلکه برای هر نوع سروری ما باید یک سری محدودیت های پیش فرض سیستم عامل رو دستکاری کرده و تغییر بدیم. به عنوان مثال یکی از پیش پا افتادهترین این محدودیت ها، تعداد Open Fileها در لحظه است که به طور پیش فرض مقدار محدودی داره.
از اونجایی که برای Socketهای ارتباطی در Network از فایل استفاده میشه، بنابراین محدود تعداد فایلهای باز در لحظه باعث محدودیت تعداد درخواستهای همزمان کاربران در لحظه میشد که با رفع محدودیت تعداد Open File ها توانستیم تعداد ریکوئستهایی که هر سرور در لحظه تحمل میکرد رو افزایش چشمگیری بدیم.
این یک مثال ساده از Tune کردن متغیرهای سیستم عاملی بود.
متغیرهای زیاد دیگری وجود دارند که جهت جلوگیری از انحراف بحث از توضیح آنها میگذرم.
دو مشکل اساسی دیگری که داشتیم، یکی مصرف غیر بهینهی Resourceها (Memory و CPU) در App Serverها بود که تا حد قابل توجهی به زبانی که App Server ها با اون نوشته شده بودند، یعنی PHP مرتبط بود و یه بحث اصلی دیگه این بود که دیتابیس MySQL پروژه روی تنها یک Server بود که به تنهایی تعداد کوئری در لحظهي آن (QPS یا Query Per Second) به ۴۰ هزار عدد در ثانیه میرسید، عددی سرسام آور که تحمل این بار، آن هم برای یک سرور به تنهایی سخت بود و در نتیجه باعث کندی بازی شده بود و مجددا در ساعات اوج ترافیک عملا بازی باز نمیشد.
برای حل مشکل App Serverها میشد با افزایش دوبارهی تعداد اونها زمان خرید اما دیتابیس MySQL در اون زمان بدون صرف هزینهی زمانی و ایجاد تغییرات زیرساختی در معماری، قابل Scale کردن نبود.
در آن زمان، پلتفرمهای کمی برای Scale کردن MySQL موجود بودند که اکثر آنها به دلیل Enterprise بودن، نیازمند صرف هزینههای بالایی بودند، مثلا برای هر Instance از MySQL در سال، ما باید چندین هزار دلار میدادیم.
حساب کنید که در حال حاضر کوییز تعداد زیادی Instance از MySQL داره و اگر قرار بود از سرویس های Enterprise استفاده کنیم الان باید با درآمد ریالیمون در سال، هزینهی گزافی بابت Scale دیتابیسها میدادیم.
با کمی تحقیق، انتخاب ما پلتفرم Opensource ای به نام Vitess شد که محصول شرکت Youtube بود و در آن زمان به جز Youtube، شرکتهای کمی از آن استفاده میکردند.
کوییز آو کینگز زمانی به سراغ Vitess رفت که بسیاری از شرکتهای بزرگی که اکنون از Vitess استفاده میکنند، از آن استفاده نمیکردند، از جمله شرکتهایی که بعد از کوییز به استفاده از Vitess روی آوردند میتوان به گیتهاب، پینترست و کمپانی بازی سازی peak اشاره کرد.
در آغاز پیادهسازی Vitess، دو مشکل اساسی پیش روی ما بود:
- داکیومنتیشن های زیادی برای آن وجود نداشت و مجبور بودیم با خواندن Source کد Vitess بفهمیم که باید چه کنیم. یا از چندین سایت چینی کمک بگیریم که آنها در آن زمان Vitess را با موفقیت پیاده سازی کرده بودند.
- کدهای Application Server پروژه در آن زمان، شدیدا وابسته به Doctrine بودند، اما Vitess در آن زمان اصلا از Doctrine پشتیبانی نمیکرد.
پیادهسازی Vitess برای ما حدود ۶ ماه زمان گرفت.
در این ۶ ماه ما کارهای زیر رو انجام دادیم:
- قدم ۱ : بازکردن راه رشد تعداد کاربران و جلوگیری از سرکوب شدن موج Virality: با توجه به اینکه پیادهسازی Vitess با توجه به مشکلاتی که گفته شد، زمانبر به نظر میرسید، ما تصمیم گرفتیم برای رشد کوییز زمان بخریم، بخشی از بازی کوییز رو که شدیدا به Mysql وابسته بود به Redis منتقل کردیم تا هم سریع تر بشه و Scalable باشه و هم فرصت کافی داشته باشیم که بتوانیم Vitess رو پیادهسازی کنیم.
درسته از Redis نباید به عنوان یک دیتابیس استفاده کرد، اما ما با این تصمیم سرعت بازی رو به بهای کاهش نسبتا کم Consistency (یکپارچگی دیتا) افزایش چشمگیری دادیم و تونستیم شیب رشد کاربران رو حفظ کنیم.
الان که به گذشته نگاه میکنم واقعا به این تصمیم افتخار میکنم.
- قدم ۲ : پیادهسازی زیرساخت Vitess : پیادهسازی این زیرساخت برای ما واقعا جذاب بود، استفاده از ابزارهایی چون ZooKeeper، Etcd، الگوریتم های توافق یا Consensus برای ما دریایی از تجربه بود.
این پیاده سازی در عین جذابیت بسیار سخت بود، به قدری سخت که وقتی کار تمام شد و کوییز رو به زیرساخت دیتابیسی Vitess مجهز کردیم، تیم Vitess در YouTube تعجب کرده بودند و میخواستند یه Skype میتینگ با ما بگذارند و ببینند ما چهطور تونستیم محصول اونها رو با توجه به اون حجم کم داکیومنتیشن به کار بگیریم و لوگوی Quiz of Kings رو به عنوان یکی دیگر از استفادهکنندگان از Vitess روی سایتشان قرار دادند.
- قدم ۳ : حذف Doctrine که بسیار کار وقت گیر و دشواری بود. چون Vitess در آن زمان از Connection مربوط به Doctrine پشتیبانی نمیکرد.
برای حذف Doctrine، مجبور شدیم خودمان یک شبه ORM بنویسیم که مستقیما از طریق تولید کوئری های خام با PDO مربوط به Vitess ارتباط برقرار میکرد و کلیه بخشهای بازی رو با این ORM بازنویسی کنیم، این کار هم دقت زیادی میطلبید و هم زمانی نزدیک حدود ۱ ماه صرف آن شد.
نهایتا با بالا آمدن Vitess و اتصال آن به ORM خودساخته، جداسازی اولین بخش بازی از دیتابیس single-node سابق را جشن گرفتیم و راهی رو باز کردیم که بتوانیم سیل کاربران روزافزون رو تحمل کنیم.
بخشهایی از بازی رو که به Redis منتقل کرده بودیم رو هم به دیتابیسهای جداسازی شدهی Vitess منتقل کردیم.
اکنون کوییز آو کینگز بیش از ۴۰ سرور دارد و بیش از ۲۰۰ عدد از Node های Mysql آن با استفاده از Vitess شارد شده و مدیریت میشوند.
پس از اینکه Migration به Vitess با موفقیت انجام شد. برای اینکه سرعت App Server ها افزایش یابد و همچنین مصرف منابع توسط آنها بهینه شود به سراغ زبان Go رفتیم و از بخشهایی از بازی که کندتر از بقیه بودند، شروع به Migrate کردن کدها از زبان PHP به زبان Go کردیم، همچنین سعی کردیم تا بخشهای جدید بازی رو با این زبان بنویسیم که حجم کدهای قدیمی زیاد نشود.
امروز بعد از گذشت ۳ سال از این تحولات، بخش زیادی از کدهای بازی به Go منتقل شده است، App Server هایی که به زبان Golang نوشته شدهاند، Dockerize شده و روی محیط Kubernetes قرارگرفته اند به طوری که به راحتی و با چشم به همزدنی میتوان تعداد App Server های مورد نیاز برای یک بخش را زیاد یا کم کرد.
بعد از مهاجرت به زبان Go به دلیل اهمیت سرعت توسعه، به معماری میکروسرویس روی آوردیم و بخشهای زیادی از بازی رو به صورت میکروسرویس های مبتنی بر زبان Go نوشتیم به نحوی که هم معماری جدید و بهینه ای برای این میکروسرویسها در نظر گرفتیم و هم اینکه به دلیل جدا شدن این پروژهها از یک پروژهی Monolithic، سرعت توسعه به شکل چشمگیری افزایش یافت و تیم فنی بکند در کوییز به میکروتیمهایی تبدیل شد که هریک مسئول ادارهی بخش خاصی از بازی، بدون وابستگی زیاد به سایز بخشها باشد.
همچنین یکی دیگر از کارهایی که شروع به انجام آن کردیم، محدود کردن زیرساختهای هر سرویس (دیتابیسها، …) تا حد امکان به خودش بود تا اگر فشار روی یک سرویس زیاد شد، تاثیر این فشار تنها روی زیرساخت مربوط به خودش باشد و روی سایر بخشهای بازی تاثیری نگذارد.
ما به انتخابهایی که کردیم و مسیری که رفتیم افتخار میکنیم و امیدواریم روزی در خدمت شما عزیزان برای انتقال تجربیات بیشتری باشیم.