توی چند سالی که کار کردم، هر وقت بحث بهبود سرعت وبسرویس ها پیش میومد، همیشه اولین قدمم بررسی وضعیت فعلی و پیدا کردن گلوگاهها (همون باتلنکها) بود. از این تجربه ها چند تا نکته کلیدی جمع بندی کردم که میتونه به درد بقیه هم بخوره.
توی مسیر بهبود سرعت، همیشه این مراحل رو دنبال میکنم:
حالا بذار یه کم بیشتر باز کنیم:
اولین قدم برای بهبود عملکرد، پیدا کردن اون بخشهاییه که بیشترین کندی رو دارن. اینجا قانون ۸۰/۲۰ خیلی کمک میکنه؛ یعنی ۲۰٪ از کدها میتونن مسئول ۸۰٪ از کندی سیستم باشن. با تمرکز روی این ۲۰٪ میتونیم بیشترین بهبود رو بگیریم. البته استفاده از ابزارهای درست مثل New Relic، Prometheus یا حتی Datadog خیلی مهمه. این ابزارها دید خوبی از پروفایلینگ سیستم میدن و دقیقتر بهت میگن کجای سیستم کندی داره.
فرض کن یه سناریو داری که توش توی حلقه، برای هر آبجکت یه درخواست به دیتابیس میفرستی. مثلاً داری برای هر userID اطلاعات کاربر رو از دیتابیس میگیری. این کار عملاً سیستم رو کند میکنه، چون به ازای هر آیتم یه کوئری جدا میزنی.
به جای این کار میتونی همه userIDها رو تو یه درخواست به دیتابیس بفرستی و یه جا جواب بگیری. اینجوری به جای هزار تا کوئری، یه دونه میزنی و کلی سرعت میگیری.
مثال دنیای واقعی: فرض کن میخوای لیست کاربرهایی که خرید کردن رو از دیتابیس بگیری. به جای اینکه هر بار توی حلقه یه کوئری بزنی، همه userIDها رو تو یه کوئری با IN بگیری و یه جا پردازش کنی. هم سریعتره، هم منابع کمتری مصرف میشه.
اگه به چند تا سرویس خارجی درخواست میفرستی که به هم وابسته نیستن، بهتره درخواستها رو همزمان بفرستی. این که منتظر بمونی یکی جواب بده و بعد دومی رو بفرستی فقط وقت تلف کردنه.
راه حل؟ از Concurrency استفاده کن. با ابزارهایی مثل Goroutines تو Go یا مثلاً Promise تو جاوااسکریپت، میتونی درخواستها رو همزمان بفرستی. منتظر همه میمونی ولی همزمان انجام میشه و کلی زمان میگیری.
مثال دنیای واقعی: فرض کن میخوای قیمت یه محصول رو از چند سرویس مختلف بگیری. به جای اینکه یکی یکی درخواست بفرستی، میتونی همه رو همزمان بفرستی و نتایج رو با هم مقایسه کنی. اینجوری کاربر سریعتر جواب میگیره.
البته اینجا باید حواست به Timeout و Circuit Breakers هم باشه که اگه یه سرویسی کلاً جواب نداد یا دیر جواب داد، کل اپلیکیشن قفل نشه. Rate Limiting هم یادت نره که فشار زیادی روی سرویسهای خارجی نذاری.
وقتی با دادههایی کار میکنی که زیاد تغییر نمیکنن و تکراری هستن، کش کردنشون یه راه نجاته. با این کار از درخواستهای مکرر به دیتابیس و سرویسهای خارجی جلوگیری میکنی و کلی بار سیستم رو کم میکنی.
فرض کن هر بار برای گرفتن لیست محصولات پرفروش یه کوئری سنگین به دیتابیس بزنی. به جای اینکه هر بار دیتابیس رو خسته کنی، میتونی نتیجه کوئری رو توی کش ذخیره کنی و دفعه بعدی از همون داده های کش شده استفاده کنی.
مثال دنیای واقعی: فرض کن یه اپلیکیشن داری که لیست محصولات پرفروش رو نشون میده. این لیست هر چند ساعت آپدیت میشه. میتونی نتیجه کوئری رو تو کش بذاری و هر وقت کاربر درخواست داد، بهجای کوئری جدید، از همون کش استفاده کنی. هم سرعت بالاتره، هم منابع کمتر مصرف میشه.
البته حواست باشه کش باید مدیریت بشه؛ مثلاً Cache Invalidation (باطلسازی کش) یا تنظیم TTL (زمان زنده بودن) خیلی مهمه تا داده های کش همیشه معتبر بمونن. اگه چند لایه کش داشته باشی (مثلاً کش سمت کلاینت، سرور و دیتابیس)، کار خیلی بهتر پیش میره.
اگه زیاد با سرویسهای خارجی کار میکنی، بهتره درخواستها رو کاهش بدی. یه راهش Batching درخواستهاست؛ یعنی چند درخواست مرتبط رو یهجا بفرستی. استفاده از GraphQL هم میتونه بهجای چندین درخواست REST کار رو راحتتر کنه و فقط دادههایی که نیاز داری رو بگیری.
این چند تا تکنیک که گفتم، همیشه برای بهبود عملکرد وبسرویسها کارسازه: شناسایی باتلنکها با ابزارهای مناسب، بهینهسازی کوئریها، استفاده از Concurrency و کش کردن دادههای تکراری. هر بار با این روشها کار کنم، تغییرات بزرگی توی سرعت و کارایی سیستم میبینم. در نهایت، بهتره همیشه تستهای Load Testing و Stress Testing رو انجام بدی تا بدونی سیستم چطور زیر بار واقعی کار میکنه و برای مقیاس پذیری آماده باشی.