Refs EventHub/EventHubBack#30 [skip ci] Co-authored-by: Cursor <cursoragent@cursor.com>
Tsung — нагрузка EventHub
Профили
| Файл | Назначение |
|---|---|
eventhub_ift_smoke.xml |
Короткий smoke на IFT после prepare |
eventhub_ift.xml |
Mix: auth_browse 60% + create_flow 40%, фазы warmup/ramp/peak |
eventhub_ift_stress.xml |
Hot-path stress: browse 70% / write 30%, ladder arrival, поиск верхней границы |
eventhub_ift_stress_hold.xml |
Controlled hold: maxusers=100, use_controller_vm, ladder 0.2→0.1→0.05→0.035 |
eventhub_ift_stress_hold_hot.xml |
Hot ladder только 0.1→0.05→0.035, maxusers=100, use_controller_vm (stress-hold-hot) |
eventhub_ift_stress_hold_beams.xml |
Опасно: use_controller_vm=false — slave beams раздувают concurrent (не для IFT без CONFIRM_DANGEROUS=1) |
eventhub_ift_stress_bump.xml |
Запрещён по умолчанию (uncapped arrival; клинит WSL IFT) |
eventhub_tsung.xml |
Legacy localhost (не для IFT) |
eventhub_http.xml |
Legacy короткий localhost |
Цель: HTTPS api.ift.eventhub.local (+ admin-api.ift.eventhub.local для seed/verify).
Правила безопасности IFT (обязательно)
IFT = Docker Swarm внутри WSL2 на Windows. Aggressive open-loop без лимитов клинит vmmem и рвёт SSH.
| Правило | Значение |
|---|---|
| Рекомендуемый stress | eventhub_ift_stress_hold.xml (stress-hold) |
maxusers |
≤ 300 (hold default 100 после ×3/2022 — CPU/RAM saturation при 200) |
thinktime |
≥ 1 s на hot-path hold |
| Плановый interarrival | ≥ 0.1 s (~10 VU/s); ниже — только с наблюдением |
| Uncapped / bump / maxusers≫300 | запрещено на IFT без явного согласования |
| Рабочий потолок (2026-07-15, ×3, с WAF) | ~10 VU/s hot-path; пересчитываем после Mnesia+loadtest-Traefik |
| Traefik loadtest | prepare подменяет conf без WAF (LOADTEST_TRAEFIK=1); rate limit 2000/1s остаётся |
| Лестница реплик | потолок сначала ×1, затем ×2, ×3 — отдельно (влияние репликации Mnesia) |
После каждого stress (обязательно — см. EventHubSpec/WORKFLOW.md §7):
- Summary:
python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log - Сбор логов IFT → exchange
ift-to-dev/loadtest-<stamp>/:- SSH ок:
\\192.168.1.116\eventhub-cursor\scripts\collect-ift-loadtest-logs.ps1(чистит remote dump stamp после download) - SSH hang:
restart-wsl-and-collect-logs.ps1илиwsl --shutdownна HOME-PC → снова collect
- SSH ок:
- Разбор: Tsung vs прошлый прогон; архив (429 / Mnesia / archive / OOM); docker-stats;
метрики бэка:
GET /metrics(Prometheus text) +GET /v1/admin/nodes/metrics?from=&to=(admin JWT, история в Mnesia); compose Grafana/Prom — опционально (KEEP_OBSERVABILITY=1), при scale 0 mid-run там пусто - Очистка leftover на IFT:
cleanup-ift-loadtest-dumps.ps1/EventHubDevOps/scripts/cleanup-ift-loadtest-dumps.sh - Отчёт в issue (#30) / строка «Результат» ниже
- Restore:
restore-ift.ps1→ift-loadtest-restore.sh
Прогон без п.1–4 не считается завершённым.
Auth path (smoke / full)
POST /v1/admin/login(admin-api) — учётка изift-admin.csvPOST /v1/register→user.idGET /v1/admin/users/:id/verification-token— спец. admin-методPOST /v1/verifyPOST /v1/login→ user JWT
Stress hot-path
Пользователи сидятся заранее (seed-ift-users.sh → ift-users.csv). Сессия Tsung: login → browse/write. Без admin/register/verify на VU.
Критерий верхней границы (2A):
- error rate > 1% (4xx/5xx + nomatch + match_stop относительно числа запросов), или
- cumulative / late-phase mean request > 500 ms (hold ≥2 мин в фазах ladder).
Разбор: python3 test/tsung/summarize-tsung-stress.py [tsung.log] → verdict=BOUNDARY_HIT|UNDER_THRESHOLD.
В error rate входят и transport-ошибки Tsung (error_connect_closed, error_timeout, error_abort_max_conn_retries).
Результат 2026-07-15 aggressive (20260715-1027/): BOUNDARY_HIT — mean ~735 ms, error ~88%, maxusers=5000, SSH wedge.
Controlled hold ×1 (20260715-1200/): фазы 0.2/0.1 GREEN; 0.05 OVER (timeout 290, ~17%); overall BOUNDARY_HIT (err ~2.9%).
Controlled hold ×2 (20260715-1336/, EVENTHUB_REPLICAS=2): фазы 0.2/0.1 GREEN (mean ≈37 ms); 0.05 OVER мягче (timeout 106, ~3%); overall UNDER_THRESHOLD (err ~0.94%, mean ~64 ms).
Controlled hold ×3 (20260715-1411/, EVENTHUB_REPLICAS=3, до фиксов): connect по фазам 0.2→0.035 GREEN (0.05 mean≈40 ms); overall BOUNDARY_HIT из‑за 3772× HTTP 429 — Traefik api-ratelimit (было 5000/1m≈83 rps).
Controlled hold ×1 (20260716-2143/, sha-6d8f02d, Traefik без WAF, maxusers=100): BOUNDARY_HIT по latency — 0 errors / 0×4xx/5xx; mean ~613 ms; late ~6.9 s; hard-stop use_controller_vm на 100 concurrent. Ранняя фаза 0.2 ~55 ms (GREEN). Образ с fix ram_copies + dump_log threshold.
Controlled hold ×3 maxusers=100 (20260715-2102/, sha-7fa24e831c16): UNDER_THRESHOLD — 0 errors, mean ~115 ms, late ~331 ms; только фазы 0.2/0.1 (~4 мин); фазы 0.05/0.035 не стартовали после hard-stop use_controller_vm. nodes/metrics: CPU peak ~60%, mem_available min ~335 MiB (без клина WSL).
Controlled hold ×3 maxusers=200 (20260715-2022/): BOUNDARY_HIT (err 1.74%, timeouts 175); CPU ~100%, mem ~11 MiB; клин SSH.
Controlled hold ×3 после фиксов (20260715-1845/, maxusers=300): 0× HTTP 4xx/5xx; mean ≈47 ms; 106× error_timeout; overall BOUNDARY_HIT (error_rate 1.018%); зависание ~194 VU + клин WSL.
Под hold также: Mnesia is overloaded: {dump_log, write_threshold|time_threshold}. Краткосрочно: MNESIA_DUMP_LOG_WRITE_THRESHOLD (IFT default 50000, EventHubBack#34). Долгосрочно: меньше sync-транзакций / hot-path вне Mnesia.
Restore fallback: оркестратор → WSL SSH, при fail Windows restore-ift.ps1. Restore после collect+анализа (см. gate выше); если SSH мёртв — wsl --shutdown → collect → restore.
Подготовка учётки admin
# из /opt/eventhub-ift/.env или локальной копии
cp test/tsung/ift-admin.csv.example test/tsung/ift-admin.csv
# одна строка без комментариев: email;password
printf '%s;%s\n' "$ADMIN_SUPER_EMAIL" "$ADMIN_SUPER_PASSWORD" > test/tsung/ift-admin.csv
ift-admin.csv в .gitignore.
Пул пользователей для stress
# из корня EventHubBack; нужны DNS/TLS до IFT и docker/.env.ift
COUNT=400 bash test/tsung/seed-ift-users.sh
# → test/tsung/ift-users.csv (email;password), gitignore
Перед прогоном (хост IFT)
EVENTHUB_REPLICAS=1 bash /opt/eventhub-ift/devops/scripts/ift-loadtest-prepare.sh
# EVENTHUB_REPLICAS=N — больше нод
Оставляет traefik + eventhub×N.
Дех-машина (WSL)
- DNS:
api.ift.eventhub.localиadmin-api.ift.eventhub.local→ IP IFT. sudo apt install tsung- TLS: доверить CA стенда (
EventHubDevOps/ift/traefik/certs). Проверка:
curl -sf https://api.ift.eventhub.local/health - Из корня
EventHubBackили оркестратор:
tsung -f test/tsung/eventhub_ift_smoke.xml start
tsung -f test/tsung/eventhub_ift.xml start
tsung -f test/tsung/eventhub_ift_stress.xml start
# оркестратор (prepare + seed при необходимости + restore):
bash ~/.cursor/eventhub/run-ift-loadtest.sh smoke|full|stress|stress-bump|stress-hold|stress-hold-hot|stress-hold-beams
# replicas: EVENTHUB_REPLICAS=1|2|3 bash ~/.cursor/eventhub/run-ift-loadtest.sh stress-hold
# фазы 0.05/0.035: stress-hold-hot (без slave beams); stress-hold-beams — только с CONFIRM_DANGEROUS=1
# Traefik без WAF в prepare: LOADTEST_TRAEFIK=1 (default); штатный WAF: LOADTEST_TRAEFIK=0
# restore fallback: если WSL SSH не отвечает — Windows OpenSSH
# powershell -File $env:USERPROFILE\.cursor\eventhub\restore-ift.ps1
Пересчёт потолка (лестница реплик)
Цель — отделить raw capacity одной ноды от стоимости репликации Mnesia.
- Задеплоить образ с рабочим
dump_log_write_threshold(доmnesia:start). - Для каждой ступени ×1 → ×2 → ×3 (отдельно, с полным gate после каждого прогона):
EVENTHUB_REPLICAS=N bash ~/.cursor/eventhub/run-ift-loadtest.sh stress-hold(зелёный baseline)- при UNDER на 0.2/0.1 —
stress-hold-hot(0.1→0.05→0.035)
- В отчёте #30: replicas, verdict по фазам, CPU/mem, Mnesia dump_log, образ
sha-*. - Не смешивать ступени в одном прогоне; не использовать beams без
CONFIRM_DANGEROUS=1.
Отчёт: ~/.tsung/log/<timestamp>/. Для stress — также ~/.tsung/ift-docker-stats-*.log.
После прогона (обязательный gate)
# 1) Tsung 2A (с дев WSL)
python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log
# 2) Логи стенда на шару (+ auto-cleanup remote dump этого stamp)
powershell -NoProfile -File \\192.168.1.116\eventhub-cursor\scripts\collect-ift-loadtest-logs.ps1
# 3) Разбор архива + метрики бэка:
# curl -sk https://api.ift.eventhub.local/metrics
# GET /v1/admin/nodes/metrics?from=&to= (admin JWT) — история узлов
# 4) Leftover dumps на IFT (все stamps)
powershell -NoProfile -File $env:USERPROFILE\.cursor\eventhub\cleanup-ift-loadtest-dumps.ps1
# 5) Restore
powershell -NoProfile -File $env:USERPROFILE\.cursor\eventhub\restore-ift.ps1
Prepare с внешним Prometheus/Grafana (не путать с /metrics приложения):
KEEP_OBSERVABILITY=1 EVENTHUB_REPLICAS=3 bash /opt/eventhub-ift/devops/scripts/ift-loadtest-prepare.sh
Настройка интенсивности
В eventhub_ift.xml / eventhub_ift_stress*.xml блок <load>: interarrival и duration фаз.
thinktime в stress XML отключён (Tsung отклоняет min="0"; дробные max тоже) — максимальная плотность для поиска потолка.
Замечания
- Все URL в IFT-сценариях абсолютные (
https://api…/https://admin-api…): после запроса на admin-api относительный путь у Tsung остаётся на том же Host → 404. - В smoke/full каждый VU делает admin login — нагрузка и на admin-api; stress этого не делает.
- Из hot path убраны: dummy refresh, reports/tickets, завышенные thinktime старого
eventhub_tsung.xml.