كيف يؤثر إعداد PHP-FPM Pool على استجابة سيرفرك تحت الضغط

إعدادات PHP-FPM pool تحدد بهدوء ما إذا كان سيرفرك سيتعامل مع ارتفاعات الزيارات بسلاسة أو سيدخل الطلبات في قائمة انتظار بطيئة. إليك كيفية ضبطها بشكل صحيح لتقليل وقت استجابة السيرفر تحت الضغط.

إذا كان موقعك يبدو سريعًا عندما لا يزوره أحد، لكنه يصبح بطيئًا جدًا بمجرد ازدياد الزيارات، فالمشكلة نادرًا ما تكون في الكود الخاص بك. غالبًا ما تكون المشكلة في PHP-FPM. وتحديدًا، في كيفية إعداد الـ pool الخاص به للتعامل مع الطلبات المتزامنة. إذا أخطأت في هذا الإعداد، سترى وقت استجابة السيرفر يرتفع رغم أن استخدام المعالج والذاكرة يبدو طبيعيًا على الورق.

دعنا نشرح ما يفعله PHP-FPM فعليًا، ولماذا تُعد إعدادات الـ pool مهمة جدًا، وكيف يمكنك ضبطها لتقليل وقت استجابة السيرفر تحت الضغط الفعلي للزيارات.

ما الذي يفعله PHP-FPM فعليًا

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

وهذه القائمة هي المكان الذي يتدهور فيه وقت الاستجابة بهدوء. الطلب الذي يفترض أن يستغرق 80 ميلي ثانية قد ينتهي به الأمر منتظرًا 2-3 ثوانٍ فقط لتتوفر له عملية، خاصة أثناء ارتفاع مفاجئ في الزيارات.

الإعدادات الأساسية التي تتحكم في السعة

إعدادات الـ pool الخاصة بـ PHP-FPM (عادة في /etc/php/8.x/fpm/pool.d/www.conf) تحتوي على عدة توجيهات تحدد عدد الطلبات التي يمكنك خدمتها في نفس الوقت:

  • pm - وضع إدارة العمليات: static أو dynamic أو ondemand
  • pm.max_children - الحد الأقصى لعدد عمليات PHP المتزامنة
  • pm.start_servers - عدد العمليات التي تبدأ عند الإقلاع
  • pm.min_spare_servers / pm.max_spare_servers - نطاق العمليات الخاملة في وضع dynamic
  • pm.max_requests - عدد الطلبات التي تعالجها العملية قبل إعادة تشغيلها (يساعد في تفادي تسريبات الذاكرة)

Static مقابل Dynamic مقابل Ondemand

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

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

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

لماذا max_children هو الإعداد الذي يخطئ فيه معظم الناس

هذا الرقم الواحد هو الفارق بين سيرفر يتعامل مع ارتفاع مفاجئ في الزيارات بسلاسة، وسيرفر ينهار تمامًا. إذا ضبطته منخفضًا جدًا، ستنتظر الطلبات في قائمة رغم أن السيرفر لديه ذاكرة ومعالج فائضين غير مستخدمين. وإذا ضبطته مرتفعًا جدًا، فأنت تخاطر بنفاد ذاكرة السيرفر، مما يشغّل آلية OOM killer ويوقف PHP-FPM بالكامل.

المعادلة بسيطة:

pm.max_children = (الذاكرة المتاحة لـ PHP) / (متوسط الذاكرة لكل عملية PHP)

على سبيل المثال، إذا خصصت 2 جيجابايت من الذاكرة لـ PHP وكان متوسط استهلاك كل عملية 40 ميجابايت، يمكنك بأمان تشغيل حوالي 50 عملية. تحقق من استهلاك الذاكرة الفعلي باستخدام:

ps -ylC php-fpm8.1 --sort:rss

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

كيف يظهر استنزاف الـ pool في وقت استجابة السيرفر

عندما تكون كل العمليات مشغولة، تدخل الطلبات الجديدة في قائمة انتظار محددة بواسطة listen.backlog. من الخارج، يبدو الأمر وكأن السيرفر أصبح بطيئًا فجأة. قد تُظهر أدوات المراقبة استخدامًا طبيعيًا للمعالج، وأوقات استعلام قاعدة بيانات طبيعية، ومع ذلك ترتفع نسبة الوقت حتى وصول أول بايت (time to first byte). وهذه هي العلامة: الطلبات لا تُعالج بشكل أبطأ، بل تنتظر وقتًا أطول لتبدأ.

يمكنك التأكد من ذلك عبر تفعيل صفحة حالة FPM:

pm.status_path = /status

مراجعتها أثناء ارتفاع الزيارات تُظهر لك أعداد listen queue وmax children reached. إذا كانت هذه الأرقام في ازدياد، فهذا يعني أن حجم الـ pool لديك أصغر من حجم الزيارات التي تستقبلها.

خطوات عملية لتقليل وقت استجابة السيرفر

  • قِس متوسط استهلاك ذاكرة عمليات PHP تحت زيارات فعلية، وليس باختبارات اصطناعية
  • اضبط max_children بناءً على الذاكرة المتاحة، مع ترك مساحة لـ MySQL وRedis ونظام التشغيل
  • استخدم وضع static إذا كانت زياراتك ثابتة ويمكن توقعها
  • اضبط pm.max_requests على قيمة بين 500-1000 لإعادة تدوير العمليات وتفادي تراكم استهلاك الذاكرة
  • راقب صفحة الحالة بانتظام، وليس فقط عندما تحدث مشكلة

هذا بالضبط نوع الضبط الذي يستفيد من المراقبة على مستوى السيرفر بدلًا من التخمين. نحن نراقب مقاييس PHP-FPM كجزء من الصحة العامة للسيرفر، بحيث يتم تعديل حجم الـ pool قبل أن يتحول إلى بطء ملحوظ وليس بعده. إذا كنت تدير سيرفرك الخاص، فإن إعداد مراقبة التوفر والأداء التي تتابع هذا النوع من سلوك قوائم الانتظار يستحق الوقت المبذول فيه.

أين يأتي دور التخزين المؤقت (Caching)

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

الخلاصة

إعداد PHP-FPM pool هو أحد تلك الإعدادات التي تبقى غير مرئية حتى تختبرها الزيارات الفعلية. الـ pool سيئ الضبط يعني أن وقت استجابة السيرفر يبدو جيدًا في الساعات الهادئة وينهار بمجرد ظهور المستخدمين الحقيقيين. قِس استهلاك الذاكرة الفعلي لديك، واضبط max_children بناءً عليه، وراقب صفحة الحالة أثناء الزيارات الفعلية، وليس فقط بعد تلقي شكوى. لمزيد من المعلومات حول تشخيص هذا النوع من البطء على مستوى السيرفر، راجع لماذا يكلفك وقت الوصول لأول بايت عمليات تحويل ضائعة وكيف تصلح ذلك.