핵심 요약
큰 테이블에 제약이 강한 변경(NOT NULL, 비싼 DEFAULT, 타입 변경)을 한 번에 걸면 긴 잠금으로 서비스가 멈춘다. 안전한 방법은 expand-contract — ① nullable로 컬럼 추가 → ② 배치로 채움 → ③ 기본값/제약 추가 → ④ 앱 전환 후 옛 컬럼 제거. 각 단계가 짧은 잠금만 쓴다.
1. 단계
| 단계 | 작업 |
|---|---|
| Expand | nullable 컬럼 추가(즉시) |
| Backfill | 배치로 값 채움 |
| Constrain | 검증 후 NOT NULL/기본값 |
| Contract | 앱 전환 후 구컬럼 제거 |
2. 포인트
- 최신 Postgres는 상수 DEFAULT 컬럼 추가가 빠르지만, 계산 DEFAULT·NOT NULL 검증은 스캔 발생
- backfill은 한 번에 전체 UPDATE 말고 chunk로(잠금·WAL 분산)
- 인덱스는
CREATE INDEX CONCURRENTLY로
3. 함정
- 마이그레이션과 배포 순서가 중요 — 신·구 코드가 모두 호환되는 중간 상태를 거쳐야 한다
- 긴 트랜잭션 안에서 DDL은 잠금을 오래 잡는다
- 롤백 계획(역방향 마이그레이션)을 미리 준비
자주 묻는 질문
그냥 NOT NULL DEFAULT로 추가하면 안 되나요?
작은 테이블이면 괜찮습니다. 큰 테이블은 전체 행 검증/재작성으로 긴 잠금이 걸려 장애가 날 수 있어, nullable 추가 후 단계적으로 제약을 거는 게 안전합니다.
인덱스는 어떻게 무중단으로 만드나요?
CREATE INDEX CONCURRENTLY를 쓰면 쓰기를 막지 않고 인덱스를 생성합니다. 다만 트랜잭션 밖에서 실행해야 합니다.

댓글 0