Дозакрыты находки ревью по слиянию сущностей
- Правило покрытия получило второй разряд (условный, как у точек), запрет вырождения формы и счёт содержательных элементов ряда: скелет из скаляров и ряд из null больше не затирают маршрут. Победитель внутри доставки стал функцией множества версий — общим помощником с точками, — а провенанс поднимается и при совпавшем хеше, иначе отложенная доставка возвращала витрину к прежнему содержимому. - Одно поле не того типа больше не уносит сущность, а пропуски видны в учётной записи доставки (миграция 00008, NULL = «не измерялось»); каноническая форма считается один раз и вне транзакции; откат бинаря поверх новой схемы отказывает на старте; текст ошибки разбора не несёт значений из тела. - Ревью кода профилем deep (девять проходов) нашло две регрессии и обе закрыты: безусловный второй разряд запирал законный досчёт навсегда, а выбор победителя был квадратичен по числу присланных версий одного ключа.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
## Why
|
||||
|
||||
Изменение «тренировки и записи с собственным `id`» (`f8200f7`) прошло ревью не
|
||||
полностью: проходы `adversary`, `ops` и архитектурный на коде не запускались.
|
||||
Дозапуск нашёл девять причин, триаж оставил семь — у каждой прогнанный оракул.
|
||||
Две из них необратимы по последствиям: обеднённая версия тренировки затирает
|
||||
маршрут молча (в экспорте Apple маршрута нет, `reindex` проиграет то же
|
||||
поражение), а одно поле не той формы уносит тренировку целиком, причём доставка
|
||||
при этом числится разобранной — то есть ретеншен, решающий «что потеряется,
|
||||
если тело удалить», получит ложное «терять нечего».
|
||||
|
||||
Остальные пять — молчание там, где обещана детерминированность: откат бинаря
|
||||
поверх новой схемы стартует без слова, победитель внутри доставки зависит от
|
||||
порядка элементов на проводе, провенанс устаревает на каждой из ~26 повторных
|
||||
присылок, канонизация идёт внутри транзакции вопреки собственному комментарию
|
||||
(измерено: 768 МиБ пика, 5.019 с удержания блокировки), а тело доставки в 8 МиБ
|
||||
целиком уезжает в текст ошибки и оттуда в `WARN`.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Сравнение содержания сущностей получает второй разряд и запрет вырождения
|
||||
формы.** Сегодня `Covers` смотрит только наличие ключа и длину
|
||||
верхнеуровневого массива, поэтому версия-скелет (каждый массив заменён
|
||||
массивом той же длины из `null`, каждый вложенный объект — скаляром)
|
||||
признаётся равной настоящей и выигрывает тай-брейк журнала. Добавляются: ключи
|
||||
без содержания вторым разрядом (как у точек — «иначе ключ с пустым значением
|
||||
исчезает по жребию»), запрет покрывающей версии подменять объект или массив
|
||||
скаляром, и счёт **содержательных элементов** верхнеуровневого массива рядом с
|
||||
его длиной. Предел правила остаётся названным вслух и не закрывается:
|
||||
сокращение **внутри** элемента ряда (точка маршрута без `altitude`) ловится
|
||||
только сверкой с телом в архиве.
|
||||
- **Победитель внутри одной доставки становится функцией множества версий, а не
|
||||
порядка элементов массива.** Стандарт уже записан для точек и для сущностей
|
||||
молча не применён: попарная свёртка частичного порядка с тотальным тай-брейком
|
||||
нетранзитивна — `[A,B,C]` даёт `C`, `[B,C,A]` даёт `A`. Механизм выносится в
|
||||
одного помощника, общего с точками. Счётчик различающихся версий при этом
|
||||
считает по **канонической форме**, а не по байтам: требование постановки
|
||||
(«differs при разных байтах») исполнено по форме, потому что порядок ключей у
|
||||
HAE нестабилен и побайтовый счётчик срабатывал бы на норме потока;
|
||||
детерминизм обеспечен тай-брейком по байтам, а не счётчиком.
|
||||
- **Провенанс обновляется при совпавшем хеше.** Сегодня совпадение хеша
|
||||
пропускает запись вместе с провенансом, и в сущности остаётся первая
|
||||
свёрнутая копия, а не победитель по журналу; при возврате содержимого к
|
||||
прежнему живая витрина расходится с `reindex`.
|
||||
- **Одно поле не того ТИПА больше не уносит сущность.** Пять полей заголовка
|
||||
читаются мягко — тем же принципом, который уже записан в коде для `duration`.
|
||||
Граница названа вслух: правило закрывает смену типа значения, но **не** смену
|
||||
формата строки, а наблюдался именно дрейф формата дат — тренировка с
|
||||
незнакомой датой по-прежнему теряется целиком, и закрыть это может только
|
||||
хранение сущности с неразобранной меткой (остаток). Пропуски сущностей при
|
||||
этом становятся видны в **учётной записи доставки**, а не только в логе,
|
||||
причём «не измерялось» отличимо от нуля.
|
||||
- **Счётчик удержанных версий выводится в отчёт пересборки.** Новое правило
|
||||
строже прежнего, а сходимость отпечатка его не проверяет по построению: живой
|
||||
приём и пересборка пользуются одним правилом и одинаково сойдутся на
|
||||
одинаково удержанной версии.
|
||||
- **`store.Open` сверяет версию схемы перед миграцией.** Версия базы выше
|
||||
версии бинаря — отказ, а не повод мигрировать; прецедент записан в
|
||||
`OpenForRead`.
|
||||
- **Канонизация сущности уезжает за транзакцию по-настоящему.** Каноническая
|
||||
форма считается один раз на версию, до входа в транзакцию, и из неё же
|
||||
берётся хеш — сегодня форма считается дважды (в хеше и в отложенном разборе),
|
||||
причём второй раз внутри `inTx`, который повторяется до пяти раз.
|
||||
- **Текст ошибки разбора перестаёт нести значения из тела**: называется тип
|
||||
токена и смещение во входе.
|
||||
- Лог `delivery failed` на приёме отличает занятость базы от прочих причин.
|
||||
- **Не входит:** хранение сущности с `id`, но неразобранной меткой (NULL-метка);
|
||||
пределы на размер одной сущности и секции и потоковый расчёт формы и хеша;
|
||||
принцип «data-миграции не отбирают строки по обрезаемым спискам»; длина
|
||||
очереди `pending` в `/stats`. Всё четыре уходят задачами беклога.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
Новых нет. Все семь пунктов — уточнения уже записанных правил разбора и
|
||||
хранения; новое понятие ввёл бы второй словарь для того же домена.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `parsing`: заголовок сущности читается мягко — поле не той формы пропускает
|
||||
само поле, а не сущность; диагностика разбора не несёт значений из тела.
|
||||
- `storage`: содержание сущностей сравнивается с двумя разрядами, запретом
|
||||
вырождения формы и счётом содержательных элементов массива; победитель внутри
|
||||
доставки объявлен функцией множества с тотальным порядком; провенанс
|
||||
обновляется при совпавшем хеше, а метка изменения — нет; учётная запись
|
||||
доставки несёт число пропущенных сущностей, отличая ноль от «не измерялось»;
|
||||
открытие базы сверяет версию схемы; каноническая форма сущности считается вне
|
||||
транзакции и один раз.
|
||||
- `ingest`: лог отказа приёма отличает занятость базы от прочих причин.
|
||||
- `reindex`: число пропущенных сущностей внесено в перечень производных полей,
|
||||
которые пересборка не переносит; отчёт пересборки называет число удержанных
|
||||
версий сущностей.
|
||||
|
||||
## Impact
|
||||
|
||||
- `internal/canon` — `Covers`, форма массива, экспорт хеша по готовой форме.
|
||||
- `internal/store` — `entity.go` (версия сущности, слияние, провенанс),
|
||||
`bucket.go` (комментарий о канонизации), `store.go` (страж версии схемы),
|
||||
новая миграция `00008` (колонка `skipped_entities` у доставки).
|
||||
- `internal/hae` — `entity.go` (мягкий заголовок), `hae.go` (текст ошибок).
|
||||
- `internal/fold`, `internal/ingest` — проброс счётчика пропусков в учёт,
|
||||
различение занятости базы в логе.
|
||||
- `docs/architecture.md`, `docs/database.md`, `docs/conventions.md`,
|
||||
`docs/review-journal.md`.
|
||||
- Оракулы из `tmp/adv/` переезжают обычными тестами в `internal/canon`,
|
||||
`internal/store`, `internal/hae`.
|
||||
Reference in New Issue
Block a user