# صف، Workerها، Supervisor و Cron

## صف

```text
Telegram Update → Webhook → Queue (Redis) → Worker → Service → Database
```

برای هر صف سه کلید در Redis:

| کلید | نوع | محتوا |
|---|---|---|
| `q:{name}` | LIST | کارهای آماده |
| `q:{name}:delayed` | ZSET | کارهای تأخیری / Retry (امتیاز = زمان آماده‌شدن) |
| `q:{name}:reserved` | ZSET | کارهای در حال اجرا (امتیاز = مهلت؛ اگر Worker بمیرد، کار به صف برمی‌گردد) |

- تحویل **حداقل‌یک‌بار** است؛ پس همه‌ی Jobها Idempotent نوشته شده‌اند.
- `Queue::push()` داخل تراکنش دیتابیس، **بعد از Commit** انجام می‌شود (Worker هیچ‌وقت شناسه‌ی ردیفِ Rollback‌شده را نمی‌بیند).
- Redis فقط حامل است: هر چیزی که نباید گم شود (اعلان، پیام همگانی، سفارش پرداخت‌شده) ابتدا در MySQL ثبت می‌شود و Jobهای دوره‌ای موارد جامانده را دوباره در صف می‌گذارند.

## Workerها

| صف | Jobها | وظیفه |
|---|---|---|
| `updates` | `ProcessUpdateJob` | پیام‌ها و دکمه‌های ربات |
| `joins` | `ProcessChatMemberJob` | Join / Leave، ظرفیت، درآمد Pending، Fraud |
| `payments` | `VerifyPaymentJob`, `PaymentMaintenanceJob` | Verify رمزارز، انقضای پرداخت |
| `orders` | `FulfilOrderJob`, `OrderMaintenanceJob` | تحویل سفارش، لغو سفارش پرداخت‌نشده |
| `campaigns` | `CampaignMaintenanceJob`, `RevokeCampaignLinksJob` | تسویه‌ی درآمد، انقضا، ابطال لینک |
| `withdrawals` | `WithdrawalMaintenanceJob` | برداشت‌های معطل، انقضای KYC، یادآوری |
| `broadcast` | `PrepareBroadcastJob`, `SendBroadcastBatchJob` | پیام همگانی |
| `notifications` | `SendNotificationJob`, `SendAdminAlertJob` | اعلان‌ها و هشدار مدیر |
| `statistics` | `StatisticsJob`, `HealthCheckJob`, `ExportJob` | آمار روزانه، رتبه‌بندی، سلامت، خروجی |
| `cleanup` | `CleanupJob` | پاکسازی داده‌ی موقت |

هر Worker:

- **Retry + Backoff**: خطا ← تلاش دوباره پس از ۵، ۱۰، ۲۰، … ثانیه (سقف ۱۵ دقیقه) تا `tries` بار؛ سپس در `failed_jobs`.
- **RetryLater**: FloodWait تلگرام یا سقف نرخ داخلی ← کار با تأخیر برمی‌گردد **بدون** مصرف Attempt.
- **Logging**: `storage/logs/workers-YYYY-MM-DD.log` با شناسه‌ی پیگیری برای هر Job.
- **Restart**: پس از `QUEUE_MAX_JOBS` کار یا عبور از `QUEUE_MEMORY_LIMIT_MB` خودش خارج می‌شود و Supervisor نسخه‌ی تازه را بالا می‌آورد. با `SIGTERM` کارِ جاری را تمام و سپس خارج می‌شود.
- **Health**: هر ۱۰ ثانیه Heartbeat در Redis؛ داشبورد و `health:check` صفِ بدون Worker را نشان می‌دهند.
- خطای زیرساخت (قطع Redis/MySQL) Worker را نمی‌کشد: لاگ، ۳ ثانیه مکث، اتصال مجدد.

اجرای دستی:

```bash
php bin/console queue:work updates,joins        # چند صف به ترتیب اولویت
php bin/console queue:work payments --once      # خالی‌کردن صف و خروج (برای عیب‌یابی)
php bin/console queue:failed                     # فهرست Jobهای ناموفق
php bin/console queue:failed --retry=all
```

## Supervisor

`deploy/supervisor/gostardeh.conf` برای هر صف یک `program` دارد (`autorestart=true`).

```bash
sudo supervisorctl status 'gostardeh:*'
sudo supervisorctl restart 'gostardeh:*'              # بعد از هر Deploy
sudo supervisorctl restart 'gostardeh:gst-updates_*'  # فقط یک نوع
sudo supervisorctl tail -f gostardeh:gst-updates_00
```

مقیاس‌دهی: `numprocs` را برای `gst-updates` و `gst-joins` بالا ببرید. `gst-broadcast` را روی ۱ نگه دارید تا نرخ ارسال هر ربات قابل پیش‌بینی بماند (سقف نرخ در هر صورت با Redis بین همه‌ی پردازه‌ها مشترک است).

## کارهای دوره‌ای

Cron هر دقیقه `schedule:run` را اجرا می‌کند؛ این فرمان Jobهایی را که موعدشان رسیده در صف می‌گذارد (`routes/schedule.php`):

| Job | هر | کار |
|---|---|---|
| `PaymentMaintenanceJob` | ۱ دقیقه | Poll پرداخت‌های رمزارزی باز، انقضای پرداخت‌ها |
| `CampaignMaintenanceJob` | ۱ دقیقه | تسویه‌ی درآمدهای سررسیدشده، انقضای کمپین و لینک، شروع کمپین‌های تأییدشده، توقف کمپینِ کانالِ از دسترس خارج‌شده |
| `OrderMaintenanceJob` | ۵ دقیقه | لغو سفارش‌های پرداخت‌نشده، بازگرداندن سفارش‌های جامانده به صف |
| `HealthCheckJob` | ۵ دقیقه | بررسی سرویس‌ها و هشدار به مدیر |
| `WithdrawalMaintenanceJob` | ۱۰ دقیقه | لغو برداشت‌های معطل (در صورت تنظیم)، انقضای KYC، یادآوری برداشت‌های بیش از ۲۴ ساعت |
| `StatisticsJob` | ۱۰ دقیقه | `daily_stats` و امتیاز رتبه‌بندی |
| `CleanupJob` | ۶۰ دقیقه | پاکسازی طبق سیاست نگهداری |

- هر Job دوره‌ای قفل Redis می‌گیرد؛ اجرای هم‌زمان دو نسخه بی‌خطر است.
- `schedule:run` برای هر بازه با `SET NX` فقط یک‌بار Dispatch می‌کند (حتی اگر Cron روی چند سرور باشد).
- افزودن کار دوره‌ای: یک `Job` + یک خط در `routes/schedule.php`.

Cron همچنین (`deploy/cron/gostardeh`):

```cron
15 3 * * *    backup:run                    # پشتیبان شبانه
*/10 * * * *  health:check --shallow        # مستقل از صف؛ خطا در syslog
```

## Health Check

```bash
php bin/console health:check
php bin/console health:check --json
curl -H "Authorization: Bearer $HEALTH_TOKEN" https://bot.example.com/health
```

| بخش | چه چیزی بررسی می‌شود |
|---|---|
| `database` | اتصال و زمان پاسخ |
| `redis` | PING |
| `queue` | طول صف‌ها، تعداد `failed_jobs` |
| `workers` | Heartbeat برای هر صفِ `config/queue.php` |
| `scheduler` | آخرین اجرای `schedule:run` (کمتر از ۳ دقیقه پیش) |
| `payment_gateway` | حداقل یک درگاهِ فعال و پیکربندی‌شده |
| `telegram` | `getMe` برای هر ربات (Cache یک‌دقیقه‌ای) |
| `disk`, `memory` | فضای آزاد دیسک و حافظه |

`/health` بدون توکن فقط `{"status": "ok|warn|fail"}` برمی‌گرداند (کد ۵۰۳ در حالت `fail`). `HealthCheckJob` هنگام خرابی نام سرویسِ مشکل‌دار را به چت مدیر می‌فرستد (حداکثر یک‌بار در ساعت برای هر مشکل) و برطرف شدن آن را هم اطلاع می‌دهد.
