Skip to Content
TroubleshootingМиграция не применяется

Миграция не применяется

Runbook по типовым сбоям Atlas-миграций — на PR-гейте (atlas migrate lint), в build миграционного образа и в PreSync-Job (atlas migrate apply). Открывай по порядку: сначала симптом, потом причина, проверка, фикс. Как писать миграции — в ../how-to/add-migration; как откатывать — в ../how-to/rollback-migration.

Прежде чем что-то делать в prod-БД — зафиксируй текущее состояние Atlas:

atlas migrate status --env k8s

Это «источник правды»: что применено, что pending, есть ли failed-ревизия. Журнал лежит в таблице atlas_schema_revisions той же БД.

Содержание

Где смотреть логи

Миграции применяет PreSync-Job (<svc>-migrate) до раскатки Deployment. Argo блокирует sync, пока Job не завершится успешно.

# статус и логи последнего PreSync-Job kubectl get jobs -n core -l app=<svc>-migrate kubectl logs -n core job/<svc>-migrate

В логах виден вывод atlas migrate apply: какие версии применены, на каком statement упало. PR-гейт (atlas migrate lint) смотри в выводе GitHub Actions (workflow atlas-lint).

1. atlas migrate lint упал в PR

Симптом. CI-проверка atlas-lint красная, merge заблокирован.

Причина. Lint прогнал миграцию на одноразовой dev-базе и нашёл проблему: destructive-изменение без защиты, ADD COLUMN NOT NULL без default на существующей таблице, неверный порядок, нарушение backward-compat, или atlas.sum не совпадает.

Проверка (локально, тот же прогон, что в CI).

atlas migrate lint --env ci --latest 1

Фикс.

  • Перепиши миграцию по expand-contract — см. ../how-to/add-migration.
  • Для осознанно-опасной операции (в maintenance-окне) пометь её директивой и задокументируй в PR; не глуши lint целиком.
  • Если ругается на atlas.sum — см. §2.

atlas migrate lint не моделирует триггеры/функции/mat-view глубоко — их корректность проверяется ревью, см. ../how-to/add-migration.

2. checksum mismatch / atlas.sum разошёлся

Симптом. atlas migrate validate / lint / apply падает с checksum mismatch или atlas.sum: ....

Причина. Файл миграции изменили после генерации, а atlas.sum не пере-хешировали. Либо кто-то отредактировал уже применённую миграцию. atlas.sum — это integrity-файл: хеш каждого .sql + суммарный хеш каталога. Он не даёт незаметно поменять историю.

Проверка.

atlas migrate validate --env ci

Фикс.

  • Если миграция ещё не применена нигде (новый PR) — пере-хешируй:

    atlas migrate hash --env ci

    и закоммить обновлённый atlas.sum вместе с .sql.

  • Если миграция уже применена (есть в atlas_schema_revisions на любом окружении) — не редактируй её. Откати правку файла к исходнику и напиши новую миграцию N+1. Иначе fresh-DB (после restore) получит другое состояние, чем prod.

3. Ревизия failed: миграция упала посередине

Симптом. PreSync-Job упал; atlas migrate status показывает версию в состоянии failed.

Причина. Миграция N упала на одном из statement’ов (например, SET NOT NULL на данных с NULL). Часть DDL до него уже применилась. Atlas записал ревизию N как частично применённую (applied < total, есть error), дальше не идёт.

Проверка.

atlas migrate status --env k8s
SELECT version, description, applied, total, error FROM atlas_schema_revisions ORDER BY version DESC LIMIT 5; -- applied < total и error IS NOT NULL → это та самая ревизия

Посмотри глазами саму миграцию N и текущую схему: какие шаги прошли, какой упал.

Фикс — два пути.

  1. Fix-forward (предпочтительно). Если оставшиеся statement’ы можно до-применить — устрани причину (например, backfill NULL-строк отдельной миграцией перед SET NOT NULL) и дай PreSync-Job повторить. Atlas продолжит с упавшего шага. Поэтому миграции пишем идемпотентно: IF NOT EXISTS / IF EXISTS на каждом DDL.

  2. Если до-применить нельзя. Вручную отмени уже применённые DDL этой ревизии под advisory-lock, приведи схему к состоянию N-1, затем синхронизируй журнал:

    atlas migrate set <N-1> --env k8s

    atlas migrate set переписывает журнал ревизий под фактическую схему. Делать только после того, как схема физически приведена к N-1.

Запрещено: atlas migrate set без сверки схемы. Зафиксируешь состояние, которого в БД нет — следующие миграции пойдут не в ту ветку DDL. Полный сценарий отката — в ../how-to/rollback-migration.

4. PreSync-Job не подключается к БД

Симптом. Job падает сразу, до применения миграций: connection refused, password authentication failed, database "..._migrate" does not exist, prepared statement ... already exists.

Причина.

  • Atlas берёт session-уровневый advisory lock → подключаться надо через сессионный PgBouncer-алиас <db>_migrate (pool_mode=session), а не через транзакционный пул на :6433. Через transaction-pool Atlas ломается на advisory lock / prepared statements.
  • Секрет <svc>-migrator не синкнулся (ExternalSecret) → пустой пароль.
  • ATLAS_URL указывает не на ту БД / без search_path / без sslmode=require.

Проверка.

kubectl get externalsecret <svc>-migrator -n core # SecretSynced=True? kubectl get secret <svc>-migrator -n core # проверь ATLAS_URL в Job (без значения пароля) kubectl get job <svc>-migrate -n core -o yaml | grep -A2 ATLAS_URL

Фикс.

  • URL вида postgres://$(MUSER):$(MPASS)@data-postgres-pgbouncer.data.svc:6433/<db>_migrate?search_path=<schema>&sslmode=require — обрати внимание на суффикс _migrate (session-pool алиас) и search_path.
  • Если алиаса <db>_migrate нет — он добавляется в инфра-слое (PgBouncer pgbouncer.ini). Без него Atlas не возьмёт session lock.
  • Если ExternalSecret не синкнут — проверь запись /products/core/<svc>/MIGRATOR_PASSWORD в Infisical и ClusterSecretStore infisical.

5. relation/column does not exist

Симптом. Миграции применились (нет failed/pending), но код падает при первом же SQL-запросе.

Причина.

  • Деплой кода обогнал миграцию (PreSync не отработал / Argo-hook отключён). В норме PreSync-Job блокирует раскатку Deployment, так что это сигнал, что hook не сработал.
  • Бампнули только образ <svc>-migrations, без app-образа. PreSync-Job — это Argo-hook, а не отслеживаемый ресурс: смена одного hook-образа не даёт diff сравниваемых ресурсов → Argo остаётся Synced → синк не запускается → hook не срабатывает, миграция остаётся pending (atlas migrate status). В обычном потоке миграционный PR пересобирает и app-образ, и бамп его тега даёт diff Deployment → синк → PreSync.
  • Код ожидает колонку из следующей миграции (нарушен expand-contract: contract-фаза выкатилась раньше, чем код перестал читать поле).
  • Сервис подключён к другой БД (другой namespace / instance).

Проверка.

\d <schema>.<table> SELECT inet_server_addr(), current_database(), current_schema();
atlas migrate status --env k8s # все ли версии применены

Фикс.

  • В prod — только fix-forward: новая миграция с недостающим DDL. Не правь применённую (см. §2).
  • Если код опередил схему — kubectl rollout undo, дождись PreSync, потом снова раскатывай код.
  • Если бампнули только migrations-образ — бампни и app-образ (или сделай force-sync с hooks), чтобы Argo запустил синк и PreSync применил миграцию.
  • Соблюдай порядок expand-contract: contract-миграция (DROP) — только после того, как ни один работающий код её не читает.

6. deadlock detected / lock timeout

Симптом. Миграция висит десятки секунд, падает с deadlock detected или canceling statement due to lock timeout.

Причина. Долгий ALTER TABLE пересекается с боевыми запросами к той же таблице — Postgres ждёт ACCESS EXCLUSIVE lock и упирается в lock_timeout либо ловит взаимоблокировку.

Проверка.

SELECT pid, state, query_start, wait_event_type, wait_event, query FROM pg_stat_activity WHERE state = 'active' OR wait_event IS NOT NULL ORDER BY query_start;

Фикс.

  • Долгий ALTER TABLE (особенно ADD COLUMN ... NOT NULL на большой таблице) — разбей на expand-contract (см. ../how-to/add-migration) или вынеси в maintenance-окно.
  • CREATE INDEX CONCURRENTLY не держит запись, но идёт минутами и не может быть в транзакции — добавь директиву -- atlas:txmode none в начало файла, чтобы Atlas не оборачивал миграцию в BEGIN/COMMIT.

7. Ожидание advisory lock

Симптом. PreSync-Job висит на старте atlas migrate apply и не двигается.

Причина. Atlas берёт session advisory lock, чтобы две копии apply не шли параллельно. Другой Job/инстанс уже держит lock и применяет миграцию. Твой запуск корректно ждёт — это защита от двойного применения.

Проверка.

SELECT pid, state, wait_event, query FROM pg_stat_activity WHERE query ILIKE '%advisory%' OR wait_event = 'advisory';

Фикс.

  • Ожидание < 1–2 минут — штатно, подожди.

  • 10 минут — найди зависший PID (процесс убитого прошлого Job, чья сессия не закрылась):

    SELECT pid, state, query_start, query FROM pg_stat_activity WHERE pid = <pid>; SELECT pg_terminate_backend(<pid>); -- освободит session lock
  • Убедись, что подключение идёт через session-pool алиас <db>_migrate (на transaction-pool advisory lock «течёт» между бэкендами и ведёт себя непредсказуемо) — см. §4.

8. canceling statement due to statement timeout

Симптом. ALTER TABLE ADD COLUMN ... NOT NULL или backfill-UPDATE прерывается по таймауту.

Причина. На роли/БД установлен statement_timeout, а операция переписывает всю таблицу.

Фикс.

  • Разбей на expand-contract: ADD COLUMN nullable → batched backfill отдельным Job’ом, не в миграции → SET NOT NULL отдельной миграцией. См. ../how-to/add-migration.
  • Не обходи statement_timeout через SET statement_timeout = 0 внутри миграции — это заморозит таблицу на неопределённое время под ACCESS EXCLUSIVE lock.

9. На не-пустой БД нужен baseline

Симптом. На существующей БД (схема уже создана руками / старым инструментом) PreSync-Job пытается применить 001..N и падает — объекты уже есть.

Причина. atlas_schema_revisions пуст, Atlas думает, что БД чистая, и пробует накатить всю историю заново.

Фикс (одноразово). Сделай baseline до текущей версии — запиши 001..N как применённые, ничего не выполняя:

atlas migrate status --env k8s # узнать текущую версию N atlas migrate apply --env k8s --baseline <N>

После baseline обычный PreSync-Job — no-op на этой БД и применяет только действительно новые миграции. На fresh БД (после restore) baseline не делай — там вся история должна примениться с нуля. Подробно — в ATLAS-BASELINE.md оверлея сервиса.

10. Permission denied при DDL

Симптом. permission denied for schema <name> / must be owner of table ... при apply.

Причина. Подключение идёт не под ролью <svc>_migrator (владелец схемы, имеет DDL), а под app-ролью (только DML, CREATE отозван).

Проверка.

SELECT current_user; -- ожидаем <svc>_migrator \dn+ <schema> -- owner схемы

Фикс. В ATLAS_URL PreSync-Job логин должен быть <svc>_migrator (env MUSER), пароль — из секрета <svc>-migrator. App-роль для миграций не используется намеренно (least privilege). Если роли/прав нет — это инфра-слой (migrator_roles.yml): роль владеет схемой через REASSIGN OWNED, app-роли REVOKE CREATE.

Общие команды диагностики

# статус миграций (применено / pending / failed) atlas migrate status --env k8s # integrity миграций и atlas.sum atlas migrate validate --env ci # прогнать lint как в CI atlas migrate lint --env ci --latest 1 # применить pending (обычно делает PreSync-Job, не руками) atlas migrate apply --env k8s # логи PreSync-Job kubectl logs -n core job/<svc>-migrate
-- журнал ревизий Atlas SELECT version, description, applied, total, error IS NOT NULL AS failed FROM atlas_schema_revisions ORDER BY version DESC LIMIT 10; -- таблицы в схеме сервиса \dt <schema>.* -- структура таблицы \d <schema>.<table> -- активность / lock'и SELECT pid, state, wait_event_type, wait_event, query_start, query FROM pg_stat_activity WHERE datname = current_database() ORDER BY query_start;

Быстрый порядок действий

  1. kubectl logs -n core job/<svc>-migrate — что сказал apply.
  2. atlas migrate status --env k8s — применено / pending / failed.
  3. Если failed-ревизия — §3.
  4. Если не подключается — §4.
  5. Если lock/deadlock — §6 / §7.
  6. Никогда не редактируй применённую миграцию — пиши следующую.
  7. Перед atlas migrate set — ручная сверка схемы.

Связанные разделы

Last updated on