- открытых вопросов не осталось: три решения владельца доведены до берущегося вида, по entity-without-parsed-label принято хранить с NULL-меткой после Read API - unseen-sections-check сжата до остатка — активная проверка появления секции; разбор невиденных секций из неё вынут, вслепую он не пишется - спринт под целью parsing-and-storage: categorical-value-dictionary и unseen-sections-check, обеим написаны критерии приёмки с оракулами
6.6 KiB
Порядок журнала при конкурентных приёмах
- Секция: ядро
- Зачем: Доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда, и живая витрина молча расходится с пересборкой
- Теги: goal:journal-and-rebuild
Решение принято владельцем 2026-08-02: вариант (в), но не раньше /stats.
До появления наблюдаемости живём вариантом (г) с уже записанным в спеке
приёма пределом — иначе повторы лечат болезнь, которую никто не наблюдает.
Задача берётся после наблюдаемости; ниже — исходная
постановка блокера, она же ТЗ.
Вынут ревью кода задачи «Разнести ответ приёма и свёртку доставки» (профиль
deep, враждебный проход, находка с построенным путём и прогоном).
Что происходит
Метка received_at доставки фиксируется в момент выпуска ULID — до записи
тела в архив и до вставки строки учёта. Порядок, в котором строки становятся
видимыми воркеру, порядку меток не подчиняется: между выпуском идентификатора и
коммитом строки проходит запись тела (измерено 184 мс на 62 МиБ) плюс ожидание
занятой базы (до пяти секунд, а с повторами транзакции дольше).
Путь построен и прогнан:
- Широкая доставка A автоматизации X получает
received_at = T1и уходит писать тело. - Узкая доставка B той же автоматизации (
T2 > T1, толькоsleep_analysis, плотных метрик нет) успевает закоммитить строку первой и будит воркер. - Воркер видит только B, сворачивает её, наследовать слой не от кого →
ErrLayerUnknown→failed. failedфоновая свёртка не подбирает никогда. Точки B в витрину не попадут.
Измерено на фикстурах: живой приём даёт B=failed и ноль часов
sleep_analysis/minute; журнальный порядок — B=parsed и два часа. То есть
живое состояние расходится с тем, что даст healthlog reindex, и расхождение
молчит: уровень лога у этого исхода WARN, такой же, как у штатного «у этой
автоматизации плотных метрик не бывает».
Это не регресс — прежде свёртка шла в порядке завершения обработчиков, то есть было хуже. Изменение окно сузило и назвало предел в спеке приёма; вопрос в том, закрывать ли его совсем.
Варианты и цена
а. Резервировать строку учёта в начале Accept (до записи тела), дописывая
raw_path/bytes/sha256 после. Тогда видимость строки монотонна вместе с
received_at. Цена: ломается инвариант «тело на диск раньше строки учёта»,
заведённый ровно затем, чтобы не было учтённой доставки без данных; появляется
новое состояние «строка есть, тела ещё нет», которое обязаны понимать пересборка
и ретеншен.
б. Откладывать свёртку доставки, пока она не «устоялась» — не сворачивать моложе N секунд. Цена: задержка N на каждую доставку и произвольное N: окно занятости базы измерено до пяти секунд и зависит от нагрузки, так что N честно не выбрать.
в. ErrLayerUnknown в живом пути не выводит доставку из очереди —
ограниченное число повторов, потом failed. Цена: колонка счётчика попыток
(миграция) и политика «сколько попыток достаточно»; зато лечит и прочие случаи
«предшественница ещё не доехала». Требует правки спеки хранения («отказ разбора
⇒ failed»).
г. Ничего не делать, оставив предел названным в спеке. Цена: редкая,
молчаливая потеря точек у автоматизаций без плотных метрик; лечится
healthlog reindex с остановкой сервиса и ручной подменой базы, но узнать о
необходимости неоткуда — счётчика failed в рантайме нет.
Что заблокировано
Ничего: задача про разнесение ответа и свёртки доведена до конца в объявленных границах, предел записан в спеке приёма. Заблокировано только закрытие предела.
Смежно: пока предел жив, полезно уметь сверять живую витрину с пересборкой —
reindex уже печатает оба отпечатка, но по расписанию их никто не сравнивает.
Рекомендация
(в), но не раньше /stats: сперва должно стать видно, сколько доставок
числится failed и как давно, — иначе повторы будут лечить болезнь, которую
никто не наблюдает. До тех пор — (г) с уже записанным пределом.