fix(deploy): resolve FDW bootstrap race + extract bootstrap DO block to .sql file #499
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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#499
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/deploy-fdw-ordering"
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?
Summary
Follow-up к PR #493 — три фикса из incident 2026-05-24 (deploy/1156 failed at bootstrap; tradein-backend USER MAPPING не создалась; main postgres не попал в gendesign_shared network). Все три проблемы починены вручную, этот PR делает workflow self-healing.
Файлы (3)
Что фиксим (root causes)
Bug A — deploy-tradein.yml ordering: compose up -d (line 157) запускает backend, который вызывает ensure_fdw_user_mapping → пробует CREATE USER MAPPING FOR ... SERVER gendesign_remote. Но FDW SERVER создаётся только потом, в 060_postgres_fdw_extension.sql (line 163). Hook падает psycopg.errors.UndefinedObject: server "gendesign_remote" does not exist и больше не дёргается. Result: USER MAPPING absent, все FDW queries fail.
Fix: docker restart tradein-backend после migrations loop → lifespan startup retry, server теперь существует, mapping создаётся.
Bug B — deploy.yml bootstrap DO block: inline psql -c с DO
... EXECUTE format(...) — multi-line backslash continuations внутри double-quoted bash string + psql var substitution = fragile escaping. deploy/1156 упал между migration apply (09:01:03) и compose up -d. Подозреваю DO block.Fix: extract в ops/db-bootstrap/set_tradein_fdw_password.sql, выполнять через psql -v pw="$GENDESIGN_FDW_PASSWORD" -f . Clean, psql-native, никакого bash escape voodoo. Сам SQL остался idempotent (DO block с IF EXISTS).
Bug C — silent network drift: docker-compose.prod.yml уже имеет правильный networks: default + shared (alias gendesign-postgres), но compose up -d иногда не пересоздаёт postgres при изменении networks:. И в deploy/1156 up -d вообще не дошёл (упал на B). Result: gendesign-postgres-1 остался в gendesign_default only → tradein-postgres не мог резолвить DNS gendesign-postgres.
Fix: после compose up -d проверять docker inspect ... | grep gendesign_shared; если нет — compose up -d --force-recreate --no-deps postgres.
Test plan
Runbook
Vault entry runbooks/deploy_fdw_cross_stack_network.md с healthy-state checks, симптомами и recovery procedures (network, USER MAPPING, password rotation).
Closes (по сути) incident 2026-05-24 deploy/1156.
Merged via deep-code-reviewer — verdict APPROVE (squash →
ed49f6b).Bug A / B / C analysis:
sleep 5buffer + 30-iter healthcheck loop handles startup time. Lifespan exception swallowed → degraded but not crashed (acceptable per existing design).:'pw'+format(... %L ...)is double-safe. Backend_PASSWORD_RE = ^[A-Za-z0-9_\-]{32,256}$whitelist is additional defense-in-depth. Bash guardif [ -n "${GENDESIGN_FDW_PASSWORD:-}" ]prevents unset-var psql error. Stdin redirect< file.sqlworks identically to-ffor-vsubstitution (psql preprocesses on input stream).docker inspect ... 2>/dev/null | grep -q gendesign_sharedself-heals fresh deploys (empty stdout → grep returns 1 → force-recreate fires). After force-recreate,aliases: [gendesign-postgres]from docker-compose.prod.yml:36-40 is restored. SQLAlchemypool_pre_ping=Truehandles connection drop on app side; postgres_fdw opens fresh per-query connection on tradein side.Follow-ups (non-blocking, NEW PR optional):
ops/db-bootstrap/set_tradein_fdw_password.sqlheader comment says-f file.sqlbutdeploy.ymluses< file.sql(stdin). Functionally identical, but doc drift. Update comment to match (or switch deploy.yml to-f).deploy.ymlhardcodesgendesign-postgres-1container name. Safer:pg_cid=$(docker compose -p gendesign -f docker-compose.prod.yml ps -q postgres). Current code correct for compose v2 default naming.Post-merge actions: deploy.yml will auto-trigger. Monitor in Forgejo Actions UI. Verify:
docker inspect gendesign-postgres-1 --format '{{json .NetworkSettings.Networks}}'showsgendesign_sharedaliasdocker exec tradein-postgres psql -U tradein -d tradein -c "SELECT count(*) FROM gendesign_cad_buildings"→ ~36k