본문 바로가기
Database2026년 4월 22일9분 읽기

Database Zero-downtime 마이그레이션 — Schema Evolution 실전 패턴

YS
김영삼
조회 1
Database Zero-downtime 마이그레이션 — Schema Evolution 실전 패턴

핵심 요약

운영 중 데이터베이스 스키마 변경은 가장 위험한 작업 중 하나. 한 번에 끝내려 하면 lock·downtime·데이터 손실. 여러 단계로 나누고 각 단계가 backward-compatible인 패턴이 표준이다.

  • 핵심 원칙: 모든 단계가 이전·다음 코드와 호환
  • 전형적 단계: 추가 → 듀얼 라이트 → 백필 → 읽기 전환 → 쓰기 전환 → 삭제
  • 도구: Liquibase·Flyway·Atlas·Prisma Migrate (각자 차이)

1. Column 추가 (NOT NULL, 기본값 있음)

가장 단순. PostgreSQL 11+는 기본값 있는 NOT NULL ALTER 즉시 완료.

-- PG 11+
ALTER TABLE users ADD COLUMN status VARCHAR(20) NOT NULL DEFAULT 'active';
-- O(1), lock 짧음

-- PG 10 이하 (전체 rewrite — 위험)
ALTER TABLE users ADD COLUMN status VARCHAR(20);
UPDATE users SET status = 'active'; -- 백필
ALTER TABLE users ALTER COLUMN status SET NOT NULL;

2. Column 이름 변경 — 7단계

가장 복잡한 케이스. 절대 한 번에 RENAME하지 말 것.

-- 단계 1: 새 컬럼 추가
ALTER TABLE users ADD COLUMN email_address VARCHAR(255);

-- 단계 2: 코드 — 듀얼 라이트 (이전 컬럼 + 새 컬럼)
UPDATE users SET email = $1, email_address = $1 WHERE id = $2

-- 단계 3: 백필
UPDATE users SET email_address = email WHERE email_address IS NULL;

-- 단계 4: NOT NULL
ALTER TABLE users ALTER COLUMN email_address SET NOT NULL;

-- 단계 5: 코드 — 읽기 전환
SELECT email_address FROM users WHERE id = $1

-- 단계 6: 코드 — 쓰기도 전환 (이전 컬럼 더 이상 안 씀)
UPDATE users SET email_address = $1 WHERE id = $2

-- 단계 7: 이전 컬럼 삭제 (충분한 검증 기간 후)
ALTER TABLE users DROP COLUMN email;

각 단계 사이 충분한 검증·롤백 가능성 확보. 보통 단계당 1주~1개월.

3. Column 타입 변경

VARCHAR(50) → VARCHAR(255) 같은 확장은 PG에서 즉시 완료. 축소·완전 다른 타입은 위 7단계와 유사.

-- 안전: 길이 늘리기 (즉시)
ALTER TABLE users ALTER COLUMN name TYPE VARCHAR(255);

-- 위험: 타입 완전 변경 (전체 rewrite)
ALTER TABLE users ALTER COLUMN age TYPE BIGINT USING age::BIGINT;
-- → 큰 테이블에서 lock·downtime 위험

-- 권장: 새 컬럼 추가 + 단계별 전환

4. NOT NULL 추가

-- 단계 1: CHECK 제약 NOT VALID로 추가
ALTER TABLE users ADD CONSTRAINT users_email_not_null 
  CHECK (email IS NOT NULL) NOT VALID;

-- 단계 2: 백필
UPDATE users SET email = 'unknown@example.com' WHERE email IS NULL;

-- 단계 3: 제약 검증 (FOR ALL)
ALTER TABLE users VALIDATE CONSTRAINT users_email_not_null;

-- 단계 4: NOT NULL 변환
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
ALTER TABLE users DROP CONSTRAINT users_email_not_null;

5. 인덱스 추가 — CONCURRENTLY

일반 CREATE INDEX는 ACCESS EXCLUSIVE lock. CONCURRENTLY로 lock 최소화.

CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
-- 트랜잭션 외부에서만, 더 느림 (2배), 그러나 lock 짧음

6. FK 추가 — NOT VALID 단계

-- 단계 1: 제약 NOT VALID로 추가 (즉시, lock 적음)
ALTER TABLE orders ADD CONSTRAINT orders_user_fk
  FOREIGN KEY (user_id) REFERENCES users(id) NOT VALID;

-- 단계 2: 검증 (lock 적음, 시간 더 걸림)
ALTER TABLE orders VALIDATE CONSTRAINT orders_user_fk;

7. 큰 데이터 백필 — 청크 단위

-- 한 번에 UPDATE — 트랜잭션 길어져 lock·replica lag 위험
-- ❌ UPDATE users SET status = 'active' WHERE status IS NULL;

-- ✅ 청크 단위
DO $$
DECLARE
  rows_affected INT;
BEGIN
  LOOP
    UPDATE users SET status = 'active'
    WHERE id IN (
      SELECT id FROM users WHERE status IS NULL LIMIT 1000 FOR UPDATE SKIP LOCKED
    );
    GET DIAGNOSTICS rows_affected = ROW_COUNT;
    EXIT WHEN rows_affected = 0;
    PERFORM pg_sleep(0.1); -- replica lag 완화
  END LOOP;
 END $$;

8. 테이블 분할 (partitioning)

큰 테이블을 partition으로 전환. 신규 데이터부터 시작.

-- 1. 기존 테이블 rename
ALTER TABLE events RENAME TO events_legacy;

-- 2. 파티션 테이블 생성
CREATE TABLE events (LIKE events_legacy INCLUDING ALL) PARTITION BY RANGE (created_at);

-- 3. 신규 파티션
CREATE TABLE events_2026_q2 PARTITION OF events FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');

-- 4. 코드는 events 테이블 그대로 사용
-- 5. 백그라운드로 events_legacy를 events로 이전

9. 운영 도구

도구특징
LiquibaseXML/YAML 변경 set, 롤백 표준
FlywaySQL 파일 기반, 단순
Atlasdeclarative, 자동 plan
Prisma Migrateschema.prisma → migration SQL
pgrollPG 전용, multi-version 자동
OpenZeebelarge table 자동 청크 백필

10. 체크리스트

  • 각 단계가 backward-compatible
  • 인덱스는 CONCURRENTLY
  • 제약은 NOT VALID + VALIDATE
  • 백필은 청크 단위
  • 각 단계 후 24시간 모니터링
  • 롤백 SQL 준비
  • replica lag·lock·CPU 알림
  • 변경 영향받는 코드 1주 이상 검증 후 다음 단계

자주 묻는 질문

모든 변경에 7단계가 필요한가?아니다. ADD COLUMN(default)·CONCURRENTLY index·NOT VALID는 즉시 가능. 7단계는 RENAME·타입 완전 변경처럼 위험한 경우만.

downtime 5분 정도면 그냥 한 번에 가도 되나?

가능. 단 큰 테이블(억 단위) ALTER는 5분 안 끝나는 경우 많아서 위험. 사전 측정 필수.

ORM (Prisma) 자동 마이그레이션 안전한가?스테이징은 OK. 운영은 Prisma가 생성한 SQL을 검토 후 수동 단계 분리해야 안전.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록