docs: документация переведена на канон av-dev-pm

- беклог и план переехали в 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 получил настройки с числовым значением
This commit is contained in:
av
2026-08-03 17:14:53 +03:00
parent de7b15d48c
commit d79189be18
94 changed files with 1234 additions and 566 deletions
+70
View File
@@ -0,0 +1,70 @@
# Пределы на размер сущности и потоковый расчёт формы
**Секция:** ядро · **Хук:** Тело 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`.
- Из того же ревью: «хеш без полного прохода по содержимому не посчитать» —
отброшено как предел по конструкции, но условием ложится сюда.