perf: add reproducible query plan gate

This commit is contained in:
ik
2026-09-13 16:46:41 +07:00
parent abe51b38d0
commit 73feb75767
6 changed files with 181 additions and 4 deletions
+1 -1
View File
@@ -64,7 +64,7 @@
Аудит выполнен на старой базе `13e04e6`; рекомендации ниже повторно проверены по текущей ветке. Уже реализованные или неприменимые предложения не возвращаются в backlog.
- [x] **Q11 · Декомпозиция API.** Catalog, activity/spots, records/community/status/import-history, submissions и весь admin API вынесены в отдельные `APIRouter`. `main.py` оставляет composition root, middleware, health/readiness и временные совместимые экспорты rate-limit для тестового контракта; URL и OpenAPI сохранены.
- [ ] **Q12 · Query-plan gate.** В рамках Q07 снять `EXPLAIN (ANALYZE, BUFFERS)` для activity, records, spot detail и public spot pages на реалистичном наборе данных. Существующие индексы миграции `0011_query_indexes` не дублировать; индекс с `fish_id`, SQL-агрегацию или materialized view добавлять только по измеренному плану и p95.
- [x] **Q12 · Query-plan gate.** Воспроизводимый TEMP-only fixture создаёт 100 000 уловов, 5 000 точек, 252 рыбы и 19 водоёмов без изменения рабочей БД; gate снимает `EXPLAIN (ANALYZE, BUFFERS)` для activity, records count/page, spot detail и public spot pages, требует index scan у селективных путей и бюджет 250 мс. Повторный прогон: 1,58 / 13,66 / 0,55 / 0,14 / 29,61 мс. Планы подтвердили существующие индексы; новый `fish_id`-индекс, SQL-агрегация и materialized view не добавлялись без оснований. Production p95 остаётся задачей после сервера.
- [x] **Q13 · Production bootstrap в CI.** Отдельный workflow запускает `deploy/test-production-bootstrap.sh` вручную или раз в неделю, а не на каждом push. Вывод bootstrap всегда сохраняется 14 дней; при падении добавляются Compose status и Playwright diagnostics.
- [x] **Q14 · Полная CSP.** Каждый Astro SSR-ответ получает отдельный nonce для динамического JSON-LD и собственный строгий CSP; `FILES_DOMAIN` валидируется как hostname. Production запрещает inline handlers, `unsafe-inline`, eval, wildcard и HTTP; page scripts/styles остаются same-origin `_astro`-ассетами, style/script attributes запрещены. Caddy сохраняет upstream policy и использует строгий fallback для API. Bootstrap проверяет совпадение nonce на главной, report и admin и доступность OG; реальный signed screenshot проверяется после наполнения production MinIO.
- [x] **Q15 · Частичная деградация главной.** SSR независимо получает activity, community signals и оба справочника через settled-результаты. Отказ секции показывает собственный `StatePanel`, сохраняет остальные данные и HTTP 200 с `X-RF4-Partial`/`Cache-Control: no-store`; только отказ всех четырёх частей возвращает 503, `Retry-After` и noindex. Client-side loading и optimistic UI не добавлялись.
+14 -2
View File
@@ -1,6 +1,6 @@
# Бюджет запросов для альфа-пилота
Дата фиксации: 7 сентября 2026 года.
Дата фиксации: 13 сентября 2026 года.
## Контракт списочных API
@@ -31,8 +31,20 @@
Это стартовый эксплуатационный бюджет, а не результат синтетического бенчмарка. Планы на пустой bootstrap-БД не показательны: PostgreSQL обоснованно выбирает последовательное чтение маленьких таблиц.
## Воспроизводимый локальный gate
`deploy/test-query-plans.sh` создаёт только session-local TEMP-копии production-таблиц и их индексов, загружает 100 000 уловов, 5 000 точек, 252 рыбы, 19 водоёмов и 500 приманок, выполняет `ANALYZE` и пять реальных форм запросов. После закрытия psql-сессии fixture исчезает; рабочая схема и данные не меняются.
```bash
./deploy/test-query-plans.sh
```
Контрольный повторный прогон 13 сентября 2026 года на локальном PostgreSQL 17: activity 72h — 1,58 мс и bitmap index scan по статусу/времени; records count — 13,66 мс; records page — 0,55 мс и backward index scan; spot detail — 0,14 мс и bitmap index scan; public spot pages — 29,61 мс с индексными join по водоёму, точке и рыбе. Все пять планов уложились в 250 мс. Это сравнительный локальный gate, а не production p95.
Измерение не подтвердило необходимость нового индекса с `fish_id`, SQL-агрегации или materialized view. Activity выбирает существующий временной индекс, точка — `ix_catch_report_spot_feed`, records — `ix_catch_report_official_records`; дублировать их нельзя. Gate проверяет бюджет и наличие index scan у селективных activity/records/spot путей, не фиксируя нестабильную полную строку плана.
## Проверка после загрузки пилотных данных
После наполнения выполнить `EXPLAIN (ANALYZE, BUFFERS)` для activity, moderation queue, records, staging queue и удаления старых submission attempts. Проверять фактическое время, `Rows Removed by Filter`, объём buffers и соответствие выбранного индекса фильтрам. Если таблица превышает 10 000 строк, а план остаётся последовательным и выходит за бюджет, сохранить план в журнал релиза и скорректировать индекс или форму запроса до открытия альфы.
Bootstrap-тест отдельно проверяет, что Alembic дошёл до `0011` и все восемь составных индексов созданы на чистой PostgreSQL.
Bootstrap-тест отдельно проверяет, что Alembic прошёл миграцию `0011` и все восемь составных индексов созданы на чистой PostgreSQL. После запуска на сервере локальный gate не заменяет три полевых замера p95 из [load-testing.md](load-testing.md).