chore(ci): деплой не убивает worker посреди скрап-прогона (заплатка до #3074) #3084
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3084
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "chore/3029-worker-recreate-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Зачем
Каждый деплой ПТИЦЫ безусловно пересоздавал
worker— а он единственный, кто несёт скрап-прогоны. Разведка попутно поправила формулировку задачи: сервисаscraperвdocker-compose.prod.ymlнет вообще,beatтолько триггерит, так что гвардить надо именноworkerи при этом не задетьbackend/beat, которые пересоздавались той же строкой.Полный прогон КН идёт часами, при неудаче — сутки. Любой мерж в это окно убивал прогон молча, и деплой при этом был зелёным.
Это временная заплатка до чекпоинтов (#3074): пока прогон не умеет продолжаться с места обрыва, дешевле не обрывать.
Логика
Пересоздание пропускается, только когда выполнены оба условия:
Маркер заводить не пришлось — он уже есть:
kn_scrape_runsиobjective_scrape_runsсоstatus='running'. Литералы статуса сверены сbackend/app/workers/lifecycle.py, а не угаданы.Анти-зомби: считаются только прогоны свежее
WORKER_GUARD_MAX_SKIP_Hчасов (дефолт 6) поCOALESCE(heartbeat_at, started_at)— той же паре, что индексируетdata/sql/55_schema_scrape_resume.sql:22и по которой фильтрует reaper. Иначе зависший навечноrunningзаморозил бы worker бесконечно.Fail-safe перевёрнут в сторону статус-кво
psql молчит, отдал пустое, отдал не число, контейнера нет, image id не читается → пересоздаём как раньше и печатаем WARNING.
Обоснование: тихий no-op деплоя опаснее убитого прогона. При обратном выборе сломанный детект оставил бы worker на старом коде навсегда и незаметно.
Что нашло ревью
Два независимых рецензента в свежем контексте нашли одну и ту же критическую дыру, из-за которой заплатка не работала бы ровно в своём целевом случае.
Guard стоял после голого
up -dпо всему стеку, а тот сам пересоздаёт любой сервис со сменившимся образом. То есть worker убивался раньше, чем guard успевал сравнить digest'ы, и к моменту проверки они уже совпадали — печаталось «образ не изменился», ни SKIPPED, ни WARNING. Защита была мертва, и незаметно.Фикс: worker явно исключён из bulk-команды, список берётся из
docker compose config --services. Проверено на живом Docker (Compose v5.1.4), чтоconfig --servicesфильтрует по активным профилям — сервисы под неактивными профилями в список не попадают, поэтому семантика сегодняшнегоup -dсохраняется и регрессии сinfra-postgresиз #3061 нет (его профиль на Beget не активен, значит он не будет назван явно и не активируется).Вторая находка:
tr -d '[:space:]'вырезал пробел внутри timestamp отNOW()(2026-08-24 09:12:33+00→2026-08-2409:12:33+00), из-за чегоCASTпадал и предупреждение «старый код + новая схема» не напечаталось бы никогда.Риск — он реальный, назван прямо
Пропуск означает, что worker остаётся на старом образе до конца прогона и следующего деплоя. Миграции применяются ДО подъёма приложения, поэтому старый код может оказаться на новой схеме.
Смягчения:
data/sql/**;Откат
Переменная репозитория
WORKER_RECREATE_GUARDвoff→ безусловное пересоздание, как раньше. Без коммита и без деплоя. Второй уровень — revert одного коммита, блок цельный.Test plan
yaml.safe_load)bash -nrun_id,heartbeat_atв обеих таблицах,_schema_migrations.applied_at${WORKER_RECREATE_GUARD:-on}даётonи при пустом значении, и при unset (важно: actions-переменная, которой нет, приезжает пустой строкой)config --servicesс профилями проверено на живом DockerRefs #3029, #3074