Триаж свёл 62 сырые находки девяти проходов к 33 причинам: 3 блокера, 4 «сейчас», 2 развилки. Все закрыты регрессионными тестами. - схема точки сна определяется по самой точке, а не по индексу в исходном массиве: одна пропущенная точка меняла эпизод и сводку местами - доставка сворачивается одной транзакцией: частичное состояние было недетерминированным (восемь прогонов — семь состояний) - граница размера на распакованном теле: 400 КиБ gzip разворачивались в 400 МиБ мимо max_body_mb - столкновение — расхождение канонических форм, а не байтов; WARN с координатами объекта; payload без HTML-экранирования - выравнивание по местной метке: получасовые зоны уводили часовую выгрузку в minute - доставка из одних суточных сводок больше не отвергается целиком - единицы не переписываются молча; счётчик считает сохранённые точки - все выходы Fold логируются, исход пишется на переживающем отмену контексте Четыре развилки вынесены блокерами в беклог.
81 lines
4.7 KiB
Markdown
81 lines
4.7 KiB
Markdown
# Разнести ответ приёма и свёртку доставки
|
||
|
||
**Приоритет:** блокеры
|
||
|
||
Вынуто ревью кода задачи `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=<nil>
|
||
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) — метка «ответ не уложился в
|
||
таймаут» должна попасть туда.
|