store: при равной полноте точек побеждает пришедшая доставка
- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
- **Секция:** ядро
|
||||
- **Зачем:** Доставка, свёрнутая раньше своей предшественницы, уходит в failed навсегда, и живая витрина молча расходится с пересборкой
|
||||
- **Теги:** goal:journal-and-rebuild
|
||||
- **Теги:** goal:journal-and-rebuild, question
|
||||
|
||||
**Решение принято владельцем 2026-08-02: вариант (в), но не раньше `/stats`.**
|
||||
До появления наблюдаемости живём вариантом (г) с уже записанным в спеке
|
||||
@@ -13,6 +13,70 @@
|
||||
Вынут ревью кода задачи «Разнести ответ приёма и свёртку доставки» (профиль
|
||||
`deep`, враждебный проход, находка с построенным путём и прогоном).
|
||||
|
||||
## Вопросы
|
||||
|
||||
**Решение (в) порядок журнала не восстанавливает, а цена окна выросла.**
|
||||
Записано 2026-08-04 задачей `tie-break-equal-completeness`.
|
||||
|
||||
Что случилось. Тай-брейк точек при равной полноте сменён на «побеждает
|
||||
пришедшая»: байтовый порядок системно хранил меньшее значение и стоил
|
||||
`step_count` его рода. Плата за это названа и внесена — порядок свёртки
|
||||
приведён к порядку журнала: проход воркера прекращается на первой отложенной
|
||||
занятостью доставке, а не перешагивает её. Это закрыло ту половину окна,
|
||||
которая была во власти воркера.
|
||||
|
||||
Вторая половина осталась и закрывается только на приёме: `received_at`
|
||||
фиксируется при выпуске ULID, строка учёта становится видимой после записи тела
|
||||
(184 мс на 62 МиБ), поэтому при конкурентном приёме доставка с более ранней
|
||||
меткой сворачивается позже своей преемницы.
|
||||
|
||||
**Что изменилось по сравнению с постановкой ниже.** Прежде цена окна была узкой:
|
||||
доставка без плотных метрик не выводила слой и уходила в `failed` — класс редкий
|
||||
(только автоматизации без плотных метрик). Теперь та же перестановка оставляет в
|
||||
витрине значение не той доставки, что стоит в журнале последней, — **у любой
|
||||
метрики**. Расхождение живой витрины с пересборкой перестало быть свойством
|
||||
редкого класса и стало свойством любого столкновения равной полноты, то есть
|
||||
98,8% спорных координат (находка 54).
|
||||
|
||||
**Почему это вопрос, а не работа.** Выбранный вариант **(в)** — повторы при
|
||||
`ErrLayerUnknown` — лечит невыводимый слой, но порядок журнала не
|
||||
восстанавливает: доставка всё равно сворачивается после своей преемницы, просто
|
||||
не уходит в `failed`. Порядок восстанавливают только **(а)** (резервировать
|
||||
строку учёта в начале `Accept`) и **(б)** (выдержка перед свёрткой). То есть
|
||||
после реализации (в) заявленное равенство «пересборка = приём» останется
|
||||
недостижимым, а спека хранения будет обещать его условно.
|
||||
|
||||
**Что сделано вместо, чтобы не молчать.** Воркер перед свёрткой спрашивает
|
||||
журнал, есть ли доставка позже этой в статусе `parsed` или `partial`; есть —
|
||||
пишется `WARN` с идентификатором. Расхождение стало наблюдаемым и лечится
|
||||
`healthlog reindex`. Это страж окна, и его сносят вместе с окном.
|
||||
|
||||
**Варианты и цена — те же, что ниже, плюс четвёртый.**
|
||||
|
||||
- **(в), как решено** — окно живёт, наблюдается `WARN`, лечится пересборкой.
|
||||
Дёшево; цена — «витрина есть свёртка журнала» держится на прогоне, который в
|
||||
гейт не входит.
|
||||
- **(а)** — резервировать строку учёта до записи тела. Закрывает окно совсем.
|
||||
Цена: ломается инвариант «тело на диск раньше строки учёта», появляется
|
||||
состояние «строка есть, тела нет», которое обязаны понимать пересборка и
|
||||
ретеншен.
|
||||
- **(а′)** — не резервировать, а **сериализовать** выпуск ULID вместе с записью
|
||||
тела и вставкой строки: тогда видимость строк монотонна вместе с метками, а
|
||||
инвариант «тело раньше строки» сохраняется. Цена: приём становится
|
||||
последовательным, и батч-доставки HAE выстраиваются в очередь (184 мс на
|
||||
62 МиБ на доставку).
|
||||
- **Провенанс на объект** (не на точку) — колонка с позицией журнала у часового
|
||||
объекта, тай-брейк по ней, как у сущностей. Правило снова становится
|
||||
коммутативным, окно перестаёт быть дефектом, барьер и `WARN` не нужны. Цена:
|
||||
миграция и смена формата, которую решение владельца 2026-08-04 запретило по
|
||||
бюджету, — но запрет там назван бюджетным, а не принципиальным.
|
||||
|
||||
**Рекомендация.** Пересмотреть (в) в пользу **(а′)**: он единственный закрывает
|
||||
окно, не трогая ни схему, ни инвариант «тело раньше строки». Если
|
||||
последовательный приём неприемлем по задержке — тогда провенанс на объект, а не
|
||||
жизнь с условным равенством: сегодня его проверяет один прогон, который гоняют
|
||||
руками.
|
||||
|
||||
## Что происходит
|
||||
|
||||
Метка `received_at` доставки фиксируется в момент выпуска ULID — **до** записи
|
||||
|
||||
@@ -31,6 +31,52 @@
|
||||
слияния, читающее собственную выдачу, повторяет дефект наследования слоя «из
|
||||
будущего» (`docs/review.md`, 2026-08-01).
|
||||
|
||||
## Вопросы
|
||||
|
||||
Записаны 2026-08-04 по итогам ревью (профиль `deep`). Работа доведена до
|
||||
коммита в объявленных границах; эти два решения не мои.
|
||||
|
||||
**1. Пересобирать ли живую витрину сейчас.** Новое правило меняет исход на
|
||||
75 494 координатах (95% — `basal_energy_burned` слоя `raw`), но действует
|
||||
только вперёд: уже сохранённые часы держат значение, выбранное прежним,
|
||||
измеримо смещённым правилом, пока витрину не пересоберут. Отпечаток живого
|
||||
`./data` с новым правилом **не сойдётся** — это ожидаемо и названо в дизайне.
|
||||
|
||||
- **(а)** `healthlog reindex` с остановкой сервиса и подменой файла базы сразу
|
||||
после выкладки. Цена: простой приёма на время прогона (минута на нынешнем
|
||||
архиве) плюс необратимое действие руками.
|
||||
- **(б)** отложить до планового окна, приняв расхождение витрины на этот срок.
|
||||
Цена: до пересборки Read API отдаёт по историческим часам прежние значения, а
|
||||
сверка отпечатков с пересборкой не сойдётся и будет выглядеть отказом.
|
||||
- **(в)** не пересобирать вовсе — витрина сойдётся только по тем координатам,
|
||||
которые переприедут доставками. Цена: смещение остаётся в истории навсегда,
|
||||
обнаружится сверкой с родным экспортом Apple, то есть месяцами позже.
|
||||
|
||||
Рекомендация: **(а)**. Подмена файла базы — необратимое действие человека
|
||||
(`CLAUDE.md`), выполнить его я не вправе; этим изменением оно и не выполняется.
|
||||
|
||||
**2. Не сузить ли тай-брейк там, где он теряет содержание.** Разряд полноты
|
||||
гаснет, когда значения общих содержательных ключей разошлись, — и тогда
|
||||
пришедшая точка побеждает, даже если унесёт ключ, которого сама не несёт.
|
||||
Замер: 2 координаты из 80 129 спорных на живом корпусе, обе — те же, что дают
|
||||
несравнимые наборы.
|
||||
|
||||
- **(а, сделано)** оставить правило и завести счётчик `PointsErased` с `WARN` и
|
||||
координатами. Цена: событие наблюдается, но не предотвращается; обратимо
|
||||
пересборкой, пока жив архив.
|
||||
- **(б)** сузить «побеждает пришедшая» до случая, когда множества
|
||||
содержательных ключей совпали, а при строгом включении имён оставлять более
|
||||
полную независимо от происхождения. Цена: правило перестаёт быть чисто
|
||||
структурным на этом разряде, дельта хранения переписывается, прогон живого
|
||||
архива надо снимать заново. Проверить обязательно: сохраняется ли при этом
|
||||
починка `step_count` — по замеру его столкновения идут с одинаковыми
|
||||
наборами `{date, qty}`, то есть должна сохраниться.
|
||||
|
||||
Рекомендация: **(а)** — она уже реализована, потому что не меняет принятого
|
||||
владельцем правила и восстанавливает наблюдаемость. Переход к (б) остаётся
|
||||
дешёвым: счётчик скажет, если событие станет массовым.
|
||||
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- ни одна метрика не теряет род из-за столкновения равной полноты; `step_count`
|
||||
|
||||
Reference in New Issue
Block a user