Files
clinicpro/.claude/prompt/server-down-oom-diagnosis.md
T
hamed 880648cae8 Add AST JSON files for new documentation and scripts
- Created JSON representation for `collect-diagnostics.sh` including nodes and edges for its structure.
- Added JSON for `ops-restarts.md` detailing various sections and their relationships.
- Introduced JSON for `server-down-oom-diagnosis.md` capturing its content and connections.
2026-07-08 18:00:01 +03:30

152 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# تشخیص عمیق down شدن سرور بعد از ~۱۰ سیکل + وریفای و تکمیل فیکس‌های پایداری
## زمینه
روی production (Coolify، تک‌کانتینر با supervisord: nginx + php-fpm + دو messenger worker) workerها هر ساعت با `--time-limit=3600` و exit code 0 عمداً recycle می‌شوند — این عادی است. مشکل واقعی: **بعد از حدود ۱۰ سیکل (~۱۰ ساعت) کل سرویس down می‌شود**. در لاگ‌های ارائه‌شده هیچ crash دیده نمی‌شود (`exit status 0; expected`، `/health` همیشه 200، اسکن‌های بات `GET /*.php` توسط nginx مستقیم 404 می‌شوند و به Symfony نمی‌رسند) — یعنی قاتل بیرون از لاگ اپ است.
فرضیه اصلی (به ترتیب احتمال): **فشار تدریجی حافظه تا OOM** و سپس **پر شدن دیسک**:
1. php-fpm قبلاً بدون `pm.max_requests` بود → پروسه‌ها هرگز recycle نمی‌شدند و نشت حافظه ساعت‌به‌ساعت جمع می‌شد؛ `memory_limit=1024M` هم به یک request اجازه می‌داد 1GB بخورد.
2. Redis (کش اپ + صف messenger، یک instance مشترک) پیش‌فرض `maxmemory=0` → رشد بی‌نهایت.
3. لاگ‌های docker (json-file) بدون `max-size` → پر شدن تدریجی دیسک.
بخشی از فیکس‌ها قبلاً در repo اعمال شده؛ این پرامپت آن‌ها را **وریفای** می‌کند، یک **اسکریپت جمع‌آوری شواهد** برای اجرای روی سرور می‌سازد، و چک‌لیست تنظیمات سمت Coolify را نهایی می‌کند.
## مشکل / هدف
- تشخیص قطعی و مبتنی بر شواهد (نه حدس): OOM؟ دیسک؟ یا چیز دیگر؟
- اطمینان از اینکه همه فیکس‌های پایداری در repo حاضرند (اگر revert شده‌اند، دوباره اعمال شوند).
- تحویل یک اسکریپت آماده که کاربر روی سرور اجرا کند و خروجی‌اش تشخیص را قطعی کند.
## فایل‌های مرتبط
| فایل | نقش |
|------|-----|
| `docker/php/php.ini` | باید `memory_limit = 256M` باشد (فیکس قبلی؛ ممکن است revert شده باشد — وریفای کن) |
| `docker/php/zz-pool.conf` | باید بلوک `pm = dynamic … pm.max_requests = 500` را داشته باشد |
| `docker/supervisord.conf` | هر دو worker بدون `-v`، هر دو با `--memory-limit=128M` |
| `docker-compose.yml` | آینه supervisord (حالت compose) |
| `supervisor.conf` | آینه (نسخه Liara) |
| `.env.coolify.example` | `MESSENGER_TRANSPORT_DSN` نمونه باید `?stream_max_entries=20000` داشته باشد |
| `docs/ops-restarts.md` | runbook — بخش‌های ۷ (OOM) و ۸ باید موجود باشند |
| `docs/collect-diagnostics.sh` | **جدید** — اسکریپت جمع‌آوری شواهد روی سرور |
## وضعیت فعلی
وضعیت انتظاری بعد از فیکس‌های قبلی (`docker/php/zz-pool.conf`):
```ini
[www]
clear_env = no
pm = dynamic
pm.max_children = 8
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500
```
و `docker/php/php.ini`:
```ini
memory_limit = 256M
```
⚠️ کاربر ممکن است `memory_limit` را به 1024M برگردانده باشد — وضعیت فعلی فایل را بخوان و اگر برگشته، با ذکر دلیل (۸ پروسه × 1024M = 8GB سقف نظری روی VPS کوچک) دوباره 256M کن، مگر اینکه کاربر صراحتاً 1024M خواسته باشد — در آن صورت دست نزن و فقط در گزارش ذکر کن.
## وظایف
### ۱. وریفای فیکس‌های repo (idempotent)
هر ۷ فایل جدول بالا را چک کن؛ هر موردی که غایب/revert شده را دوباره اعمال کن:
```bash
grep -n "memory_limit" docker/php/php.ini # انتظار: 256M
grep -n "pm.max_requests" docker/php/zz-pool.conf # انتظار: 500
grep -n "consume" docker/supervisord.conf docker-compose.yml supervisor.conf
# انتظار: هیچ ` -v` ای نباشد؛ هر دو worker دارای --time-limit=3600؛ هر دو --memory-limit=128M
grep -n "stream_max_entries" .env.coolify.example # انتظار: 20000
grep -n "OOM\|maxmemory" docs/ops-restarts.md # بخش ۷ موجود
```
سه فایل supervisor/compose باید آینه هم بمانند.
### ۲. ساخت `docs/collect-diagnostics.sh` — اسکریپت شواهد
اسکریپت shell (POSIX sh، بدون وابستگی خاص) که کاربر روی **هاست سرور** (نه داخل کانتینر) اجرا می‌کند و همه شواهد لازم را یک‌جا چاپ می‌کند:
```sh
#!/bin/sh
# ClinicPro production diagnostics — run ON THE HOST as root (or with docker access).
# Usage: sh collect-diagnostics.sh [container-name-or-id]
set -u
C="${1:-$(docker ps --format '{{.Names}}' | grep -i -m1 clinic || true)}"
echo "=== 1. OOM evidence (kernel, last 48h) ==="
journalctl -k --since "48 hours ago" 2>/dev/null | grep -i -E "oom|killed process" | tail -20 \
|| dmesg | grep -i -E "oom|killed process" | tail -20
echo "=== 2. Memory / swap now ==="
free -h
echo "=== 3. Disk ==="
df -h | head -10
du -sh /var/lib/docker/containers/*/*-json.log 2>/dev/null | sort -h | tail -5
echo "=== 4. Container state ==="
docker ps -a --format 'table {{.Names}}\t{{.Status}}'
[ -n "$C" ] && docker inspect --format \
'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} RestartCount={{.RestartCount}} StartedAt={{.State.StartedAt}} FinishedAt={{.State.FinishedAt}}' "$C"
echo "=== 5. Live memory per container ==="
docker stats --no-stream
echo "=== 6. php-fpm inside app container ==="
[ -n "$C" ] && docker exec "$C" sh -c 'php -i 2>/dev/null | grep -E "^memory_limit"; ps -o rss,args 2>/dev/null | grep -E "php-fpm|messenger" | grep -v grep'
echo "=== 7. Redis memory (set host/port/pass if needed) ==="
R="$(docker ps --format '{{.Names}}' | grep -i -m1 redis || true)"
[ -n "$R" ] && docker exec "$R" redis-cli info memory 2>/dev/null | grep -E "used_memory_human|maxmemory_human|maxmemory_policy"
echo "=== 8. Docker log driver config ==="
docker info --format '{{.LoggingDriver}}'
cat /etc/docker/daemon.json 2>/dev/null || echo "(no daemon.json)"
echo "=== done — paste this whole output back for diagnosis ==="
```
اجرایی‌اش کن (`chmod +x`). در انتهای `docs/ops-restarts.md` یک خط ارجاع به این اسکریپت اضافه کن.
### ۳. جدول تفسیر شواهد در runbook
به `docs/ops-restarts.md` (بخش ۷) جدول تصمیم اضافه کن:
| شاهد در خروجی اسکریپت | تشخیص | اقدام |
|---|---|---|
| بخش ۱: `Out of memory: Killed process ... php-fpm` | نشت fpm | فیکس‌های pm/memory_limit + redeploy کافی است |
| بخش ۱: `Killed process ... mariadbd` یا `redis` | فشار کل RAM | Redis maxmemory + Coolify memory limit + swap |
| بخش ۴: `OOMKilled=true` | سقف حافظه کانتینر | سقف را بالاتر ببر یا مصرف را کم کن |
| بخش ۳: دیسک ≥ 90% یا json.log چند GB | دیسک پر | log rotation daemon.json + پاکسازی |
| بخش ۷: `maxmemory:0` و `used_memory` رشد کرده | Redis بی‌سقف | `maxmemory 256mb` + `maxmemory-policy volatile-lru` |
| هیچ‌کدام | فرضیه رد شد | خروجی کامل + لاگ ۵۰ خط آخر قبل از down برای تحلیل بعدی |
### ۴. چک‌لیست تنظیمات سمت Coolify (فقط مستندسازی — قابل اجرا از repo نیست)
در همان بخش runbook، چک‌لیست کوتاه:
1. Redeploy بعد از merge این تغییرات (configها فقط با image جدید اعمال می‌شوند).
2. env واقعی Coolify: به `MESSENGER_TRANSPORT_DSN` پارامتر `?stream_max_entries=20000` اضافه شود.
3. Redis resource: `maxmemory 256mb` + `maxmemory-policy volatile-lru` (نه `allkeys-lru` — پیام‌های صف TTL ندارند و evict می‌شوند).
4. App resource → Memory Limit = 1G.
5. `/etc/docker/daemon.json`: `{"log-driver":"json-file","log-opts":{"max-size":"20m","max-file":"3"}}` + restart docker.
6. اگر `free -h` نشان داد swap صفر است: 2G swap بساز.
7. Coolify Notifications برای container stop/unhealthy فعال شود.
## نکات مهم
- هیچ تغییری در رفتار workerها نده — recycle ساعتی عمدی است؛ فقط `--memory-limit=128M` باید روی هر دو باشد.
- این پرامپت PHP/React لمس نمی‌کند → migration و `docs/api/` لازم ندارد؛ فقط configهای deploy + `docs/`.
- اسکریپت باید بدون bash-ism باشد (`sh` خالص) چون روی هاست Ubuntu اجرا می‌شود نه داخل کانتینر alpine.
- بعد از هر تغییر config، سه فایل supervisord/compose/liara آینه بمانند (کامنت بالای فایل‌ها همین را می‌گوید).
- تست repo-side: `docker compose -f docker-compose.yml config -q` (فقط warning env، بدون خطا) و `sh -n docs/collect-diagnostics.sh` برای syntax اسکریپت.