- находки 48 и 49: единицы не менялись ни разу; настоящих столкновений 0.65%, несравнимых наборов полей нет, тай-брейк берёт меньшее в 96% случаев - edinicy-metriki-v-razreze закрыт: гипотеза не подтвердилась замером - тай-брейк отложен до каталога рода агрегации
93 lines
6.0 KiB
Markdown
93 lines
6.0 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` при старте.**
|
||
|
||
Почему он, а не альтернативы:
|
||
|
||
- Поднять `write_timeout` до согласованного с `foldTimeout` — дёшево, но
|
||
худший случай (64 МиБ) всё равно минуты, и молчание `accessLog` остаётся.
|
||
Это лечит симптом.
|
||
- Оставить как есть — широкие проходы продолжают рваться.
|
||
|
||
Вариант (а) решает причину и попутно снимает две смежные дыры: параллельные
|
||
доставки одной автоматизации перестают гонять наследование слоя (сейчас вторая
|
||
может не найти слоя первой и уйти в `failed`), и доставка, застрявшая в
|
||
`pending` из-за сбоя записи, наконец кем-то подбирается.
|
||
|
||
## Что делать
|
||
|
||
1. Воркер свёртки: одна горутина, очередь идентификаторов доставок, обработка
|
||
**строго в порядке журнала** (`received_at`, `id`) — от этого зависит
|
||
наследование слоя и воспроизводимость.
|
||
2. Приём отвечает `200` после архивации и вставки доставки; свёртку ставит в
|
||
очередь. Очередь переполнена — доставка остаётся `pending`, это не отказ.
|
||
3. Подбор `pending` при старте, тем же путём. Это половина `reindex`, поэтому
|
||
код должен быть общим с ним, а не соседним.
|
||
4. Остановка сервиса дожидается текущей доставки: свёртка — одна транзакция,
|
||
рвать её нечем, но очередь надо дренировать осознанно.
|
||
5. Метка «доставка ждала свёртки дольше N» — в наблюдаемость, чтобы отставание
|
||
воркера было видно до того, как оно станет отставанием на сутки.
|
||
6. Тесты: порядок журнала соблюдается при конкурентных доставках; `pending`
|
||
подбирается при старте; отмена контекста не оставляет половинчатого
|
||
состояния; `task verify:archive` даёт то же состояние.
|
||
|
||
## Что стоит без решения
|
||
|
||
Ничего: свёртка работает, просто рискует не уложиться в таймаут на самых
|
||
широких доставках. Данные при этом не теряются — тело ложится в архив **до**
|
||
свёртки.
|
||
|
||
## Связано
|
||
|
||
- [reindex-iz-arhiva](reindex-iz-arhiva.md) — подбор `pending` это её половина;
|
||
делать одним кодом.
|
||
- [stats-nablyudaemost](stats-nablyudaemost.md) — метка «ответ не уложился в
|
||
таймаут» и отставание воркера должны попасть туда.
|