[tradein][infra] Вынести scheduler/sweep'ы в отдельный контейнер tradein-scraper (деплой API не убивает скрейпы) #1182

Closed
opened 2026-06-12 07:36:32 +00:00 by lekss361 · 2 comments
Owner

Решение Anton 2026-06-12 (план «выделение парсинга», шаг 1; ADR в vault → decisions/). Боль: in-app scheduler живёт в tradein-backend → каждый merge/deploy рестартует API и убивает бегущие sweep'ы (zombie-runs), а деплоев из бот-пайплайна много ежедневно.

Ground truth

  • tradein-mvp/backend/app/main.py L77-93: scheduler стартует в FastAPI lifespan, уже есть флаг SCHEDULER_ENABLE (settings.scheduler_enable) — выключение в API-контейнере = env var, кода почти не нужно.
  • Scheduler = scheduler_loop() из app/services/scheduler.py, источники в scrape_schedules table.

Scope

  1. docker-compose (tradein prod): новый сервис tradein-scraper — тот же image, что backend; команда — uvicorn НЕ нужен: отдельный entrypoint python -m app.scheduler_main (тонкая обёртка: init logging/db → asyncio.run(scheduler_loop()) + SIGTERM handler) ИЛИ тот же uvicorn без публикации порта — выбрать проще. SCHEDULER_ENABLE=true только здесь.
  2. API-контейнер: SCHEDULER_ENABLE=false в env.
  3. deploy-tradein.yml: path-filter — рестарт tradein-scraper только если менялись services/scrapers/**, services/scheduler.py, tasks/**; иначе рестартует только backend. (Образ общий — pull нового образа без рестарта scraper-контейнера допустим: он подхватит код при следующем своём рестарте.)
  4. Graceful/zombie: на старте scraper-контейнера — существующий zombie-reap; на SIGTERM — cancel + пометить бегущий run failed/zombie (не молча).

DoD

  • Деплой backend-only изменения → tradein-scraper uptime не прерван, бегущий sweep доживает до конца.
  • docker ps: scheduler-loop лог только в scraper-контейнере, в API — «in-app scheduler disabled».
  • Нет двойного триггера расписаний (один активный scheduler).

Effort: M · Связано: #973 (nightly enrichment повесить уже на новый контейнер), #944 (форсайт-скрейперы)

**Решение Anton 2026-06-12** (план «выделение парсинга», шаг 1; ADR в vault → `decisions/`). Боль: in-app scheduler живёт в tradein-backend → каждый merge/deploy рестартует API и убивает бегущие sweep'ы (zombie-runs), а деплоев из бот-пайплайна много ежедневно. ## Ground truth - `tradein-mvp/backend/app/main.py` L77-93: scheduler стартует в FastAPI lifespan, **уже есть флаг `SCHEDULER_ENABLE`** (`settings.scheduler_enable`) — выключение в API-контейнере = env var, кода почти не нужно. - Scheduler = `scheduler_loop()` из `app/services/scheduler.py`, источники в `scrape_schedules` table. ## Scope 1. **docker-compose (tradein prod):** новый сервис `tradein-scraper` — тот же image, что backend; команда — uvicorn НЕ нужен: отдельный entrypoint `python -m app.scheduler_main` (тонкая обёртка: init logging/db → `asyncio.run(scheduler_loop())` + SIGTERM handler) ИЛИ тот же uvicorn без публикации порта — выбрать проще. `SCHEDULER_ENABLE=true` только здесь. 2. **API-контейнер:** `SCHEDULER_ENABLE=false` в env. 3. **deploy-tradein.yml:** path-filter — рестарт `tradein-scraper` только если менялись `services/scrapers/**`, `services/scheduler.py`, `tasks/**`; иначе рестартует только backend. (Образ общий — pull нового образа без рестарта scraper-контейнера допустим: он подхватит код при следующем своём рестарте.) 4. **Graceful/zombie:** на старте scraper-контейнера — существующий zombie-reap; на SIGTERM — cancel + пометить бегущий run failed/zombie (не молча). ## DoD - Деплой backend-only изменения → `tradein-scraper` uptime не прерван, бегущий sweep доживает до конца. - `docker ps`: scheduler-loop лог только в scraper-контейнере, в API — «in-app scheduler disabled». - Нет двойного триггера расписаний (один активный scheduler). **Effort:** M · Связано: #973 (nightly enrichment повесить уже на новый контейнер), #944 (форсайт-скрейперы)
lekss361 added the
automation
scope/backend
status/ready
labels 2026-06-12 07:37:01 +00:00
Author
Owner

Беру в работу (main session по прямому указанию Anton). Ground-truth уточнения к спеке:

  • tradein-postgres и tradein-backend уже в gendesign_shared — для scraper-сервиса достаточно tradein-net (postgres + browser там же).
  • Path-filter в deploy-tradein.yml сам по себе не спасёт: deploy делает блочный docker compose up -d, а scraper на том же :latest image → пересоздавался бы при каждом backend-деплое. Решение: селективный up -d по списку сервисов без scraper + условный up -d scraper только при scraper-paths/infra изменениях (новый output scraper в changes filter).
  • Entrypoint: python -m app.scheduler_main (без uvicorn) — отдельный модуль с sentry-init + SIGTERM-handler, уважает SCHEDULER_ENABLE.

PR будет следом. #976 пойдёт вторым PR (sequential — оба трогают deploy-tradein.yml/compose).

Беру в работу (main session по прямому указанию Anton). Ground-truth уточнения к спеке: - `tradein-postgres` и `tradein-backend` уже в `gendesign_shared` — для scraper-сервиса достаточно `tradein-net` (postgres + browser там же). - Path-filter в deploy-tradein.yml сам по себе не спасёт: deploy делает блочный `docker compose up -d`, а scraper на том же `:latest` image → пересоздавался бы при каждом backend-деплое. Решение: селективный `up -d` по списку сервисов без scraper + условный `up -d scraper` только при scraper-paths/infra изменениях (новый output `scraper` в changes filter). - Entrypoint: `python -m app.scheduler_main` (без uvicorn) — отдельный модуль с sentry-init + SIGTERM-handler, уважает `SCHEDULER_ENABLE`. PR будет следом. #976 пойдёт вторым PR (sequential — оба трогают deploy-tradein.yml/compose).
lekss361 added
status/review
and removed
status/ready
labels 2026-06-12 07:57:45 +00:00
lekss361 added
status/qa
and removed
status/review
labels 2026-06-12 08:02:10 +00:00
Author
Owner

QA PASS — закрываю (PR #1183 merged 7a06839, прод-верификация qa-tester через ssh):

  1. /opt/gendesign на 7a06839 — деплой прошёл.
  2. tradein-scraper Up, image tradein-backend; backend/frontend/postgres Up.
  3. tradein-backend лог: «FastAPI lifespan: in-app scheduler disabled (SCHEDULER_ENABLE=false)»
  4. tradein-scraper лог: GlitchTip enabled (scheduler_main) → FDW user mapping refreshed → «scheduler: started (tick=60s)», без Traceback
  5. /health{"status":"ok","environment":"production"}
  6. «scheduler task spawned» в API-логе — 0 совпадений → ровно один scheduler

DoD «backend-only деплой не трогает scraper» проверится естественно на следующем backend-merge (SCRAPER_CHANGED=false → селективный up без scraper).

⚠️ Side-finding (НЕ регрессия этого PR): tradein-browser в crash-loop — camoufox InvalidProxy: mproxy.site:16928 (прокси недоступен/истёк). Заведён отдельный issue.

✅ **QA PASS — закрываю** (PR #1183 merged 7a06839, прод-верификация qa-tester через ssh): 1. `/opt/gendesign` на 7a06839 — деплой прошёл. 2. `tradein-scraper` Up, image tradein-backend; backend/frontend/postgres Up. 3. tradein-backend лог: «FastAPI lifespan: in-app scheduler disabled (SCHEDULER_ENABLE=false)» ✅ 4. tradein-scraper лог: GlitchTip enabled (scheduler_main) → FDW user mapping refreshed → «scheduler: started (tick=60s)», без Traceback ✅ 5. `/health` → `{"status":"ok","environment":"production"}` ✅ 6. «scheduler task spawned» в API-логе — 0 совпадений → ровно один scheduler ✅ DoD «backend-only деплой не трогает scraper» проверится естественно на следующем backend-merge (`SCRAPER_CHANGED=false` → селективный up без scraper). ⚠️ Side-finding (НЕ регрессия этого PR): `tradein-browser` в crash-loop — `camoufox InvalidProxy: mproxy.site:16928` (прокси недоступен/истёк). Заведён отдельный issue.
lekss361 added
status/done
and removed
status/qa
labels 2026-06-12 08:13:00 +00:00
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#1182
No description provided.