سایت بالا میآید، چند دقیقه کار میکند، بعد ناگهان نصف درخواستها با 502 Bad Gateway برمیگردند و در لاگ nginx خط connect() to unix:/run/php/php8.2-fpm.sock failed (11: Resource temporarily unavailable) تکرار میشود. اگر این را دیدهاید، تقریباً مطمئن باشید که مشکل کد نیست. مشکل این است که استخر PHP-FPM تمام ورکرهایش را مصرف کرده و صف درخواستها پر شده است.
PHP-FPM یک مدیر فرآیند است: تعدادی ورکر از پیش ساخته یا در لحظه ساخته میشود، هر ورکر در هر لحظه فقط یک درخواست PHP را اجرا میکند، و بقیه درخواستها در صف میمانند. وقتی صف پر شود، nginx دیگر جایی برای وصل شدن ندارد و همان 502 را میدهد. کل تنظیم درست، جواب دادن به یک سؤال است: چند ورکر میتوانم همزمان داشته باشم بدون اینکه حافظه سرور تمام شود؟
pm.max_children را از روی حافظه حساب کنید، نه از روی حس
عدد pm.max_children سقف ورکرهای همزمان است. اگر آن را بزرگتر از ظرفیت واقعی سرور بگذارید، بهجای 502 خطای بدتری میگیرید: OOM Killer لینوکس یکی از فرآیندها را میکشد و ممکن است MySQL را از دست بدهید.
اول اندازه واقعی یک ورکر را اندازه بگیرید. یک اسکریپت ساده بسازید که فقط sleep(30) کند، چند درخواست همزمان به آن بفرستید، و بعد:
ps -o rss= -C php-fpm8.2 | awk '{s+=$1; n++} END {print s/n/1024 " MB avg, " n " workers"}'
خروجی معمول برای یک سایت وردپرسی با چند افزونه سنگین بین 60 تا 120 مگابایت برای هر ورکر است. حالا حساب ساده است. روی سروری با 4 گیگابایت رم که 1.5 گیگابایت آن به MySQL و کش و سیستمعامل میرسد، حدود 2.5 گیگابایت برای PHP میماند. با ورکر 80 مگابایتی، سقف منطقی حدود 30 ورکر است، نه 100.
یک نکته که خیلیها از دست میدهند: این عدد را روی سرور خالی حساب نکنید. ورکرها با بزرگ شدن کش opcache و بارگذاری افزونهها رشد میکنند. اگر بعد از یک هفته آپتایم مصرف هر ورکر 30 درصد بیشتر شده، سقف شما واقعی نیست.
dynamic یا ondemand: کدام را انتخاب کنم؟
حالت dynamic تعدادی ورکر آماده نگه میدارد و بین pm.start_servers و pm.max_spare_servers نوسان میکند. حالت ondemand هیچ ورکر آمادهای نگه نمیدارد و فقط وقتی درخواست بیاید فرآیند میسازد.
| معیار | dynamic | ondemand |
|---|---|---|
| تأخیر اولین درخواست | کمتر، ورکر آماده است | بیشتر، ساخت فرآیند 20 تا 60 میلیثانیه |
| حافظه در حالت بیکاری | به اندازه start_servers مصرف میشود | تقریباً صفر |
| ترافیک پیکدار | مناسب | مناسب اگر pm.max_children کافی باشد |
| ترافیک یکنواخت و بالا | مناسب | ساخت مکرر فرآیند، سربار CPU |
انتخاب من برای سایت پربازدید dynamic است. دلیلش ساده است: در سایتی که هر ثانیه چند درخواست میآید، هزینه ساخت فرآیند از هزینه نگه داشتن چند ورکر آماده بیشتر است. ondemand را وقتی انتخاب میکنم که سرور چند سایت کمترافیک را میزبانی میکند و میخواهم حافظه را برای دیتابیس آزاد کنم. اگر روی یک هاست لینوکس اشتراکی یا VPS کوچک کار میکنید، این تصمیم تفاوت محسوسی در مصرف رم ایجاد میکند.
یک تنظیم متعادل برای سایت پربازدید روی سرور 4 گیگابایتی:
pm = dynamic
pm.max_children = 30
pm.start_servers = 8
pm.min_spare_servers = 6
pm.max_spare_servers = 12
pm.max_requests = 500
request_terminate_timeout = 60s
pm.max_requests = 500 ورکر را بعد از 500 درخواست بازنشسته میکند. این کار نشتی حافظه افزونههای بدقلق را مهار میکند و هزینهاش کمی سربار ساخت مجدد فرآیند است. اگر افزونههایتان تمیز هستند، میتوانید این را به 2000 یا 0 ببرید.
نشانههای کمبود ورکر را از کجا بفهمیم
502 فقط یکی از نشانههاست. اگر TTFB سایتتان زیر بار سنگین از 200 میلیثانیه به 3 ثانیه میرسد ولی CPU سرور بالای 40 درصد نیست، احتمالاً درخواستها در صف PHP-FPM منتظرند، نه اینکه پردازنده مشغول باشد. برای دیدن وضعیت واقعی استخر:
curl -s http://127.0.0.1/status?full | grep -E "active|idle|listen queue|max children"
اگر listen queue عددی غیر از صفر نشان میدهد، یعنی درخواستها پشت صف ماندهاند. اگر max children reached بزرگتر از صفر است، سقف ورکر را لمس کردهاید. این دو عدد را باید در بازههای پیک اندازه بگیرید، نه ساعت 3 بامداد.
اینجا اشتباه میکنند: خیلیها صف را میبینند و فوراً pm.max_children را دو برابر میکنند. نتیجه این است که چند ساعت بعد سرور با OOM مواجه میشود، MySQL ریاستارت میشود و سایت کامل پایین میآید. صف پر، نشانه کمبود ورکر نیست؛ نشانه این است که هر ورکر بیش از حد طول میکشد. اول باید بفهمید چرا یک درخواست 2 ثانیه طول میکشد. اگر کوئری کند یا درخواست HTTP به یک API بیرونی است، اضافه کردن ورکر فقط تعداد فرآیندهای منتظر را زیاد میکند.
برای پیدا کردن درخواستهای کند، slowlog را فعال کنید:
request_slowlog_timeout = 5s
slowlog = /var/log/php8.2-fpm-slow.log
بعد از چند ساعت، فایل را باز کنید و ببینید کدام تابع در پشته فراخوانی گیر کرده. در بیشتر موارد یک file_get_contents به یک سرویس بیرونی یا یک کوئری بدون ایندکس پیدا میشود. برای ریشهیابی قطعیهای پراکنده، روش ریشهیابی قطعیهای متناوب را هم ببینید؛ الگوی لاگها معمولاً همان چیزی را نشان میدهد که در FPM پنهان مانده.
تنظیمات جانبی که روی پایداری اثر میگذارند
opcache را جدی بگیرید
بدون opcache، هر ورکر در هر درخواست فایلهای PHP را از دیسک میخواند و کامپایل میکند. با opcache.memory_consumption = 256 و opcache.max_accelerated_files = 20000 زمان اجرای هر درخواست بهطور محسوسی کم میشود و در نتیجه ورکرها سریعتر آزاد میشوند. این یعنی همان سقف 30 ورکر، بار بیشتری را جواب میدهد.
سوکت یا TCP؟
سوکت یونیکس روی همان سرور کمی سریعتر است و فایلسیستم را درگیر نمیکند. اگر nginx و PHP روی یک ماشین هستند، سوکت را انتخاب کنید. اگر PHP روی سرور دیگری است، چارهای جز TCP نیست و باید listen = 0.0.0.0:9000 را با فایروال محدود کنید؛ در غیر این صورت پورت 9000 باز روی اینترنت میماند و این یک اشتباه امنیتی رایج است.
timeout ها را هماهنگ کنید
اگر request_terminate_timeout در FPM روی 60 ثانیه باشد ولی fastcgi_read_timeout در nginx روی 30، کاربر بهجای خطای واضح، 504 میگیرد. این سه مقدار را همراستا کنید: timeout خود PHP، مقدار FPM و مقدار nginx. برای وقتی که 504 میگیرید و مطمئن نیستید کدام لایه مقصر است، راهنمای رفع خطای 504 ترتیب بررسی را نشان میدهد.
قبل از تغییر، یک خط پایه بگیرید
هر تغییری در این فایل بدون اندازهگیری، حدس است. قبل از دست زدن به php-fpm.conf، مصرف رم، تعداد ورکر فعال و TTFB را در سه بازه (صبح، پیک، شب) ثبت کنید. بعد یک پارامتر را عوض کنید و 24 ساعت صبر کنید. تغییر همزمان پنج پارامتر یعنی هیچوقت نمیفهمید کدام مؤثر بود.
اگر سرور فعلیتان بعد از این تنظیمات هم در پیک کم میآورد، مسئله دیگر تنظیم نیست؛ ظرفیت است. در آن مرحله سرور اختصاصی یا یک پلن با رم بیشتر انتخاب منطقیتری از بالا بردن کورکورانه pm.max_children است. برای پایش سریع پاسخدهی سایت هم میتوانید از ابزارهای رایگان وبمستر استفاده کنید تا ببینید تغییرات واقعاً چه اثری گذاشتهاند.
پرسشهای پرتکرار
pm.max_children را روی چه عددی بگذارم؟
عدد ثابتی وجود ندارد. مصرف واقعی یک ورکر را با ps -o rss= اندازه بگیرید، حافظهای که به دیتابیس و سیستمعامل میرسد را کم کنید، و باقیمانده را تقسیم بر مصرف هر ورکر کنید. برای اطمینان، 10 تا 20 درصد هم حاشیه امن بگذارید.
چرا سایت من با وجود رم آزاد 502 میدهد؟
احتمالاً صف درخواستهای PHP-FPM پر شده، نه اینکه رم تمام شده باشد. با فعال کردن status page مقدار listen queue را چک کنید. اگر غیر از صفر بود، مشکل کندی هر درخواست است نه کمبود ورکر.
تفاوت dynamic و ondemand در عمل چقدر مهم است؟
در سایت پربازدید با ترافیک یکنواخت، dynamic تأخیر کمتری میدهد چون ورکر آماده همیشه وجود دارد. در سروری که چند سایت کمترافیک دارد، ondemand حافظه بیشتری آزاد میکند و ارزشش را دارد. تفاوت در ترافیک پیک میتواند چند صد میلیثانیه در TTFB باشد.
بعد از تغییر تنظیمات باید چه کاری انجام دهم؟
با systemctl reload php8.2-fpm تنظیمات را بدون قطع اتصالهای فعال بارگذاری کنید. سپس 24 ساعت لاگ slow و مقدار max children reached را پایش کنید. اگر عدد صفر ماند و TTFB پایین آمد، تنظیم درست بوده است.
دیدگاهها ۰
هنوز دیدگاهی ثبت نشده؛ اولین نفر باشید!