# Разнести ответ приёма и свёртку доставки **Приоритет:** блокеры Вынуто ревью кода задачи `razbor-metrik-v-obekty` (профиль `deep`, находка №4 триажа, severity major). ## Что решить Свёртка выполняется **синхронно внутри обработчика запроса**, поэтому время ответа равно времени свёртки. Вопрос: разносить ли их, и какой ценой. ## Оракул: измерено `WriteTimeout` в Go ставится в `readRequest` — **до** чтения тела и до вызова обработчика (`net/http/server.go:993-997`, прочитано в исходниках). Значит 30 секунд по умолчанию это бюджет на всё сразу: дочитать до 64 МиБ по мобильной сети, сделать `fsync` архива, вставить доставку и свернуть. Воспроизведено минимальной программой: сервер с `WriteTimeout=200ms`, обработчик спит 500 мс. ``` handler: WriteHeader(200), body Write err= client: elapsed=501ms err=EOF ``` Сервер считает, что отдал `200` — ошибки записи не видно, ответ ушёл в буфер и сбрасывается позже. Клиент получил обрыв. Код обработчика этого не видит, а `accessLog` честно запишет `status_code=200`: единственный сегодняшний канал наблюдаемости в этом сценарии врёт. Стоимость свёртки измерена **до** перехода на одну транзакцию на доставку: | тело | объектов | свёртка | |---|---|---| | 80 КиБ | 1001 | 815 мс | | 323 КиБ | 4001 | 3.07 с | | 1302 КиБ | 16001 | 11.07 с | Одна транзакция на доставку убрала около 0.7 мс на объект (прогон живого архива ускорился с 64 до 52 секунд), но порядок величины остался: широкая доставка по-прежнему измеряется секундами. Бьёт это по **широким проходам** — `Today`, `Previous 7 Days`, ручной экспорт, — то есть ровно по тем, ради которых заведён инвариант «дыры закрываются сами». ## Варианты и цена **(а) Отвечать `200` сразу после архивации и учёта; свёртка — воркером в порядке журнала, с подбором `pending` при старте.** Цена: средняя — воркер, очередь, подбор при старте. Бонусом закрываются ещё две дыры: параллельные доставки одной автоматизации перестают гонять наследование слоя (сейчас вторая может не найти слоя первой и уйти в `failed`), и доставка, застрявшая в `pending` из-за сбоя записи, наконец кем-то подбирается. **(б) Поднять `write_timeout` до согласованного с `foldTimeout`.** Цена: малая. Но худший случай (64 МиБ) всё равно минуты, и молчание `accessLog` остаётся. **(в) Оставить как есть, задокументировав потолок размера доставки.** Цена: нулевая. Широкие проходы продолжают рваться. ## Рекомендация **(а).** Единственный вариант, который решает причину, а не симптом, и попутно снимает две смежные находки. Он же приближает `reindex`: подбор `pending` при старте — его половина. ## Что стоит без решения Ничего: свёртка работает, просто рискует не уложиться в таймаут на самых широких доставках. Данные при этом не теряются — тело ложится в архив **до** свёртки. Связано: [reindex-iz-arhiva](reindex-iz-arhiva.md), [stats-nablyudaemost](stats-nablyudaemost.md) — метка «ответ не уложился в таймаут» должна попасть туда.