الأمن والامتثالتأمين الأنظمة عالية الحمل وإطلاقها: حافة محصَّنة ونشر بلا توقف
طبقات الأمان من CDN وWAF إلى الشبكة الخاصة، وخط تسليم يعتمد النشر التدريجي وblue/green وترحيلات expand and contract، وينتهي بقائمة جاهزية لليوم الذي تأتي فيه الذروة.
الذروة المجدولة تبلغ قمتها في ثوانٍ، بينما يحتاج الخادم الجديد إلى دقائق. تعرّف على ما يتوسع تحت الضغط، ولماذا يتفوق التوسع المسبق على القواعد التفاعلية، وكيف تضبط طبقتي SSR وPHP-FPM وتختبر الحمل.
Author
Anichur Rahaman

عشرون طلبًا في الثانية، بلا أي ارتفاع: هذه زيارات متجر إلكتروني عند الساعة 11:59 مساءً في الليلة التي تنطلق فيها حملة تخفيضات خاطفة، ومسؤول المنصة يراقب لوحة المتابعة، وقاعدة التوسع التلقائي (autoscaling) جاهزة: أضف خادمًا إذا بقي استخدام المعالج فوق 70% لمدة خمس دقائق. (مشهد توضيحي افتراضي، وليس متجرًا حقيقيًا.)
عند منتصف الليل تنطلق الحملة، ويتحرك نحو ألف مشترٍ في الدقيقة نفسها. يقفز الحمل إلى 440 طلبًا في الثانية. عند 12:01 ما زالت لوحة المتابعة خضراء، لأن متوسط الدقائق الخمس بالكاد تحرك. وعند 12:03 تنتهي مهلة صفحة الدفع. ينضم الخادم الجديد عند 12:06، بعد أن يكون المشترون قد ذهبوا إلى منافس.
التوسع التلقائي يستجيب بعد الحمل، والذروة المجدولة تصل قبل الاستجابة. يشرح هذا المقال ما الذي يتوسع فعلًا تحت ضغط مفاجئ، وما الذي لا يجوز أن نطلب منه التوسع، وكيف تتأكد من ذلك قبل الحدث لا في أثنائه.
الأرقام أدناه من اختباراتي الخاصة على إعداد واحد يعمل بـ Laravel وNext.js. هي تُظهر الطريقة، وليست معيارًا عامًا يصلح لكل نظام، فأعد القياس على نظامك.
هذا هو الجزء الأول من سلسلة «هندسة الأنظمة عالية الحمل» المؤلفة من خمسة أجزاء. يتناول مسار الطلب من الحافة إلى التطبيق. أما قوائم الانتظار وطبقة البيانات والمراقبة والنشر الآمن فتأتي في الأجزاء من 2 إلى 5.
نمطان من الزيارات يحتاجان إلى جوابين مختلفين. الارتفاع التدريجي يمنح أي حلقة تحكم وقتًا لتلاحظ وتستجيب. أما القفزة المفاجئة فلا تمنحها شيئًا. إذا انتقل الحمل من 20 إلى 440 طلبًا في الثانية خلال دقيقة، فإن قاعدة تراقب متوسط استخدام المعالج على مدى خمس دقائق لم ترَ شيئًا بعد.
والخبر الجيد أن الذروة المجدولة يمكن توقعها. تعرف وقت البدء، وتعرف تقريبًا عدد القادمين، وغالبًا تستطيع إعادة تشغيل مزيج الطلبات من الحدث السابق. هذا العلم المسبق هو أقوى أدواتك، ومعظم هذا المقال عن كيفية استثماره.
قبل أن تقرر ما الذي ستوسّعه، ارسم المسار الذي يسلكه الطلب. في الإعدادات التي أشغّلها يبدو هكذا: شبكة توصيل المحتوى (CDN) وجدار حماية التطبيقات (WAF) عند الحافة، ثم موازن أحمال مُدار، ثم عقدتان للويب في الواجهة الخلفية، وعقدة واحدة أو أكثر لواجهة العرض المُصيَّرة من الخادم (SSR)، وعقدة عمّال للمهام الخلفية، وخلف كل ذلك طبقة البيانات.

لكل صندوق نمط فشله ورافعة توسعه. ومعاملة الجميع كأنهم «خادم واحد» هي ما يدفع الفريق إلى زيادة الذاكرة في الطبقة التي لم تكن يومًا سبب المشكلة.
ابدأ بوضع CDN في المقدمة. الملفات الثابتة والصور وأي صفحة واحدة لكل الزوار لا ينبغي أن تصل إلى خوادمك أثناء الذروة. وقواعد WAF وإدارة الروبوتات في الطبقة نفسها تمنع أدوات الكشط من استهلاك طاقة حجزتها للمشترين.
خلف CDN استخدم موازن أحمال مُدارًا مع فحوص الصحة وتفريغ الاتصالات (connection draining). فحوص الصحة تُخرج العقدة المريضة تلقائيًا. والتفريغ يتيح للعقدة إنهاء طلباتها الجارية قبل أن تغادر، وبهذا تصبح عمليات النشر وتقليص العقد غير مرئية للمستخدمين.
هناك تفصيلان يستحقان التحقق. الأول خوارزمية التوزيع. كثير من موازنات السحابة تعمل افتراضيًا بالتناوب الدائري (round robin)، وهو يفترض أن كلفة كل طلب متساوية. وهذا غير صحيح لعمليتي الدفع والبحث. نمط «أقل الطلبات المعلّقة» أو «أقل الاتصالات» يرسل الطلب التالي إلى العقدة التي بين يديها أقل عمل. في AWS مثلًا، التناوب الدائري هو الإعداد الافتراضي لموازن Application Load Balancer، وأقل الطلبات المعلّقة خيار على مستوى مجموعة الأهداف. الثاني أن موازن الأحمال نفسه خدمة تتوسع. توضح وثائق AWS أن موازن Application Load Balancer يستطيع مضاعفة طاقته تقريبًا في خمس دقائق، وتتيح حجز طاقة مسبقًا للأحداث التي تزيد فيها الزيارات على الضعف في أقل من خمس دقائق (راجع دليل حجز الطاقة). ولدى المزوّدين الآخرين حدود مشابهة، فاسأل مزوّدك.
في اختبارات الحمل التي أجريتُها لم يكن موازن الأحمال عنق الزجاجة قط: بقي استخدام المعالج عند 8% أو أقل دون أي أخطاء عند أعلى معدل. وهذا هو المعتاد. ابحث أولًا في أعماق المسار.
لا يمكنك وضع عقدتين خلف موازن الأحمال إلا إذا كانت أي عقدة قادرة على خدمة أي طلب. تسمى هذه الخاصية «انعدام الحالة» (stateless)، وضبطها من البداية رخيص، أما ترقيعها لاحقًا فمؤلم. استخدم هذه القائمة قبل إضافة العقدة الثانية.
المنصة ذاتية الاستضافة مثل StoreConsole تحفظ الجلسات والذاكرة المؤقتة في Redis لهذا السبب، لتبقى العقد قابلة للتبديل.
هنا فوجئت أكثر من مرة. خادم 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 مجمّعًا من عمليات العمّال (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 دقائق أو أكثر | النمو التدريجي، والزيارات غير المخطط لها |
| التوسع على مستوى العمليات | ثوانٍ | عمّال قوائم الانتظار داخل الحاوية |

لم يأتِ أي رقم مما سبق بالتخمين. جاءت كلها من اختبارات تشبه الحدث. الاختبار الذي يطرق الصفحة الرئيسية فقط لا يثبت شيئًا يُذكر. اتبع هذا التسلسل.
لا تُرقِّ العتاد قبل أن يثبت الاختبار أين الحد. في حالتي كانت الأجهزة سليمة. الحد كان العملية أحادية الخيط، ولم يكلّف الحل سوى تغيير في الإعدادات.
أعد تشغيل المشهد الأول بعد إتمام الاستعداد. عند 11:00 مساءً يرتفع الحد الأدنى لعقد الويب من اثنتين إلى ثلاث، وتعمل طبقة SSR بثلاث عمليات خلف least_conn. الذاكرة المؤقتة دافئة، ومجمّع FPM يضم 30 عاملًا من البداية.
عند منتصف الليل تصل 440 طلبًا في الثانية. السقف المقيس نحو 660، فيبقى p95 قرب مستواه المعتاد ولا تعمل قاعدة التوسع التلقائي أصلًا، لأن أحدًا لا يحتاج إليها. وعند 12:30 يعود الحد الأدنى إلى ما كان. كلّف الحدث كله بضع ساعات من عقدة إضافية واحدة.
الطبقات الخلفية لهذا المسار تنكسر بطرق أخرى. فالمهام الخلفية هي التي تنهار عادةً أولًا تحت الضغط المفاجئ، والجزء 2 يتناول قوائم الانتظار والعمّال على نطاق واسع. ويتجه الجزء 3 إلى طبقة البيانات تحت الحمل، والجزء 4 إلى المراقبة (observability)، والجزء 5 إلى الأمن والنشر بلا انقطاع. ولتحديد حجم الخادم الذي يحتاجه نشاطك مع نموه، راجع مقالي السابق عن خارطة نمو الخادم.
Anichur Rahaman مهندس برمجيات معماري ومؤسس StoreConsole، يصمّم أنظمة التجارة وERP للشركات النامية، ويهتم خصوصًا بالبنية القائمة على الأحداث وسلامة البيانات وتشغيل الأنظمة على خوادم الشركة نفسها.
About the Author
Anichur Rahaman
Continue Reading