chore(ci): деплой не убивает worker посреди скрап-прогона (заплатка до #3074) #3084
Merged
lekss361
merged 1 commit from 2026-08-24 17:25:23 +00:00
chore/3029-worker-recreate-guard into main
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6bb3189bc4 |
chore(ci): деплой не убивает worker посреди скрап-прогона
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Successful in 2m10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Successful in 17m32s
Каждый деплой ПТИЦЫ безусловно пересоздавал worker — а он единственный, кто несёт скрап-прогоны (beat только триггерит, отдельного сервиса scraper в этом compose нет вовсе). Полный прогон КН идёт часами, при неудаче — сутки; любой мерж в это окно убивал его молча, и деплой при этом был зелёным. Заплатка до чекпоинтов (#3074): пока прогон не умеет продолжаться с места обрыва, дешевле не обрывать. Пересоздание worker'а пропускается, только когда выполнены оба условия: образ действительно сменился (иначе пересоздание и так no-op) и прямо сейчас есть живой прогон. Маркер заводить не пришлось, он уже есть — kn_scrape_runs и objective_scrape_runs со status='running'; литералы сверены с backend/app/workers/lifecycle.py, а не угаданы. Анти-зомби: считаются только прогоны свежее WORKER_GUARD_MAX_SKIP_H часов по COALESCE(heartbeat_at, started_at) — той же паре, что индексирует data/sql/55_schema_scrape_resume.sql и по которой фильтрует reaper. Иначе зависший навечно 'running' заморозил бы worker бесконечно. Fail-safe перевёрнут в сторону статус-кво: psql молчит, отдал пустое, не число, контейнера нет, image id не читается — пересоздаём как раньше и печатаем WARNING. Тихий no-op деплоя опаснее убитого прогона: сломанный детект иначе оставил бы worker на старом коде навсегда и незаметно. Ревью в свежем контексте нашло критическую дыру, из-за которой заплатка не работала бы ровно в своём целевом случае. Guard стоял ПОСЛЕ голого `up -d` по всему стеку, а тот сам пересоздаёт любой сервис со сменившимся образом. То есть worker убивался раньше, чем guard успевал сравнить digest'ы, и к моменту проверки они уже совпадали — печаталось «образ не изменился», ни SKIPPED, ни WARNING. Теперь worker явно исключён из bulk-команды: список берётся из `config --services`, который фильтрует по активным профилям, так что семантика сегодняшнего `up -d` сохраняется, а решение по worker'у принимает только guard. Там же поправлено: `tr -d '[:space:]'` вырезал пробел ВНУТРИ timestamp от NOW(), из-за чего CAST падал и предупреждение «старый код + новая схема» не печаталось бы никогда. Риск заплатки назван прямо, потому что он реальный: пропуск означает, что worker остаётся на старом образе до конца прогона. Миграции применяются ДО подъёма приложения, поэтому старый код может оказаться на новой схеме — на этот случай отдельная строка в логе с готовой командой ручного recreate, и предупреждение о расхождении digest'ов повторяется в каждом следующем деплое, пока расхождение живо. Откат — переменная репозитория WORKER_RECREATE_GUARD в 'off', без коммита. Проверено: YAML парсится, шелл-блок проходит bash -n, имена колонок сверены со схемой (run_id, heartbeat_at в обеих таблицах, _schema_migrations.applied_at), поведение `config --services` с профилями проверено на живом Docker — сервисы под неактивными профилями в список не попадают, регрессии с #3061 нет. Refs #3029, #3074 |