- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
4.1 KiB
Цена слияния на широкой доставке
Приоритет: средний
Вынуто ревью кода задачи pravilo-sliyaniya-tochek (профиль deep, враждебный
проход и независимая реализация — независимо друг от друга).
Оракул: измерено
Тело 63 МБ (в запросе ~200 КБ gzip — предел приёма 64 МиБ), 119 точек на ОДНОЙ координате:
транзакция держалась 5.149 с, аллоцировано 6108 МБ, пик кучи 189 МБ
При busy_timeout 5000 мс и _txlock=immediate параллельная доставка
получает SQLITE_BUSY, inTx повторяет её до пяти раз (каждый повтор —
полный пересчёт слияния), и на исчерпании попыток доставка уходит в
parse_status=failed. Тело при этом в архиве остаётся, но пересборки, которая
его подберёт, ещё нет.
Отдельный вклад, измеренный бенчмарком на часовом объекте нижнего слоя (3 600 точек):
HashAll на объект 9.46 мс 6.7 МБ аллокаций
Form одной точки 2.2 мкс
hashPoints пересчитывает каноническую форму ВСЕХ точек часа на каждое
слияние, включая неизменившиеся. Утверждение docs/architecture.md о
дешевизне глубокого прохода («4400 сравнений хеша, почти все сойдутся»)
стоимости самих сравнений не учитывает: чтобы сравнить хеш, его надо посчитать.
Часть цены задачей pravilo-sliyaniya-tochek уже снята: isEmpty больше не
материализует значение (было 410 мс на точку 16.5 МБ), а разбор точки
переиспользуется через canon.Fields. Осталось структурное.
Что делать
- Держать хеш каждой точки в
payloadрядом с координатами (storedPointуже есть) и пересчитывать форму только для новых и выигравших столкновение — стоимость станет пропорциональна дельте, а не объёму часа. - Считать
canon.Formточки один раз на столкновение и переиспользовать вEqual,Relateи порядке вместо трёх независимых разборов. - Проверять
ctx.Err()в цикле по точкам: сейчас дедлайн свёртки неисполним, прервать слияние нечем. - Оценить потолок числа кандидатов на одной координате: выбор победителя квадратичен по ним, и 119 точек на одну метку — вход, который приём принимает.
Связано
- Разнесение ответа приёма и свёртки сделано (архив change
2026-08-02-otvet-i-svyortka): воркер убрал влияние на время ответа, но не на блокировку записи — длинная транзакция слияния держит её по-прежнему. Заодно оттуда взято главное смягчение: занятость базы больше не выводит доставку из очереди, она остаётсяpendingи пересворачивается. Оракул окна —task verify:busy. - Пересборка (
healthlog reindex) подбирает доставки, ушедшие вfailedпо другим причинам.