The first assistant round after a restart waited ~6 s before reaching the model on the live server (later rounds ~1 s); locally a cold 10-ask burst took 7.5 s vs 6.1 s with this hook (simulated model, 5.2 s). Workers are recycled by max_requests, so the delay recurred. A failed warm-up is logged and does not stop the worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
61 lines
4.3 KiB
Python
61 lines
4.3 KiB
Python
"""
|
||
تنظیماتِ 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 است، نه طولِ هر
|
||
درخواست، پس استریمِ طولانی دیگر کشته نمیشود.
|
||
|
||
۸ → ۳۲ نخ (۲۰۲۶-۱۰-۰۴، لودتست): هر جوابِ دستیار یک نخ را تا آخرِ جواب نگه میدارد
|
||
(روی سرور میانه ۵ و p90 ۱۳ ثانیه). با ۳×۸ از حدودِ ۲۰ پرسشِ همزمان، صفحههای عادیِ اپ
|
||
پشتِ استریمها صف کشیدند (تا ۲۳ ثانیه در ۵۰ پرسش؛ فرانت بعد از ۱۵ ثانیه خطا میدهد)، چون
|
||
gthread اتصال را حتی وقتی همهی نخهایش مشغولاند میپذیرد و workerها یکنواخت پر نمیشوند.
|
||
با ۳×۳۲ تا ۷۵ پرسشِ همزمان صفحهها زیرِ ۱۱۰ میلیثانیه ماندند. این روی لپتاپ با مدل و
|
||
Qdrantِ شبیهسازیشده سنجیده شد؛ CPUِ مشترکِ سرور و رفتارِ GapGPT زیرِ این بار جدا
|
||
سنجیده نشدهاند. نخِ منتظرِ شبکه تقریباً هزینه ندارد؛ هر نخ اتصالِ 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 بار میکند، نه سرِ اولین
|
||
سوالِ دستیار. در لودتستِ ۲۰۲۶-۱۰-۰۴ اولین دورِ پرسش بعد از ریاستارت روی سرور ~۶ ثانیه
|
||
پیش از رسیدن به مدل معطل ماند و دورهای بعد ~۱ ثانیه؛ روی لپتاپ همین تفاوت ۱٫۷ ثانیه بود
|
||
(import ِ qdrant_client بهتنهایی ~۰٫۷ ثانیه). با max_requests هر worker گاهبهگاه از نو
|
||
بالا میآید، پس بدونِ این هر چند هزار درخواست یک دانشآموز همین تأخیر را میدید.
|
||
|
||
ساختنِ کلاینت تماسِ شبکهای ندارد. شکستِ این گرمکردن نباید 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('گرم کردنِ کلاینتهای دستیار شکست خورد؛ اولین سوال آنها را میسازد.')
|