One self-hosted console to run your entire business — commerce, ERP, HRM, CRM & manufacturing

التوسع التلقائي عند ذروة الزيارات: ما الذي يتوسع فعلًا وما الذي لا ينبغي له

الذروة المجدولة تبلغ قمتها في ثوانٍ، بينما يحتاج الخادم الجديد إلى دقائق. تعرّف على ما يتوسع تحت الضغط، ولماذا يتفوق التوسع المسبق على القواعد التفاعلية، وكيف تضبط طبقتي SSR وPHP-FPM وتختبر الحمل.

Author

Anichur Rahaman

منذ 3 أسابيع11 min read2 views
التوسع التلقائي عند ذروة الزيارات: ما الذي يتوسع فعلًا وما الذي لا ينبغي له

عشرون طلبًا في الثانية، بلا أي ارتفاع: هذه زيارات متجر إلكتروني عند الساعة 11:59 مساءً في الليلة التي تنطلق فيها حملة تخفيضات خاطفة، ومسؤول المنصة يراقب لوحة المتابعة، وقاعدة التوسع التلقائي (autoscaling) جاهزة: أضف خادمًا إذا بقي استخدام المعالج فوق 70% لمدة خمس دقائق. (مشهد توضيحي افتراضي، وليس متجرًا حقيقيًا.)

عند منتصف الليل تنطلق الحملة، ويتحرك نحو ألف مشترٍ في الدقيقة نفسها. يقفز الحمل إلى 440 طلبًا في الثانية. عند 12:01 ما زالت لوحة المتابعة خضراء، لأن متوسط الدقائق الخمس بالكاد تحرك. وعند 12:03 تنتهي مهلة صفحة الدفع. ينضم الخادم الجديد عند 12:06، بعد أن يكون المشترون قد ذهبوا إلى منافس.

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

الأرقام أدناه من اختباراتي الخاصة على إعداد واحد يعمل بـ Laravel وNext.js. هي تُظهر الطريقة، وليست معيارًا عامًا يصلح لكل نظام، فأعد القياس على نظامك.

هذا هو الجزء الأول من سلسلة «هندسة الأنظمة عالية الحمل» المؤلفة من خمسة أجزاء. يتناول مسار الطلب من الحافة إلى التطبيق. أما قوائم الانتظار وطبقة البيانات والمراقبة والنشر الآمن فتأتي في الأجزاء من 2 إلى 5.

الذروة ليست يومًا مزدحمًا

نمطان من الزيارات يحتاجان إلى جوابين مختلفين. الارتفاع التدريجي يمنح أي حلقة تحكم وقتًا لتلاحظ وتستجيب. أما القفزة المفاجئة فلا تمنحها شيئًا. إذا انتقل الحمل من 20 إلى 440 طلبًا في الثانية خلال دقيقة، فإن قاعدة تراقب متوسط استخدام المعالج على مدى خمس دقائق لم ترَ شيئًا بعد.

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

مسار الطلب من الحافة إلى التطبيق

قبل أن تقرر ما الذي ستوسّعه، ارسم المسار الذي يسلكه الطلب. في الإعدادات التي أشغّلها يبدو هكذا: شبكة توصيل المحتوى (CDN) وجدار حماية التطبيقات (WAF) عند الحافة، ثم موازن أحمال مُدار، ثم عقدتان للويب في الواجهة الخلفية، وعقدة واحدة أو أكثر لواجهة العرض المُصيَّرة من الخادم (SSR)، وعقدة عمّال للمهام الخلفية، وخلف كل ذلك طبقة البيانات.

بنية مرجعية: CDN وWAF، موازن الأحمال، عقد ويب خلفية وعقد واجهة SSR، عقدة العمّال، ثم قاعدة البيانات وRedis وتخزين الكائنات
مسار الطلب من الحافة إلى البيانات. كل طبقة تتوسع بطريقة مختلفة وبسرعة مختلفة.

لكل صندوق نمط فشله ورافعة توسعه. ومعاملة الجميع كأنهم «خادم واحد» هي ما يدفع الفريق إلى زيادة الذاكرة في الطبقة التي لم تكن يومًا سبب المشكلة.

الحافة وموازن الأحمال

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

خلف CDN استخدم موازن أحمال مُدارًا مع فحوص الصحة وتفريغ الاتصالات (connection draining). فحوص الصحة تُخرج العقدة المريضة تلقائيًا. والتفريغ يتيح للعقدة إنهاء طلباتها الجارية قبل أن تغادر، وبهذا تصبح عمليات النشر وتقليص العقد غير مرئية للمستخدمين.

هناك تفصيلان يستحقان التحقق. الأول خوارزمية التوزيع. كثير من موازنات السحابة تعمل افتراضيًا بالتناوب الدائري (round robin)، وهو يفترض أن كلفة كل طلب متساوية. وهذا غير صحيح لعمليتي الدفع والبحث. نمط «أقل الطلبات المعلّقة» أو «أقل الاتصالات» يرسل الطلب التالي إلى العقدة التي بين يديها أقل عمل. في AWS مثلًا، التناوب الدائري هو الإعداد الافتراضي لموازن Application Load Balancer، وأقل الطلبات المعلّقة خيار على مستوى مجموعة الأهداف. الثاني أن موازن الأحمال نفسه خدمة تتوسع. توضح وثائق AWS أن موازن Application Load Balancer يستطيع مضاعفة طاقته تقريبًا في خمس دقائق، وتتيح حجز طاقة مسبقًا للأحداث التي تزيد فيها الزيارات على الضعف في أقل من خمس دقائق (راجع دليل حجز الطاقة). ولدى المزوّدين الآخرين حدود مشابهة، فاسأل مزوّدك.

في اختبارات الحمل التي أجريتُها لم يكن موازن الأحمال عنق الزجاجة قط: بقي استخدام المعالج عند 8% أو أقل دون أي أخطاء عند أعلى معدل. وهذا هو المعتاد. ابحث أولًا في أعماق المسار.

عقد الويب عديمة الحالة: ثمن الدخول

لا يمكنك وضع عقدتين خلف موازن الأحمال إلا إذا كانت أي عقدة قادرة على خدمة أي طلب. تسمى هذه الخاصية «انعدام الحالة» (stateless)، وضبطها من البداية رخيص، أما ترقيعها لاحقًا فمؤلم. استخدم هذه القائمة قبل إضافة العقدة الثانية.

  1. الجلسات في Redis أو Valkey، لا في ملفات على قرص العقدة.
  2. ذاكرة التطبيق المؤقتة (cache) في Redis أو Valkey، تشترك فيها كل العقد.
  3. الملفات المرفوعة والمولَّدة في تخزين الكائنات (متوافق مع S3)، وليس على القرص المحلي أبدًا.
  4. مُجدوِل واحد فقط. المهام الدورية على نمط cron تعمل على عقدة واحدة بالضبط، وإلا نُفّذت كل مهمة مرتين.
  5. بناء متطابق. كل عقدة تشغّل الصورة نفسها، والإعدادات تأتي من البيئة.
  6. حدود رفع متوافقة في كل طبقة. لكل وكيل (proxy) حدّه الخاص لحجم الطلب (client_max_body_size في nginx، وpost_max_size وupload_max_filesize في PHP). في أحد الإعدادات ظل الحد الافتراضي 1 ميغابايت على وكيل المضيف يرفض الملفات بصمت حتى تطابقت كل الطبقات.

المنصة ذاتية الاستضافة مثل StoreConsole تحفظ الجلسات والذاكرة المؤقتة في Redis لهذا السبب، لتبقى العقد قابلة للتبديل.

طبقة SSR: عملية Node واحدة تساوي نواة واحدة

هنا فوجئت أكثر من مرة. خادم Next.js يُصيِّر الصفحات داخل عملية Node.js، وNode ينفّذ شيفرة JavaScript على خيط واحد. فمهما كبر الجهاز، تستطيع العملية الواحدة استخدام نواة واحدة تقريبًا في التصيير.

أعدتُ بأداة k6 مزيج الطلبات الحقيقي لذروة سابقة على عملية واجهة واحدة. توقفت عن الاستجابة عند نحو 220 إلى 250 طلبًا في الثانية. بعد ذلك قفز زمن الاستجابة p95 من نحو 260 ملّي ثانية إلى نحو 3 ثوانٍ، وبلغ p99 ‏6.4 ثانية. وطوال ذلك ظلت أكثر من نواتين خاملتين على المضيف. أما دقيقة الذروة في الحدث الحقيقي فاحتاجت إلى نحو 440 طلبًا في الثانية، أي أن العملية الواحدة كانت ستفشل في اللحظة التي تلزم فيها أكثر من أي وقت.

كان الحل بسيطًا: تشغيل عدة حاويات واجهة متطابقة من الصورة نفسها (ثلاث في حالتي) خلف nginx بإعداد least_conn الذي يمرر كل طلب إلى الخادم الأقل اتصالات نشطة، ثم إعادة الاختبار عند المعدل المستهدف. وهناك نقطة تخص Next.js: كل نسخة تحتفظ افتراضيًا بذاكرة مؤقتة محلية خاصة بها، فإذا شغّلت عدة نسخ فاضبط الإطار على مخزن مؤقت مشترك كما تشرح وثائقه.

الحساب

تخطيط الطاقة هنا ضرب بسيط. خذ الحد المقيس لكل عملية، واضربه في عدد العمليات، وقارنه بالذروة المتوقعة مع هامش أمان.

البندالقيمة (إعداد واحد)ملاحظة
المطلوب في دقيقة الذروة~440 req/sمن إعادة تشغيل الحدث الحقيقي السابق
عملية SSR واحدة، مقيسة220-250 req/sبعدها ارتفع p95 من ~260 ms إلى ~3 s
ثلاث عمليات SSR~660-750 req/sنحو 1.5 ضعف الذروة كهامش أمان
استخدام معالج موازن الأحمال8% أو أقللم يكن عنق الزجاجة

PHP-FPM: حدّد حجم المجمّع عن قصد

الواجهة الخلفية للويب لها المشكلة المعاكسة. يشغّل PHP-FPM مجمّعًا من عمليات العمّال (workers)، وكل عامل يعالج طلبًا واحدًا في كل مرة. إن كان المجمّع صغيرًا انتظرت الطلبات في طابور. وإن كان كبيرًا جدًا نفدت ذاكرة العقدة وبدأ التبديل إلى القرص أو إنهاء العمليات.

قاعدة تقريبية تنفع في العمل: عدد العمّال المنشغلين المطلوب ≈ الطلبات في الثانية × متوسط زمن الطلب. مثال توضيحي: 200 طلب في الثانية بزمن 150 ملّي ثانية لكل طلب يحتاج نحو 30 عاملًا منشغلًا، فمجمّع من 60 يترك مجالًا للطلبات البطيئة. ثم تحقق من الذاكرة: إذا كان العامل الواحد يستهلك نحو 100 ميغابايت فإن 60 عاملًا يحتاجون نحو 6 غيغابايت، فيجب أن تزيد ذاكرة العقدة على ذلك.

القيم التي استخدمتها في أحد الإعدادات هي pm.max_children=60 وstart_servers=30 وmin_spare_servers=20 وmax_spare_servers=30 وmax_requests=1000، مع اتصال keepalive بين nginx وFPM (keepalive 16 وfastcgi_keep_conn on). البدء بـ 30 عاملًا مهم: يكون المجمّع دافئًا عند وصول الموجة بدل أن يتفرّع تحت الضغط.

عادتان أخريان تؤتيان ثمارهما. فعّل OPcache في الإنتاج مع validate_timestamps=0، وأعد تشغيل العمّال عند كل نشر. وانظر في استخدام fastcgi_next_upstream error timeout http_500 http_503 مع fastcgi_next_upstream_tries 2، فقد حوّل ذلك أعطال FPM العابرة إلى إعادة محاولة صامتة. وهذا آمن فقط للطلبات المتماثلة الأثر (idempotent). إعادة محاولة طلب POST لدفعة مالية ليست حلًّا، بل خلل.

لماذا يصل التوسع التفاعلي متأخرًا

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

الذروة المجدولة تبلغ قمتها في ثوانٍ. فيصل الجهاز الجديد بعد أن يكون الناس قد رحلوا، أو أسوأ من ذلك: بعد أن رأوا الأخطاء. كما أن الجهاز الافتراضي لا يمكن رفع ذاكرته دون إعادة تشغيل، فلا يُجدي أيضًا «التكبير في مكانه».

جدول زمني يقارن التوسع التفاعلي الذي يضيف الطاقة بعد دقائق من قمة الذروة بالتوسع المسبق الذي يجهز الطاقة قبل الحدث
التوسع التفاعلي يلاحق الحمل، والتوسع المسبق ينتظره جاهزًا. جدول زمني توضيحي.

وسّع مسبقًا للأحداث التي تعرفها

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

أبقِ القواعد التفاعلية شبكة أمان لما لا تتوقعه، لا خطة أساسية. ووسّع ما يتوسع بسرعة. العمليات داخل الحاوية تبدأ في ثوانٍ، أما الأجهزة فتحتاج دقائق، ولهذا أوسّع عمّال قوائم الانتظار بالعمليات لا بالأجهزة (في الجزء 2 من هذه السلسلة).

الأسلوبزمن الاستجابةالأنسب لـ
التوسع المسبق (رفع الحد الأدنى)جاهز قبل الحدثالذروات المجدولة
التوسع التفاعلي للأجهزة الافتراضية1-3 دقائق أو أكثرالنمو التدريجي، والزيارات غير المخطط لها
التوسع على مستوى العملياتثوانٍعمّال قوائم الانتظار داخل الحاوية
مخطط انسيابي: هل موعد الذروة معروف، وهل يرتفع الحمل ببطء، وهل العمل مهام في قائمة انتظار، وإلى أي أسلوب توسع تقود كل إجابة
أي رافعة تسحب ومتى: ثلاثة أسئلة تحسم الاختيار بين التوسع المسبق والتفاعلي وعلى مستوى العمليات وحماية الحافة.

اختبر الحمل كما لو كان الحدث الحقيقي

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

  1. التقط مزيج الطلبات الحقيقي من سجلات الوصول للحدث السابق: أي عناوين، وبأي نسب، وبأي فترات توقف بين طلبات المستخدم.
  2. أعد تشغيله بأداة k6 (أو أداة مماثلة) بالمعدل المتوقع، ثم بـ 1.5 ضعفه.
  3. ضع عتبات إيقاف في السكربت، مثل p95 فوق 3 ثوانٍ، أو أي خطأ 5xx أو مهلة، كي يتوقف الاختبار الفاشل بنفسه ولا يضر نظامًا مشتركًا.
  4. أضف سكربت حراسة يراقب استخدام المعالج في العقد ويوقف التشغيل إذا بلغت الحدود.
  5. قارن مقاييسك بمقاييس مزوّد السحابة. في اختباراتي تطابقت ضمن نسبة قليلة، فاطمأننت إلى أن اللوحات موثوقة.
  6. غيّر شيئًا واحدًا في كل مرة، وأعد التشغيل عند المعدل المستهدف بعد كل إصلاح.

لا تُرقِّ العتاد قبل أن يثبت الاختبار أين الحد. في حالتي كانت الأجهزة سليمة. الحد كان العملية أحادية الخيط، ولم يكلّف الحل سوى تغيير في الإعدادات.

منتصف الليل نفسه، وقد استعددنا

أعد تشغيل المشهد الأول بعد إتمام الاستعداد. عند 11:00 مساءً يرتفع الحد الأدنى لعقد الويب من اثنتين إلى ثلاث، وتعمل طبقة SSR بثلاث عمليات خلف least_conn. الذاكرة المؤقتة دافئة، ومجمّع FPM يضم 30 عاملًا من البداية.

عند منتصف الليل تصل 440 طلبًا في الثانية. السقف المقيس نحو 660، فيبقى p95 قرب مستواه المعتاد ولا تعمل قاعدة التوسع التلقائي أصلًا، لأن أحدًا لا يحتاج إليها. وعند 12:30 يعود الحد الأدنى إلى ما كان. كلّف الحدث كله بضع ساعات من عقدة إضافية واحدة.

الطبقات الخلفية لهذا المسار تنكسر بطرق أخرى. فالمهام الخلفية هي التي تنهار عادةً أولًا تحت الضغط المفاجئ، والجزء 2 يتناول قوائم الانتظار والعمّال على نطاق واسع. ويتجه الجزء 3 إلى طبقة البيانات تحت الحمل، والجزء 4 إلى المراقبة (observability)، والجزء 5 إلى الأمن والنشر بلا انقطاع. ولتحديد حجم الخادم الذي يحتاجه نشاطك مع نموه، راجع مقالي السابق عن خارطة نمو الخادم.

أهم الخلاصات

  • الذروة المجدولة قفزة لا منحدر. والتوسع التفاعلي يحتاج دقائق، فيتحرك بعد أن تمر.
  • للأحداث المعروفة وسّع مسبقًا: ارفع الحد الأدنى لعدد العقد قبل البداية، وأنزله بعدها.
  • أبقِ عقد الويب عديمة الحالة: الجلسات والذاكرة المؤقتة في Redis، والملفات في تخزين الكائنات، ومُجدوِل واحد.
  • عملية Node.js واحدة لـ SSR تستهلك نواة واحدة تقريبًا. شغّل عدة عمليات متطابقة خلف least_conn واختبر عند المعدل المستهدف.
  • حدّد حجم مجمّع PHP-FPM من الطلبات في الثانية وزمن الطلب وذاكرة كل عامل، وسخّنه مسبقًا.
  • اختبر الحمل بمزيج الطلبات الحقيقي وعتبات الإيقاف وسكربت حراسة، وأصلح العائق المقيس قبل شراء العتاد.

Anichur Rahaman مهندس برمجيات معماري ومؤسس StoreConsole، يصمّم أنظمة التجارة وERP للشركات النامية، ويهتم خصوصًا بالبنية القائمة على الأحداث وسلامة البيانات وتشغيل الأنظمة على خوادم الشركة نفسها.

About the Author

Anichur Rahaman

Continue Reading