Триаж свёл 62 сырые находки девяти проходов к 33 причинам: 3 блокера, 4 «сейчас», 2 развилки. Все закрыты регрессионными тестами. - схема точки сна определяется по самой точке, а не по индексу в исходном массиве: одна пропущенная точка меняла эпизод и сводку местами - доставка сворачивается одной транзакцией: частичное состояние было недетерминированным (восемь прогонов — семь состояний) - граница размера на распакованном теле: 400 КиБ gzip разворачивались в 400 МиБ мимо max_body_mb - столкновение — расхождение канонических форм, а не байтов; WARN с координатами объекта; payload без HTML-экранирования - выравнивание по местной метке: получасовые зоны уводили часовую выгрузку в minute - доставка из одних суточных сводок больше не отвергается целиком - единицы не переписываются молча; счётчик считает сохранённые точки - все выходы Fold логируются, исход пишется на переживающем отмену контексте Четыре развилки вынесены блокерами в беклог.
4.3 KiB
Правило выбора победителя при столкновении точек
Приоритет: блокеры
Вынуто ревью кода задачи razbor-metrik-v-obekty (профиль deep, находка №7
триажа, severity major). Трогает записанный инвариант «при столкновении
выигрывает более полная точка».
Что решить
Как выбирать победителя, когда по одним координатам приехали разные содержимые. Сегодняшнее правило измеримо неверно в двух местах.
Оракул: измерено
Полнота — это счётчик ключей, чьё значение не null, не "" и не пусто.
Поэтому {}, [], 0 и false считаются содержательными:
точка {"qty":0,"a":0,"b":0,"c":{},"d":[]} полнота 5
точка {"date":"…","qty":123.4} полнота 2
Вторая точка — настоящее измерение — проигрывает первой и стирается безвозвратно. Восстановить можно только из сырого архива, пока он жив.
Тай-брейк при равной полноте — лексикографический порядок канонических
форм, то есть исход зависит от самого значения. Для накопительной метрики,
которую досчитывают задним числом, это систематическая победа меньшего
числа: qty:1 бьёт qty:100. Недосчёт, неотличимый от нормы.
Частота на живом потоке не измерена — стоит прогнать по архиву до решения.
Варианты и цена
(1) Полнота по множеству ключей: побеждает надмножество; несравнимые
множества — объединять поля, а не выбирать точку целиком.
Цена: средняя — правка спеки, resolve и тестов. Самое честное прочтение
инварианта «ничего не теряем молча»: при несравнимых наборах не теряется
ничего вообще.
(2) Оставить счёт ключей, но не считать содержательными {}, [], 0,
false, "".
Цена: малая. Правило остаётся играбельным (точку с лишними непустыми полями
никто не мешает прислать), и произвол тай-брейка не решён.
(3) При равной полноте побеждает точка более поздней доставки по
received_at.
Цена: малая, но спека должна признать зависимость от журнала, и нужен
детерминированный порядок внутри одной доставки — а именно там измерены
столкновения (31 координата в 21 доставке из 89).
Рекомендация
(1). Объединение полей при несравнимых наборах — единственный вариант, при котором столкновение вообще перестаёт быть выбором «кого потерять».
Что стоит без решения
Правило работает и теперь наблюдаемо: столкновение считается расхождением
канонических форм (а не байтов, как было), даёт WARN с координатами объекта
и счётчик overwrites. Если правило начнёт терять — это станет видно в логе,
а не через месяцы при сверке с экспортом Apple.
Связано: openspec/specs/storage → «Разрешение столкновений по полноте».