Files
av 8db2ec7ff4 Цена читающего маршрута: чекпойнт WAL по таймеру и условный запрос
- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал
  пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не
  только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под
  удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а
  при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как
  «разобрано целиком»
- каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка
  собрана из всего, от чего зависит ответ: версии витрины (`data_version` с
  закреплённого соединения плюс поколение — значение локально для соединения и
  не переживает переоткрытия), горизонта измерения и области действия ресурса.
  Версия снимается до и после сборки: снятая после пометила бы устаревший снимок
  свежим номером
- предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной
  ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший
  значение точки в сыром буфере записи лога
2026-08-02 20:42:22 +03:00

5.6 KiB
Raw Permalink Blame History

Why

Первый читающий маршрут (GET /api/v1/metrics) обошёлся дороже, чем выглядел: два прохода ревью измерили 693 мс и +153 МиБ живой кучи на враждебном запросе, а непрерывная запись вместе с четырьмя читающими транзакциями внахлёст дала рост -wal около 7 МБ/с без верхней границы (40 МБ за пять секунд). Приём живёт в том же процессе, и обе цены платит он: OOM убивает приём, а доставка, не попавшая в архив, телефоном не переприсылается. Третье проявление той же причины — повтор: спека каталога уже требует побайтового совпадения двух ответов на неизменившейся витрине, то есть ресурс по построению пригоден для условного запроса, а ETag не выставляется вовсе.

Задача берётся перед Read API точек намеренно: тот строится поверх этой же машинерии, и решать один вопрос трижды (каталог, точки, MCP) нельзя.

What Changes

  • Периодический чекпойнт WAL. Рядом с воркером свёртки живёт горутина, которая раз в минуту выполняет PRAGMA wal_checkpoint(PASSIVE) и останавливается дренированием, как воркер. Автоматический чекпойнт SQLite срабатывает только по концу записи, поэтому WAL, раздутый всплеском, остаётся неразобранным до следующей доставки — а ночью телефон молчит часами.
  • Наблюдаемость непродвинувшегося чекпойнта. Пассивный чекпойнт не идёт дальше снимка самого старого активного читателя и ошибки при этом не возвращает: измерено — busy=0, log=6256, checkpointed=5. Значит единственный различимый признак — «страниц в журнале много, перенесено меньше», и именно он идёт в WARN владельцу.
  • Названный предел файла журнала. journal_size_limit в строке подключения: пассивный чекпойнт возвращает страницы в базу, но файл оставляет на пике (измерено: 51 МБ до и после успешного чекпойнта на 12502 страницы). Роста это не ограничивает — усечение делает первая запись после полного чекпойнта, — и так и сказано в спеке.
  • Версия витрины и условный запрос. Хранилище отдаёт версию витрины по PRAGMA data_version, каталог выставляет ETag, а на If-None-Match с непротухшей версией отвечает 304 не открывая снимок вовсе.
  • Не делается (отложено): предел размера ответа и собственный дедлайн маршрута — их проектирует Read API точек; потоковое измерение по метрике; кеш ответа; HEAD на маршруте каталога.

Capabilities

New Capabilities

Новых нет: обе части ложатся на существующие домены.

Modified Capabilities

  • storage: добавляется версия витрины (признак изменения «в базу никто не коммитил»; монотонной она не является) и обслуживание WAL (чекпойнт по таймеру, признак непродвижения, остановка дренированием).
  • catalog: добавляется условный запросETag на ответе каталога и 304 на If-None-Match, связанный с уже существующим требованием побайтового совпадения двух ответов на неизменившейся витрине.

Impact

  • internal/store — закреплённое соединение-щуп для data_version, метод чекпойнта WAL, journal_size_limit в DSN.
  • internal/catalog — снимок каталога уезжает вместе с версией витрины.
  • internal/httpapi — общий помощник условного запроса (им же будут пользоваться точки и MCP), ETag/304 на маршруте каталога.
  • cmd/healthlog/serve.go — горутина чекпойнта и её дренирование в общем бюджете остановки.
  • Схема базы не меняется: миграции нет.
  • Контракт приёма не меняется. Контракт чтения расширяется совместимо: клиент, не присылающий If-None-Match, получает ровно то же, что и сегодня.