scale prep — persistent DB connections, read throttle, prod renderer, auth-cache knob

settings.py
  - DATABASES.default.CONN_MAX_AGE from DB_CONN_MAX_AGE env (default 60);
    harmless on SQLite, saves a TCP+auth handshake per request on Postgres.
  - DEFAULT_RENDERER_CLASSES: BrowsableAPIRenderer only when DEBUG — no
    HTML API browser or its overhead in production.
  - DEFAULT_THROTTLE_RATES['read'] = READ_THROTTLE_RATE env (default
    120/min): a cheap ceiling for the hot GET endpoints.
  - AUTH_USER_CACHE_TTL env (default 0 = off): TTL for the per-request
    user-row cache added in a later commit; 0 keeps current behaviour.
.env.example
  - document DB_CONN_MAX_AGE / AUTH_USER_CACHE_TTL / READ_THROTTLE_RATE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 08:44:50 +03:30
parent 40d358ecb5
commit eeb6bd6138
2 changed files with 30 additions and 1 deletions

View File

@@ -53,3 +53,14 @@ RAG_COLLECTION_NAME_TEMPLATE={subject} {grade}
RAG_RETRIEVAL_TOP_K=4 RAG_RETRIEVAL_TOP_K=4
# RAG_FORCE_MOCK=1 یعنی حتی با وجودِ کلیدها هم مسیرِ RAG خاموش بمونه. # RAG_FORCE_MOCK=1 یعنی حتی با وجودِ کلیدها هم مسیرِ RAG خاموش بمونه.
RAG_FORCE_MOCK= RAG_FORCE_MOCK=
# -----------------------------------------------------------------------
# تنظیماتِ مقیاس‌پذیری (همه دیفالتِ امن دارن)
# -----------------------------------------------------------------------
# persistent DB connection (ثانیه). با SQLite بی‌اثر؛ بعد از Postgres مهم.
DB_CONN_MAX_AGE=60
# کشِ کوتاهِ ردیفِ کاربر در auth تا هر request یک SELECT نزنه. 0 = خاموش
# (پیش‌فرض). توصیه‌ی پروداکشن با Redis: 30. با هر save/delete روی User باطل می‌شه.
AUTH_USER_CACHE_TTL=0
# سقفِ ارزانِ خواندن روی endpointهای پرترافیکِ GET (home/leaderboard/summary/...).
READ_THROTTLE_RATE=120/min

View File

@@ -149,6 +149,10 @@ DATABASES = {
'OPTIONS': { 'OPTIONS': {
'timeout': 20, 'timeout': 20,
}, },
# کانکشنِ DB به‌ازای هر request باز/بسته نشه؛ تا این تعداد ثانیه
# persistent بمونه. با SQLite بی‌اثرِ عملی ولی بی‌ضرره؛ بعد از
# مهاجرت به Postgres جلوی هزینه‌ی TCP+auth در هر request رو می‌گیره.
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '60')),
} }
} }
@@ -235,9 +239,11 @@ REST_FRAMEWORK = {
# Http404/PermissionDenied) به‌جای پاسخِ JSON، صفحه‌ی HTML عمومیِ ۵۰۰ # Http404/PermissionDenied) به‌جای پاسخِ JSON، صفحه‌ی HTML عمومیِ ۵۰۰
# جنگو رو برمی‌گردونه (نگاه کنید به config/exceptions.py). # جنگو رو برمی‌گردونه (نگاه کنید به config/exceptions.py).
'EXCEPTION_HANDLER': 'config.exceptions.custom_exception_handler', 'EXCEPTION_HANDLER': 'config.exceptions.custom_exception_handler',
# BrowsableAPIRenderer فقط در dev؛ در پروداکشن هم overhead داره هم یک
# مرورگرِ HTMLِ API رو در معرض دید می‌ذاره.
'DEFAULT_RENDERER_CLASSES': [ 'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer', *(['rest_framework.renderers.BrowsableAPIRenderer'] if DEBUG else []),
], ],
'DEFAULT_THROTTLE_CLASSES': [ 'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.ScopedRateThrottle', 'rest_framework.throttling.ScopedRateThrottle',
@@ -255,6 +261,10 @@ REST_FRAMEWORK = {
'bazaar_verify': '10/hour', 'bazaar_verify': '10/hour',
'ai_ask': '20/hour', 'ai_ask': '20/hour',
'rag_ingest': '30/hour', 'rag_ingest': '30/hour',
# سقفِ ارزانِ خواندن روی endpointهای پرترافیکِ GET (home/leaderboard/
# summary/notifications). فقط جلوی کلاینتِ خراب/اسکرپر رو می‌گیره؛
# به UX عادی دست نمی‌زنه.
'read': os.environ.get('READ_THROTTLE_RATE', '120/min'),
'purchase': '10/hour', 'purchase': '10/hour',
'token_refresh': '30/hour', 'token_refresh': '30/hour',
'question_answer': '60/min', 'question_answer': '60/min',
@@ -295,6 +305,14 @@ SPECTACULAR_SETTINGS = {
ACCESS_TOKEN_TTL_SECONDS = 3600 ACCESS_TOKEN_TTL_SECONDS = 3600
REFRESH_TOKEN_TTL_DAYS = 30 REFRESH_TOKEN_TTL_DAYS = 30
# کشِ کوتاهِ ردیفِ کاربر در TadrisinoTokenAuthentication تا هر request
# authenticated یک SELECT روی accounts_user نزنه. ۰ = خاموش (رفتارِ فعلی، و
# پیش‌فرض تا وقتی Redis در پروداکشن هست). توصیه‌ی پروداکشن: ۳۰. با هر save/
# delete روی User، کلیدِ کاربر باطل می‌شه (accounts/receivers.py)، پس تنها
# پنجره‌ی کهنگی برای تغییری که از مسیرِ دیگری (مثلاً shell/DB) اعمال شده،
# همین چند ثانیه‌ست.
AUTH_USER_CACHE_TTL = int(os.environ.get('AUTH_USER_CACHE_TTL', '0'))
# سقفِ گیرنده‌ی اکشنِ ادمینِ send_sms_to_selected؛ این اکشن synchronous توی # سقفِ گیرنده‌ی اکشنِ ادمینِ send_sms_to_selected؛ این اکشن synchronous توی
# همون requestه (بدون صفِ پس‌زمینه)، پس این سقف جلویِ تایم‌اوت/overloadِ # همون requestه (بدون صفِ پس‌زمینه)، پس این سقف جلویِ تایم‌اوت/overloadِ
# provider رو می‌گیره — بیشتر از سقف یعنی هیچ پیامکی ارسال نمی‌شه. # provider رو می‌گیره — بیشتر از سقف یعنی هیچ پیامکی ارسال نمی‌شه.