fix(db/ci): чистый старт БД больше не падает на 077 и не может уехать на пустой схеме
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Failing after 55s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m3s
CI Trade-In / frontend-checks (pull_request) Successful in 1m47s
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI / backend-tests (pull_request) Successful in 17m51s
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Failing after 55s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m3s
CI Trade-In / frontend-checks (pull_request) Successful in 1m47s
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI / backend-tests (pull_request) Successful in 17m51s
Блокер переезда (#2990). Чистый старт на пустом томе падал: 077 читает foreign table gendesign_rosreestr_deals, а USER MAPPING создаёт бэкенд при старте (app/core/fdw.py), то есть ПОСЛЕ docker-entrypoint-initdb.d. Контейнер не поднимался вообще. Путь «пустой том» на реальном железе не исполнялся ни разу, а CI этот файл явно пропускал — гейт, который должен был поймать, был ослаблен. Проверено по всем 14 миграциям, упоминающим FDW-таблицы: читает ровно одна — 077. Остальные только CREATE/DROP FOREIGN TABLE и COMMENT, им ни USER MAPPING, ни связь с чужой БД не нужны. 077 не удалена, а сделана самозащитной: гард считает строки в md5-форме и выходит раньше обращения к FDW, если мигрировать нечего. На чистой БД таких строк нет по определению. Удаление файла было бы неверным — прод помнит миграции по bare-filename в _schema_migrations, и test_applied_migration_is_not_renamed_or_deleted падает на удалении. Исключение в ci-tradein.yml снято: теперь цепочка применяется целиком, то есть CI сам стал репетицией чистого старта. Отдельно закрыт тихий отказ в deploy-tradein.yml. Ветка baseline срабатывала по одному лишь отсутствию _schema_migrations, а это состояние неоднозначно: так выглядит и наполненный прод до внедрения tracking, и пустая БД нового сервера. Во втором случае baseline пометил бы все миграции применёнными, ни одной не прогнав, и деплой уехал бы зелёным на пустой схеме. Добавлен sentinel по listings: пусто → baseline пропускается, цепочка применяется с нуля. Refs #2990, #2989
This commit is contained in:
parent
1663f22795
commit
efc965a257
3 changed files with 86 additions and 37 deletions
|
|
@ -179,16 +179,13 @@ jobs:
|
||||||
"CREATE EXTENSION IF NOT EXISTS postgis;
|
"CREATE EXTENSION IF NOT EXISTS postgis;
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
CREATE ROLE gendesign_reader;"
|
CREATE ROLE gendesign_reader;"
|
||||||
|
# Исключений НЕТ (#2990). Раньше здесь пропускалась 077 — единственная
|
||||||
|
# миграция, читающая foreign table через postgres_fdw, которой в CI нет.
|
||||||
|
# Пропуск означал, что гейт не проверял ровно тот файл, который потом
|
||||||
|
# ронял чистый старт на реальном железе. Теперь 077 сама выходит раньше
|
||||||
|
# обращения к FDW, если мигрировать нечего, и в CI проходит честно.
|
||||||
for sql_file in $(ls -1 tradein-mvp/backend/data/sql/*.sql | sort); do
|
for sql_file in $(ls -1 tradein-mvp/backend/data/sql/*.sql | sort); do
|
||||||
fname=$(basename "$sql_file")
|
fname=$(basename "$sql_file")
|
||||||
# ЕДИНСТВЕННОЕ исключение, и оно названо вслух: 077 — не DDL, а
|
|
||||||
# backfill, читающий foreign table gendesign_rosreestr_deals из БД
|
|
||||||
# ДРУГОГО стека через postgres_fdw. В CI второй БД нет, USER MAPPING
|
|
||||||
# создать не из чего. На пустых таблицах backfill всё равно no-op.
|
|
||||||
if [ "$fname" = "077_dedup_hash_plain_key_backfill.sql" ]; then
|
|
||||||
echo "⚠ пропускаю $fname — postgres_fdw к БД gendesign, которой в CI нет"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q < "$sql_file" \
|
docker exec -i "$CI_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -q < "$sql_file" \
|
||||||
|| { echo "::error::миграция $fname не применилась"; docker logs --tail 20 "$CI_PG" 2>&1 || true; exit 1; }
|
|| { echo "::error::миграция $fname не применилась"; docker logs --tail 20 "$CI_PG" 2>&1 || true; exit 1; }
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -748,6 +748,19 @@ jobs:
|
||||||
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -tAc \
|
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -tAc \
|
||||||
"SELECT to_regclass('public._schema_migrations') IS NOT NULL;" | tr -d '[:space:]')
|
"SELECT to_regclass('public._schema_migrations') IS NOT NULL;" | tr -d '[:space:]')
|
||||||
|
|
||||||
|
# Sentinel (#2990): отсутствия _schema_migrations НЕДОСТАТОЧНО, чтобы
|
||||||
|
# заключить «схема уже накачена». Ровно два разных состояния дают одно
|
||||||
|
# и то же отсутствие таблицы:
|
||||||
|
# 1) наполненный прод до внедрения tracking → baseline корректен;
|
||||||
|
# 2) ПУСТАЯ БД на новом сервере → baseline пометил бы все миграции
|
||||||
|
# применёнными, ни одной не прогнав, и деплой уехал бы зелёным
|
||||||
|
# на пустой схеме. Отказ тихий и обнаружился бы уже под нагрузкой.
|
||||||
|
# Различаем по живой таблице listings: она есть только если схема реально
|
||||||
|
# применялась (initdb или предыдущим циклом миграций).
|
||||||
|
schema_already_present=$(docker compose -p gendesign-tradein -f docker-compose.prod.yml exec -T postgres \
|
||||||
|
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -tAc \
|
||||||
|
"SELECT to_regclass('public.listings') IS NOT NULL;" | tr -d '[:space:]')
|
||||||
|
|
||||||
docker compose -p gendesign-tradein -f docker-compose.prod.yml exec -T postgres \
|
docker compose -p gendesign-tradein -f docker-compose.prod.yml exec -T postgres \
|
||||||
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -v ON_ERROR_STOP=on -c "
|
psql -U "${TRADEIN_POSTGRES_USER:-tradein}" -d tradein -v ON_ERROR_STOP=on -c "
|
||||||
CREATE TABLE IF NOT EXISTS _schema_migrations (
|
CREATE TABLE IF NOT EXISTS _schema_migrations (
|
||||||
|
|
@ -756,12 +769,18 @@ jobs:
|
||||||
);
|
);
|
||||||
"
|
"
|
||||||
|
|
||||||
if [ "$migrations_table_existed" != "t" ]; then
|
if [ "$migrations_table_existed" != "t" ] && [ "$schema_already_present" != "t" ]; then
|
||||||
# BASELINE: таблицы не было → seed ВСЕ текущие миграции как applied
|
# ПУСТАЯ БД: baseline пропускаем намеренно. Цикл ниже применит всю
|
||||||
# БЕЗ их прогона. prod уже работает на этой схеме; помечаем её
|
# цепочку с нуля под ON_ERROR_STOP — это и есть штатный путь чистого
|
||||||
# текущим состоянием, чтобы под строгий gate попадали только НОВЫЕ
|
# старта на новом сервере.
|
||||||
|
echo "→ БД пуста (нет ни _schema_migrations, ни listings) — baseline ПРОПУЩЕН,"
|
||||||
|
echo " вся цепочка миграций будет применена циклом ниже."
|
||||||
|
elif [ "$migrations_table_existed" != "t" ]; then
|
||||||
|
# BASELINE: таблицы не было, но схема есть → seed ВСЕ текущие миграции
|
||||||
|
# как applied БЕЗ их прогона. prod уже работает на этой схеме; помечаем
|
||||||
|
# её текущим состоянием, чтобы под строгий gate попадали только НОВЫЕ
|
||||||
# (077+) миграции. INSERT ... ON CONFLICT DO NOTHING — идемпотентно.
|
# (077+) миграции. INSERT ... ON CONFLICT DO NOTHING — идемпотентно.
|
||||||
echo "→ _schema_migrations отсутствовала — baseline существующих миграций (без прогона)"
|
echo "→ _schema_migrations отсутствовала, но схема на месте — baseline существующих миграций (без прогона)"
|
||||||
for sql_file in $(ls -1 backend/data/sql/*.sql 2>/dev/null | sort); do
|
for sql_file in $(ls -1 backend/data/sql/*.sql 2>/dev/null | sort); do
|
||||||
fname=$(basename "$sql_file")
|
fname=$(basename "$sql_file")
|
||||||
echo " baseline: $fname"
|
echo " baseline: $fname"
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,23 @@
|
||||||
-- не могут совпасть с md5-hex (разный формат), поэтому транзиентного UNIQUE-violation
|
-- не могут совпасть с md5-hex (разный формат), поэтому транзиентного UNIQUE-violation
|
||||||
-- по deals_dedup_hash_key во время UPDATE не возникает.
|
-- по deals_dedup_hash_key во время UPDATE не возникает.
|
||||||
--
|
--
|
||||||
-- FDW dependency: читает foreign table gendesign_rosreestr_deals (SERVER gendesign_remote,
|
-- ── Гард чистого старта (#2990) ───────────────────────────────────────────────
|
||||||
-- создан в 060_postgres_fdw_extension.sql; foreign table — в 072_..._cian_rosreestr.sql;
|
-- Это ЕДИНСТВЕННАЯ миграция во всей цепочке, которая ЧИТАЕТ foreign table
|
||||||
-- USER MAPPING — backend startup core/fdw.py). Деплой применяет миграции ПОСЛЕ `compose up -d`,
|
-- (проверено 2026-08-20 по всем 14 файлам, упоминающим FDW-таблицы: остальные —
|
||||||
-- т.е. FDW-сервер уже поднят и foreign table читаема.
|
-- только CREATE/DROP FOREIGN TABLE и COMMENT, им ни USER MAPPING, ни связь не нужны).
|
||||||
|
--
|
||||||
|
-- А USER MAPPING создаёт бэкенд при старте (app/core/fdw.py), то есть ПОСЛЕ initdb.
|
||||||
|
-- На чистом томе (новый сервер, CI, локальная разработка) FDW ещё не отображён и
|
||||||
|
-- SELECT падал бы с "user mapping not found", обрывая docker-entrypoint-initdb.d
|
||||||
|
-- и не давая контейнеру подняться вообще.
|
||||||
|
--
|
||||||
|
-- Поэтому backfill выполняется ТОЛЬКО если есть что мигрировать. На чистой БД строк
|
||||||
|
-- в md5-форме нет по определению (их создавал исторический импорт), гард выходит
|
||||||
|
-- раньше обращения к FDW, и миграция становится честным no-op вместо падения.
|
||||||
|
--
|
||||||
|
-- Файл НЕ удалён намеренно: прод помнит его по bare-filename в _schema_migrations,
|
||||||
|
-- а tests/test_migration_numbering.py::test_applied_migration_is_not_renamed_or_deleted
|
||||||
|
-- падает на удалении применённой миграции.
|
||||||
--
|
--
|
||||||
-- Фильтр src CTE — байт-в-байт совпадает с live-импортом (scheduler.py:437-444 /
|
-- Фильтр src CTE — байт-в-байт совпадает с live-импортом (scheduler.py:437-444 /
|
||||||
-- import-rosreestr.sh), чтобы каждая импортированная сделка нашлась и сконвертировалась.
|
-- import-rosreestr.sh), чтобы каждая импортированная сделка нашлась и сконвертировалась.
|
||||||
|
|
@ -24,25 +37,45 @@
|
||||||
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
|
|
||||||
WITH src AS (
|
DO $migration_077$
|
||||||
SELECT
|
DECLARE
|
||||||
id,
|
pending bigint;
|
||||||
md5('ros:dkp:' || CAST(id AS text)) AS h
|
BEGIN
|
||||||
FROM gendesign_rosreestr_deals
|
-- Гард: считаем строки, оставшиеся в md5-форме. Плоский ключ 'ros:dkp:N'
|
||||||
WHERE region_code = 66
|
-- под этот шаблон не подходит, поэтому после успешного прогона pending = 0.
|
||||||
AND city ILIKE '%катеринбург%'
|
SELECT count(*) INTO pending
|
||||||
AND realestate_type_code = '002001003000'
|
FROM deals
|
||||||
AND area BETWEEN 18 AND 200
|
WHERE source = 'rosreestr'
|
||||||
AND deal_price BETWEEN 1000000 AND 100000000
|
AND dedup_hash ~ '^[0-9a-f]{32}$';
|
||||||
AND street IS NOT NULL AND trim(street) <> ''
|
|
||||||
AND doc_type = 'ДКП'
|
IF pending = 0 THEN
|
||||||
AND period_start_date >= DATE '2024-01-01'
|
RAISE NOTICE '077: строк в md5-форме нет — backfill пропущен, FDW не читается';
|
||||||
)
|
RETURN;
|
||||||
UPDATE deals d
|
END IF;
|
||||||
SET dedup_hash = 'ros:dkp:' || CAST(src.id AS text),
|
|
||||||
source_id = CAST(src.id AS text)
|
RAISE NOTICE '077: строк к конвертации: %', pending;
|
||||||
FROM src
|
|
||||||
WHERE d.source = 'rosreestr'
|
WITH src AS (
|
||||||
AND d.dedup_hash = src.h;
|
SELECT
|
||||||
|
id,
|
||||||
|
md5('ros:dkp:' || CAST(id AS text)) AS h
|
||||||
|
FROM gendesign_rosreestr_deals
|
||||||
|
WHERE region_code = 66
|
||||||
|
AND city ILIKE '%катеринбург%'
|
||||||
|
AND realestate_type_code = '002001003000'
|
||||||
|
AND area BETWEEN 18 AND 200
|
||||||
|
AND deal_price BETWEEN 1000000 AND 100000000
|
||||||
|
AND street IS NOT NULL AND trim(street) <> ''
|
||||||
|
AND doc_type = 'ДКП'
|
||||||
|
AND period_start_date >= DATE '2024-01-01'
|
||||||
|
)
|
||||||
|
UPDATE deals d
|
||||||
|
SET dedup_hash = 'ros:dkp:' || CAST(src.id AS text),
|
||||||
|
source_id = CAST(src.id AS text)
|
||||||
|
FROM src
|
||||||
|
WHERE d.source = 'rosreestr'
|
||||||
|
AND d.dedup_hash = src.h;
|
||||||
|
END
|
||||||
|
$migration_077$;
|
||||||
|
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue