fix(tradein/deploy): скрапер пересобирается вместе с бэкендом + сверка образов после деплоя (#2679) #2680
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2680
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2679-scraper-deploy-parity"
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?
Что было
tradein-backend,tradein-scraper,tradein-tgbot— один образgendesign-tradein-backend(вdocker-compose.prod.ymlтри сервиса с одной строкойimage:и разнымиcommand). Но пересоздание скрапера было привязано к отдельномуpaths-фильтру
scraper— allowlist'у «файлов, которые исполняет планировщик».Список перечислял только то, что вспомнили, и выстрелил дважды одинаково:
tradein-backend, но не доtradein-scraper; починили добавлением путей (matching/**,house_dedup_merge.py);house_imv_backfill.pyиproduct_handlers.pyв списке незначились; планировщик (
SCHEDULER_ENABLE=trueживёт именно в скрапере) час крутилстарый код, деплой при этом отчитался успехом.
Масштаб дыры: за июнь–август 48% (193 из 402) мержей, затрагивающих backend-образ,
не попадали ни в один путь списка — то есть половина правок доезжала до скрапера только
со следующим «удачным» деплоем.
Что стало
scraperудалён вместе с выходомchanges.outputs.scraper. Признакпересоздания (
SCRAPER_RECREATE) теперь буквально повторяетif:джобыbuild-backend:backend || infra || workflow_dispatch— «в реестре мог появитьсяновый
:latest».docker compose pullсравнивается ID подтянутого образа с тем, на котором реально бежит
tradein-scraper.Совпало → «пересоздавать нечего», пятиминутное ожидание слива
scrape_runsпропускается, in-flight сбор не трогаем. Не совпало (или контейнера/тега нет) →
drain как раньше. Порядок обязателен: до
pullпод тегом:latestлежит ещё старыйобраз, и сравнение «совпало бы» ровно тогда, когда ждать как раз нужно.
tradein-backend(эталон — он пересоздаётся на каждом деплое) иtradein-scraper/tradein-tgbot. Расхождение →exit 1до записи.tradein-deployed-sha, с командой ручного лечения в логе. «Контейнер отстал» и«контейнера нет» — разные сообщения: это разные аварии.
Побочно закрыт ложный startup-reap: чекпоинт и reap завязаны на тот же признак «образ
действительно новый». Без этого прогон, переживший таймаут drain'а в не пересозданном
контейнере, помечался бы
cancelled, продолжая работать.Снят «Phase 0»-компромисс (infra-правки намеренно не трогали скрапер, чтобы не убить
многочасовой прогон): с #1951 есть graceful drain + startup-reap, а теперь ещё и
пропуск ожидания, когда образ не поменялся.
Почему именно так, а не «дописать пути»
Дописанный путь чинит один случай; механизм остаётся угадыванием и ломается на следующем
новом файле — это уже воспроизвелось через месяц. Здесь признак сведён к тому, от чего
он физически зависит: собран новый образ → пересоздаём все контейнеры этого образа.
Список файлов больше не нужно поддерживать, забыть его нечем.
Сверка образов оставлена дополнительно к этому, а не вместо: она ловит расхождение
любого происхождения (ручной
dockerна проде, неудавшийся recreate, будущая правкапайплайна) и делает дыру громкой в момент деплоя.
Цена по времени
43 прод-деплоя tradein за 31.07–05.08 (Forgejo API, длительность job
deploy):Что меняется по факту: платят только деплои с реально новым образом — те 48%
backend-мержей, что раньше молча проходили мимо скрапера, теперь пересоздают его и
получают drain (median +37s, mean +72s, в худшем случае +5 мин — потолок ожидания при
длинном full-load'е). Скрап-прогоны занимают 37% wall-clock времени (14 дней, 1132
прогона, средний остров занятости 11 мин, максимальный 11 ч) — отсюда разброс.
Infra-правки (compose / workflow /
deploy/**) не платят ничего: образ собирается покэшу тем же, сравнение совпадает, drain пропускается. Таких мержей с июня было 7 из 499
— редкий, но это ровно класс PR'ов вроде текущего, и раньше каждый из них рисковал
прервать многочасовой сбор ради no-op
up -d.Логика пропуска джоб не затронута:
test/build-*/deployзависят отbackend/infra/frontend/browser,scraper-выход нигде больше не использовался.Что будет при следующем деплое
Этот PR трогает сам
deploy-tradein.yml→infra=true→build-backend+SCRAPER_RECREATE=true. Сравнение образов покажет расхождение (сейчас на продеtradein-backend=ff98603ba3cc,tradein-scraper=da26154c64a6часовой давности),значит drain отработает и скрапер пересоздастся — код #2675 наконец доедет до
планировщика. Сверка образов после этого пройдёт зелёной и с этого момента охраняет
инвариант.
Если бы расхождение осталось, деплой упал бы на новой проверке — и это чинится само:
маркер SHA не записан → следующий прогон возьмёт ту же базу, пересоберёт накопленное и
пересоздаст скрапер.
Проверка
yaml.safe_loadна файле;bash -nна теле deploy-скрипта.docker:сверка — совпали → exit 0, отстал (реальные ID инцидента) → exit 1 с указанием
контейнера, контейнера нет → exit 1 с другим сообщением, нет
tradein-backend→exit 1; решение о drain — образ тот же → пропуск, новый → drain, контейнера нет →
drain, тег не разрешился → drain (fail-safe в сторону ожидания).
docker ps,docker inspect, SELECT изscrape_runs). Ничего не перезапускалось.tradein-scraperвстал на тот жеimage ID, что
tradein-backend.Известный потолок
Правка compose, меняющая только конфиг сервиса
scraper(env, лимиты) при том же образе,пересоздаст контейнер без drain'а: сравниваются образы, а не конфиг. Страхуют
SIGTERM-drain (#1182) +
stop_grace_period120s, строку прогона подчистит периодический6h zombie-reaper. Вариант «спросить у compose через
up --dry-run, будет ли recreate»сознательно не делал — парсинг его вывода хрупче, чем цена этого случая.
Не входит в PR
deploy.yml(ПТИЦА) не тронут — там своя история (#2462). На момент правкирасхождения в том стеке нет:
gendesign-backend-1иgendesign-beat-1на одномimage ID,
workerсобирается из отдельного образа.Refs #2679, #2675, #2188
done#2698done#2698