# TELEGRAM_LIMITS_AND_SCALE_FA.md — راهنمای مقیاس‌پذیری و محدودیت‌های Telegram API

این سند نتیجه‌ی بازبینی سراسری «ضد بلاک/ضد هنگ تلگرام» (Update Pack
P3-6) روی کل ربات است — چه چیزهایی برای امنیت اجرا روی هاست ضعیف
اشتراکی اصلاح/تضمین شده‌اند، و چه محدودیت‌هایی واقعی و باید در نظر
گرفته شوند.

## ۱) محدودیت‌های واقعی Telegram Bot API که این پروژه به آن‌ها احترام می‌گذارد

- **~۳۰ پیام در ثانیه** در کل ربات (تقریبی، رسمی نیست ولی عملاً همین
  حدود اعمال می‌شود). broadcast هرگز تلاش نمی‌کند از این عبور کند —
  batch پیش‌فرض ۲۵ پیام هر چند ثانیه، نه صدها پیام همزمان.
- **پاسخ `429 Too Many Requests`** همیشه شامل `retry_after` است.
  `TelegramClient` این مقدار را می‌خواند و دقیقاً همان مدت (با سقف ۳
  ثانیه برای هر تلاش، تا در یک درخواست وب‌هوک کوتاه هاست اشتراکی گیر
  نکند) صبر می‌کند، سپس **فقط یک‌بار** دوباره تلاش می‌کند.
- **حجم callback_data محدود به ۶۴ بایت** — `CallbackCodec` و
  `AdminCallbackCodec` این را رعایت می‌کنند و در صورت عبور، یک
  `LengthException` صریح پرتاب می‌کنند (خطای توسعه، نه رفتار در
  production).
- **طول متن پیام حداکثر ۴۰۹۶ کاراکتر** — همه‌جای پروژه پیام‌ها را قبل
  از ارسال با `mb_substr(..., 4096)` کوتاه می‌کنند.

## ۲) چه چیزهایی در این بازبینی اصلاح شدند (نه فقط ادعا)

### الف) مرکزی‌سازی retry در TelegramClient
قبلاً هر تماس Telegram جدا منطق retry نداشت. الان:
- تعداد تلاش مجدد از تنظیم `telegram_max_retries` (پیش‌فرض ۲) خوانده
  می‌شود و به‌صورت متمرکز در `TelegramClient::send()` اعمال می‌شود —
  هیچ فراخوانی دیگری در پروژه منطق retry جدا ندارد.
- خطاهای شبکه (curl) و کد ۵xx هم retry می‌شوند (backoff کوتاه ۳۰۰
  میلی‌ثانیه)، نه فقط ۴۲۹.
- خطاهای دائمی (۴۰۰/۴۰۳/۴۰۴) هرگز retry نمی‌شوند — retry فقط برای
  خطاهای موقتی معنا دارد.

### ب) موتور Broadcast صف‌دار/chunk‌شده (به‌جای حلقه یک‌مرحله‌ای)
جزئیات کامل در `docs/BROADCAST.md`. خلاصه: هیچ‌وقت بیش از یک batch
کوچک (پیش‌فرض ۲۵ کاربر) در یک اجرای `pump()` پردازش نمی‌شود، و
`pump()` با یک سقف زمانی (پیش‌فرض ۳ ثانیه) هم محدود است — یعنی حتی
اگر batch بزرگ باشد، یک تماس هرگز طولانی نمی‌شود.

### ج) رفع باگ عضویت فیک (F12) — بدون storm در getChatMember
قبلاً هر بار که کاربر دکمه «عضو شدم» را می‌زد، حتی اگر از قبل عضو بود،
به‌عنوان «join جدید» شمرده می‌شد. الان وضعیت واقعی عضویت
(`users.main_channel_status`) فقط از رویداد واقعی `chat_member` تلگرام
یا یک چک زنده صریح به‌روزرسانی می‌شود، و شمارش join فقط روی یک انتقال
واقعی (`left/unknown` → `member`) اتفاق می‌افتد. این یعنی:
- audience انتخاب «غیرعضوها» برای broadcast از دیتابیس محلی خوانده
  می‌شود، **هرگز** با فراخوانی زنده `getChatMember` روی هزاران کاربر.
- آمار داشبورد دیگر «join» تورم‌یافته نشان نمی‌دهد.

### د) لاگ‌گیری بدون افت کارایی
سیستم لاگ تشخیصی (Phase 07، دست‌نخورده در این بسته) طبقه‌بندی‌شده است
— حالت `diagnostic_mode` پیش‌فرض خاموش است و در صورت روشن بودن هم
سقف حجم روزانه (۲۰ مگابایت) و دیدوپلیکیشن برای فایل مشکلات (problems
log) وجود دارد؛ این بازبینی چیزی به آن اضافه نکرده که این تضمین‌ها را
نقض کند.

### هـ) Backup/Update هرگز روی دیسک باقی نمی‌ماند
فایل‌های zip بکاپ کد/دیتابیس بلافاصله بعد از آپلود به کانال فایل‌ها از
دیسک محلی حذف می‌شوند (`@unlink()` صریح در `AdminUpdateController`).
پوشه‌ی staging آپدیت هم بعد از هر swap (چه موفق چه ناموفق) پاک‌سازی
می‌شود.

## ۳) چه چیزی «بی‌خطر روی هاست ضعیف» به‌حساب می‌آید

| عملیات | ایمن؟ | چرا |
|---|---|---|
| پاسخ به یک پیام دانش‌آموز | ✅ | ۱-۲ کوئری دیتابیس، بدون حلقه |
| نمایش داشبورد ادمین | ✅ | چند aggregate query با ایندکس، بدون full scan |
| گزارش آنالیتیکس (Global/Test/Campaign) | ✅ | حداکثر ~۱۰ کوئری ثابت، صرف‌نظر از حجم داده |
| بکاپ دیتابیس (چند صد ردیف) | ✅ | INSERT دسته‌ای (۲۰۰ ردیفی)، فایل SQL متنی |
| بکاپ دیتابیس (چند صد هزار ردیف) | ⚠️ | ممکن است چند ثانیه طول بکشد؛ برای دیتابیس‌های خیلی بزرگ، بکاپ را در ساعات کم‌ترافیک اجرا کنید |
| broadcast به ۱۰۰ کاربر | ✅ | در یک تک pump کامل می‌شود |
| broadcast به ۴۰,۰۰۰ کاربر | ⚠️ | چند دقیقه تا حدود یک ساعت طول می‌کشد بسته به فرکانس cron/تعداد تپ ادمین — طراحی‌شده که کند ولی امن باشد، نه سریع و پرخطر |
| آپدیت زیپ (چند مگابایت) | ✅ | extract+swap چند ثانیه |
| آپدیت زیپ (نزدیک به ۶۰ مگابایت، سقف مجاز) | ⚠️ | ممکن است چند ثانیه بیشتر طول بکشد؛ اگر هاست timeout کوتاهی دارد (کمتر از ۳۰ ثانیه)، با پشتیبانی هاست هماهنگ کنید |

## ۴) توصیه‌ی Cron برای broadcast در مقیاس بزرگ (اختیاری)

برای broadcastهای بزرگ (چند هزار کاربر به بالا)، به‌جای تکیه بر تپ
دستی مکرر ادمین روی «ادامه ارسال»، می‌توانید یک cron job در cPanel
اضافه کنید که هر یک دقیقه یک درخواست به یک endpoint داخلی (که باید
طبق نیاز پروژه خودتان با یک secret احراز هویت شود) بزند و
`BroadcastService::pump()` را روی تمام jobهای `status='running'` صدا
بزند. این پروژه به‌صورت پیش‌فرض چنین endpoint عمومی از پیش‌ساخته‌ای
ندارد (خارج از scope این بسته) — تپ دستی «ادامه ارسال» برای اکثر
سناریوهای واقعی (چند صد تا چند هزار کاربر) کافی است.

## ۵) چه کاری نباید انجام شود (قوانین سخت پروژه)

- هرگز `getChatMember` را داخل یک حلقه‌ی روی چند کاربر صدا نزنید —
  همیشه از `users.main_channel_status` محلی استفاده کنید.
- هرگز یک broadcast/آپدیت را مستقیم و کامل داخل یک درخواست وب‌هوک
  اجرا نکنید — همیشه از الگوی chunk/pump استفاده کنید.
- هرگز فایل باینری کاربر (ویس/فایل) را روی هاست ذخیره نکنید — همیشه
  `file_id` تلگرام؛ تنها استثنای صریح پروژه، فایل‌های موقت
  backup/update zip هستند که بلافاصله بعد از استفاده پاک می‌شوند.
- هرگز از `diagnostic_logging=true` روی production به‌صورت دائمی
  استفاده نکنید — فقط برای عیب‌یابی کوتاه‌مدت.
