fix(db/ci): чистый старт БД не падает на 077 и не может уехать на пустой схеме #3011
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#3011
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2990-clean-start-initdb"
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?
Закрывает блокер переезда #2990. Эпик #2989.
Что было сломано
Чистый старт на пустом томе падал.
077_dedup_hash_plain_key_backfill.sqlчитает foreign tablegendesign_rosreestr_deals, аUSER MAPPINGсоздаёт бэкенд при старте (app/core/fdw.py:57-62) — то есть послеdocker-entrypoint-initdb.d. На новом сервере контейнер не поднялся бы вообще.Не всплывало потому, что путь «пустой том» на реальном железе не исполнялся ни разу, а CI этот файл явно пропускал — гейт, который должен был поймать, был ослаблен именно на нём.
Проверка диагноза
Прошёл все 14 миграций, упоминающих FDW-таблицы. Реально читает ровно одна — 077 (
FROM gendesign_rosreestr_deals). Остальные (060, 066, 072, 085, 124, 168, 169 и др.) — толькоCREATE/DROP FOREIGN TABLEиCOMMENT, которым ниUSER MAPPING, ни связь с чужой БД не нужны. Так что точечного фикса достаточно.Ревью нашло, что первая версия сама не чинила заявленный баг (deep-review BLOCK)
Первая версия гарда в 077 считала «pending» по
source='rosreestr' AND dedup_hash ~ '^[0-9a-f]{32}$'и пропускала backfill, только если таких строк 0. Это было неверно: на чистой БД такие строки есть —003_seed_deals.sql:18-19сеет синтетические сделки, гдеsourceвыбираетсяCASE WHEN random() < 0.6 THEN 'rosreestr' ELSE 'domklik' END, аdedup_hash— чистыйmd5(...). То естьpending > 0уже на пустом томе, гард не срабатывал, и миграция всё равно падала наuser mapping not found(воспроизведено в CI run 8257 — именно тот баг, который PR должен был закрыть).Исправлено: первичный гард теперь проверяет напрямую наличие USER MAPPING для
gendesign_remote(идиома изapp/core/fdw.py:57-62,pg_user_mappings), а не количество строк:Счётчик pending-строк оставлен вторым гардом (после проверки мэппинга) — просто чтобы не трогать FDW зря, когда мигрировать уже нечего.
Две поправки к плану из issue (не тронуты, ревью подтвердило верными)
077 не удалена. Issue предлагал удалить файл. Это сломало бы CI: есть гейт
test_applied_migration_is_not_renamed_or_deleted, который падает на удалении применённой миграции — прод трекает их по bare-filename в_schema_migrations, и под новым именем миграция прогналась бы повторно.Монтирование
initdb.dоставлено. Issue предлагал перестать монтироватьbackend/data/sqlв/docker-entrypoint-initdb.d. Но тогда на чистом сервере схему не создаст никто.Что дополнительно закрыто (тоже переработано по ревью)
Проба готовности postgres теперь по TCP, а не по сокету
deploy-tradein.ymlждал postgres черезpg_isreadyбез-h— это unix-сокет. На пустом томе временный init-сервер отвечает на сокете, покаdocker-entrypoint-initdb.dещё прогоняет всю цепочку миграций (270+ файлов). Проба зеленела посреди initdb, а не после него — тот же класс проблемы, который уже задокументирован и исправлен вci-tradein.yml:157(pg_isready -h 127.0.0.1). Добавлен-h 127.0.0.1и в deploy: TCP-порт открывается только когда initdb.d полностью отработал и postgres перезапустился как настоящий сервер. Заодно увеличен бюджет ожидания (30 → 90 попыток × 2с), потому что на пустом томе ожидание теперь включает полный прогон всей цепочки миграций, а не только старт postgres.Sentinel baseline-детекции — по обоим концам цепочки, а не только по listings
Первая версия различала «схема уже накачена» одной таблицей
listings(миграция 002 — почти голова цепочки). Это было недостаточно: если гонка готовности (см. выше) когда-нибудь вернётся,listingsуже будет создан, а хвост цепочки — ещё нет, и baseline тихо пометит недостающие миграции применёнными без прогона (ровно тот сценарий из issue: 249+ миграций помечаются applied на частично применённой схеме).Теперь проверяются оба конца:
listings(голова) иhouses_geog_gist_idx— GiST-индекс из миграции 270 (#2997, самая свежая на момент правки):exit 1) вместо того, чтобы гадать и молча баселайнить недокачанную схему.На текущем проде
_schema_migrationsуже существует, поэтому ни одна из трёх веток не задействуется и поведение обычного (не-clean-start) деплоя не меняется.Проверено
yaml.safe_load+ извлечённый bash-блок деплоя проходитbash -n.main— приехали миграции 269/270 (в том числе индекс, ставший хвостовым сентинелом) иchmod +x deploy/*.sh, конфликтов не было.ON_ERROR_STOPбез исключений, то есть является репетицией чистого старта.Что осталось за рамками
Refs #2990, #2989
CI красный, и падение содержательное — гейт поймал, что гард не работает. Разбор, чтобы не искать заново.
Что показал прогон
Лог
CI Trade-In / backend-tests, run 8257:Почему гард не сработал
Он опирается на посылку из комментария в самой миграции:
Эта посылка неверна, и CI это доказал: на свежей базе, собранной прогоном всей цепочки с нуля, таких строк 51. Значит какая-то из более ранних миграций засевает
dealsстроками сdedup_hashв md5-форме. Гард считаетpending = 51, не выходит, доходит доgendesign_rosreestr_deals— и падает на отсутствующемUSER MAPPING.То есть проверяется не то условие. «Есть ли что мигрировать» и «доступен ли FDW» — разные вопросы, а падение вызывает именно второй.
Что предлагаю
Гардить по фактической доступности FDW, а не по числу строк. Реальное предусловие миграции — «USER MAPPING существует», и его можно проверить в каталоге, не трогая foreign table:
Существующий гард по
pendingпри этом стоит оставить — он полезен сам по себе как ранний выход, просто он не защищает от чистого старта. Порядок: сначала проверка маппинга, потомpending.Запасной вариант, если проверка каталога окажется неточной в каком-то краевом случае, — обернуть чтение foreign table в блок с
EXCEPTION WHEN OTHERSи трактовать сбой как пропуск. Менее прицельно, но переживает любую причину недоступности FDW, а не только отсутствующий маппинг.Что в этом PR хорошо и должно остаться
Часовой в
deploy-tradein.yml— самая ценная часть, и она тут вне подозрений. Различение «наполненный прод без tracking» и «пустая БД» по живой таблицеlistingsзакрывает сценарий, где baseline помечает все миграции применёнными на пустой схеме и деплой уезжает зелёным. Это тихий отказ, который обнаружился бы уже под нагрузкой, и его в моей исходной постановке (#2990) не было — находка сверх задачи.Снятие исключения из CI тоже правильно и уже окупилось: ровно оно и вскрыло, что гард не работает. Возвращать исключение не надо — надо чинить гард.
Смежное: эпик #2989, где чистый старт БД — предусловие переезда.
prune -afубиваетcompose pullдеплоя ПТИЦЫ (разные группы concurrency, один докер-демон) #2950