reindex: пересборка витрины проигрыванием журнала

- `healthlog reindex` собирает витрину из журнала (тела архива + учёт
  доставок) в ОТДЕЛЬНЫЙ файл базы, строго по `(received_at, id)`; рабочую
  базу читает без наката миграций и не трогает вовсе. Подмену делает
  человек при остановленном сервисе: переименование поверх открытого
  дескриптора портит базу молча.
- Журналом считается архив, а не таблица доставок: тело без учётной записи
  заводится заново (метка из ULID, размер и хеш по распакованному телу),
  запись без тела переносится, но не сворачивается. Оракул сходимости
  встроен — два отпечатка и «объектов было/стало»; пустой журнал успехом не
  считается.
- Прогон живого архива переехал на новый пакет: второго проигрывателя
  журнала в проекте не осталось, а его утверждение о ключе сна перестало
  быть константой, протухающей с каждой доставкой.
This commit is contained in:
av
2026-08-02 09:07:46 +03:00
parent 84bcbbea5c
commit 5ae0c5ff81
36 changed files with 4452 additions and 258 deletions
+44 -4
View File
@@ -61,9 +61,12 @@ iPhone ──HTTPS POST──► healthlog ──► журнал доставо
полноте. Секции, которых разбор пока не покрывает (`workouts`, `stateOfMind`
половина потока), принимаются, хранятся и честно помечаются как неразобранные.
Чего ещё нет: пересборки хранилища из архива (`reindex`), каталога метрик с
измеренным родом агрегации и **read API** — данные наружу пока не отдаются
никак. План в [docs/plan.md](docs/plan.md).
Есть и пересборка: `healthlog reindex` проигрывает журнал доставок в свежую
витрину и сверяет её отпечаток с накопленной — на живом архиве из 116 тел
пересборка воспроизводима и повторный прогон ничего не меняет.
Чего ещё нет: каталога метрик с измеренным родом агрегации и **read API**
данные наружу пока не отдаются никак. План в [docs/plan.md](docs/plan.md).
Разведка формата закончена: 50 находок на живом потоке, половина расходится с
документацией Health Auto Export — [docs/local-research.md](docs/local-research.md).
@@ -73,10 +76,47 @@ iPhone ──HTTPS POST──► healthlog ──► журнал доставо
```
healthlog serve приём + read API + MCP
healthlog import родной экспорт Apple Health (в планах)
healthlog reindex пересборка хранилища из архива (в планах)
healthlog reindex пересборка витрины из журнала
healthlog healthcheck проверка живости для docker HEALTHCHECK
```
### Пересборка витрины
Разбор пишется по реальным данным и будет ошибаться. Исправленный разбор
применяется к уже разобранному пересборкой:
```
healthlog reindex --config ./config.toml
```
Команда собирает витрину в **отдельный файл** рядом с рабочей базой и печатает
два отпечатка — рабочей витрины и пересобранной. Рабочую базу она не трогает
вовсе (открывает её только на чтение и без наката миграций), поэтому запускать
её при живом сервисе безопасно — так и стоит делать, если нужно просто сверить.
**Применить** результат — другое дело. Подмена возможна только при остановленном
сервисе: он держит файл базы открытым, и переименование поверх живого процесса
портит базу молча. Сервис при этом надо остановить **до** пересборки, а не после:
доставки, приехавшие за время прогона, в собранный файл не попадут, и подмена
стёрла бы их учёт вместе с заголовками, которые не восстанавливаются ниоткуда.
Команда это проверяет и в таком случае процедуру подмены не печатает вовсе.
```
task down
healthlog reindex --config ./config.toml
mv ./data/healthlog.db.rebuild ./data/healthlog.db
rm -f ./data/healthlog.db-wal ./data/healthlog.db-shm
task up
```
Прогон идёт линейно по архиву: на 116 телах — около полуминуты, и время растёт
вместе с архивом. Свободного места нужно не меньше текущего размера базы:
собранный файл ложится рядом с ней, на тот же том.
Прогон, убитый жёстко (`SIGKILL`, потеря питания), оставляет рядом с базой файлы
`*.partial*` — это его недособранный результат. Штатное прерывание (`Ctrl+C`) их
убирает само; оставшиеся можно удалять руками, следующему прогону они не мешают.
## Локальный запуск
Конфиг необязателен — без него берутся умолчания (`:8080`, `./healthlog.db`,