""" تنظیماتِ gunicorn — خودِ gunicorn این فایل را از پوشه‌ی کاری (WorkingDirectory=~/mathapp-v2 در unitِ systemd) خودکار می‌خواند، پس بدونِ دست زدن به /etc با همان deploy.sh به سرور می‌رسد. آرگومان‌های خطِ فرمانِ unit (مثلِ `--workers 3` و `--bind`) بر این فایل مقدم‌اند. چرا لازم شد (۲۰۲۶-۰۹-۲۴): unit با workerهای sync و timeoutِ پیش‌فرضِ ۳۰ ثانیه اجرا می‌شد. هر استریمِ «از پی بپرس» یک worker را تا پایانِ جواب (~۱۰ ثانیه، چون GapGPT خودش تا آماده شدنِ کلِ جواب نگه می‌دارد) اشغال می‌کرد؛ سه دانش‌آموزِ هم‌زمان با دستیار یعنی بقیه‌ی اپ — نقشه، آزمون، همه‌چیز — در صف. جوابِ بالای ۳۰ ثانیه هم وسطِ کار کشته می‌شد. gthread هر worker را چند نخی می‌کند: ۳ worker × ۸ نخ = ۲۴ درخواستِ هم‌زمان. کدِ جنگو برای نخ امن است و SQLite با `OPTIONS.timeout=20` نوشتنِ هم‌زمان را تحمل می‌کند. timeout در gthread به ضربانِ خودِ worker است، نه طولِ هر درخواست، پس استریمِ طولانی دیگر کشته نمی‌شود. ۳۲ نخ: هر جوابِ دستیار یک نخ را تا آخرِ جواب نگه می‌دارد (روی سرور میانه‌ی ۳ تا ۶ ثانیه، گاهی ۴۰). gthread اتصال را حتی وقتی همه‌ی نخ‌هایش مشغول‌اند می‌پذیرد و workerها یکنواخت پر نمی‌شوند، پس با نخِ کم، صفحه‌های عادیِ اپ پشتِ استریم‌های دستیار صف می‌کشند. در لودتستِ ۲۰۲۶-۱۰-۰۴ (لپ‌تاپ، مدل و Qdrantِ شبیه‌سازی‌شده با زمان‌بندیِ سرور) ۳×۳۲ تا ۷۵ پرسشِ هم‌زمان صفحه‌ها را زیرِ ۰٫۱ ثانیه نگه داشت و از ۱۰۰ به بعد صف پیدا شد. CPUِ مشترکِ سرور در این بار سنجیده نشده. نخِ منتظرِ شبکه تقریباً هزینه ندارد؛ هر نخ اتصالِ SQLiteِ خودش را دارد. """ worker_class = 'gthread' threads = 32 timeout = 120 graceful_timeout = 30 keepalive = 5 # worker بعد از چند هزار درخواست تازه می‌شود تا نشتِ حافظه‌ی احتمالی جمع # نشود؛ jitter نمی‌گذارد هر سه با هم ری‌استارت شوند. max_requests = 2000 max_requests_jitter = 200 def post_worker_init(worker): """ کتابخانه‌های Qdrant و OpenAI را همان لحظه‌ی بالا آمدنِ worker بار می‌کند، نه سرِ اولین سوالِ دستیار. بدونِ این، اولین پرسش‌ها بعد از هر شروعِ worker (ری‌استارت، یا تعویض با max_requests) چند ثانیه پیش از رسیدن به مدل معطل می‌مانند: روی سرور ~۶ ثانیه در برابرِ ~۱، روی لپ‌تاپ ۱٫۷ ثانیه (import ِ qdrant_client به‌تنهایی ~۰٫۷). ساختنِ کلاینت تماسِ شبکه‌ای ندارد. شکستِ این گرم‌کردن نباید worker را بخواباند (کلِ اپ به‌خاطرِ یک بهینه‌سازی پایین نمی‌آید)؛ ثبت می‌شود و اولین سوال همان مسیرِ عادی را می‌رود. """ from rag.services.config import rag_is_configured if not rag_is_configured(): return try: from rag.services import clients clients.get_openai_client() clients.get_qdrant_client() except Exception: worker.log.exception('گرم کردنِ کلاینت‌های دستیار شکست خورد؛ اولین سوال آن‌ها را می‌سازد.')