reindex: пересборка витрины проигрыванием журнала
- `healthlog reindex` собирает витрину из журнала (тела архива + учёт доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую базу читает без наката миграций и не трогает вовсе. Подмену делает человек при остановленном сервисе: переименование поверх открытого дескриптора портит базу молча. - Журналом считается архив, а не таблица доставок: тело без учётной записи заводится заново (метка из ULID, размер и хеш по распакованному телу), запись без тела переносится, но не сворачивается. Оракул сходимости встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не считается. - Прогон живого архива переехал на новый пакет: второго проигрывателя журнала в проекте не осталось, а его утверждение о ключе сна перестало быть константой, протухающей с каждой доставкой.
This commit is contained in:
@@ -21,7 +21,6 @@
|
||||
|
||||
## высокий
|
||||
- [Тренировки и секции с собственными id](trenirovki-i-zapisi.md) — Тренировки с геотреком и состояние разума приходят, но не разбираются — без них не закрыть ни трекер, ни агента-медика
|
||||
- [Пересборка хранилища из сырого архива](reindex-iz-arhiva.md) — Ошибка разбора без пересборки становится потерей данных — исправленный код не применится к уже разобранному
|
||||
- [Измеренный род агрегации и каталог разрезов](rod-agregacii-i-katalog.md) — Без рода метрики свёртка в ответе неотличима от угадывания — а суммировать нижний слой значит завысить втрое
|
||||
- [Read API: точки, выбор слоя, свёртка по сетке](read-api-tochki.md) — Данные видны только через sqlite на хосте — ни один из трёх потребителей ничего прочитать не может
|
||||
- [OpenAPI-спека и Swagger UI](openapi-swagger.md) — Потребителей три и один из них агент — контракт должен читаться машиной, а не пересказываться в чате
|
||||
@@ -41,6 +40,7 @@
|
||||
- [Умолчания конфига указывают на прежнюю раскладку](umolchaniya-konfiga-data.md) — Запуск без конфига заведёт пустую базу в корне рядом с настоящей — тихая ловушка
|
||||
- [Счётчики слияния переживают ротацию логов](nablyudenie-za-sliyaniem-v-bd.md) — единственный след несравнимых наборов — строка WARN в docker-логе с ротацией 3×10 МБ: событие может произойти и не оставить ничего
|
||||
- [Цена слияния на широкой доставке](cena-sliyaniya-na-shirokoj-dostavke.md) — 63 МБ на одной координате держат транзакцию 5.15 с при busy_timeout 5 с — соседние доставки уходят в failed
|
||||
- [Заголовки доставки в архиве рядом с телом](zagolovki-dostavki-v-arhive.md) — Заголовки живут только в базе — потеря базы навсегда ломает вывод слоя при пересборке
|
||||
|
||||
## низкий
|
||||
- [Устаревание нижнего слоя после экспорта](ustarevanie-nizhnego-sloya.md) — Нижний слой растёт на ~100 тысяч координат в сутки, а после экспорта Apple он избыточен
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Пересборка хранилища из сырого архива
|
||||
|
||||
**Приоритет:** высокий
|
||||
|
||||
Разбор пишется по реальным данным и будет ошибаться — это норма, а не риск.
|
||||
Риск в другом: без пересборки ошибка разбора становится потерей данных —
|
||||
исправленный код не применится к тому, что уже разобрано неверно.
|
||||
|
||||
Пересчёт по всей истории сразу ещё и **точнее** приёма: вывод слоя и род
|
||||
агрегации на полном ряду доставок надёжнее, чем на одной.
|
||||
|
||||
Проектировать это надо сразу как **свёртку по журналу**, а не как разовую
|
||||
утилиту: состояние есть `import(снапшот экспорта) + replay(доставки после его
|
||||
даты)`, и пересборка из архива — вырожденный случай с пустым снапшотом. Тогда
|
||||
`reindex` и `import` окажутся одной операцией с разным входом, а не двумя
|
||||
похожими.
|
||||
|
||||
Отсюда требование, которое легко упустить: **свёртка обязана быть
|
||||
детерминированной.** Проигрывание должно давать то же состояние, что приём в
|
||||
реальном времени. Слияние «выигрывает более полная точка» коммутативно, но две
|
||||
одинаково полные точки с разными значениями разрешает порядок — значит
|
||||
воспроизведение идёт строго по `received_at`, а не по порядку файлов в каталоге.
|
||||
|
||||
Готово, когда пересборка с нуля даёт состояние, совпадающее с накопленным
|
||||
приёмом, и повторный прогон ничего не меняет.
|
||||
|
||||
Связано: план → шаг «Разбор и хранилище», `docs/architecture.md` → «Сырой архив».
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Заголовки доставки в архиве рядом с телом
|
||||
|
||||
**Приоритет:** средний
|
||||
|
||||
Состояние объявлено свёрткой по журналу, а журналом — сырой архив. Но в архиве
|
||||
лежит только **тело**: заголовки запроса (`automation-id`,
|
||||
`automation-aggregation`, `Accept-Language` и всё незадокументированное) живут
|
||||
единственной копией — в колонке `delivery.headers`.
|
||||
|
||||
Отсюда дыра, которую пересборка обнажила, а не создала. `healthlog reindex`
|
||||
читает учёт из рабочей базы именно потому, что восстановить заголовки неоткуда.
|
||||
Пока база цела, это работает. Если базу потерять, весь журнал становится
|
||||
«телами без учётной записи»: `automation-id` пуст, наследовать слой не от чего,
|
||||
заголовок не подтверждает ничего — и доставки без плотных метрик не сохранятся
|
||||
никогда, сколько ни пересобирай. То есть «пересобираемо из архива» верно с
|
||||
оговоркой, которой в инварианте нет.
|
||||
|
||||
Prior art прямой: **WARC** (формат веб-архивов) хранит запрос вместе с его
|
||||
заголовками именно потому, что тело без метаданных запроса события не
|
||||
воспроизводит. Смотреть у него стоит на устройство записи «заголовки + тело» и
|
||||
на то, что заголовки лежат рядом текстом, а не в отдельной базе.
|
||||
|
||||
Развилка формы (решать при взятии, не сейчас):
|
||||
|
||||
- заголовки внутрь того же `.json.gz` отдельным первым объектом — одна запись и
|
||||
одна операция, но файл перестаёт быть «телом как пришло»;
|
||||
- файл-спутник `<ulid>.headers.json` — тело остаётся дословным, зато на доставку
|
||||
два файла и два fsync, а атомарность пары надо обеспечивать самому;
|
||||
- отдельный журнал заголовков (файл на сутки, дописыванием) — дешевле всего по
|
||||
операциям, но появляется третья сущность.
|
||||
|
||||
Цена ошибки высокая: правится **путь приёма**, а доставка, не попавшая в архив,
|
||||
теряется навсегда. Значит профиль ревью — `deep`, и менять надо так, чтобы
|
||||
старые тела без заголовков продолжали читаться.
|
||||
|
||||
Готово, когда пересборка на архиве, у которого рабочей базы нет вовсе, даёт то
|
||||
же состояние, что пересборка с базой.
|
||||
|
||||
Связано: `docs/architecture.md` → «Сырой архив и восстановление состояния»,
|
||||
`internal/replay`.
|
||||
Reference in New Issue
Block a user