Приём отвечает 200 до свёртки, свёртку ведёт фоновый воркер
- Очередью служит сама таблица: доставка ждёт свёртки в статусе `pending`, канал несёт только бит «есть работа». Переполнять нечего, падение процесса очередь не теряет, а подбор `pending` при старте — обычный проход воркера, а не отдельный код. Классификация исхода общая с пересборкой журнала. - Исход разбора начал отражать доставку, а не обстоятельства: отмена и занятость базы статус не меняют (иначе конкуренция за базу выводила бы доставку из очереди навсегда), паника свёртки больше не валит процесс, а учёт доставки идёт через транзакцию с повторами. - Длинный бюджет ответа выдан маршруту приёма, а не всему серверу: `write_timeout` в Go покрывает и чтение тела, и общий подъём снял бы защиту с остальных маршрутов.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
## Why
|
||||
|
||||
Свёртка выполняется **внутри обработчика запроса**, поэтому время ответа равно
|
||||
времени свёртки: 16 тысяч точек — 11 секунд. `WriteTimeout` в Go ставится в
|
||||
`readRequest`, то есть до вызова обработчика, и его 30 секунд — общий бюджет на
|
||||
всё: дочитать тело по мобильной сети, записать архив, вставить строку, свернуть.
|
||||
Когда бюджет выходит, сервер считает, что отдал `200` (ошибки записи
|
||||
обработчику не видно, ответ ушёл в буфер), клиент получает обрыв, а `accessLog`
|
||||
пишет `status_code=200` — единственный канал наблюдаемости в этом сценарии врёт.
|
||||
|
||||
Бьёт это по **широким проходам** (`Today`, `Previous 7 Days`, ручной экспорт) —
|
||||
ровно по тем, ради которых заведён инвариант «дыры закрываются сами».
|
||||
|
||||
## What Changes
|
||||
|
||||
- Приём отвечает `200` **после архивации тела и вставки строки `delivery`**.
|
||||
Свёртка из обработчика уходит: время ответа перестаёт зависеть от ширины
|
||||
доставки.
|
||||
- Свёртку ведёт **фоновый воркер** — одна горутина, обработка в порядке журнала
|
||||
(`received_at`, `id`) среди доставок, видимых ему на момент выборки.
|
||||
- **Очередью служит сама таблица**, а не список идентификаторов в памяти:
|
||||
доставка ждёт свёртки в статусе `pending`, канал несёт только сигнал «есть
|
||||
работа». Отсюда три следствия: переполнять нечего (доставка и так `pending`,
|
||||
это не отказ), падение процесса очередь не теряет, а «подбор `pending` при
|
||||
старте» перестаёт быть отдельным кодом — это обычный проход воркера.
|
||||
- Классификация исхода свёртки (`folded` / слой не выведен / содержимое не
|
||||
разобрано / прочее / частичный разбор) становится **общей с пересборкой**:
|
||||
один проигрыватель в `internal/replay`, а не второй рядом.
|
||||
- **Исход свёртки начинает отражать доставку, а не обстоятельства.** Сегодня
|
||||
`failed` пишется на любой ошибке, включая занятость базы; с воркером это
|
||||
означало бы, что доставка выбывает из очереди навсегда — а конкуренция за базу
|
||||
как раз становится штатной. Отмена и занятость статус больше не меняют,
|
||||
доставка остаётся `pending`; всё прочее по-прежнему `failed` и возвращается
|
||||
только пересборкой.
|
||||
- Остановка сервиса формулируется **инвариантом**, а не обещанием досчитать:
|
||||
приём прекращается раньше воркера, и после остановки нет доставки, которая
|
||||
числится разобранной, а записана наполовину. Свёртка идёт на контексте,
|
||||
отвязанном от остановки.
|
||||
- Наблюдаемость воркера: `WARN`, когда доставка ждала свёртки дольше периода
|
||||
быстрого прохода, и одна строка `INFO` о размере задолженности при старте.
|
||||
Метка считается на выборке прохода, а сам проход будит не только сигнал, но и
|
||||
тик — иначе «работа есть, прогресса нет» неотличимо от пустого потока.
|
||||
Счётчики в `/stats` — задача `stats-nablyudaemost`, здесь только метки в логе.
|
||||
- Убирается второй, оставшийся источник молчаливого обрыва: общий `write_timeout`
|
||||
(30 с) меньше `read_timeout` (5 мин), а он покрывает и чтение тела — то есть
|
||||
медленная загрузка 64 МиБ обрывается независимо от свёртки. Длинный бюджет
|
||||
даётся **маршруту приёма** собственным дедлайном ответа; общий таймаут и
|
||||
конфиг не меняются, и прочие маршруты защиту не теряют.
|
||||
- Миграция: частичный индекс по неразобранным доставкам — воркер спрашивает их
|
||||
чаще, чем раз в минуту, а таблица растёт на ~300 строк в сутки.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `ingest`: приём доставки как самостоятельное поведение — что делает ответ
|
||||
`200` заслуженным, когда он отдаётся, кто и в каком порядке сворачивает
|
||||
принятое, что происходит с несвёрнутым при остановке и рестарте.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `parsing`: требование «Разбор не влияет на код ответа приёма» уточняется —
|
||||
разбор идёт **после** ответа, поэтому исход становится виден не в ответе и не
|
||||
сразу, а асинхронно, в `parse_status` и в логе.
|
||||
- `storage`: требование «Учёт частично разобранной доставки» уточняется — отказ
|
||||
обстоятельств (отмена, занятость базы) статуса не меняет вовсе, а прежняя
|
||||
формулировка «ошибка ⇒ `failed`» этого не допускала.
|
||||
- `reindex`: требование «Отчёт, оракул и исход команды» получает четвёртый класс
|
||||
отказа — «работа отложена по обстоятельствам»: классы у пересборки и у фоновой
|
||||
свёртки общие.
|
||||
|
||||
## Impact
|
||||
|
||||
- `internal/ingest` — теряет зависимость от `internal/fold`: `Accept` кладёт
|
||||
тело, учитывает доставку и будит воркер.
|
||||
- `internal/replay` — общий проигрыватель (свернуть доставку, классифицировать
|
||||
исход) и фоновый воркер поверх него.
|
||||
- `internal/store` — выборка неразобранных доставок в порядке журнала с
|
||||
курсором, сентинел занятости базы; миграция `00006` с частичным индексом.
|
||||
- `internal/fold` — отмена и занятость базы больше не переводят доставку в
|
||||
`failed`.
|
||||
- `cmd/healthlog/serve.go` — жизненный цикл воркера и согласованная остановка.
|
||||
- `internal/httpapi` — сборка `ingest.Service` без свёртки; собственный дедлайн
|
||||
ответа на маршруте приёма. `config.example.toml` — комментарий к
|
||||
`write_timeout`.
|
||||
- `docs/architecture.md`, `docs/database.md` — путь приёма и новый индекс.
|
||||
Reference in New Issue
Block a user