Files
healthlog/docs/backlog/cena-chitayushchego-marshruta.md
T
av 03edf1087d Каталог разрезов и измеренный род агрегации
- род метрики выводится сверкой минутного слоя с часовым: часовое значение
  сходится с суммой минутных — накопительная, со средним — мгновенная, иначе
  `unknown` и свёртка не предлагается вовсе. На живом архиве (123 доставки,
  31 метрика) 7 накопительных, 9 мгновенных, противоречащих часов ноль
- `GET /api/v1/metrics` под токеном чтения отдаёт единицы, слои с границами и
  род вместе с основанием измерения; род нигде не хранится — он функция витрины,
  а витрина функция журнала, устаревать в нём нечему
- миграция 00009: покрывающий индекс, чтобы каталог отвечал по учётным колонкам,
  не разжимая содержимое объектов
2026-08-02 19:23:59 +03:00

84 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Цена первого читающего маршрута: память, WAL и повторный опрос
**Приоритет:** блокеры
## Что решить
Чем ограничить стоимость маршрута чтения, у которого нет ни предела ответа, ни
собственного дедлайна, ни условного запроса. Вопрос поднялся на каталоге
(`GET /api/v1/metrics`, change `2026-08-02-katalog-i-rod-agregacii`), но
принадлежит не ему: тот же ответ понадобится Read API точек и MCP, и решать его
трижды нельзя.
Три измеренных проявления одной причины.
**Память.** Снимок каталога держит разжатые точки окна по всем метрикам сразу,
хотя измерение идёт по одной метрике. Замер враждебного прохода ревью: 20 метрик
× 8 часов × 5000 точек — 693 мс и +153 МиБ живой кучи на один запрос.
Предварительный отбор по учётным колонкам (сделан) снял разжатие заведомо
непригодных часов, но множители «метрики × окно × точки × одновременные запросы»
остались без потолка. Приём живёт в том же процессе и уже даёт пик 768 МиБ на
теле 40 МиБ; OOM убивает приём, а доставка, не попавшая в архив, телефоном не
переприсылается.
**WAL.** Замер эксплуатационного прохода на копии с драйвером и PRAGMA проекта:
непрерывная запись плюс четыре читающих транзакции внахлёст дают рост `-wal`
около 7 МБ/с без верхней границы (40 МБ за пять секунд), тогда как тот же
писатель без читателей стабилизируется на 4 МБ. Пассивный чекпойнт SQLite не
продвигается дальше снимка самого старого активного читателя, и ошибки при этом
нет — виден только растущий файл. `PRAGMA wal_checkpoint` в проекте не
вызывается нигде.
**Повторный опрос.** Спека каталога требует побайтового совпадения двух ответов
на неизменившейся витрине — то есть ресурс по построению пригоден для условного
запроса, а `ETag`/`304` не выставляется. Потребителей трое (агент-медик, трекер,
игра), и самый частый их запрос — повтор неизменившегося.
## Варианты и цена
**а. Предел и дедлайн у маршрута.** Потолок числа метрик и точек в одном ответе,
собственный `context.WithTimeout`, честный отказ при превышении. Цена: клиент
обязан уметь читать частичный каталог, то есть появляется пагинация — контракт
чтения усложняется на первой же ручке.
**б. Измерение потоком по метрике внутри той же транзакции.** Точки метрики
освобождаются сразу после вердикта; требование «один снимок» не нарушается. Цена:
хранилище перестаёт возвращать снимок значением и начинает отдавать его
последовательно (итератор или колбэк) — то есть меняется форма границы
`store`/`catalog`, ради случая, которого живой поток пока не производит.
**в. Условный запрос: `ETag` по `PRAGMA data_version`.** Снимает и стоимость
повтора, и большую часть читающих транзакций разом: клиент с непротухшим `ETag`
получает `304`, и снимок не открывается вовсе. Цена: один лишний запрос к базе на
каждый вызов и обещание клиенту, что версия витрины меняется не чаще, чем данные.
**г. Периодический `wal_checkpoint(PASSIVE)` по таймеру рядом с воркером.**
Лечит только WAL, зато дёшево и без изменения контракта. Память и повтор
остаются.
**д. Кеш ответа на короткий TTL.** Закрывает всё сразу, но заводит третье
представление того же факта, и его инвалидация становится новым местом, где можно
ошибиться молча. Дизайн каталога отверг кеш именно поэтому.
## Что заблокировано
Ничего сегодня: на живом корпусе каталог собирается за 45 мс, потребителей у него
пока нет, а маршрут живёт в доверенной сети. Блокировано будущее — Read API
точек, где объёмы на порядок больше, и выкладка наружу, где опрос станет
непрерывным.
## Рекомендация
**г + в, именно в таком порядке.** Чекпойнт по таймеру закрывает единственное
проявление, которое ломает приём (диск), и стоит одной горутины без изменения
контракта. `ETag` по `data_version` — один запрос к базе, снимает и повтор, и
большую часть читающих транзакций, и делает это без кеша ответа.
Вариант «а» откладывать до Read API точек: там предел размера ответа всё равно
проектируется (`read-api-tochki.md`), и делать его дважды не нужно. Вариант «б»
не брать, пока счётчик не заговорит: он меняет форму границы ради случая,
которого поток не производит. Вариант «д» — последним, если «в» окажется мало.
Связано: `docs/architecture.md` → «Измерение рода агрегации», `read-api-tochki.md`,
`stats-nablyudaemost.md`.