Spike readiness: AI gets its own slice of threads, cheaper hot paths, classroom-friendly login
- /api/ai/ask: at most AI_MAX_CONCURRENT_PER_WORKER (12 of 32 threads) answers per worker; beyond that 503 assistant_busy + Retry-After before any quota is reserved. Without it a burst of questions (3-40 s each) took every thread and the map and quizzes queued behind. The slot is held until the stream is actually finished or closed. - Answer submission 21 -> 14 queries, 15.4 -> 11.0 ms: one query for all choices, locked get_or_create for node progress and streak, and today's streak recorded once per day via the cache instead of a transaction per answer. - /api/home 12.4 -> 8.6 ms: the user's completions are read by user_id instead of an IN list of ~600 lesson ids; the dict is returned directly (serializer stays the Swagger contract, HomeMatchesItsSerializerTests keeps them equal). - otp_login per-IP 30 -> 300/hour: a class of 30 on one school Wi-Fi filled it; guessing is bounded per code and per phone. - /api/health aiConcurrency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,11 +15,11 @@ from .models import Lesson, Node, NodeProgress, UserLessonCompletion, UserNodeCo
|
||||
|
||||
|
||||
def _get_or_create_progress(user_id, node):
|
||||
"""get_or_create ایجاد میکند، سپس ردیف با select_for_update دوباره خوانده و قفل میشود
|
||||
تا خواندن/تغییر/ذخیره در برابر concurrent update ایمن باشد. باید داخل transaction.atomic صدا زده شود."""
|
||||
NodeProgress.objects.get_or_create(user_id=user_id, node=node)
|
||||
progress = NodeProgress.objects.select_for_update().get(user_id=user_id, node=node)
|
||||
return progress, False
|
||||
"""ردیف را با قفل (select_for_update) میگیرد یا میسازد، در یک کوئری برای حالتِ معمول (ردیف هست).
|
||||
دو ساختِ همزمان به قیدِ یکتاییِ (user_id, node) میخورند و get_or_create دومی را دوباره میخواند.
|
||||
باید داخلِ transaction.atomic صدا زده شود."""
|
||||
progress, created = NodeProgress.objects.select_for_update().get_or_create(user_id=user_id, node=node)
|
||||
return progress, created
|
||||
|
||||
|
||||
def _emit_if_changed(progress, old_status, event, user_id):
|
||||
|
||||
Reference in New Issue
Block a user