# Бюджет запросов для альфа-пилота Дата фиксации: 13 сентября 2026 года. ## Контракт списочных API Все списочные endpoint'ы принимают ограниченный `limit` и неотрицательный `offset`. Справочники ограничены 500 строками, публичные и административные журналы — 100–200 строками. Сортировка всегда имеет уникальный `id` последним ключом, поэтому соседние страницы не меняются местами при одинаковых датах или названиях. Значения перечислимых фильтров проверяет FastAPI; неподдерживаемое значение возвращает `422`. Фильтры применяются в SQL до `offset` и `limit`. Это особенно важно для `GET /api/v1/records?category=...`: фильтрация JSON-поля после пагинации могла возвращать пустую страницу при наличии подходящих записей. ## Индексы Миграция `0011` добавляет составные индексы для основных путей чтения: - activity и лента точки — статус модерации, удаление, время и точка; - очередь модерации — источник, статус, удаление и время; - официальные рекорды — источник, дата и вес; - журнал импорта — источник, статус и время запуска; - staging — статус, система-источник и время последнего наблюдения; - rate limit — отпечаток клиента и время попытки; - retention аудита — время события модерации. ## Измеримый бюджет На сервере альфа-пилота при объёме до 100 000 уловов и до 100 000 staging-наблюдений принимаются следующие server-side цели без учёта сети и браузерного рендера: - p95 публичных списков и activity — не более 250 мс; - p95 административных очередей — не более 500 мс; - один запрос очистки rate limit или retention-пакет — не более 2 с; - ни один интерактивный запрос не должен читать более 10 000 строк фактов по `Rows Removed by Filter`. Это стартовый эксплуатационный бюджет, а не результат синтетического бенчмарка. Планы на пустой 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 строк, а план остаётся последовательным и выходит за бюджет, сохранить план в журнал релиза и скорректировать индекс или форму запроса до открытия альфы. Для новых каталогов снастей и аналитики сочетаний используется отдельный TEMP-only gate: ```bash ./deploy/test-tackle-query-plans.sh ``` Он создаёт 5 000 предметов, 100 000 уловов и 100 000 ordered-компонентов только в текущей psql-сессии, проверяет фильтр каталога и группировку approved-компонентов за 72 часа. Фильтр каталога должен использовать индексный путь, аналитика — селективный индексный путь по временной ленте и уложиться вместе с каталогом в 250 мс; широкий scan компонентов допустим после ограничения уловов. Production-таблицы не изменяются. Bootstrap-тест отдельно проверяет, что Alembic прошёл миграцию `0011` и все восемь составных индексов созданы на чистой PostgreSQL. После запуска на сервере локальный gate не заменяет три полевых замера p95 из [load-testing.md](load-testing.md).