Theory
Database
데이터베이스 무중단 마이그레이션

데이터베이스 무중단 마이그레이션 전략

결론

데이터베이스 무중단 마이그레이션을 위한 주요 전략은 다음 5가지입니다:

MySQL 공식 문서에서 "INPLACE algorithm performs operations in-place to the original table and avoids the table copy and rebuild, whenever possible"라고 명시되어 있습니다.

Bytebase 문서에서 "gh-ost uses a triggerless, asynchronous approach that captures data changes by tailing binary logs"라고 설명합니다.

Pete Hodgson의 블로그에서 "The pattern works by first expanding with backward-compatible columns or tables, deploying new code that uses the new fields, then finally contracting by removing old fields after all clients migrate"라고 설명합니다.

  • CDC (Change Data Capture): 데이터베이스 로그를 실시간으로 캡처하여 변경사항을 스트리밍하는 방식입니다 (Crashbytes (opens in a new tab))

Crashbytes 문서에서 "The most robust approach involves implementing sophisticated change data capture (CDC) mechanisms that maintain real-time synchronization between source and target systems throughout the migration process"라고 명시되어 있습니다.


각 전략의 상세 설명

1. Online DDL

MySQL INPLACE

MySQL의 Online DDL은 테이블을 복사하지 않고 in-place로 스키마를 변경합니다 (MyDBOps Blog (opens in a new tab))

동작 방식:

  • 기본적으로 INSTANT > INPLACE > COPY 순서로 알고리즘을 시도합니다
  • INPLACE는 테이블 복사 없이 작업을 수행하며 동시 DML을 허용합니다

MyDBOps 블로그에서 "If ALGORITHM is not specified, the server will first try the DEFAULT=INSTANT algorithm for all column addition. If it can not be done, then the server will try INPLACE algorithm; and if that can not be supported, at last server will finally try COPY algorithm"라고 설명합니다.

적합한 상황:

  • 인덱스 추가, 컬럼 추가 등 일반적인 스키마 변경
  • 동시 DML 트래픽이 있는 운영 환경
  • 디스크 공간이 제한적인 경우

PostgreSQL CONCURRENTLY

PostgreSQL의 CREATE INDEX CONCURRENTLY는 테이블을 잠그지 않고 인덱스를 생성합니다 (Bytebase - Postgres CREATE INDEX CONCURRENTLY (opens in a new tab))

동작 방식:

  • 테이블을 두 번 스캔하며 인덱스를 생성합니다
  • 기존 트랜잭션이 완료될 때까지 대기합니다

적합한 상황:

  • 인덱스 생성이 필요한 경우
  • 읽기/쓰기 트래픽이 지속되는 환경

2. Shadow Table 전략

pt-online-schema-change

Percona Toolkit의 도구로, 트리거 기반 동기 방식입니다 (Percona Documentation (opens in a new tab))

장점:

  • 간단한 설정과 검증된 안정성
  • 외래키 지원
  • 병렬 처리 가능

Severalnines 블로그에서 "pt-online-schema-change uses a trigger-based, synchronous mechanism with DML triggers (INSERT, UPDATE, DELETE) to capture changes"라고 설명합니다.

단점:

  • 약 12%의 성능 오버헤드
  • 트리거의 제약사항 존재

적합한 상황:

  • 외래키 제약조건이 있는 테이블
  • 레거시 환경
  • 간단하고 검증된 솔루션이 필요한 경우

gh-ost

GitHub에서 개발한 바이너리 로그 기반 비동기 방식입니다 (GitHub gh-ost (opens in a new tab))

장점:

  • 낮은 성능 오버헤드
  • 일시정지/재개 기능으로 유연한 제어
  • 운영 중 리소스 조절 가능

Bytebase에서 "With gh-ost, you can pause the whole process and the workload pattern will go back to what you are used to see with no additional load whatsoever related to the schema change"라고 설명합니다.

단점:

  • 외래키 미지원
  • 단일 스레드로 고부하에서 처리 속도 제한 (바이너리 로그를 따라잡지 못할 수 있음)
  • Row-Based Replication 필수

Percona 블로그에서 "When load gets higher, gh-ost can't keep up with binary log processing and might never finish, as it processes the binary log in a single thread"이라고 지적합니다.

적합한 상황:

  • 대규모, 고트래픽 환경
  • 외래키가 없는 테이블
  • 세밀한 제어가 필요한 경우

3. Blue-Green Deployment + Expand-Contract

Blue-Green Deployment

두 개의 동일한 환경(Blue, Green)을 유지하고 트래픽을 순간 전환합니다 (VegaStack Blog (opens in a new tab))

장점:

  • 즉각적인 롤백 가능
  • 위험도가 낮은 배포

단점:

  • 인프라 비용 2배
  • 데이터베이스 일관성 유지의 복잡성

VegaStack 블로그에서 "Complex database migrations pose challenges, as the system must ensure that both blue and green environments have consistent data"라고 설명합니다.

Expand-Contract Pattern

Blue-Green과 함께 사용하여 하위 호환성을 유지하는 패턴입니다 (Tim Wellhausen's Paper (opens in a new tab))

동작 방식:

  1. Expand: 새 컬럼/테이블을 추가하여 구/신 스키마 모두 지원
  2. Migrate: 애플리케이션을 점진적으로 새 스키마로 전환
  3. Contract: 구 컬럼/테이블 제거

Tim Wellhausen의 논문에서 "The expand-contract pattern involves first expanding the system to support both old and new schemas, then contracting by removing support for the old schema once all parts support the new version"이라고 정의합니다.

적합한 상황:

  • 마이크로서비스 환경
  • 여러 버전의 애플리케이션이 동시에 실행되는 경우
  • Breaking change를 점진적으로 적용해야 하는 경우

4. CDC (Change Data Capture)

Debezium 같은 CDC 도구를 사용하여 데이터베이스 로그를 실시간으로 캡처합니다 (Debezium (opens in a new tab))

CDC의 목적:

CDC는 데이터 복제 및 실시간 동기화를 위한 전략입니다. 스키마 변경을 위한 도구가 아닙니다 (What Is Change Data Capture (CDC)? | Confluent (opens in a new tab))

Confluent 문서에서 "CDC enables zero-downtime database migrations through real-time data movement, ensuring up-to-date data availability for analytics and reporting"이라고 명시합니다.

  • 주요 용도: 데이터베이스 간 데이터 실시간 복제, 클라우드 마이그레이션, 분석용 데이터 이동
  • 스키마 변경과의 관계: 스키마 변경(schema drift)은 CDC가 해결해야 하는 도전 과제이지 CDC의 목적이 아님 (Zero Downtime Data Migration Best Practices (opens in a new tab))

검색 결과에서 "Source schemas evolve constantly with new columns, data type changes, or deprecated fields—these 'schema drift' events often break pipelines, causing ingestion errors and downtime"라고 지적합니다.

일부 CDC 도구(Striim 등)는 스키마 변경을 자동으로 감지하고 전파하는 기능을 제공하지만, 이는 CDC가 계속 작동할 수 있도록 하기 위한 지원 기능입니다.

장점:

  • 데이터베이스 부하가 거의 없음 (로그 기반)
  • 실시간 변경사항 캡처
  • 완전한 변경 이력 보존

Estuary 블로그에서 "Debezium CDC implements log-based CDC, capturing changes made to transactional databases and streaming them as change events. Little to no performance impact as there is no additional overhead on the database"라고 설명합니다.

단점:

  • Kafka 같은 추가 인프라 필요
  • 운영 복잡도 증가
  • 단일 커넥터 태스크로 초당 약 7,000 이벤트 제한

Estuary 블로그에서 "Debezium's default design uses a single Kafka Connect task per connector, limiting processing capacity to about 7,000 events per second for the Postgres connector, and if changes exceed this rate, Debezium will lag"이라고 지적합니다.

적합한 상황:

  • 대규모 데이터베이스 간 데이터 마이그레이션
  • 다른 시스템으로의 데이터 전환 (예: On-premise → Cloud)
  • 실시간 데이터 동기화가 필요한 경우
  • 스키마는 Blue-Green이나 Expand-Contract 같은 다른 전략과 결합하여 처리

CDC 상세 동작 방식

CDC는 데이터베이스의 트랜잭션 로그를 기반으로 변경사항을 실시간으로 캡처하여 타겟 시스템에 전달하는 방식입니다. Debezium이 대표적인 오픈소스 CDC 플랫폼입니다 (Debezium Architecture (opens in a new tab))

Debezium 공식 문서에서 "Debezium is an open-source platform for CDC built on top of Apache Kafka, with its primary use to record all row-level changes committed to each source database table in a transaction log"이라고 설명합니다.

CDC 전체 워크플로우

Phase 1: Initial Snapshot (초기 스냅샷)

CDC는 먼저 기존 데이터의 스냅샷을 생성합니다:

  1. Lock 획득 (선택적): 일관성 있는 스냅샷을 위해 minimal lock을 짧은 순간만 획득하거나, lock-free 방식을 사용할 수 있습니다

Debezium 공식 문서에서 "By default, the snapshot locking mode is minimal. In this mode, the connector holds the global read lock for the initial portion of the snapshot as it reads the database schemas and other metadata"라고 명시되어 있습니다.

  1. Schema 캡처: 데이터베이스 스키마 구조를 읽고 기록합니다
  2. 데이터 읽기: 각 테이블에 대해 SELECT 쿼리를 실행하여 모든 행을 읽습니다
  3. 이벤트 생성: 각 행을 change event로 변환하여 Kafka topic에 전송합니다

무중단 마이그레이션이 가능한 이유:

  • Lock은 WRITE를 막지 않는 read lock이거나, 매우 짧은 순간만 유지되거나, 아예 lock-free 방식 사용
  • Incremental Snapshot 방식은 변경 이벤트 스트리밍과 병렬로 실행되어 부하 최소화
  • 프로덕션 환경에서는 lock-free 설정을 통해 완전한 무중단 운영 가능

Phase 2: Streaming Changes (스트리밍)

스냅샷 완료 후 실시간 변경사항을 캡처합니다:

  1. Transaction Log 모니터링: MySQL의 binlog 또는 PostgreSQL의 WAL을 지속적으로 읽습니다

Debezium 공식 문서에서 "The MySQL connector uses a client library for accessing the binlog, and the PostgreSQL connector reads from a logical replication stream"이라고 명시합니다.

  1. Change Event 생성: 각 INSERT, UPDATE, DELETE 작업을 change event로 변환합니다
  2. Kafka 전송: 변경 이벤트를 해당 Kafka topic으로 스트리밍합니다
  3. Consumer 처리: Consumer가 이벤트를 소비하여 target 시스템에 적용합니다

Baeldung 문서에서 "Debezium's MySQL connector reads MySQL's binary log to understand what and in what order data has changed, then produces a change event for every row-level insert, update, and delete operation in the binlog"이라고 설명합니다. (Baeldung - Introduction to Debezium (opens in a new tab))

Kafka 통합

Debezium은 Kafka Connect 프레임워크 위에서 동작합니다:

Debezium 공식 문서에서 "Kafka Connect operates as a separate service besides the Kafka broker"라고 명시되어 있습니다.

  • Source Connector: Debezium이 데이터베이스에서 변경사항을 캡처
  • Kafka Topics: 각 테이블마다 별도의 topic에 변경 이벤트 기록
  • Sink Connector: Kafka에서 이벤트를 읽어 target 시스템에 적용

성능 특성

CDC의 성능 제약사항:

Estuary 블로그에서 "Debezium's default design uses a single Kafka Connect task per connector, limiting processing capacity to about 7,000 events per second for the Postgres connector"라고 지적합니다. (Estuary - Debezium Pain Points (opens in a new tab))

  • 단일 스레드 처리로 인한 throughput 제한
  • 고부하 환경에서 replication lag 발생 가능
  • Schema 변경 시 추가 처리 필요

Blue-Green Deployment 상세 동작 방식

Blue-Green 배포는 두 개의 동일한 환경을 유지하고 트래픽을 순간 전환하는 방식입니다 (Medium - Blue/Green Database Deployment Strategy (opens in a new tab))

Medium 문서에서 "Blue-green deployment is a method of installing changes to a web, app, or database server by swapping alternating production and staging servers"라고 정의합니다.

Blue-Green 전체 워크플로우

Phase 1: 환경 구성 (Pre-Cutover Setup)

Blue-Green의 첫 단계는 Green 환경을 구성하는 것입니다:

  1. 데이터베이스 복제

AWS 문서에서 "The first step is to duplicate your production database using tools like pg_dump, mysqldump, or pg_basebackup for PostgreSQL/MySQL, or automated snapshots for AWS RDS"라고 설명합니다. (AWS RDS Blue/Green Deployments (opens in a new tab))

  1. 스키마 변경 적용: Green 환경에 새로운 스키마를 적용합니다
  2. 하위 호환성 확인: 양쪽 환경에서 애플리케이션이 동작하는지 검증합니다

AWS Whitepaper에서 "Database updates must be backward compatible, so the old version of the application can still interact with the data"라고 강조합니다. (AWS Best Practices (opens in a new tab))

Phase 2: 데이터 동기화 (CDC Replication)

Green 환경을 최신 상태로 유지합니다:

  1. CDC 복제 시작

AWS 문서에서 "When the restore on the green environment is complete, start the online logical replication from the blue to green environment"이라고 설명합니다.

  1. 실시간 변경사항 적용: Blue에서 발생하는 모든 변경사항을 Green에 실시간 반영합니다

AWS 문서에서 "Create an AWS DMS CDC-only migration task to enable replication of data changes from the source (blue) to the target (green) databases"라고 명시합니다.

  1. Replication Lag 모니터링: 동기화 지연 시간을 지속적으로 모니터링합니다

Phase 3: Cutover (트래픽 전환)

가장 중요한 단계로, 실제 트래픽을 전환합니다:

  1. 쓰기 중단 (Scheduled Downtime)

Medium 문서에서 "With the scheduled downtime, writes are stopped, allowing only reads on the blue environment for data consistency between environments"라고 설명합니다.

  1. 최종 동기화 확인: Blue와 Green의 데이터가 완전히 일치하는지 확인합니다
  2. DNS/Route 전환

Medium 문서에서 "Amazon Route 53 friendly DNS names (CNAME records) are created to point to the different and changing Aurora endpoints in blue/green deployment, to minimize the amount of manual work during cutover - this is the point at which downtime stops"라고 설명합니다.

  1. 애플리케이션 트래픽 전환: 모든 읽기/쓰기 트래픽이 Green으로 이동합니다

Phase 4: Post-Cutover (롤백 준비)

Cutover 후 안정화 및 롤백 준비:

  1. 역방향 CDC 설정

AWS 문서에서 "Set up a reverse AWS DMS CDC migration task to apply all the changes from the green cluster to the blue cluster, ensuring no data loss will occur on the blue database, should a rollback be necessary"라고 명시합니다.

  1. 모니터링: Green 환경의 성능과 안정성을 지속적으로 모니터링합니다
  2. 롤백 시나리오: 문제 발생 시 DNS를 Blue로 즉시 복귀할 수 있도록 준비합니다

하위 호환성 유지 전략

Blue-Green 배포에서 가장 중요한 것은 하위 호환성입니다:

Fly.io Django Beats에서 "There are two requirements for implementing blue-green deployments using a shared database: Make all database changes backward compatible. Any changes made to the database schema must not break the existing application"이라고 강조합니다. (Fly.io - Smooth Database Changes (opens in a new tab))

Expand-Contract 패턴 적용:

VX Company 문서에서 "Blue-green deployments and canary-releases require your database to be backward-compatible. Use a pattern called Expand and Contract, or Parallel Change to get it sorted!"이라고 권장합니다. (VX Company - Expand and Contract (opens in a new tab))

실제 적용 예시 (컬럼명 변경):

VX Company 문서에서 "Successfully applying the backward incompatible change of renaming a column can be done by doing a couple of backward compatible deploys, involving multiple deployment phases where the application writes to both old and new columns before finally transitioning to use only the new column"이라고 설명합니다.

  1. Deploy 1: 새 컬럼 추가, 양쪽 컬럼에 쓰기, 구 컬럼에서 읽기
  2. Deploy 2: 새 컬럼에서 읽기 시작
  3. Deploy 3: 구 컬럼 제거

Best Practices

Blue-Green 배포 시 권장사항:

  1. Idempotent 마이그레이션 스크립트

Liquibase 블로그에서 "Best Practice: Make your migration scripts idempotent (they can be run multiple times safely)"라고 권장합니다. (Liquibase Blog (opens in a new tab))

  1. DNS TTL 설정

Liquibase 블로그에서 "If using DNS-based switching, TTL (Time to Live) settings can impact switchover speed - configure lower TTL values before deployment to ensure rapid DNS updates, then revert to higher values afterward for stability"라고 조언합니다.

  1. 데이터베이스 변경 분리

Spring.io 블로그에서 "Decouple database changes from application changes. Since all database changes are backward compatible, they do not have to be deployed at the same time as the application changes"라고 설명합니다. (Spring.io - Zero Downtime Deployment (opens in a new tab))


트레이드 오프 비교

전략복잡도인프라 비용DML 허용롤백 용이성외래키 지원적합한 규모
Online DDL (INPLACE)낮음낮음⚠️ 제한적소~중규모
pt-osc중간중간중규모
gh-ost중간중간대규모
Blue-Green높음높음 (2배)✅ 매우 쉬움중~대규모
Expand-Contract높음낮음⚠️ 단계적모든 규모
CDC매우 높음높음 (Kafka 등)⚠️ 복잡대규모

Online DDL이 안되는 경우

MySQL INPLACE 제약사항

다음 경우에는 ALGORITHM=INPLACE를 사용할 수 없습니다 (MySQL 8.4 Reference Manual - Limitations (opens in a new tab)):

  1. Primary Key 변경
    • Clustered Index 재구성이 필요하여 항상 테이블 복사가 발생합니다

MySQL 공식 문서에서 "Restructuring the clustered index always requires copying of table data, so it is best to define the primary key when you create a table"이라고 명시되어 있습니다.

  1. NOT NULL 제약조건 변경
    • 특정 조건에서 컬럼을 NOT NULL로 변환할 때 INPLACE 불가

"ALGORITHM=INPLACE is not permitted under certain conditions if columns have to be converted to NOT NULL"

  1. CASCADE/SET NULL 제약조건
    • LOCK=NONE과 함께 사용 불가

"The ALTER TABLE clause LOCK=NONE is not permitted if there are ON...CASCADE or ON...SET NULL constraints on the table"

  1. Metadata Lock 대기
    • 기존 트랜잭션이 metadata lock을 보유 중이면 완료까지 대기 필요

"Before an in-place online DDL operation can finish, it must wait for transactions that hold metadata locks on the table to commit or roll back"

  1. I/O/CPU 조절 불가
    • Online DDL 작업의 리소스 사용량을 제어할 방법이 없습니다

"There is no mechanism to pause an online DDL operation or to throttle I/O or CPU usage for an online DDL operation"

PostgreSQL CONCURRENTLY 제약사항

다음 경우에는 CONCURRENTLY 옵션을 사용할 수 없습니다 (PostgreSQL Documentation (opens in a new tab), Bytebase - Postgres CREATE INDEX CONCURRENTLY (opens in a new tab)):

  1. 트랜잭션 블록 내 실행 불가

Bytebase에서 "CREATE INDEX CONCURRENTLY cannot run inside a transaction block, which means other parts of your migration cannot be automatically rolled back if something goes wrong"이라고 설명합니다.

  1. 파티션 테이블 직접 사용 불가
    • 각 파티션에 개별적으로 인덱스를 생성해야 합니다

PostgreSQL 공식 문서에서 "One limitation when creating new indexes on partitioned tables is that it is not possible to use the CONCURRENTLY qualifier"라고 명시되어 있습니다.

  1. 실패 시 Invalid 인덱스 남음
    • 실패한 인덱스를 수동으로 정리해야 합니다

"If an index build fails with the CONCURRENTLY option, this index is left as 'invalid', requiring manual cleanup before retrying"

  1. 성능 오버헤드
    • 테이블을 2번 스캔하며 기존 트랜잭션 완료 대기

"PostgreSQL must perform two scans of the table, and in addition it must wait for all existing transactions that could potentially modify or use the index to terminate"

  1. Unique 제약조건 타이밍
    • 인덱스가 완전히 생성된 후에만 유일성 검사

"Creating unique indexes CONCURRENTLY is possible, but it only checks for uniqueness after the index is fully built. If there are existing non-unique records in the table, the migration will fail"


상황별 권장사항

소규모 테이블 (< 1GB)

  • 권장: Online DDL (INPLACE/CONCURRENTLY)
  • 이유: 간단하고 빠르며 추가 인프라 불필요

중규모 테이블 (1GB ~ 100GB)

  • 외래키 있음: pt-online-schema-change
  • 외래키 없음: gh-ost (더 나은 제어 및 낮은 오버헤드)

대규모 테이블 (> 100GB)

  • 일반 스키마 변경: gh-ost
  • 데이터베이스 이전: CDC (Debezium 등)
  • 복잡한 변경: Blue-Green + Expand-Contract

특수 상황

  • 마이크로서비스 환경: Expand-Contract Pattern
  • 레거시 시스템: pt-online-schema-change
  • 클라우드 마이그레이션: CDC + Blue-Green

New Relic 블로그에서 "Make migrations gradual (and instantly reversible without deploying code changes), with a 6-stage migration process"라고 권장합니다. (New Relic Blog (opens in a new tab))


참고 자료

Online DDL

Shadow Table

CDC (Change Data Capture)

Blue-Green Deployment

Expand-Contract Pattern

General Resources