Summary#
Zero-downtime database schema migration은 “스키마 변경”과 “애플리케이션 의미 변경”을 한 번에 묶지 않고, 여러 배포와 검증 단계를 통해 호환성 경계를 유지하는 운영 패턴이다. 핵심 순서는 보통 expand → migrate/backfill → cutover → contract 이며, 각 단계는 독립적으로 배포·관찰·롤백 가능해야 한다.
주요 실패 모드는 네 영역에 집중된다.
- expand/contract 순서 오류: 기존 코드가 아직 읽거나 쓰는 컬럼을 rename/drop하거나, nullable/default/constraint 변경을 기존 버전과 호환되지 않게 적용하는 경우.
- dual-write drift: 신·구 컬럼 또는 신·구 테이블에 동시에 쓰는 동안 변환 로직, 트랜잭션 경계, retry/idempotency, partial failure 처리 차이로 데이터가 벌어지는 경우.
- backfill checkpointing 실패: 대량 backfill이 lock, replication lag, vacuum/undo pressure, hot row contention을 만들거나, 재시작 가능한 checkpoint 없이 중단되어 누락·중복·순서 역전이 생기는 경우.
- 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 VALIDconstraint 후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/
Related#
- Write Cutovers, and Rollback Safety
- Database Transaction Isolation Failure Modes: Write Skew, Phantom Reads, Snapshot Isolation, and Retry Boundaries
- Hexagonal Architecture Boundary Failure Modes: Port Contract Drift, Transaction Leakage, and Side-Effect Orchestration
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 주의점도 유효함