المدوّنة

كيف يمكن لمحادثة واحدة ثقيلة أن تُسقط واجهة البرمجة لدينا

في 28 سبتمبر، بعد نحو 17 دقيقة من إطلاق نسخة جديدة، نفدت الذاكرة في خادمي واجهة البرمجة الإنتاجيين في اللحظة نفسها، فأعيد تشغيلهما، وبقي موازن الأحمال لنحو خمس دقائق دون أي خادم سليم يوجّه إليه الطلبات. كان إصلاحنا الأول مضاعفة ذاكرة واجهة البرمجة. أما السبب الحقيقي فظهر لاحقاً في اليوم نفسه: فتح محادثة بعينها كان يطلب من واجهة البرمجة تحميل 53 ميغابايت من الرسائل في طلب واحد. وصار سجل المحادثات يُحمّل في صفحات محدودة بالحجم والعدد معاً، وتُفتح تلك المحادثة الآن بصفحة أولى بحجم 5 ميغابايت بدلاً من أن تُسقط الخادم.

ما الذي تعطّل

تعمل واجهة البرمجة الخاصة بنا كحاوية (container) على كل من خادمين خلف موازن أحمال. كان لكل حاوية حد ذاكرة قدره 384 ميبيبايت. وفي وضع الراحة تستخدم 130 إلى 140 ميبيبايت، فبدا الحد وكأن هناك هامشاً كبيراً.

بلغت الحاويتان الحد معاً وتعطّلتا برسالة «JavaScript heap out of memory». والكومة (heap) هي الذاكرة التي تستخدمها عملية Node.js للكائنات التي تعمل عليها. ولأن الخادمين فشلا معاً، لم يبق مكان يوجَّه إليه الطلبات. وهذا أسوأ أنواع الانقطاع لإعداد بخادمين: التكرار يحمي فقط عندما لا تحدث الأعطال في الوقت نفسه.

الإصلاح الأول: مساحة أكبر

كان التوقيت يشير إلى النشر. فبعد الإطلاق، تُستأنف جولات الوكيل التي قاطعها النشر، وتحفظ نصوصها من جديد، وبدا ذلك الانفجار في العمل المصدر الواضح لارتفاع الذاكرة.

لذلك رفع أول تغيير حد الذاكرة لواجهة البرمجة من 384 ميبيبايت إلى 768 ميبيبايت. طبّقناه مباشرةً على الخادمين في اليوم نفسه، وأودعناه في إعدادات النشر حتى لا يعيد النشر التالي القيمة 384 دون أن ننتبه. والإصلاح اليدوي على خادم يتراجع عنه النشر التالي طريقة شائعة لعودة الانقطاع نفسه.

كانت الذاكرة الإضافية هامش أمان معقولاً، لكنها لم تكن تفسيراً.

السبب الحقيقي: طلب واحد، 71 ميغابايت

لاحقاً في ذلك اليوم أعدنا إنتاج الانهيار على نسخة من محادثة واحدة، باستخدام حد الذاكرة القديم للإنتاج (384 ميبيبايت). تسبّبت النسخة في انهيار واجهة البرمجة وحدها، دون أي نشر.

عندما تفتح مشروعاً، يطلب تطبيق الويب من واجهة البرمجة سجل المحادثة. وكانت نقطة النهاية تعيد أحدث 30 رسالة كاملة. بالنسبة لمعظم المحادثات، 30 رسالة شيء صغير. أما في هذه المحادثة، فكانت أحدث 30 رسالة بحجم 53 ميغابايت في قاعدة البيانات، واستجابة بحجم 71 ميغابايت. وكانت بعض ردود الوكيل تتراوح بين 4 و10 ميغابايت لكل منها، معظمها نصوص الوكلاء الفرعيين: وهم المساعدون الذين يبدؤهم الوكيل الرئيسي لتنفيذ جزء من المهمة بالتوازي، وتُحفظ جولاتهم الكاملة مع الرد.

بناء تلك الاستجابة الواحدة احتاج كومة أكبر مما تملكه واجهة البرمجة. وكل فتح أو تحديث للمشروع كان يعيد إرسال الطلب. وهكذا يمكن لمحادثة واحدة أن تُسقط أي خادم تصل إليه، وبما أن الصفحة قابلة لإعادة التحميل، يمكنها أن تصل إلى الخادمين كليهما.

كانت عبارة «30 رسالة» لم تكن حداً فعلياً للحجم أبداً. كانت حداً للعدد، وكان هذا العدد صغيراً بما يكفي للمحادثات العادية.

الحل: صفحات محدودة بالحجم إلى جانب العدد

صارت الصفحة من السجل تتكون من 30 رسالة أو نحو 4 ميغابايت منها، أيهما يأتي أولاً.

تعمل على خطوتين:

  1. اطلب الأحجام أولاً. تطلب واجهة البرمجة من قاعدة البيانات معرّفات الرسائل الأحدث وأحجامها المخزنة، دون محتوياتها. وهذا الاستعلام رخيص مهما كانت الرسائل كبيرة.
  2. حمّل ما يتسع فقط. بالسير من الأحدث إلى الأقدم، تضيف الرسائل حتى تتجاوز الرسالة التالية حد 4 ميغابايت للصفحة. ثم تُحمَّل تلك الصفوف فقط كاملةً.

قاعدتان تحفظان هذا الأمر:

  • تُضاف أحدث رسالة دائماً إلى الصفحة، مهما كان حجمها. فالصفحة التي لا تعيد شيئاً تترك المحادثة عالقة دون طريقة للرجوع إلى الرسائل السابقة.
  • لا يُقطع شيء. لا تُقتطع رسالة ولا تُحذف. يُحمَّل بقية المحادثة عبر عنصر «مرّر للأعلى للرسائل السابقة» الذي كانت الرسائل القديمة تستخدمه من قبل، صفحةً صفحةً.

وجدنا موضعاً آخر بالشكل نفسه. عندما توقف جولة، تفحص واجهة البرمجة آخر بضعة ردود لمعرفة إن كان أي منها فارغاً ويجب تنظيفه. وكانت تفعل ذلك بتحميل ثماني رسائل كاملة. وفي محادثة فيها ردود بعدة ميغابايت، تكون هذه المشكلة نفسها بشكل أصغر. أصبحت الآن تطرح على قاعدة البيانات سؤالاً بنعم أو لا، هل تحتوي هذه الرسالة على أي محتوى؟، دون تحميل المحتوى نفسه.

كيف تحققنا منه

على نسخة المحادثة، مع تشغيل الحد القديم البالغ 384 ميبيبايت:

  • الصفحة الأولى بحجم 5 ميغابايت. قبل ذلك، كان الطلب نفسه يُسقط واجهة البرمجة.
  • خمس علامات تبويب في المتصفح تفتح المحادثة في وقت واحد، وتبلغ ذروة الذاكرة 154 ميغابايت.
  • التمرير للخلف عبر المحادثة كلها يعيد جميع 190 رسالة، كل واحدة مرة واحدة، وبالترتيب.

منطق اختيار الصفحات له اختبارات وحدة أيضاً، ومنها اختبار بُني على الأحجام الحقيقية لأحدث رسائل تلك المحادثة. تعيد الصفحة الأولى رسالة واحدة، وتشير إلى وجود المزيد، وتبدأ الصفحة التالية من الرسالة التي تليها.

ما استخلصناه

  • العدد ليس حد حجم. أي نقطة نهاية تعيد «أحدث N» من شيء يمكن أن ينمو بلا حدود تحتاج إلى ميزانية بايتات أيضاً. ستجد البيانات الفجوة عاجلاً أم آجلاً.
  • المشتبه به الواضح ليس دائماً السبب. كان النشر محفِّزاً حقيقياً للعمل الإضافي، وكانت الذاكرة الأكبر تستحق الإضافة. لكن الانهيار أعيد إنتاجه دون نشر، ولولا ذلك لتوقفنا عند زيادة الذاكرة.
  • يتعطل خادمان معاً عندما يتلقيان الطلب نفسه. التكرار يحمي من تعطل خادم واحد. لا يحمي من طلب يُسقط أي خادم يصل إليه، خصوصاً طلب سيعيد المستخدم إرساله تلقائياً بتحديث الصفحة.
  • أصلح الأمر في الإعدادات، لا على الجهاز فقط. أُودعت زيادة الذاكرة في إعدادات النشر في اليوم نفسه، فحافظ النشر التالي عليها.

هذه ثاني قصة ذاكرة حديثة تتعلق بمخرجات الوكيل. والأولى كانت تسمية حالة نصية وضعت 690 ميغابايت من السلاسل في الطابور. وإذا كنت تستخدم وكلاء فرعيين في Mythex، فيمكنك قراءة كيف يعملون في الوثائق.

Mythex · Pricing · ابنِ أي شيء بالذكاء الاصطناعي · قوالب المواقع والتطبيقات · مقارنات Mythex · مصطلحات بناء التطبيقات بالذكاء الاصطناعي · أدوات مجانية لبناء التطبيقات بالذكاء الاصطناعي · English

English አማርኛ Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)