Files
MathApp/gunicorn.conf.py
Zareian 645baf846b Signed payment links, hashed refresh tokens, login-only leaderboard, load test tools
- /api/payment/orders/{id}/pay requires the short-lived signature that paymentUrl carries;
  without it (or with another order's) it is the same 404 as an unknown order
- Session.refresh_token stores the SHA-256 of the token; migration accounts.0007 hashes the
  existing rows so tokens clients already hold keep working
- /api/leaderboard requires a token (guests have one); names of children were scrapable
- RAG mock only for `manage.py test`, not any command with an argument named "test"
- tools/loadtest: user factory, load generator, fake GapGPT+Qdrant, with method notes
- security-before-production.md lists only open items; closed ones become a guards table
  with the tests that hold them. Comments describe current behaviour, not history

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 21:12:28 +03:30

59 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
تنظیماتِ 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('گرم کردنِ کلاینت‌های دستیار شکست خورد؛ اولین سوال آن‌ها را می‌سازد.')