tradein: SIGTERM-drain при деплое не помечает app-task бэкфиллы (cian_detail/cian_history/avito/domclick detail) — hard-cancel через 100 с, строки остаются running → boot-reap делает их zombie #3391

Closed
opened 2026-09-06 02:42:14 +00:00 by bot-backend · 2 comments
Collaborator

Факт (первый реальный SIGTERM-drain после #3363, деплой 3fe2d310 07.09 02:36 UTC), хронология из Loki ({host="apps"}):

02:36:47 tradein-scraper scheduler_main: signal 15 received — requesting cooperative drain
02:36:47 tradein-scraper scheduler_main: SIGTERM-drain — waiting up to 100s for in-flight unit to commit
02:37:36 tradein-scraper scheduler: SIGTERM-drain — stop claiming new runs, exiting tick loop
02:37:36 tradein-scraper scheduler: draining 2 in-flight run task(s) on shutdown
02:38:27 tradein-scraper scheduler_main: drain exceeded 100s grace — hard-cancelling scheduler task
02:38:27 tradein-scraper scheduler_main: scheduler drained cleanly (SIGTERM)      ← противоречит строке выше
02:38:59 tradein-scraper scheduler: boot-reap — 2 прогонов предыдущего контейнера сняты с 'running': [(6173, 'cian_history_backfill'), (6167, 'cian_detail_backfill')]

Итог в scrape_runs: 6167 (cian_detail_backfill, 75 мин, heartbeat 02:38:18, listings_processed=162) и 6173 (cian_history_backfill, стартовал 02:34) — status='zombie', counters.boot_reaped=true, interrupted отсутствует. Прогоны были живые и убиты нашим деплоем, а в базе выглядят как зависшие.

Причина. interrupted=1 на SIGTERM пишут kit-пайплайны (orchestration/pipeline.py — city sweeps, full-load, domclick sweep; #3363) и DKP-импорт (app/services/scheduler.py:384). App-task бэкфиллы (cian_detail_backfill, cian_history_backfill, avito_detail_backfill, domclick_detail_backfill) на CancelledError при hard-cancel строку не финализируют → остаётся running → boot-reap нового контейнера → zombie. Grace 100 с (scheduler_main._DRAIN_TIMEOUT_S) для detail-бэкфилла с одним объявлением в 10-30 с — редко достаточно, чтобы «доехать до чекпоинта».

Чинить в одном месте: kit-scheduler (orchestration/scheduler.py, ветка «draining N in-flight run task(s)») знает run_id всех in-flight задач; при истечении grace / CancelledError — для каждой ещё running строки писать interrupted=1 и выводить из running (mark_done partial, как у свипов, — резюм у бэкфиллов SQL-driven). Тогда _pick_resume/разборы читают честное «прерван деплоем», а не «завис». Тест: настоящий run_scheduler с in-flight корутиной, которая не отвечает на cancel вовремя → после hard-cancel строка done + interrupted=1, не running. Второе: строка «scheduler drained cleanly (SIGTERM)» печатается и при hard-cancel — лог врёт, поправить.

Приёмка: следующий деплой при идущем detail-бэкфилле — строка done/failed с interrupted=1, boot-reap печатает 0 прогонов.

Refs #3363, #3355, #3168, #1182.

**Факт (первый реальный SIGTERM-drain после #3363, деплой `3fe2d310` 07.09 02:36 UTC), хронология из Loki (`{host="apps"}`):** ``` 02:36:47 tradein-scraper scheduler_main: signal 15 received — requesting cooperative drain 02:36:47 tradein-scraper scheduler_main: SIGTERM-drain — waiting up to 100s for in-flight unit to commit 02:37:36 tradein-scraper scheduler: SIGTERM-drain — stop claiming new runs, exiting tick loop 02:37:36 tradein-scraper scheduler: draining 2 in-flight run task(s) on shutdown 02:38:27 tradein-scraper scheduler_main: drain exceeded 100s grace — hard-cancelling scheduler task 02:38:27 tradein-scraper scheduler_main: scheduler drained cleanly (SIGTERM) ← противоречит строке выше 02:38:59 tradein-scraper scheduler: boot-reap — 2 прогонов предыдущего контейнера сняты с 'running': [(6173, 'cian_history_backfill'), (6167, 'cian_detail_backfill')] ``` Итог в `scrape_runs`: 6167 (`cian_detail_backfill`, 75 мин, heartbeat 02:38:18, `listings_processed=162`) и 6173 (`cian_history_backfill`, стартовал 02:34) — `status='zombie'`, `counters.boot_reaped=true`, **`interrupted` отсутствует**. Прогоны были живые и убиты нашим деплоем, а в базе выглядят как зависшие. **Причина.** `interrupted=1` на SIGTERM пишут kit-пайплайны (`orchestration/pipeline.py` — city sweeps, full-load, domclick sweep; #3363) и DKP-импорт (`app/services/scheduler.py:384`). App-task бэкфиллы (`cian_detail_backfill`, `cian_history_backfill`, `avito_detail_backfill`, `domclick_detail_backfill`) на `CancelledError` при hard-cancel строку не финализируют → остаётся `running` → boot-reap нового контейнера → `zombie`. Grace 100 с (`scheduler_main._DRAIN_TIMEOUT_S`) для detail-бэкфилла с одним объявлением в 10-30 с — редко достаточно, чтобы «доехать до чекпоинта». **Чинить в одном месте:** kit-scheduler (`orchestration/scheduler.py`, ветка «draining N in-flight run task(s)») знает run_id всех in-flight задач; при истечении grace / `CancelledError` — для каждой ещё `running` строки писать `interrupted=1` и выводить из `running` (`mark_done` partial, как у свипов, — резюм у бэкфиллов SQL-driven). Тогда `_pick_resume`/разборы читают честное «прерван деплоем», а не «завис». Тест: настоящий `run_scheduler` с in-flight корутиной, которая не отвечает на cancel вовремя → после hard-cancel строка `done` + `interrupted=1`, не `running`. Второе: строка «scheduler drained cleanly (SIGTERM)» печатается и при hard-cancel — лог врёт, поправить. **Приёмка:** следующий деплой при идущем detail-бэкфилле — строка `done`/`failed` с `interrupted=1`, `boot-reap` печатает 0 прогонов. Refs #3363, #3355, #3168, #1182.
Author
Collaborator

PR #3392 смержен и задеплоен (06.09 ~04:22 UTC, BUILD_SHA=aeb1b2b в backend и scraper; маркер mark_inflight_interrupted в orchestration/scheduler.py контейнера scraper — есть, в старой сборке 0). Сам этот деплой прошёл без in-flight прогонов (гейт дождался конца cian_city_sweep 6179; новый контейнер: boot-reap — прогонов предыдущего контейнера нет (0)), поэтому эффект не наблюдался.

Критерий приёмки (событийный, до 13.09): первый деплой при идущем detail-бэкфилле — в логах scraper scheduler: drain — N прогон(ов) сняты с 'running' как interrupted: [...], без «drained cleanly» после «hard-cancelling»; в scrape_runs строка done (или failed по honest-status-гейту, см. #3393) с counters.interrupted='1', без boot_reaped, прежние счётчики на месте; boot-reap нового контейнера печатает 0. Отрицательный контроль — 06.09 02:36 UTC (6167/6173 → zombie, boot_reaped=true).

PR #3392 смержен и задеплоен (06.09 ~04:22 UTC, `BUILD_SHA=aeb1b2b` в backend и scraper; маркер `mark_inflight_interrupted` в `orchestration/scheduler.py` контейнера scraper — есть, в старой сборке 0). Сам этот деплой прошёл без in-flight прогонов (гейт дождался конца `cian_city_sweep` 6179; новый контейнер: `boot-reap — прогонов предыдущего контейнера нет (0)`), поэтому эффект не наблюдался. **Критерий приёмки (событийный, до 13.09):** первый деплой при идущем detail-бэкфилле — в логах scraper `scheduler: drain — N прогон(ов) сняты с 'running' как interrupted: [...]`, без «drained cleanly» после «hard-cancelling»; в `scrape_runs` строка `done` (или `failed` по honest-status-гейту, см. #3393) с `counters.interrupted='1'`, без `boot_reaped`, прежние счётчики на месте; boot-reap нового контейнера печатает 0. Отрицательный контроль — 06.09 02:36 UTC (6167/6173 → `zombie`, `boot_reaped=true`).
Author
Collaborator

Событийная приёмка выполнена (06.09 08:24 UTC, деплой 06ad504 при бегущем cian_detail_backfill 6200, 60 мин работы). Loki, старый контейнер:

08:24:07 scheduler_main: signal 15 received — requesting cooperative drain
08:24:21 scheduler: SIGTERM-drain — stop claiming new runs; draining 1 in-flight run task(s) on shutdown
08:25:41 scheduler: 1 task(s) did not drain in 80s — leaving for hard-cancel
08:25:41 scheduler: drain — 1 прогон(ов) сняты с 'running' как interrupted: [6200]
08:25:41 mark_done: run_id=6200 оборван деплоем (SIGTERM-drain), частичный результат — honest-status-гейты пропущены (#3393)
08:26:13 (новый контейнер) scheduler: boot-reap — прогонов предыдущего контейнера нет (0)

scrape_runs 6200: status='done', counters.interrupted='1', boot_reaped пуст, listings_processed=210 на месте (heartbeat 08:25:42 = finished_at). Отрицательный контроль — 06.09 02:36 UTC (6167/6173 → zombie, boot_reaped=true).

**Событийная приёмка выполнена (06.09 08:24 UTC, деплой `06ad504` при бегущем `cian_detail_backfill` 6200, 60 мин работы).** Loki, старый контейнер: ``` 08:24:07 scheduler_main: signal 15 received — requesting cooperative drain 08:24:21 scheduler: SIGTERM-drain — stop claiming new runs; draining 1 in-flight run task(s) on shutdown 08:25:41 scheduler: 1 task(s) did not drain in 80s — leaving for hard-cancel 08:25:41 scheduler: drain — 1 прогон(ов) сняты с 'running' как interrupted: [6200] 08:25:41 mark_done: run_id=6200 оборван деплоем (SIGTERM-drain), частичный результат — honest-status-гейты пропущены (#3393) 08:26:13 (новый контейнер) scheduler: boot-reap — прогонов предыдущего контейнера нет (0) ``` `scrape_runs` 6200: `status='done'`, `counters.interrupted='1'`, `boot_reaped` пуст, `listings_processed=210` на месте (heartbeat 08:25:42 = finished_at). Отрицательный контроль — 06.09 02:36 UTC (6167/6173 → `zombie`, `boot_reaped=true`).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3391
No description provided.