- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги переименованы с транслита на английские, 85 ссылок поправлены - conventions.md разобран в docs/conventions/, local-research.md — в docs/research/, review-journal.md — в docs/review.md с разделом настройки конвейера; заведены security.md, adr/ и .pm.json - шаг docs.py check добавлен в task gate; поведение в architecture.md помечено девятью маркерами долга, database.md получил настройки с числовым значением
63 lines
4.3 KiB
Markdown
63 lines
4.3 KiB
Markdown
# Цена слияния на широкой доставке
|
||
|
||
**Секция:** ядро · **Хук:** 63 МБ на одной координате держат транзакцию 5.15 с при busy_timeout 5 с — соседние доставки уходят в failed · **Теги:** goal:limits-and-load
|
||
|
||
Вынуто ревью кода задачи `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`. Осталось структурное.
|
||
|
||
## Что делать
|
||
|
||
1. Держать хеш каждой точки в `payload` рядом с координатами (`storedPoint`
|
||
уже есть) и пересчитывать форму только для новых и выигравших столкновение
|
||
— стоимость станет пропорциональна дельте, а не объёму часа.
|
||
2. Считать `canon.Form` точки один раз на столкновение и переиспользовать в
|
||
`Equal`, `Relate` и порядке вместо трёх независимых разборов.
|
||
3. Проверять `ctx.Err()` в цикле по точкам: сейчас дедлайн свёртки неисполним,
|
||
прервать слияние нечем.
|
||
4. Оценить потолок числа кандидатов на одной координате: выбор победителя
|
||
квадратичен по ним, и 119 точек на одну метку — вход, который приём
|
||
принимает.
|
||
|
||
## Связано
|
||
|
||
- Разнесение ответа приёма и свёртки **сделано** (архив change
|
||
`2026-08-02-otvet-i-svyortka`): воркер убрал влияние на время ответа, но не на
|
||
блокировку записи — длинная транзакция слияния держит её по-прежнему. Заодно
|
||
оттуда взято главное смягчение: занятость базы больше не выводит доставку из
|
||
очереди, она остаётся `pending` и пересворачивается. Оракул окна —
|
||
`task verify:busy`.
|
||
- Пересборка (`healthlog reindex`) подбирает доставки, ушедшие в `failed` по
|
||
другим причинам.
|