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

9.3 KiB
Raw Blame History

تشخیص عمیق 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):

[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:

memory_limit = 256M

⚠️ کاربر ممکن است memory_limit را به 1024M برگردانده باشد — وضعیت فعلی فایل را بخوان و اگر برگشته، با ذکر دلیل (۸ پروسه × 1024M = 8GB سقف نظری روی VPS کوچک) دوباره 256M کن، مگر اینکه کاربر صراحتاً 1024M خواسته باشد — در آن صورت دست نزن و فقط در گزارش ذکر کن.

وظایف

۱. وریفای فیکس‌های repo (idempotent)

هر ۷ فایل جدول بالا را چک کن؛ هر موردی که غایب/revert شده را دوباره اعمال کن:

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، بدون وابستگی خاص) که کاربر روی هاست سرور (نه داخل کانتینر) اجرا می‌کند و همه شواهد لازم را یک‌جا چاپ می‌کند:

#!/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 اسکریپت.