- рядом с воркером свёртки живёт горутина, раз в минуту разбирающая журнал пассивным чекпойнтом; «журнал не разбирается» видно строкой владельцу, а не только по `df`. Признак — пара чисел, а не флаг занятости: тот молчит под удерживаемым читателем (`busy=0` при 6256 страницах и пяти перенесённых), а при занятой блокировке отдаёт `-1` вместо ответа, и `-1 >= -1` читалось бы как «разобрано целиком» - каталог отвечает `304` на `If-None-Match`, не открывая снимок витрины. Метка собрана из всего, от чего зависит ответ: версии витрины (`data_version` с закреплённого соединения плюс поколение — значение локально для соединения и не переживает переоткрытия), горизонта измерения и области действия ресурса. Версия снимается до и после сборки: снятая после пометила бы устаревший снимок свежим номером - предел и дедлайн ответа отложены в задачу Read API точек вместе с измеренной ценой первого запроса; попутно починен флаки-тест чужой задачи, искавший значение точки в сыром буфере записи лога
5.6 KiB
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, получает ровно то же, что и сегодня.