fix: reduce memory_limit to 256M and configure PHP-FPM process management settings

This commit is contained in:
hamed
2026-07-08 17:54:20 +03:30
parent c6fa92b9a4
commit a5460cbaa9
15 changed files with 563 additions and 458 deletions
+47 -1
View File
@@ -104,6 +104,52 @@ GET /.well-known/acme-challenge/xxxx HTTP/1.1" 404 ... "Let's Encrypt validation
- **کانتینر app**: `docker/healthcheck.sh` مسیر واقعی `/health` را از داخل می‌زند. پارامترها در `Dockerfile` و `docker-compose.yml` یکسان‌اند: `interval=15s, timeout=5s, retries=5, start_period=60s`. یعنی برای unhealthy شدن باید ۵ بار پیاپی (~۷۵ ثانیه) fail شود.
- **کانتینرهای worker** (حالت compose): healthcheck فقط وجود پروسه `messenger:consume` را با `ps | grep` چک می‌کند (`interval=30s, retries=3`). در لحظه exit ساعتیِ worker ممکن است **یک** چک fail شود — بی‌اهمیت است؛ برای unhealthy شدن ۳ شکست پیاپی (~۹۰ ثانیه) لازم است و worker در همان چند ثانیه اول برمی‌گردد. unhealthy شدن worker فقط وقتی رخ می‌دهد که consumer واقعاً بالا نیاید (مثلاً Redis در دسترس نباشد).
## ۷. اعمال تغییرات config
## ۷. down شدن بعد از چند ساعت — کمبود حافظه (OOM)
الگو: workerها چند بار recycle عادی می‌شوند و بعد از چند ساعت کل سرویس down می‌شود. مظنون اول: پر شدن حافظه سرور و کشته شدن پروسه‌ها توسط OOM-killer.
### تأیید روی سرور
```bash
# آیا کانتینر با OOM کشته شده؟
docker inspect --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.FinishedAt}}' <container>
# ردپای OOM-killer در کرنل (مهم‌ترین دستور)
journalctl -k --since "24 hours ago" | grep -i -E "oom|killed process"
# مصرف لحظه‌ای هر کانتینر
docker stats --no-stream
# حافظه و swap کل سرور
free -h
```
اگر `OOMKilled=true` یا در journalctl خط `Out of memory: Killed process ... (php-fpm|mariadbd)` دیدید، تشخیص قطعی است.
### چرا این اتفاق می‌افتاد (و فیکس اعمال‌شده)
- `memory_limit` هر request برابر **1024M** بود → به 256M کاهش یافت (`docker/php/php.ini`).
- pool فقط `clear_env=no` داشت؛ **`pm.max_requests` تنظیم نشده بود** → پروسه‌های php-fpm هرگز recycle نمی‌شدند و نشت‌های کوچک حافظه در طول ساعت‌ها جمع می‌شد. حالا `pm.max_requests=500` + سقف `pm.max_children=8` (`docker/php/zz-pool.conf`).
- workerهای messenger از قبل با `--time-limit=3600` و `--memory-limit=128M` محافظت می‌شدند — مشکل از آن‌ها نبود.
### اقدامات تکمیلی روی سرور (خارج از repo)
1. در Coolify برای resource اپ **Memory Limit** بگذارید (مثلاً 1G) تا در بدترین حالت فقط همان کانتینر ری‌استارت شود، نه کل سرور.
2. اگر سرور swap ندارد، 1-2G swap اضافه کنید: `fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile`.
3. اسکن‌های بات (درخواست‌های `GET /xxx.php → 404` پشت‌سرهم) توسط nginx مستقیم 404 می‌شوند و به Symfony نمی‌رسند — عامل مرگ نیستند، فقط نویز لاگ.
4. **Redis بدون سقف حافظه**: کش اپ (`cache.adapter.redis`) و صف messenger هر دو روی یک Redis resource هستند و Redis پیش‌فرض `maxmemory=0` (رشد بی‌نهایت) دارد — یکی از عوامل خورده شدن تدریجی RAM. روی Redis resource در Coolify ست کنید:
```
maxmemory 256mb
maxmemory-policy volatile-lru
```
⚠️ `allkeys-lru` ممنوع — پیام‌های صف messenger (بدون TTL) را حذف می‌کند؛ `volatile-lru` فقط کلیدهای کش (TTL‌دار) را evict می‌کند. در DSN صف هم `?stream_max_entries=20000` بگذارید (نمونه در `.env.coolify.example`).
5. **چرخش لاگ Docker**: اگر daemon محدودیت ندارد، فایل‌های `*-json.log` تا پر شدن دیسک رشد می‌کنند. چک: `df -h` و `du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail`. فیکس در `/etc/docker/daemon.json`:
```json
{ "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" } }
```
(بعدش `systemctl restart docker` — کانتینرها باید recreate شوند تا اعمال شود.)
6. **جدول `messenger_messages` (failed)**: پیام‌های شکست‌خورده (مثلاً SMS بعد از ۳ retry) برای همیشه در DB می‌مانند. هر چند وقت: `php bin/console messenger:failed:show` و پاکسازی با `messenger:failed:remove`.
## ۸. اعمال تغییرات config
فایل‌های `docker/supervisord.conf`، `docker-compose.yml` و `supervisor.conf` باید آینه هم بمانند (workerهای یکسان، فلگ‌های یکسان). هر تغییری در آن‌ها فقط بعد از **Redeploy در Coolify** روی سرور اثر می‌کند.