блокеры разобраны замером: три стали задачами, один закрыт

- находки 48 и 49: единицы не менялись ни разу; настоящих столкновений 0.65%,
  несравнимых наборов полей нет, тай-брейк берёт меньшее в 96% случаев
- edinicy-metriki-v-razreze закрыт: гипотеза не подтвердилась замером
- тай-брейк отложен до каталога рода агрегации
This commit is contained in:
av
2026-08-01 19:33:47 +03:00
parent 37413bb551
commit 349a227ab1
8 changed files with 211 additions and 165 deletions
+39 -27
View File
@@ -1,16 +1,14 @@
# Разнести ответ приёма и свёртку доставки
**Приоритет:** блокеры
**Приоритет:** высокий
Вынуто ревью кода задачи `razbor-metrik-v-obekty` (профиль `deep`, находка №4
триажа, severity major).
Была блокером, вынутым ревью кода задачи `razbor-metrik-v-obekty` (профиль
`deep`, находка №4 триажа, severity major). **Решение принято** — ниже задача.
## Что решить
## Что не так сегодня
Свёртка выполняется **синхронно внутри обработчика запроса**, поэтому время
ответа равно времени свёртки. Вопрос: разносить ли их, и какой ценой.
## Оракул: измерено
ответа равно времени свёртки.
`WriteTimeout` в Go ставится в `readRequest`**до** чтения тела и до вызова
обработчика (`net/http/server.go:993-997`, прочитано в исходниках). Значит
@@ -46,28 +44,39 @@ client: elapsed=501ms err=EOF
экспорт, — то есть ровно по тем, ради которых заведён инвариант «дыры
закрываются сами».
## Варианты и цена
## Что решено
**(а) Отвечать `200` сразу после архивации и учёта; свёртка — воркером в
порядке журнала, с подбором `pending` при старте.**
Цена: средняя — воркер, очередь, подбор при старте. Бонусом закрываются ещё
две дыры: параллельные доставки одной автоматизации перестают гонять
наследование слоя (сейчас вторая может не найти слоя первой и уйти в
`failed`), и доставка, застрявшая в `pending` из-за сбоя записи, наконец
кем-то подбирается.
Вариант (а): **отвечать `200` сразу после архивации и учёта; свёртка —
воркером в порядке журнала, с подбором `pending` при старте.**
**(б) Поднять `write_timeout` до согласованного с `foldTimeout`.**
Цена: малая. Но худший случай (64 МиБ) всё равно минуты, и молчание
`accessLog` остаётся.
Почему он, а не альтернативы:
**(в) Оставить как есть, задокументировав потолок размера доставки.**
Цена: нулевая. Широкие проходы продолжают рваться.
- Поднять `write_timeout` до согласованного с `foldTimeout` — дёшево, но
худший случай (64 МиБ) всё равно минуты, и молчание `accessLog` остаётся.
Это лечит симптом.
- Оставить как есть — широкие проходы продолжают рваться.
## Рекомендация
Вариант (а) решает причину и попутно снимает две смежные дыры: параллельные
доставки одной автоматизации перестают гонять наследование слоя (сейчас вторая
может не найти слоя первой и уйти в `failed`), и доставка, застрявшая в
`pending` из-за сбоя записи, наконец кем-то подбирается.
**(а).** Единственный вариант, который решает причину, а не симптом, и попутно
снимает две смежные находки. Он же приближает `reindex`: подбор `pending` при
старте — его половина.
## Что делать
1. Воркер свёртки: одна горутина, очередь идентификаторов доставок, обработка
**строго в порядке журнала** (`received_at`, `id`) — от этого зависит
наследование слоя и воспроизводимость.
2. Приём отвечает `200` после архивации и вставки доставки; свёртку ставит в
очередь. Очередь переполнена — доставка остаётся `pending`, это не отказ.
3. Подбор `pending` при старте, тем же путём. Это половина `reindex`, поэтому
код должен быть общим с ним, а не соседним.
4. Остановка сервиса дожидается текущей доставки: свёртка — одна транзакция,
рвать её нечем, но очередь надо дренировать осознанно.
5. Метка «доставка ждала свёртки дольше N» — в наблюдаемость, чтобы отставание
воркера было видно до того, как оно станет отставанием на сутки.
6. Тесты: порядок журнала соблюдается при конкурентных доставках; `pending`
подбирается при старте; отмена контекста не оставляет половинчатого
состояния; `task verify:archive` даёт то же состояние.
## Что стоит без решения
@@ -75,6 +84,9 @@ client: elapsed=501ms err=EOF
широких доставках. Данные при этом не теряются — тело ложится в архив **до**
свёртки.
Связано: [reindex-iz-arhiva](reindex-iz-arhiva.md),
[stats-nablyudaemost](stats-nablyudaemost.md) — метка «ответ не уложился в
таймаут» должна попасть туда.
## Связано
- [reindex-iz-arhiva](reindex-iz-arhiva.md) — подбор `pending` это её половина;
делать одним кодом.
- [stats-nablyudaemost](stats-nablyudaemost.md) — метка «ответ не уложился в
таймаут» и отставание воркера должны попасть туда.