- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history). Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules, announce-before-acting, password handling, and the full "deploy it" procedure with verification. - README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations, 973 tests, PostgreSQL/Redis). - Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has the real client). Swagger's external-docs link now points at the frontend repo. - architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login, demo accounts, question packs and the port-80 note replaced by what is true now. - Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only login, subscriptions, current data model); PDF rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.6 KiB
لودتستِ دستیار
سه اسکریپت، برای اینکه «چند دانشآموزِ همزمان» هر بار با یک روش سنجیده شود. خروجیِ هر اجرا یک JSON است با زمانِ هر پرسش و هر درخواستِ صفحهی عادی؛ جمعبندی در خودِ خروجیِ ترمینال چاپ میشود.
| فایل | کار |
|---|---|
mkusers.py |
کاربرانِ آزمایشی (شمارهی جعلیِ 0900…، بیپیامک، بیسیگنال) با توکن و سه سوالِ متفاوت از بانکِ سوال برای هر کدام |
loadtest.py |
N نفر همزمان میپرسند (استریم، دقیقاً مثلِ فرانت) و B نفرِ دیگر در خانه/آزمون/خلاصه/سهمیه میچرخند |
fake_upstream.py |
جایگزینِ GapGPT و Qdrant برای آزمایشگاه: embedding، جوابِ استریم با زمانِ لاگنرمال (پیشفرض: میانهی ۵٫۲ و صدکِ ۹۰ِ ۱۳ ثانیه، از جوابهای واقعیِ سرور) و جستوجوی Qdrant |
آزمایشگاه (بیهزینه، روی لپتاپ)
روی یک کپی از پروژه اجرا کنید تا دیتابیسِ اصلیِ لوکال پر نشود. Qdrant هم شبیهسازی میشود، چون از لپتاپ پهنای باندِ بالارفتن (هر بردار ~۳۰ کیلوبایت) جستوجوها را سریالی نشان میدهد؛ از خودِ سرور ۲۰ جستوجوی همزمان ۰٫۲۶ ثانیه است.
python tools/loadtest/fake_upstream.py & # :8901 (FAKE_SIGMA=0 یعنی همیشه ۵٫۲ ثانیه)
export DJANGO_DEBUG=0 CACHE_URL= NUM_PROXIES=0 AI_DAILY_QUOTA_FREE=1000 HEARTS_ENFORCED=0 RAG_FORCE_MOCK=0 \
DJANGO_ALLOWED_HOSTS=127.0.0.1 OPENAI_BASE_URL=http://127.0.0.1:8901/v1 OPENAI_API_KEY=x \
QDRANT_URL=http://127.0.0.1:8901 QDRANT_API_KEY=x
LT_N=150 python manage.py shell -c "exec(open('tools/loadtest/mkusers.py').read())" | grep ^LTJSON | cut -c7- > users.json
gunicorn --bind 127.0.0.1:8800 config.wsgi:application & # همان gunicorn.conf.py ِ سرور
python tools/loadtest/loadtest.py http://127.0.0.1:8800 users.json 50 10 0 out.json --writes
پیش از هر اجرا gunicorn را از نو بالا بیاورید (کشِ جوابِ هر worker خالی شود) و مطمئن شوید پروسهی قبلی واقعاً رفته است؛ وگرنه سوالهای تکراری از کش در ۰٫۱ ثانیه جواب میگیرند و عدد دروغ میگوید.
سرورِ اصلی (هزینهی واقعی: ~۰٫۱۸ سنت برای هر جواب)
۱. بکاپ از دیتابیس (docs/operations.md). ۲. کاربرانِ آزمایشی را روی سرور با mkusers.py بسازید
(LT_PREFIX یک پیشوندِ جعلی که روی سرور نیست) و خروجی را فقط در یک فایلِ 600 نگه دارید.
۳. loadtest.py را با https://mathapi.tadrisino2.ir و بدونِ --writes اجرا کنید.
۴. زمانِ داخلِ GapGPT برای هر پیام در ai_tutor.Message.latency_ms است؛ اختلافِ آن با زمانِ کلاینت
یعنی کارِ سرورِ ما و صفِ انتظار برای نخِ آزاد. ۵. manage.py delete_users --ids … --yes و پاک کردنِ
فایلِ توکنها.
روی سرورِ مشترک، throttleهای پروداکشن دست نمیخورند. اگر سقفِ نرخ مانع است، یک gunicornِ موقت روی سوکتِ جدا در
~/mathapp-v2/.lt/ با READ_THROTTLE_RATE بالا و DB_POOL_MAX_SIZE=4 --workers 2 بالا بیاورید (تا با ۳۲ اتصالِ
پروداکشن از ۵۷ نگذرد)، کوتاه اجرا کنید و آخرش .lt را پاک کنید. در اسکریپت روی آن gunicornِ پسزمینه wait نزنید:
تا ابد منتظر میماند.
بازیکنها (نه دستیار): mkplayers.py، play.py، agg.py
هر کاربرِ مجازی خانه ← آزمونِ یک جزیره ← ۴ جواب ← درس ← خلاصه را میچرخد، با مکثِ تصادفیِ ۱ تا THINK_MAX ثانیه. کاربرها اشتراکِ آزمایشیِ یکروزه میگیرند (بی دیوارِ پرداخت و بی کمبودِ قلب). روی سرور، مستقیم روی سوکتِ gunicorn:
cd ~/mathapp-v2 && mkdir -m 700 .lt # اول بکاپِ دیتابیس
LT_N=1000 LT_PREFIX=0900770 venv/bin/python manage.py shell -c "exec(open('tools/loadtest/mkplayers.py').read())" \
| grep ^LTJSON | cut -c7- > .lt/users.json
for p in $(seq 0 9); do venv/bin/python tools/loadtest/play.py /run/gunicorn/mathapp.sock .lt/users.json .lt/out-$p.json \
"300:40,600:40,1000:60" 5 40 $p 10 & done; wait
venv/bin/python tools/loadtest/agg.py ".lt/out-*.json" "300:40,600:40,1000:60"
مولد را چندپروسهای اجرا کنید (آخرین دو آرگومان). یک پروسهی httpx با ۶۰۰ اتصال خودش گلوگاه شد: کلاینت ۲۱ ثانیه
دید درحالیکه لاگِ اپ برای همان درخواستها ۰٫۲ تا ۰٫۵ ثانیه میگفت (۲۰۲۶-۱۰-۱۳). همیشه duration_msِ logs/app.log
(به وقتِ UTC) را با عددِ کلاینت مقایسه کنید. پاک کردن: delete_users از راهِ پایتون روی phone__startswith=LT_PREFIX
(فهرستِ id از manage.py shell با خطِ هشدار قاطی میشود)، کشِ رتبهبندی، و rm -rf .lt.