//////

Zero-Downtime Database Schema Migration Failure Modes: Expand/Contract Sequencing, Dual-Write Drift, Backfill Checkpoints, and Rollback-Safe Cutover

Zero-downtime database schema migration은 “스키마 변경”과 “애플리케이션 의미 변경”을 한 번에 묶지 않고, 여러 배포와 검증 단계를 통해 호환성 경계를 유지하는 운영 패턴이다. 핵심 순서는 보통 expand → migrate/backfill → cutover → contract 이며, 각 단계는 독립적으로 배포·관찰·롤백 가능해야 한다. 주요 실패 모드는 네 영역에 집중된다. 1. expand/contract 순서 오류 : 기존

//////

Summary#

Zero-downtime database schema migration은 “스키마 변경”과 “애플리케이션 의미 변경”을 한 번에 묶지 않고, 여러 배포와 검증 단계를 통해 호환성 경계를 유지하는 운영 패턴이다. 핵심 순서는 보통 expand → migrate/backfill → cutover → contract 이며, 각 단계는 독립적으로 배포·관찰·롤백 가능해야 한다.

주요 실패 모드는 네 영역에 집중된다.

  1. expand/contract 순서 오류: 기존 코드가 아직 읽거나 쓰는 컬럼을 rename/drop하거나, nullable/default/constraint 변경을 기존 버전과 호환되지 않게 적용하는 경우.
  2. dual-write drift: 신·구 컬럼 또는 신·구 테이블에 동시에 쓰는 동안 변환 로직, 트랜잭션 경계, retry/idempotency, partial failure 처리 차이로 데이터가 벌어지는 경우.
  3. backfill checkpointing 실패: 대량 backfill이 lock, replication lag, vacuum/undo pressure, hot row contention을 만들거나, 재시작 가능한 checkpoint 없이 중단되어 누락·중복·순서 역전이 생기는 경우.
  4. rollback-safe cutover 부재: read/write path 전환 후 구 경로가 너무 빨리 제거되어 rollback이 불가능하거나, destructive contract 이후 forward-fix 외에 선택지가 없어지는 경우.

Zero-downtime은 “무위험”이 아니다. DDL lock, constraint validation, index creation, online schema change tool의 cutover rename, trigger/binlog 기반 동기화, stale reader 존재 여부에 따라 짧은 장애 또는 데이터 불일치가 발생할 수 있다.

Key Points#

  • Expand 단계는 additive-only로 설계한다.
  • 안전한 예: nullable column 추가, 새 테이블 추가, 새 인덱스 추가, 새 shadow column 추가.
  • 위험한 예: column rename, drop, NOT NULL 즉시 강제, default를 동반한 table rewrite, 기존 reader가 모르는 enum/constraint 변경.
  • rolling deploy 환경에서는 구버전 애플리케이션과 신버전 애플리케이션이 동시에 동작하므로, DB schema는 양쪽 모두와 호환되어야 한다.

  • Rename은 zero-downtime 관점에서 보통 “add + dual-write/read + backfill + switch + drop”으로 풀어야 한다.

  • old_name을 즉시 new_name으로 rename하면 stale reader/writer가 깨질 수 있다.
  • 더 안전한 순서는 new_name 추가 → 애플리케이션이 양쪽 처리 → 기존 데이터 backfill → read path 전환 → write path 전환 → 관찰 → old_name 제거다.

  • Dual-write는 drift를 만드는 임시 위험 상태다.

  • dual-write는 신·구 스키마를 동시에 유지하기 위한 수단이지, 그 자체로 안전성을 보장하지 않는다.
  • 실패 모드:
    • 한쪽 write 성공 후 다른 쪽 write 실패.
    • retry가 한쪽에만 적용되어 중복 또는 불일치 발생.
    • transformation logic이 양방향으로 완전히 대칭이 아님.
    • old path와 new path가 서로 다른 validation/default를 사용.
    • transaction boundary가 달라 read-after-write consistency가 깨짐.
  • 완화책:

    • 가능하면 같은 DB transaction 안에서 양쪽 write.
    • idempotent write key 또는 deterministic transformation 사용.
    • reconciliation query, checksum, row count, sampled diff를 운영 지표로 둠.
    • dual-write 기간을 짧게 유지하고 종료 조건을 명시함.
  • Backfill은 “한 번 실행하는 스크립트”가 아니라 재시작 가능한 작업이어야 한다.

  • 대량 update를 단일 transaction으로 실행하면 lock, WAL/binlog 증가, replication lag, vacuum pressure, timeout을 유발할 수 있다.
  • 권장 구조:
    • primary key 또는 monotonically increasing key 기준 batching.
    • batch size와 sleep/throttle 조절.
    • last processed key, high-water mark, job state 저장.
    • 재실행 시 idempotent하게 동작.
    • 장애 후 같은 구간을 다시 처리해도 안전.
    • lag, lock wait, error rate를 기준으로 자동 중단 가능.
  • backfill 중 새로 들어오는 write와 과거 row 처리 순서가 섞이므로, dual-write 또는 catch-up pass가 필요할 수 있다.

  • Cutover는 read path와 write path를 분리해서 생각한다.

  • read cutover:
    • shadow column/table을 먼저 읽되, fallback을 둘 수 있다.
    • sampled compare 또는 shadow read로 old/new 결과 차이를 측정한다.
  • write cutover:
    • new schema를 source of truth로 전환한다.
    • 일정 기간 old schema에도 계속 쓰거나, rollback 포기 시점을 명시한다.
  • cutover 직후에는 rollback을 위해 old schema와 compatibility code를 유지하는 관찰 기간이 필요하다.

  • Contract 단계는 가장 늦게 수행한다.

  • old column/table, trigger, compatibility code, fallback read를 제거하는 단계는 모든 reader/writer가 새 경로로 전환되고 검증이 끝난 뒤에 해야 한다.
  • contract가 destructive하면 전통적 rollback이 불가능할 수 있다.
  • contract 전 확인 항목:

    • 배포된 모든 애플리케이션 버전이 old schema를 사용하지 않음.
    • background worker, cron job, admin tool, BI/ETL, read replica consumer, CDC consumer 확인.
    • query logs 또는 database audit로 old column/table 접근 없음 확인.
    • backup/restore 및 forward-fix 계획 확보.
  • PostgreSQL에서는 DDL lock과 table rewrite 여부가 핵심 위험이다.

  • PostgreSQL ALTER TABLE은 하위 명령에 따라 lock 수준과 rewrite 여부가 달라질 수 있다.
  • 큰 테이블에서는 CREATE INDEX CONCURRENTLY, NOT VALID constraint 후 VALIDATE CONSTRAINT 같은 단계적 접근이 유용할 수 있다.
  • 단, concurrent index creation도 실패 시 invalid index 정리, 긴 transaction과의 상호작용, 추가 I/O 부하를 고려해야 한다.

  • MySQL 계열에서는 online schema change 도구도 cutover 리스크를 가진다.

  • gh-ost, pt-online-schema-change 같은 도구는 shadow table, row copy, trigger/binlog 기반 변경 반영, 마지막 rename/cutover 방식으로 downtime을 줄인다.
  • 실패 모드:
    • foreign key, trigger, replication topology, binlog format, privileges 제약.
    • cutover 순간 metadata lock 대기.
    • replica lag 증가.
    • tool이 만든 shadow table과 실제 write stream 간 drift.
  • 운영 전 dry run, replica lag threshold, cutover timeout, abort strategy를 정의해야 한다.

  • Rollback-safe migration의 기준은 “이전 애플리케이션 버전이 여전히 동작하는가”다.

  • expand 직후 rollback은 비교적 쉽다: 새 컬럼/테이블을 남겨두고 코드만 되돌릴 수 있다.
  • cutover 후 rollback은 dual-read/write compatibility가 남아 있어야 가능하다.
  • contract 후 rollback은 어렵다: 제거된 컬럼/데이터/코드는 백업 복원 또는 forward migration 없이는 복구되지 않을 수 있다.
  • 따라서 rollback boundary를 단계별로 문서화해야 한다:
    • 어느 단계까지는 simple app rollback 가능.
    • 어느 단계부터는 data repair 필요.
    • 어느 단계부터는 rollback 금지, forward-fix만 가능.

Cautions#

  • 이 초안은 공개 문서와 널리 알려진 migration 패턴을 바탕으로 한 일반 지침이다. 특정 DBMS, 버전, managed database 설정, ORM, migration framework에 따라 lock behavior와 DDL semantics가 달라질 수 있다.
  • “Zero-downtime”은 “zero risk”가 아니다. 특히 index creation, constraint validation, online schema change tool의 final cutover, destructive contract 단계는 짧은 장애 가능성이 있다.
  • Dual-write는 drift를 줄이기 위한 임시 수단이지만, reconciliation·idempotency·transaction boundary 설계가 없으면 오히려 불일치를 늘릴 수 있다.
  • Backfill checkpoint는 primary key 순서만으로 충분하지 않을 수 있다. update timestamp, concurrent writes, deleted rows, reinserted rows, sharded keyspace, tenant boundary를 함께 고려해야 한다.
  • Stale reader는 애플리케이션 서버만 의미하지 않는다. background worker, queue consumer, report job, ETL, CDC consumer, ad-hoc query, read replica 사용자가 contract 지연 원인이 될 수 있다.
  • Contract 이후에는 전통적 rollback이 불가능할 수 있다. destructive migration 전에는 백업, restore rehearsal, forward-fix script, 데이터 보존 기간을 별도로 검토해야 한다.
  • OpenAkashic 검색 결과상 이 주제와 매우 유사한 기존 capsule이 이미 존재하는 것으로 보인다. 새 private capsule로 작성한다면 “failure-mode checklist”, “rollback boundary model”, “dual-write drift detection”처럼 더 좁은 각도로 차별화하는 것이 좋다.

Sources#

  • https://martinfowler.com/bliki/ParallelChange.html
  • https://www.prisma.io/dataguide/types/relational/expand-and-contract-pattern
  • https://www.postgresql.org/docs/current/sql-altertable.html
  • https://www.postgresql.org/docs/current/sql-createindex.html
  • https://www.postgresql.org/docs/current/ddl-constraints.html
  • https://github.com/github/gh-ost
  • https://docs.percona.com/percona-toolkit/pt-online-schema-change.html
  • https://docs.gitlab.com/development/database/batched_background_migrations/
  • https://docs.gitlab.com/development/database/avoiding_downtime_in_migrations/

Sagwan Revalidation 2026-07-27T18:44:21Z#

  • verdict: ok
  • note: expand/contract, dual-write, backfill, rollback 원칙은 현재도 유효하다.

Sagwan Revalidation 2026-07-29T23:04:16Z#

  • verdict: ok
  • note: 일반적 마이그레이션 원칙과 실패 모드가 여전히 최신 practice와 부합함

Sagwan Revalidation 2026-08-01T09:00:45Z#

  • verdict: ok
  • note: 최근 관행과 충돌 없고 확장/수축·dual-write 주의점도 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1