Документация для следующего разработчика (4 файла, ~1500 строк по
существу), реальный нагрузочный baseline на stage, и автоматический
smoke на каждый push.
Доки:
- docs/ARCHITECTURE.md — карта слоёв, модулей, Program.cs composition
root, полный поток signup→post с трассировщиком ASP.NET pipeline.
- docs/MULTI-TENANCY.md — ITenantEntity + reflection query-filter,
stamping в SaveChanges, SuperAdmin override (read-only + edit-mode
с reason), 8 подводных камней, чеклист «как добавить tenant-сущность».
- docs/RUNBOOK.md — health-чеки, backup/restore с примером, смена SDK,
disaster-recovery на новый сервер, 6 описанных инцидентов
(включая docker-compose project name), БД-troubleshooting.
- docs/DEVELOPER-GUIDE.md — локальный setup, гочи integration-тестов,
полные паттерны (controller с permission + tenant-сущность с
RowVersion + 5 шагов миграции), валидация, structured-логирование,
«НЕ делать» список.
k6 baseline:
- tests/load/ — 3 скрипта (signup-burst, retail-sales-parallel,
sales-report-heavy) + README с инструкциями.
- docs/performance-baseline.md — реальные цифры на stage:
* signup p95 446ms @ 50 RPM (IP-лимит 60/мин держит);
* retail-sale sequential — 17/sec, p95 71ms;
* retail-sale @ VU>1 — 53% failure из-за race в
GenerateNumberAsync (unique-violation 23505 не ловится в
SaveOrFkErrorAsync) — P0 для следующего рефакторинга;
* reports на 1500 чеков — p95 50-114ms до VU=5.
CI:
- .forgejo/workflows/stage-verify.yml — on workflow_run после Docker
API/Web, wait-for-ready → tests/stage-smoke.sh → Telegram пинг.
- tests/stage-smoke.sh — 7-секундный bash-смок (curl+jq+python3),
5 этапов: health, signup, token, multi-tenant изоляция (B → 404
на product A, B → пустой список), полный документ-цикл
(supplier+supply.post → stock=100 → sale.post → stock=99).
Локальный прогон против stage — все этапы зелёные.
Build чистый, локальный прогон smoke зелёный. Sprint 12 закрывает
автономно-безопасный цикл — дальше нужен вход от user'а.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
132 lines
7.9 KiB
Markdown
132 lines
7.9 KiB
Markdown
# Sprint 12 — документация, runbook, нагрузочное тестирование
|
||
|
||
Цель: переложить «то что знаю только я и комментарии в коде» в
|
||
читаемые документы для следующего разработчика, замерить реальную
|
||
производительность под нагрузкой, и закрыть автоматическую верификацию
|
||
stage-стэйджа на каждый push.
|
||
|
||
Старт: 2026-06-07. Исполнитель: Claude Opus 4.7.
|
||
|
||
Это **последний автономно-безопасный спринт**. Дальше нужны входы от
|
||
user'а: реальные ОФД-ApiKey, MoySklad webhook-token'ы, Windows-машина
|
||
для POS WPF, прод-деплой план, казахские переводы, реальный SMTP-провайдер.
|
||
|
||
## Принципы
|
||
|
||
- Документация — для человека, не «AI-портянка». Конкретные пути, имена
|
||
типов, причины решений. Без воды и эмоций.
|
||
- k6 — реальные числа. Если p95 высокий — пишем как есть.
|
||
- НЕ трогать: `global.json`, прод-стек, POS WPF.
|
||
|
||
## Чек-лист
|
||
|
||
- [x] **1. docs/ARCHITECTURE.md** — карта слоёв, модулей, потоков
|
||
signup→bootstrap→операции. Реальные имена типов и путей, не маркетинг.
|
||
- [x] **2. docs/MULTI-TENANCY.md** — `ITenantEntity` + reflection
|
||
query-filter, stamping в SaveChanges, SuperAdmin override (read-only +
|
||
edit-mode с reason), 8 подводных камней (IgnoreQueryFilters, фоновые
|
||
jobs без HttpContext, raw SQL, и т.д.).
|
||
- [x] **3. docs/RUNBOOK.md** — health-чеки, backup/restore (включая
|
||
disaster-recovery), смена SDK, перенос на новый сервер, **6 описанных
|
||
инцидентов** (включая docker-compose project name из ТЗ),
|
||
troubleshooting БД (stock-агрегат расхождения, audit-log
|
||
размер, EFMigrationsHistory).
|
||
- [x] **4. docs/DEVELOPER-GUIDE.md** — локальный setup, запуск тестов,
|
||
гочи integration-тестов (Ryuk, rate-limiter eager-config, один
|
||
ApiFactory), полные паттерны: добавить controller с permission +
|
||
добавить tenant-сущность с RowVersion + 5 шагов миграции, валидация
|
||
(DataAnnotations / FluentValidation / бизнес), structured-логирование.
|
||
- [x] **5. k6 нагрузочный тест** — `tests/load/` + 3 скрипта
|
||
(signup-burst, retail-sales-parallel, sales-report-heavy) +
|
||
`docs/performance-baseline.md` с **реальными цифрами** на stage'е.
|
||
Главное найденное: race в `GenerateNumberAsync` при VU > 1 на одном
|
||
tenant'е (unique-violation 23505 не ловится → 500). Прогон зарегистрирован
|
||
как P0 для следующего рефакторинга.
|
||
- [x] **6. CI workflow `.forgejo/workflows/stage-verify.yml`** —
|
||
`on workflow_run` после `Docker API`/`Docker Web`, ждёт
|
||
`/health/ready` и запускает `tests/stage-smoke.sh` (~7с,
|
||
full-cycle smoke: signup → multi-tenant isolation → supply.post →
|
||
retail-sale.post → stock check). Telegram-нотификация по
|
||
успеху/падению.
|
||
|
||
## Журнал
|
||
|
||
### 2026-06-07 старт
|
||
Sprint 11 закрыт (7/7 ✓). Поехали по docs-чек-листу.
|
||
|
||
### 2026-06-07 п.1–п.4 (документация)
|
||
Прочитал реальный код: `Program.cs` composition root, `AppDbContext`
|
||
reflection-фильтры, `HttpContextTenantContext` с AsyncLocal-override,
|
||
`SuperAdminOverrideClaimsTransformer` + `ReadonlyOverrideMiddleware`,
|
||
`RequiresPermissionAttribute` + policy-handler, `HangfireJobsConfigurator`
|
||
recurring jobs, deploy/Dockerfile + docker-compose, backup-скрипт +
|
||
systemd-timer.
|
||
|
||
Написал 4 документа на основе этого:
|
||
- `ARCHITECTURE.md` (372 строки) — слои + модули + composition root +
|
||
поток signup→post с детальным трассировщиком ASP.NET pipeline.
|
||
- `MULTI-TENANCY.md` (256 строк) — query-filter, stamping,
|
||
SuperAdmin override, 8 подводных камней + чеклист «как добавить
|
||
tenant-сущность».
|
||
- `RUNBOOK.md` (337 строк) — health-чеки, backup/restore с примером,
|
||
смена SDK, disaster-recovery, 6 инцидентов, БД-troubleshooting.
|
||
- `DEVELOPER-GUIDE.md` (332 строки) — локальный setup, тесты,
|
||
паттерны (controller + entity + валидация + логирование), "НЕ
|
||
делать" список.
|
||
|
||
### 2026-06-07 п.5 (k6 baseline)
|
||
k6 v0.55.0 standalone в `~/bin/k6`. 3 скрипта в `tests/load/`:
|
||
|
||
- `signup-burst.js`: 50 RPM → p95 446ms, 0% errors. 100 RPM → 39% 429
|
||
(IP-лимит работает, by design).
|
||
- `retail-sales-parallel.js`: VU=1 — 17 sales/sec, p95 71ms, 0%
|
||
failures. VU=5 — **53% failure** из-за race в `GenerateNumberAsync`
|
||
(unique violation на `RetailSale.Number`). Это **реальная находка**,
|
||
P0 для следующего спринта.
|
||
- `sales-report-heavy.js`: на tenant'е с 1500 чеков, VU=1 — p95 54ms,
|
||
VU=4 — p95 81ms, VU=5 — p95 114ms (один аномальный прогон показал
|
||
3.8с — autovacuum suspect).
|
||
|
||
Все цифры в `docs/performance-baseline.md` с воспроизведением.
|
||
|
||
### 2026-06-07 п.6 (CI workflow)
|
||
`.forgejo/workflows/stage-verify.yml` — `on: workflow_run` после
|
||
`Docker API` и `Docker Web`, не запускается на failed parent (нет
|
||
смысла верифировать незадеплоенное). Шаги: wait-for-ready (60с
|
||
retry loop) → запустить `tests/stage-smoke.sh` → Telegram пинг.
|
||
|
||
`tests/stage-smoke.sh` — bash-скрипт без зависимостей кроме
|
||
curl+jq+python3. 5 этапов: health, signup A, token A, multi-tenant
|
||
isolation (A создаёт продукт, B получает 404 + список без продукта A),
|
||
полный документ-цикл (supplier+supply.post → проверка stock=100 →
|
||
sale.post → проверка stock=99). Локальный прогон против stage —
|
||
**7 секунд**, всё зелёное.
|
||
|
||
### Итог
|
||
|
||
Все 6 пунктов ✓. Документация:
|
||
- 4 новых файла в `docs/` (~1300 строк суммарно).
|
||
- `docs/performance-baseline.md` — реальные цифры + 1 находка P0.
|
||
|
||
Тестирование:
|
||
- 3 k6 скрипта в `tests/load/`.
|
||
- `tests/stage-smoke.sh` — 7-секундный smoke против stage.
|
||
|
||
CI:
|
||
- `.forgejo/workflows/stage-verify.yml` — auto-verify на каждый
|
||
successful deploy.
|
||
|
||
Следующие шаги, требующие user'а (за пределами автономного режима):
|
||
1. Реальный ОФД ApiKey (Webkassa предпочтительно) — Sprint 11-fiscal
|
||
ждёт это для активации.
|
||
2. Решение по прод-деплой (домен + cert + DNS).
|
||
3. MoySklad webhook-токены для inline-импорта.
|
||
4. Windows-машина (или CI runner) для POS WPF сборки.
|
||
5. Казахский переводчик для UI (i18n уже подготовлен).
|
||
6. Реальный SMTP-провайдер для платформы (Mailgun / Postmark / Yandex).
|
||
|
||
Plus P0-задача из baseline'а: исправить race в `GenerateNumberAsync`
|
||
для `RetailSalesController` и аналогичных контроллеров — это уже
|
||
автономно делается, но требует дизайн-решения (per-tenant sequence vs
|
||
counter table vs retry-loop).
|