- три решения опровергнуты экспериментами: повтор при SQLITE_BUSY не сходится без _txlock=immediate (242 из 800 против 800 из 800), разбор в map[string]any держит 197 МиБ против 54, канонизация без json.Number теряет литерал - канонизации назначен дом: общий internal/canon вместо hae, иначе импорт экспорта Apple потребует второй реализации и хеш-детектор станет бесполезен - вывод слоя вернул шаг наследования, WARN сравнивается только с надёжным заголовком; в bucket возвращены units и границы содержимого
11 KiB
ADDED Requirements
Requirement: Идентичность точки по координатам
Система SHALL адресовать точку координатами метрика + слой + метка времени.
Поле source в ключ входить MUST NOT: оно нестабильно — то же измерение с тем
же значением приезжает то как Apple Watch Ultra 3|iPad (Anton), то как
Apple Watch Ultra 3, потому что Health переосмысливает атрибуцию задним
числом.
Идентичность по хешу содержимого проверялась и отвергнута: она задваивала минутный слой целиком — 120 точек в часе вместо 60.
Scenario: Повторная доставка той же точки ничего не меняет
- WHEN точка с теми же координатами и тем же содержимым приезжает снова
- THEN хранилище не изменяется
Scenario: Смена источника не создаёт вторую точку
- WHEN точка с теми же координатами приезжает с другой строкой
source - THEN она остаётся одной точкой, а не превращается в две
Requirement: Эпизодные схемы не сохраняются до решения об идентичности
Система MUST NOT сохранять точки схем, у которых метка времени не уникальна, — пока модель их идентичности не определена. Такие точки разбираются и учитываются счётчиком, но в объекты не пишутся.
Известная такая схема одна: поэпизодный sleep_analysis. Измерено: 31
координата в 21 доставке из 89 задвоена внутри одной доставки, где
received_at общий и тай-брейк по нему неприменим в принципе. Сохранять их по
правилу, о котором известно, что оно теряет, хуже, чем не сохранять: тела
остаются в архиве, и после решения их подберёт пересборка.
Развилка вынесена блокером identichnost-epizodnyh-metrik. Суточной сводки
(sleep_analysis_summary) ограничение не касается — у неё метка на полуночи
уникальна.
Scenario: Поэпизодная запись сна не попадает в объект
- WHEN разобрана точка метрики
sleep_analysisпоэпизодной схемы - THEN она не сохраняется в часовой объект
- AND факт учитывается счётчиком в итоге разбора доставки
Requirement: Разрешение столкновений по полноте
Когда по одним координатам приходят разные содержимые, система SHALL оставлять
более полную точку — ту, у которой больше значащих полей, — а не последнюю
пришедшую. Иначе бедная доставка стирает start/end у богатой.
Если полнота равна, а значения различаются, исход MUST быть детерминированным и не зависеть от порядка, в котором доставки дошли до хранилища: свёртка по журналу обязана давать то же состояние, что приём в реальном времени.
Сравнение по received_at для этого не годится: у сохранённой точки нет
провенанса — ни времени приёма, ни идентификатора доставки, — и сравнивать
не с чем. Детерминизм обеспечивается свойством самих значений (например,
порядком канонических форм), а не порядком событий.
Scenario: Бедная точка не стирает поля богатой
- WHEN сохранена точка с
qty,startиend - AND по тем же координатам приезжает точка только с
qty - THEN сохранённая точка остаётся с
startиend
Scenario: Одинаково полные точки с разными значениями
- WHEN по одним координатам приходят две одинаково полные точки с разными значениями
- THEN исход определяется детерминированно и не зависит от порядка воспроизведения доставок
Scenario: Столкновение с различием содержимого оставляет след
- WHEN по одним координатам сохраняется точка, каноническая форма которой отличается от уже сохранённой
- THEN система пишет запись уровня
WARNбез значений точки - AND увеличивает счётчик перезаписей в итоге разбора доставки
Без этого следа допущение «меньше полей не значит новее» не получит ни одного наблюдения, а отказ правила будет неотличим от нормальной работы до сверки с экспортом Apple — то есть месяцами.
Requirement: Хранение часовыми объектами
Система SHALL хранить точки часовыми объектами с ключом
метрика + слой + час (UTC). Содержимое объекта — сжатый gzip блоб; точки
внутри упорядочены по времени.
Объект SHALL нести единицы измерения метрики. Внутри точки их нет — они
живут на уровне метрики (проверено: поле units не встретилось ни в одной
точке за 89 доставок), поэтому дословное хранение точек их не сохраняет. Без
колонки единицы восстановимы только из архива, а для метрик, переставших
приходить, — теряются навсегда.
Объект SHALL нести границы содержимого (первая и последняя метка) и идентификатор доставки, создавшей его. Первое нужно каталогу разрезов, чтобы не разжимать каждый блоб ради диапазона; второе — провенанс для разбора слияний.
Запись — чтение объекта, слияние точек, запись обратно. Точки из объекта MUST NOT удаляться.
Scenario: Точки за один час ложатся в один объект
- WHEN приходят точки одной метрики и слоя за один час UTC
- THEN они хранятся одним объектом
Scenario: Дозапись в существующий час
- WHEN приходят новые точки за уже существующий час
- THEN объект перечитывается, точки сливаются, объект записывается обратно
- AND ранее сохранённые точки остаются в объекте
Requirement: Хеш как детектор изменений
Система SHALL хранить хеш канонической формы объекта и пропускать запись, если хеш не изменился. Хеш — детектор, а не ключ.
Это то, что делает широкие проходы синхронизации дешёвыми: глубокий проход переприсылает неделю, но почти все сравнения сходятся и записи не происходит.
Scenario: Повторная присылка того же часа не пишет в базу
- WHEN приезжает доставка, целиком повторяющая уже сохранённый час
- THEN хеш совпадает и запись не выполняется
Requirement: Признак запечатанного часа
Система SHALL хранить признак sealed у часового объекта и SHALL реагировать
на изменение запечатанного объекта сигналом, а не отказом.
Правило перевода часа в sealed в этой дельте не определяется: порог
глубины досчёта ставится по наблюдениям, которых пока нет (наблюдалось до
22 минут). До появления правила признак остаётся невыставленным, и сценарий
ниже проверяется только явной установкой в тесте — это осознанная граница, а
не упущение.
Scenario: Изменение запечатанного часа
- WHEN приходят точки за час, помеченный
sealed - THEN система пишет запись уровня
WARN - AND данные всё равно сохраняются
Requirement: Значения точек не попадают в логи
Данные о здоровье чувствительнее токенов. Система MUST NOT писать значения
точек и тела доставок в записи лога уровня выше DEBUG.
Scenario: Разбор доставки логируется без значений
- WHEN доставка разобрана
- THEN запись лога содержит счётчики (метрик, точек, объектов) и идентификатор доставки
- AND не содержит ни значений точек, ни имён устройств