# Пределы на размер сущности и потоковый расчёт формы - **Секция:** ядро - **Зачем:** Тело 40 МиБ даёт 768 МиБ пика кучи, 63 МиБ держат блокировку 5.019 с — предела на одну сущность нет вовсе - **Теги:** goal:limits-and-load Остаток задачи «Дозакрыть находки ревью по слиянию сущностей» (архивный change `dozakryt-nahodki-sushchnostej`). Та задача убрала канонизацию приехавшей сущности из транзакции и перестала считать каноническую форму дважды. Осталось структурное: **предела на размер одной сущности нет вовсе**, а форма и хеш считаются материализацией значения целиком. ## Оракул: измерено Оракулы жили в `tmp/adv/mem_test.go` и `tmp/adv/lock_test.go`; числа снимались на теле в пределах приёма (64 МиБ): ``` тело 40 МиБ → пик HeapAlloc 768.3 МиБ тело 63 МиБ → повторная доставка держит блокировку 5.019 с при busy_timeout 5000 ``` При `_txlock=immediate` конкурентный `CreateDelivery` получает `SQLITE_BUSY`, `inTx` повторяет до пяти раз и на исчерпании отдаёт `store.ErrBusy` — приём отвечает 500 по доставке, тело которой уже в архиве. Осиротевшее тело подберёт `reindex`, но узнать о нём можно только из лога. ## Что делать 1. Предел на размер **одной сущности** и на суммарный размер секции, отдельно от предела тела (64 МиБ). Сегодня одна тренировка законно может занять всё тело целиком. Вход, превышающий предел, обязан отклоняться **до** канонизации, а не после. 2. Потоковый расчёт канонической формы и хеша: `canon.Form` разворачивает значение в дерево `any`, из-за чего пик кучи кратен размеру входа (замер даёт множитель около 19×). Хеш считается по потоку; форма нужна целиком только для сравнения, и только когда хеш разошёлся. 3. Разбор **сохранённой** версии всё ещё идёт внутри транзакции: её содержимое читается оттуда же. Убрать это можно оптимистичным чтением до транзакции — но только с перепроверкой хеша и провенанса **внутри** транзакции, иначе две конкурентные свёртки одного `id` дадут потерянное обновление и исход снова станет функцией порядка коммитов, а не журнала. ## Условия, пришедшие из закрывающей задачи 1. Мягкое чтение заголовка сущности увеличило долю тел, доходящих до канонизации: сущность, которая раньше отсекалась на `json.Unmarshal` заголовка почти бесплатно, теперь разбирается и канонизируется целиком. То есть худший случай по памяти стал достижим на входах, которые до него не доходили, — предел из пункта 1 после этого **обязателен**, а не желателен. 2. Каноническая форма и множества ключей всех версий доставки теперь **удерживаются** до конца транзакции слияния (раньше считались лениво и на одной доставке из сорока четырёх). Расход стал пропорционален размеру ДОСТАВКИ, а не самой большой её сущности; предел обязан считать суммарный размер секции, а не только одной сущности. 3. **Потолок на число версий одного ключа в одной доставке.** Выбор победителя квадратичен по числу кандидатов; версии с совпавшей канонической формой схлопываются, но различных тело вмещает сколько угодно. Отмена цикл прерывает (дедлайн свёртки снова работает), но доставка при этом уходит в `failed` — то есть отравленное тело стоит полного дедлайна воркера. Тот же вопрос открыт для точек на одной координате: `merge-cost-wide-delivery.md`, пункт 4. ## Связано - [Цена слияния на широкой доставке](merge-cost-wide-delivery.md) — та же плата со стороны **точек** (`hashPoints` пересчитывает форму всех точек часа). Задачи делать вместе: половина решения общая — `canon`. - Из того же ревью: «хеш без полного прохода по содержимому не посчитать» — отброшено как предел по конструкции, но условием ложится сюда.