store: при равной полноте точек побеждает пришедшая доставка
- байтовый порядок канонических форм остался тай-брейком только внутри одной доставки: на живом корпусе он решал 98,8% спорных координат и системно хранил меньшее значение, из-за чего step_count терял род и verify:archive был красным - правило перестало быть коммутативным осознанно, поэтому порядок свёртки приведён к журнальному: проход воркера прекращается на отложенной доставке, а свёртка вне порядка журнала пишет WARN - заведены счётчики PointsHeld и PointsErased — удержание полнотой и единственное направление, в котором правило теряет содержание
This commit is contained in:
@@ -1774,6 +1774,73 @@ instant heart_rate, respiratory_rate, blood_oxygen_saturation,
|
||||
заполненности (`xFilesFactor`) измерению не нужно: две конкурирующие гипотезы
|
||||
отсеивают неполный час сами.
|
||||
|
||||
## 54. Перемер тай-брейка: 98,8% спорных координат решает не полнота, а порядок форм
|
||||
|
||||
Замер 2026-08-04, повод — `task verify:archive` покраснел на `master` без
|
||||
единого коммита, с ростом корпуса. Метод назван целиком, потому что прежняя
|
||||
оценка (находка 49) и эта расходятся в 29 раз, и расхождение объясняется
|
||||
методом, а не данными.
|
||||
|
||||
**Метод.** 155 тел архива, разбор настоящий (`hae.Parse` с наследованием слоя по
|
||||
цепочке), ключ координаты **настоящий** — `метрика + слой + начало + конец`.
|
||||
Кандидаты схлопываются по канонической форме (`canon.SortKey`, округление до 12
|
||||
значащих цифр, находка 30); полнота — `canon.Fields.Relate`, то есть с условием
|
||||
«значения общих содержательных ключей совпали». Программа лежала в `tmp/`
|
||||
(вне репозитория: она ходит в рабочий архив).
|
||||
|
||||
Прежний замер того же дня давал «84 978 спорных из 453 171» — он считал ключ
|
||||
**без слоя**, а без слоя часовая точка сталкивается с минутной, и это не
|
||||
столкновение, а два разных ряда (та же ошибка названа в находке 49 первой
|
||||
строкой её таблицы).
|
||||
|
||||
| что мерялось | сколько |
|
||||
| --- | --- |
|
||||
| координат всего | 460 995 |
|
||||
| спорных (больше одной канонической формы) | 80 129 (17,4%) |
|
||||
| из них полнота кого-то отбрасывает | 981 (1,2%) |
|
||||
| из них все кандидаты непревзойдённые — решает тай-брейк | **79 148 (98,8%)** |
|
||||
| несравнимых пар среди непревзойдённых | 2 |
|
||||
| координат, где смена тай-брейка меняет исход | 75 494 |
|
||||
|
||||
**Соотношение 1,2% / 98,8% устойчиво** — оно совпало у обоих методов, и именно
|
||||
оно, а не абсолютное число, было основанием решения: инвариант «выигрывает
|
||||
более полная точка» на живом потоке отвечает в одном случае из восьмидесяти.
|
||||
|
||||
**Изменение сосредоточено в одной метрике одного слоя.** Из 75 494 изменившихся
|
||||
координат 71 773 (95%) — `basal_energy_burned` слоя `raw`, то есть посекундная
|
||||
развёртка HAE, которую Read API суммировать и так не имеет права. Следом
|
||||
`basal_energy_burned/minute` (1 833), `walking_running_distance/raw` (777),
|
||||
`step_count/raw` (746). Ошибка «системно храним меньшее» была массовой по
|
||||
координатам и узкой по метрикам.
|
||||
|
||||
**Несравнимых наборов больше не ноль.** Находка 49 фиксировала 0 из 2 897; на
|
||||
155 доставках их 2. Порог «объединять поля не будем, пока счётчик молчит»
|
||||
поэтому подтверждается, но уже не абсолютен: событие наступило, просто редко.
|
||||
|
||||
**Направление, в котором новое правило теряет содержание, замерено отдельно.**
|
||||
Разряд полноты гаснет, когда значения общих содержательных ключей разошлись, —
|
||||
и тогда пришедшая точка побеждает, даже если у проигравшей был содержательный
|
||||
ключ, которого у неё нет. Таких координат на корпусе **2**, обе
|
||||
`sleep_analysis_summary/day`, и обе — ровно те же, что дают несравнимые наборы.
|
||||
То есть случай «сохранённая беднее по именам, но значения разошлись» на живом
|
||||
потоке не наблюдался вовсе. Прежний байтовый порядок давал ту же потерю по
|
||||
жребию и так же молча; теперь она детерминирована и считается
|
||||
(`MergeStats.PointsErased`, `WARN`).
|
||||
|
||||
**Исход починки, тем же прогоном.** Смена тай-брейка на «побеждает пришедшая»
|
||||
вернула род двум метрикам: `step_count` (`unknown` → `cumulative`, ноль
|
||||
противоречащих часов вместо одного) и `headphone_audio_exposure`
|
||||
(`unknown` → `instant`). Итог каталога: накопительных 6 → 7, мгновенных 9 → 10,
|
||||
неизвестных 16 → 14. Отпечаток витрины сменился, как и требовалось: 3 194
|
||||
объекта, `bf36b477…` → `03aace91…`. Удержаний правилом полноты на весь
|
||||
корпус — 1 247, потерь содержания — 2.
|
||||
|
||||
**Проверено ещё раз на выросшем корпусе.** Пока шла работа, телефон прислал ещё
|
||||
три доставки; прогон на 158 телах остался зелёным (3 255 объектов, отпечаток
|
||||
`c4fbb1c7…`, ноль противоречащих часов, `step_count` по-прежнему
|
||||
`cumulative`). Это и есть ответ на то, чем дефект был найден: прежнее правило
|
||||
покраснело именно от роста корпуса, новое рост пережило.
|
||||
|
||||
## Открытые вопросы
|
||||
|
||||
- **Переживает ли «Since Last Sync» неудачную отправку.** Ключевой вопрос для
|
||||
|
||||
Reference in New Issue
Block a user