0a52712258
Refs EventHub/EventHubBack#30 [skip ci] Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
12 KiB
Markdown
186 lines
12 KiB
Markdown
# 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):**
|
||
|
||
1. Summary: `python3 test/tsung/summarize-tsung-stress.py ~/.tsung/log/<stamp>/tsung.log`
|
||
2. Сбор логов 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
|
||
3. Разбор: 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 там пусто
|
||
4. Очистка leftover на IFT: `cleanup-ift-loadtest-dumps.ps1` / `EventHubDevOps/scripts/cleanup-ift-loadtest-dumps.sh`
|
||
5. Отчёт в issue (#30) / строка «Результат» ниже
|
||
6. Restore: `restore-ift.ps1` → `ift-loadtest-restore.sh`
|
||
|
||
Прогон без п.1–4 не считается завершённым.
|
||
|
||
### Auth path (smoke / full)
|
||
|
||
1. `POST /v1/admin/login` (admin-api) — учётка из `ift-admin.csv`
|
||
2. `POST /v1/register` → `user.id`
|
||
3. `GET /v1/admin/users/:id/verification-token` — спец. admin-метод
|
||
4. `POST /v1/verify`
|
||
5. `POST /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
|
||
|
||
```bash
|
||
# из /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
|
||
|
||
```bash
|
||
# из корня 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)
|
||
|
||
```bash
|
||
EVENTHUB_REPLICAS=1 bash /opt/eventhub-ift/devops/scripts/ift-loadtest-prepare.sh
|
||
# EVENTHUB_REPLICAS=N — больше нод
|
||
```
|
||
|
||
Оставляет `traefik` + `eventhub×N`.
|
||
|
||
## Дех-машина (WSL)
|
||
|
||
1. DNS: `api.ift.eventhub.local` и `admin-api.ift.eventhub.local` → IP IFT.
|
||
2. `sudo apt install tsung`
|
||
3. TLS: доверить CA стенда (`EventHubDevOps/ift/traefik/certs`). Проверка:
|
||
`curl -sf https://api.ift.eventhub.local/health`
|
||
4. Из корня `EventHubBack` или оркестратор:
|
||
|
||
```bash
|
||
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.
|
||
|
||
1. Задеплоить образ с рабочим `dump_log_write_threshold` (до `mnesia:start`).
|
||
2. Для каждой ступени **×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)
|
||
3. В отчёте #30: replicas, verdict по фазам, CPU/mem, Mnesia dump_log, образ `sha-*`.
|
||
4. Не смешивать ступени в одном прогоне; не использовать beams без `CONFIRM_DANGEROUS=1`.
|
||
|
||
Отчёт: `~/.tsung/log/<timestamp>/`. Для stress — также `~/.tsung/ift-docker-stats-*.log`.
|
||
|
||
## После прогона (обязательный gate)
|
||
|
||
```powershell
|
||
# 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` приложения):
|
||
|
||
```bash
|
||
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`.
|