- Published on
DB 마이그레이션 도구 2026 — Atlas / Flyway / Liquibase / Bytebase / pgroll / Squawk / gh-ost 심층 비교
- Authors

- Name
- Youngju Kim
- @fjvbn20031
프롤로그 — "마이그레이션 한 줄로 5분 다운"
2026년 어느 팀의 사후 보고서.
23:14, 평소처럼 배포.
ALTER TABLE orders ADD COLUMN status_v2 VARCHAR(32) NOT NULL DEFAULT 'pending'. 23:14:08, 프라이머리 락. 23:14:09, 모든 쿼리가 큐에 쌓이기 시작. 23:14:42, 백엔드가 connection pool 고갈, 5xx 시작. 23:19:31, 마이그레이션 종료. 23:19:34, 큐 소진, 정상화. 5분 22초 다운. 테이블에는 4억 행이 있었다.
이게 2026년에도 흔한 풍경이다. 도구는 충분히 많다 — Flyway, Liquibase, Atlas, Bytebase, pgroll, Squawk, gh-ost, pt-osc, Reshape, dbmate, golang-migrate, Diesel, Alembic, Prisma Migrate, Drizzle Kit. 그런데 "어떤 마이그레이션이 안전한가"를 도구가 알려주지 않으면, 결국 5분 다운은 반복된다.
이 글은 2026년 DB 마이그레이션 도구의 지도를 그린다. 누가 무엇을 잘하고, 누가 무엇을 못 하고, 우리 팀은 무엇을 골라야 하는지까지.
1장 · 2026년 DB 마이그레이션 지도 — 선언적 vs 명령적 vs 온라인 변경
먼저 큰 그림. 모든 마이그레이션 도구는 세 축 중 어디에 서느냐로 갈린다.
축 1: 선언적(declarative) vs 명령적(imperative)
- 명령적:
ALTER TABLE,CREATE INDEX같은 SQL 스크립트를 사람이 직접 적고, 도구는 "어떤 스크립트를 어떤 순서로 돌렸는지"를 추적한다. Flyway·Liquibase·dbmate·golang-migrate·Alembic·Sqitch·Diesel·Prisma Migrate(기본). - 선언적: 사람은 "원하는 스키마 상태"만 적고, 도구가 현재 상태와 비교해서 diff를 계산해 ALTER 문을 생성한다. Atlas·Drizzle Kit·Prisma Migrate(prototype/db push)·Bytebase의 GitOps 모드.
선언적은 Terraform이 인프라에 한 일을 DB 스키마에 한다. 장점: 스키마가 코드로 한눈에 보인다. 단점: 도구가 만든 ALTER가 항상 안전하다는 보장이 없다(컬럼 rename을 drop+add로 풀어버리는 등).
축 2: 온라인(online) vs 오프라인(offline)
- 오프라인: 마이그레이션 동안 테이블에 락이 걸리거나, 쿼리가 막힌다. 대부분의 전통적 도구의 기본.
- 온라인: 라이브 트래픽이 도는 동안 스키마를 바꾼다. gh-ost·pt-osc·Reshape·pgroll. shadow 테이블 + trigger/logical replication으로 천천히 복사하면서 cutover.
작은 테이블(수십만 행 이하)은 오프라인이 단순하다. 큰 테이블(수억 행, 트래픽 많음)에서는 온라인이 필수.
축 3: forward-only vs reversible
- reversible: 모든 up 마이그레이션에 짝이 되는 down이 있어야 함. Flyway·Alembic·golang-migrate가 권장. 사고 시 롤백.
- forward-only: 롤백은 새 마이그레이션을 추가해서 한다. Sqitch·Atlas의 권장 모드. 이유: "실제로 4억 행 INSERT를 5분 만에 되돌릴 수 있는 down은 거의 없다 — 환상에 가깝다."
2026년 시니어들의 합의: forward-only + 짧고 안전한 변경의 연속이 reversible보다 현실적이다. down은 "방금 만든 빈 컬럼 drop" 정도까지만 의미가 있다.
한 장 요약
| 도구 | 모델 | 온라인? | 언어/대상 | 권장 사용처 |
|---|---|---|---|---|
| Atlas | 선언적 | 직접 ❌, 통합 ✅ | 다중 DB | 선언적 스키마 + CI |
| Flyway 11 | 명령적 | ❌ | Java 친화 | 엔터프라이즈 표준 |
| Liquibase 4.30 | 명령적 + 메타 | ❌ | Java·XML/YAML | 대기업·다중 DB |
| Sqitch | 명령적 + DAG | ❌ | DB 중립 | 의존성 명시형 |
| Bytebase 3 | 둘 다 + 워크플로 | 통합 ✅ | 다중 DB | DB DevOps·리뷰 |
| Squawk | 린터 | n/a | Postgres | CI 게이트 |
| pgroll | 선언적 + expand/contract | ✅ | Postgres | 무중단 Postgres |
| gh-ost | online change | ✅ | MySQL | 대규모 MySQL |
| pt-osc | online change | ✅ | MySQL | 클래식 MySQL |
| Reshape | online change | ✅ | Postgres | Rust·무중단 |
| dbmate | 명령적 | ❌ | 다중 DB·Go CLI | 가벼운 CLI |
| golang-migrate | 명령적 | ❌ | Go·다중 DB | Go 표준 |
| Diesel | 명령적 + ORM | ❌ | Rust | Rust 친화 |
| Alembic | 명령적 + 오토젠 | ❌ | Python·SQLAlchemy | Python 표준 |
| Prisma Migrate | 양방향 | ❌ | TypeScript | Node 친화 |
| Drizzle Kit | 선언적 + 명령적 | ❌ | TypeScript | TS·경량 |
이게 2026년 지도. 이제 한 칸씩 들어가본다.
2장 · Expand-Contract 패턴 + 흔한 발자국들
도구를 고르기 전에 패턴을 먼저 알아야 한다. 도구가 좋아도 잘못된 패턴으로 쓰면 락이 걸린다.
Expand-Contract (Parallel Change)
무중단 마이그레이션의 핵심 패턴. 세 단계로 쪼갠다.
- Expand: 이전 + 새 스키마가 공존하도록 추가만 한다. drop 없음.
- Migrate: 코드가 새 스키마로 쓰기/읽기를 함께. backfill로 과거 데이터를 새 스키마로 복사.
- Contract: 모든 코드가 새 스키마만 쓰는 게 확인되면, 옛 스키마를 drop.
예시 — 컬럼 rename(email → email_address).
- 단순 rename은 위험: 옛 컬럼을 쓰는 배포 중인 코드가 즉시 깨진다.
- expand-contract:
email_address컬럼을 NULL 허용으로 추가 → 코드가 둘 다 쓰기 → backfill로 옛 데이터 복사 → 코드가 새 컬럼만 읽기 → 옛 컬럼 drop.
pgroll·Reshape·gh-ost가 이걸 기본 모델로 자동화한다. 다른 도구는 사람이 마이그레이션을 3개로 쪼개야 한다.
흔한 발자국 (foot-guns)
1) NOT NULL 추가 (큰 테이블)
Postgres 11+에서 ALTER TABLE ... ADD COLUMN x INT NOT NULL DEFAULT 0은 메타데이터만 바꾸고 끝(빠름). 하지만 ALTER TABLE ... ALTER COLUMN x SET NOT NULL은 전체 테이블을 스캔하면서 ACCESS EXCLUSIVE 락. 4억 행이면 분 단위로 락.
- 안전:
ALTER TABLE ... ADD CONSTRAINT x_not_null CHECK (x IS NOT NULL) NOT VALID→VALIDATE CONSTRAINT x_not_null→ 다음 배포에서SET NOT NULL. NOT VALID는 락 없음. - MySQL: 비슷한 패턴 + gh-ost/pt-osc.
2) 컬럼 rename
- 단순
ALTER TABLE ... RENAME COLUMN은 보통 메타데이터만 바꾸지만, 배포 중인 코드가 옛 이름을 참조 중이면 즉시 깨진다. - 안전: expand-contract (위 참조).
3) 타입 변경 (INT → BIGINT)
- Postgres: 보통 전체 테이블 다시 씀(rewrite). 큰 테이블은 락 분 단위.
- 안전: 새 컬럼을 BIGINT로 추가 → backfill → 코드 전환 → 옛 컬럼 drop.
4) 인덱스 추가
- Postgres:
CREATE INDEX CONCURRENTLY(락 안 잡음). 마이그레이션 도구가 트랜잭션 안에서 돌리면 fail — 트랜잭션 밖에서 돌아야 함. - MySQL: 작은 테이블은 즉시. 큰 테이블은 gh-ost/pt-osc.
5) 디폴트 값 변경 (Postgres 11 이전 / 일부 타입)
- Postgres 11 미만: 디폴트 추가는 전체 테이블 다시 씀. 11+에서는 메타데이터.
- 안전: 11+로 업그레이드. 또는 디폴트 없이 추가하고 backfill.
6) Foreign key 추가
- 락이 짧긴 하지만 양쪽 테이블에 락. 라이브 트래픽이 많으면 timeout 위험.
- 안전:
NOT VALID로 추가 → 별도 트랜잭션에서VALIDATE CONSTRAINT.
7) 큰 DELETE / UPDATE
- 한 트랜잭션 안에서 수백만 행 갱신 = WAL 폭증, replica lag, 락 보유.
- 안전: 배치(예: 1만 행씩, sleep 끼워서). 도구는 이걸 직접 안 해줌 — 별도 backfill 잡으로.
핵심: 마이그레이션 도구는 "스크립트를 순서대로 돌리는 무언가"일 뿐, 안전성을 보장하지 않는다. 안전성은 위 패턴 + (Squawk 같은) 린터 + 리뷰에서 나온다.
3장 · Atlas (Ariga) — 선언적 스키마 관리의 대표
Atlas(ariga.io/atlas)는 2022년부터 시작해서 2024-2025년에 빠르게 점유율을 늘렸다. HashiCorp의 Terraform을 DB 스키마에 한 도구.
모델
schema.hcl(또는 SQL/JSON) 파일에 원하는 스키마를 적는다. Atlas가 현재 DB와 비교해서 diff를 계산하고 ALTER 문을 생성.
schema "public" {}
table "users" {
schema = schema.public
column "id" {
type = bigint
null = false
}
column "email" {
type = varchar(255)
null = false
}
primary_key { columns = [column.id] }
index "idx_users_email" {
columns = [column.email]
unique = true
}
}
CLI는 두 모드를 지원.
- Declarative:
atlas schema apply --to file://schema.hcl --url $DB_URL— diff 계산해서 즉시 적용. - Versioned:
atlas migrate diff add_user_email— diff를 SQL 파일로 떨어뜨리고, 나중에atlas migrate apply로 적용. 운영에서는 이쪽 권장.
강점
- DB 중립: Postgres·MySQL·SQLite·SQL Server·MariaDB·ClickHouse·MS SQL·Redshift 등.
- CI 통합:
atlas migrate lint로 위험한 변경(NOT NULL 추가·DROP COLUMN·인덱스 누락) 자동 감지. GitHub Actions·GitLab CI 연동. - ORM 통합: GORM·Ent·Hibernate·Prisma·Sequelize에서 스키마 추출해서 Atlas로 관리.
- schema visualization:
atlas schema inspect | atlas schema fmt+ ERD 생성.
약점
- 선언적 모델은 컬럼 rename을 drop+add로 처리하기 쉽다. Atlas는
--diff-policy힌트로 막을 수 있지만 자동은 아님. - 무중단(expand-contract)은 직접 지원 X — versioned 모드로 사람이 쪼개야 함.
- 무료 버전과 유료(Atlas Cloud, Pro) 기능 차이가 큼: 일부 lint·schema 모니터링·multi-tenant은 Pro 전용.
권장 사용처
- 새로 시작하는 Go 백엔드.
- Terraform 같은 IaC에 익숙한 팀.
- 다중 DB 환경에서 단일 도구로 통일하고 싶을 때.
2026년 기준 Atlas는 선언적 진영의 사실상 표준 위치.
4장 · Flyway 11 — Java 진영의 클래식
Flyway는 2010년부터, JBoss/Spring Boot 생태계의 표준. 2024년에 Redgate가 인수, 11.x는 모듈 정리와 새 데이터베이스 어댑터 중심.
모델
V{version}__{description}.sql 명명 규약. 단순 명령적.
db/migration/
V1__create_users.sql
V2__add_email_index.sql
V3__add_orders.sql
R__create_view_active_users.sql # Repeatable
U2__rollback_email_index.sql # Undo (Teams)
DB의 flyway_schema_history 테이블이 어떤 V가 돌았는지 추적. flyway migrate로 미적용분만 순서대로 적용.
강점
- 단순함: SQL만 알면 끝. 새 팀원이 30분 안에 이해.
- Spring Boot 자동 통합:
spring-boot-starter-flyway한 줄. - DB 폭이 넓다: Postgres·MySQL·Oracle·SQL Server·DB2·Sybase·MongoDB(Pro)·Snowflake·BigQuery 등 50+.
- callback 훅:
beforeMigrate·afterMigrate·beforeEachMigrate등 라이프사이클 훅. - baseline: 이미 있는 DB에 Flyway 도입할 때 현재 상태를 baseline으로 마킹.
약점
- rollback이 유료(Teams/Enterprise): OSS 버전은 forward-only.
Undo마이그레이션은 Teams 전용. - 온라인 스키마 변경 미지원: 큰 테이블은 gh-ost/pt-osc/pgroll과 조합 필요.
- 선언적 모드 없음: 11.x에서 일부 기능(Auto-generate)이 들어왔지만 Atlas/Drizzle 수준의 선언은 아님.
- 린팅 약함: 위험 패턴 자동 감지는 Squawk 같은 외부 도구에 의존.
권장 사용처
- Spring Boot/Java/Kotlin 백엔드.
- Oracle·DB2 같은 엔터프라이즈 DB.
- "SQL 그대로 보고 싶다, 추상화 적게 원한다"는 팀.
2026년에도 JVM 진영의 디폴트. 다만 새 프로젝트가 Flyway만 쓰는 일은 줄고, Atlas/Squawk와 조합하는 경우가 늘었다.
5장 · Liquibase 4.30 — XML/YAML 친화
Liquibase는 Flyway의 라이벌. 2006년부터, 4.30(2025-2026)은 changelog 포맷·DB 지원·UX 개선 중심.
모델
changelog 파일에 changeSet을 나열. 포맷은 XML·YAML·JSON·SQL 모두 가능.
databaseChangeLog:
- changeSet:
id: 1
author: alice
changes:
- createTable:
tableName: users
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints: { primaryKey: true, nullable: false }
- column:
name: email
type: VARCHAR(255)
constraints: { nullable: false, unique: true }
rollback:
- dropTable: { tableName: users }
강점
- DB 추상화 강함: 같은 changeSet이 Postgres·MySQL·Oracle에서 각 DB의 적절한 SQL로 변환. cross-DB 프로젝트에서 유리.
- rollback이 OSS에서 1급 시민: 각 change에 명시적 rollback 정의.
- policy checks (Pro): 위험 패턴 자동 차단.
- GUI 도구(Liquibase Hub, DBeaver 통합).
- 수많은 change 타입:
addColumn·createIndex·addForeignKey·mergeColumns등 30+ 추상 change.
약점
- XML/YAML 장황함: SQL 1줄이 YAML 10줄이 되는 일.
- 추상화의 단점: 추상 change가 생성하는 SQL이 항상 최적은 아님. 큰 테이블에서는 결국 raw SQL로 떨어뜨림.
- 선언적 모드 약함: Liquibase는 명령적. 선언이 필요하면 별도.
- OSS와 Pro 기능 차이 큼: policy check·rollback의 일부 기능·Hub은 Pro.
Flyway vs Liquibase 한 줄
- Flyway: SQL 좋아하면.
- Liquibase: cross-DB·추상화·rollback 명시가 필요하면.
2026년 기준 둘은 거의 동률, 다만 신규 프로젝트는 Atlas로 옮겨가는 추세가 분명함.
6장 · Sqitch — 의존성 기반의 오래된 학파
Sqitch(sqitch.org)는 2012년에 David Wheeler가 만든, "Git 같은 마이그레이션 도구". 버전 번호가 아니라 의존성 그래프로 마이그레이션을 추적.
모델
각 변경은 이름이 있는 change. sqitch add appchanges로 만들면 세 파일이 생김.
deploy/appchanges.sql -- 적용 SQL
revert/appchanges.sql -- 되돌리기 SQL
verify/appchanges.sql -- 검증 SELECT
sqitch.plan에 의존성을 명시.
add_users 2026-05-16T12:00:00Z alice
add_orders [add_users] 2026-05-16T13:00:00Z alice -- add_users에 의존
sqitch deploy는 의존성을 만족하는 순서로 적용. sqitch verify는 verify SQL을 돌려서 실제로 들어갔는지 확인.
강점
- 의존성 명시: 마이그레이션이 100개 넘어가면 의존성 그래프가 진가를 발휘.
- verify 1급 시민: "마이그레이션이 적용됐다"와 "실제로 작동한다"를 구분.
- DB 중립: Postgres·MySQL·Oracle·SQLite·Snowflake·Firebird·Vertica·Exasol.
- VCS 친화: 브랜치별 마이그레이션 충돌이 적음(의존성 기반이라).
약점
- 러닝 커브: 명령어가 많고(
deploy·revert·verify·bundle·tag·rework), 다른 도구와 다른 멘탈 모델. - 커뮤니티가 작음: GitHub stars·StackOverflow 답변 수가 Flyway/Liquibase의 1/10 수준.
- CI 통합·GUI는 약함: 모든 게 CLI.
- Perl 의존성: 설치가 다른 도구보다 번거로움(Docker로 우회 가능).
권장 사용처
- 마이그레이션이 1000개 넘어가는 모놀리식 DB.
- "버전 번호 충돌"이 자주 나는 멀티 브랜치 환경.
- 의존성 그래프 + verify 모델을 좋아하는 팀.
2026년 Sqitch는 틈새지만 충성도 높은 도구. PostgreSQL의 일부 코어 컨트리뷰터들이 여전히 선호.
7장 · Bytebase 3 — DB DevOps + Review
Bytebase(bytebase.com)는 2021년 시작, 2025년 3.x로 점프. GitLab/GitHub for DBs 컨셉.
모델
웹 UI + GitOps. 마이그레이션 SQL을 PR로 올리면 Bytebase가:
- 자동 lint (SQL Review, Squawk-style 룰셋).
- 승인 워크플로(DBA가 리뷰).
- stage별 자동 배포(dev → staging → prod).
- schema drift 감지(Bytebase가 모르는 변경이 prod에 들어가면 알람).
- 데이터 마스킹·access control(누가 어떤 컬럼을 볼 수 있는지).
강점
- 사람 워크플로 1급: 다른 도구는 CLI/CI에 그치는데 Bytebase는 사람이 리뷰하는 UI가 메인.
- 다중 DB·다중 환경: 50+ DB 지원, dev/staging/prod 라이프사이클.
- schema editor (GUI): 비개발자도 마이그레이션 제안 가능.
- data masking: PII 컬럼을 개발자에게 가리기.
- 자체 호스팅 OSS: 자체 호스팅 무료(라이선스로 일부 기능 제한).
약점
- 인프라 부담: Bytebase 자체가 서버 + DB. 작은 팀에는 오버킬.
- 벤더 종속: GitOps 모드를 쓰면 다른 도구로 옮기기 어려움.
- 마이그레이션 자체는 다른 도구를 호출: Bytebase가 직접 ALTER를 안 함, gh-ost/pt-osc/Flyway/Liquibase를 호출. orchestrator에 가까움.
권장 사용처
- DBA 팀이 있고, 개발자가 직접 prod DB에 접근하면 안 되는 환경.
- 50+ DB 인스턴스를 운영하는 큰 조직.
- "PR + 승인" 워크플로를 DB에도 적용하고 싶은 팀.
2026년 Bytebase는 DB DevOps의 사실상 선두. PlanetScale·Neon 같은 서버리스 DB가 자체 UI를 잘 만들어서 경쟁이 있지만, on-prem/multi-cloud는 Bytebase 우위.
8장 · Squawk — Postgres 마이그레이션 린터
Squawk(squawkhq.com)는 2020년에 Steve Dignam이 만든 작은 OSS 도구. 마이그레이션 SQL을 읽고 위험한 패턴을 잡는다. 마이그레이션 도구가 아니라 린터.
모델
squawk migration.sql
위험한 패턴을 발견하면 에러/경고.
migration.sql:1:1: warning: adding-not-nullable-field
ALTER TABLE "user" ADD COLUMN "email" TEXT NOT NULL
Adding a NOT NULL field requires a table rewrite ...
Use ADD COLUMN ... DEFAULT NULL, backfill, then ALTER ... SET NOT NULL.
검출 룰 (Postgres 중심, 30+)
adding-not-nullable-field— NOT NULL 추가 → 락.changing-column-type— 타입 변경 → rewrite.disallowed-unique-constraint— UNIQUE constraint 추가 → 전체 스캔, 락.prefer-text-field— VARCHAR(n) 대신 TEXT.require-concurrent-index-creation—CREATE INDEX(CONCURRENTLY 없이) → 락.transaction-nesting— 트랜잭션 안에서CREATE INDEX CONCURRENTLY→ fail.renaming-column— rename은 기존 코드를 즉시 깨뜨림.renaming-table— 위와 동일.ban-drop-column— DROP COLUMN은 expand-contract로.constraint-missing-not-valid—ADD CONSTRAINT(NOT VALID 없이) → 전체 스캔.
강점
- CI에 한 줄:
squawk **/V*.sql— 모든 마이그레이션에 자동 게이트. - 빠름: Rust로 작성, 파일당 ms 수준.
- GitHub Actions 댓글: PR에 직접 코멘트로 위험을 알림.
- 마이그레이션 도구 중립: Flyway·Liquibase·Atlas·dbmate 등 어느 도구의 SQL이든 lint 가능.
약점
- Postgres 전용: MySQL 룰은 거의 없음.
- 거짓 양성: "이 테이블은 작으니 NOT NULL OK"를 모름. 룰별 disable로 회피.
- 데이터 안전성은 못 봄: 락은 봐도 "이 컬럼 drop이 정말 안 쓰이는지"는 못 봄.
권장 사용처
- Postgres를 쓰는 모든 팀의 CI에 추가 (무료, 1줄).
- 큰 테이블이 있는 환경에서는 거의 필수.
2026년 시니어들의 합의: Postgres 쓰면 Squawk는 디폴트. 도구 선택과 무관하게 CI에 넣는다.
9장 · pgroll (Xata) — 무중단 Postgres 마이그레이션
pgroll(github.com/xataio/pgroll)은 Xata가 2023-2024년에 오픈소스로 공개. expand-contract를 도구가 자동화한다는 야심.
모델
JSON으로 마이그레이션을 정의. pgroll이 자동으로 view + trigger를 만들어서 옛 버전 + 새 버전 스키마를 동시에 노출.
{
"name": "rename_email_column",
"operations": [
{
"rename_column": {
"table": "users",
"column": "email",
"to": "email_address"
}
}
]
}
pgroll start로 시작 → 두 버전 공존(view로 노출) → pgroll complete로 옛 컬럼 제거. 중간에 pgroll rollback도 가능.
작동 원리
pgroll start가 새 컬럼/테이블/인덱스 추가 + view 생성.- 옛 view = 옛 스키마, 새 view = 새 스키마. 코드는
SET search_path로 어느 view를 쓸지 고름. - trigger가 양방향으로 데이터를 동기화(옛 컬럼 쓰면 새 컬럼에도, 새 컬럼 쓰면 옛 컬럼에도).
- backfill이 백그라운드로 옛 데이터를 새 컬럼으로 복사.
- 모든 트래픽이 새 view로 옮겨가면
pgroll complete로 옛 컬럼 drop.
강점
- 진짜 무중단: 트래픽이 도는 동안 컬럼 rename·타입 변경·NOT NULL 추가가 됨.
- rollback이 안전: cutover 전에는 옛 view가 살아 있어서 즉시 복귀.
- 선언적 + expand-contract: 사람이 3개 마이그레이션을 쪼갤 필요 없음.
- Postgres 14+ 네이티브: trigger·view·logical decoding을 활용. 외부 의존 없음.
약점
- Postgres 전용.
- trigger 오버헤드: 양방향 sync trigger가 쓰기 부하 증가.
- 사용 인구가 아직 적음: 2026년에도 production 사용 사례는 토스·Notion·Xata 정도. 사례가 더 쌓여야 함.
- 모든 변경을 지원하지는 않음: partition·복잡한 constraint는 아직.
권장 사용처
- Postgres 위에서 진짜 무중단이 필요한 서비스 (24/7 결제·메시징).
- 컬럼 rename·타입 변경이 잦은 빠르게 진화하는 스키마.
2026년 pgroll은 Postgres 무중단 마이그레이션의 best bet. 다만 안정성은 gh-ost(MySQL)에 비하면 아직 어림.
10장 · gh-ost / pt-online-schema-change / Reshape — 온라인 스키마 변경
큰 테이블 + 라이브 트래픽 = 온라인 스키마 변경 필요. 세 도구.
gh-ost (GitHub, MySQL)
2016년에 GitHub이 만들고 OSS화. trigger 없이, binlog 기반으로 동작.
작동:
- shadow 테이블을 새 스키마로 만듦.
- 옛 테이블의 행을 chunk 단위로 shadow에 INSERT.
- binlog를 읽어서 진행 중 발생한 변경(INSERT/UPDATE/DELETE)을 shadow에도 적용.
- cutover: 짧은 락으로 옛 테이블 ↔ shadow를 atomic swap.
gh-ost \
--user="root" --password="..." \
--host=primary.db \
--database="app" --table="orders" \
--alter="ADD COLUMN status_v2 VARCHAR(32) NOT NULL DEFAULT 'pending'" \
--execute
- 강점: trigger 없음 → 옛 테이블에 부하 적음. throttle(replica lag 보고 자동 감속) 좋음. interactive(
-cli로 진행 중 일시정지·취소). - 약점: MySQL/MariaDB 전용. binlog 읽기 권한 필요. foreign key 있는 테이블은 까다로움.
pt-online-schema-change (Percona, MySQL)
Percona Toolkit의 일부. trigger 기반.
작동:
- shadow 테이블 + 옛 테이블에 INSERT/UPDATE/DELETE trigger 설치.
- chunk 단위로 옛 → shadow 복사.
- cutover: RENAME TABLE 두 번으로 swap.
- 강점: 오래되고 안정. gh-ost가 못 다루는 일부 케이스(주로 foreign key) 처리.
- 약점: trigger가 옛 테이블의 쓰기 부하를 늘림. throttle이 gh-ost보다 약함.
Reshape (Postgres, Rust)
2022년에 Fabian Lindfors가 만든 Rust 도구. Postgres용 expand-contract.
작동: pgroll과 비슷. SQL이 아니라 TOML로 마이그레이션 정의 + view + trigger로 옛/새 공존.
[[actions]]
type = "add_column"
table = "users"
column = "email_address"
data_type = "text"
nullable = true
- 강점: Rust로 빠름, 깔끔한 API.
- 약점: 메인테이너 활동이 2024년부터 줄었다는 평. 2026년 production 사용은 적음. pgroll이 사실상 추월.
셋의 비교
| gh-ost | pt-osc | Reshape | pgroll | |
|---|---|---|---|---|
| DB | MySQL | MySQL | Postgres | Postgres |
| trigger | ❌ | ✅ | ✅ | ✅ |
| binlog | ✅ | ❌ | ❌ | ❌ |
| 활발도 (2026) | 중간 | 낮음 | 낮음 | 높음 |
| 권장 | MySQL 대규모 | gh-ost 안 되는 케이스 | (회피) | Postgres |
2026년 합의: MySQL은 gh-ost가 디폴트, fallback이 pt-osc. Postgres는 pgroll이 신흥 표준.
11장 · dbmate / golang-migrate / Diesel / Alembic — 언어별 옵션
각 언어 진영의 디폴트.
dbmate (Go, 다중 DB)
amacneil/dbmate. 한 파일 짜리 Go 바이너리, CLI 디자인이 깔끔.
dbmate new add_users
# db/migrations/20260516120000_add_users.sql 생성
파일은 up/down 두 섹션.
-- migrate:up
CREATE TABLE users (id BIGINT PRIMARY KEY, email TEXT NOT NULL);
-- migrate:down
DROP TABLE users;
- 강점: Postgres·MySQL·SQLite·ClickHouse·BigQuery 지원. 의존성 0. shell-friendly. Docker 친화.
- 약점: 기능이 적음(lint·schema drift·rollback workflow는 없음). 그 단순함이 매력.
golang-migrate (Go, 다중 DB)
golang-migrate/migrate. CLI + Go 라이브러리.
migrations/
000001_create_users.up.sql
000001_create_users.down.sql
- 강점: Go 코드에서 라이브러리로 직접 호출 가능. CockroachDB·Spanner·MongoDB 어댑터.
- 약점: GitHub stars는 많은데 메인테이너가 줄었다는 평. issue 누적 중. 일부 어댑터는 maintenance 모드.
dbmate와 golang-migrate 사이 — 2024-2025년에 dbmate로 옮긴 팀이 늘었음. 새 Go 프로젝트는 dbmate 디폴트 (또는 Atlas).
Diesel migrations (Rust)
Diesel ORM의 일부. diesel migration generate add_users → up.sql/down.sql.
- 강점: Diesel과 통합 — schema.rs를 자동 생성, 컴파일 타임 타입 체크.
- 약점: Diesel 자체가 무거운 ORM. SQLx/sqlx::migrate를 선호하는 팀도 많음.
Alembic (Python, SQLAlchemy)
SQLAlchemy의 마이그레이션 도구. Python 코드로 마이그레이션을 정의.
def upgrade():
op.add_column('users', sa.Column('email', sa.String(255), nullable=False))
def downgrade():
op.drop_column('users', 'email')
- 강점: SQLAlchemy 모델에서 autogenerate — 모델 바꾸면 diff를 마이그레이션으로 자동 생성. Python 코드라 조건부 로직(
if dialect == 'postgresql':) 가능. - 약점: autogenerate가 컬럼 rename을 drop+add로 오해. Python 의존성 무거움. SQLAlchemy 안 쓰면 안 어울림.
2026년에도 Python/Django는 Django migrations, Python/FastAPI/SQLAlchemy는 Alembic이 디폴트.
12장 · Prisma Migrate / Drizzle Kit — TypeScript 진영
Node/TypeScript는 분열 상태. 둘이 양강.
Prisma Migrate
Prisma ORM의 일부. schema.prisma에 모델 적고, prisma migrate dev로 마이그레이션 생성/적용.
model User {
id BigInt @id @default(autoincrement())
email String @unique
}
- 강점: 스키마 → 마이그레이션 → 클라이언트 타입 한 번에. DX 압도적.
- 약점: 무거운 Rust 엔진. raw SQL 통제력 약함. 큰 테이블에 안전한 마이그레이션 생성을 보장 안 함(rename을 drop+add로). pgbouncer transaction 모드와 충돌.
Drizzle Kit
Drizzle ORM의 일부. 코드(TypeScript)로 스키마 정의 → drizzle-kit generate로 SQL diff 생성.
export const users = pgTable("users", {
id: bigserial("id").primaryKey(),
email: varchar("email", { length: 255 }).notNull().unique(),
});
- 강점: 가볍다(런타임 의존성 작음). raw SQL 가까이 — 생성된 SQL을 사람이 읽고 수정. edge runtime(Cloudflare Workers)에서 OK.
- 약점: 2026년에도 ecosystem이 Prisma보다 작음. 일부 어드밴스 기능(relations, complex types)은 Prisma가 강함.
2026년 TS 합의:
- DX 최우선, 풀스택 Node → Prisma.
- edge·serverless·raw SQL 통제 → Drizzle.
- 대규모·무중단 → 둘 다 부족, 별도 마이그레이션 도구(Atlas/pgroll/gh-ost) 병행.
13장 · 누가 무엇을 골라야 하나 — 결정 트리
| 상황 | 추천 |
|---|---|
| 단일 앱, Postgres, 트래픽 적음 | dbmate 또는 golang-migrate + Squawk |
| 단일 앱, MySQL, 트래픽 적음 | Flyway 또는 dbmate |
| Java/Spring Boot | Flyway (또는 Liquibase) + Squawk |
| Node/TypeScript, 풀스택 | Prisma + Atlas(scale 시) |
| Node/TypeScript, edge | Drizzle Kit + Squawk |
| Python/Django | Django migrations |
| Python/FastAPI | Alembic |
| Rust | Diesel 또는 SQLx + dbmate |
| Go, 새 프로젝트 | Atlas (선언적) 또는 dbmate (단순) |
| 마이크로서비스 50+ | Bytebase + 각 서비스가 선택한 도구 |
| 대규모 Postgres, 무중단 필수 | pgroll + Squawk + Atlas |
| 대규모 MySQL, 무중단 필수 | gh-ost + Flyway + Squawk-MySQL 대체 |
| DBA 팀 있음, 리뷰 워크플로 | Bytebase 중심 |
| Cross-DB (Postgres + MySQL + Oracle) | Liquibase 또는 Bytebase |
| 마이그레이션 1000+ 누적 | Sqitch (의존성 그래프) |
흔한 안티패턴
- "Prisma Migrate 하나로 production까지 다 해결": 작은 테이블이면 OK, 큰 테이블에 들어가면 lock으로 죽음. Squawk를 게이트로.
- "Liquibase의 abstract change가 알아서 잘 해줄 것": cross-DB에서는 자주 suboptimal SQL 생성. raw SQL로 떨어뜨려서 확인.
- "down 마이그레이션을 항상 작성": 4억 행 INSERT의 down이 진짜로 5분 안에 돌 거라 믿나? forward-only로 가는 게 현실.
- "마이그레이션 도구가 안전을 보장": 어떤 도구도 NOT NULL을 안전하게 추가해주지 않는다. expand-contract + lint + 리뷰가 안전이다.
- "gh-ost 한 번 돌리면 무조건 무중단": throttle 잘못 설정하면 replica lag 폭증. cutover 직전 락이 5초 이상이면 실패.
14장 · 한국 / 일본 사례 — 토스 / 카카오 / Mercari
토스 (Toss)
토스는 2023년 컨퍼런스(SLASH)에서 gh-ost를 MySQL에서 광범위하게 사용한다고 공개. 결제 도메인은 24/7이라 락은 곧 매출 손실. gh-ost로 chunk 크기·throttle을 도메인별로 튜닝.
2025-2026년 토스의 일부 신규 Postgres 서비스에서는 pgroll 검토 중이라는 발표. Squawk는 모든 신규 마이그레이션 PR의 필수 CI 단계.
카카오
카카오는 메신저·페이·뱅크 각각 DB 전략이 다름. 카카오뱅크는 Oracle 기반이라 Liquibase + 자체 워크플로. 카카오 페이의 일부 신규 서비스는 Flyway + gh-ost(MySQL) 조합. 카카오 메신저는 자체 분산 storage라 일반 마이그레이션 도구 영역 밖.
2024년 카카오의 일부 팀이 Atlas를 도입해서 Go 백엔드를 통일했다는 이야기가 컨퍼런스에서 나왔음.
Mercari (일본)
Mercari는 micro-service 100+ 환경. 각 서비스가 도구 선택은 자율, 다만 lint(Squawk·자체 룰)는 공통 CI. Spanner를 쓰는 서비스가 많아서 Spanner CLI + 자체 마이그레이션 프레임워크.
Mercari Engineering blog에서 "Schema migration as code" 컨셉을 자주 언급 — Atlas/Bytebase 비슷한 선언적 모델을 사내에서 구현.
LY Corporation (LINE + Yahoo Japan)
2023년 합병 후 LINE 코어 백엔드의 DB 마이그레이션은 Flyway 중심으로 표준화 중. 일부 신규 Go 서비스는 Atlas. Squawk-MySQL 대체로 자체 룰을 추가한 sql-lint 도구를 사내에서 운영.
공통 패턴:
- 큰 테이블엔 무조건 gh-ost (MySQL) / pgroll (Postgres).
- CI에 lint 게이트는 디폴트.
- DBA 리뷰가 prod 배포 게이트 (Bytebase든 자체 툴이든).
이게 2026년 한국·일본 빅테크의 흐름.
결론 — 도구는 많고, 패턴은 적다
긴 글을 한 장으로:
- 도구는 충분히 많다. Atlas, Flyway, Liquibase, Sqitch, Bytebase, pgroll, Squawk, gh-ost, pt-osc, Reshape, dbmate, golang-migrate, Diesel, Alembic, Prisma, Drizzle. 새로 하나 더 만들 필요 없음.
- 선택은 언어/팀 친화도로 시작. Java면 Flyway, Python이면 Alembic, Go면 Atlas/dbmate, TS면 Prisma/Drizzle.
- 무중단이 필요하면 패턴이 도구를 결정한다. expand-contract → pgroll(Postgres) / gh-ost(MySQL).
- Squawk(또는 동등 린터)는 디폴트. Postgres 쓰는 한 CI에 무조건.
- 포지티브 down은 환상. forward-only + 짧고 안전한 변경의 연속이 reversible보다 현실.
- 마이그레이션 도구는 안전을 보장 안 한다. expand-contract 패턴 + 린트 + 리뷰가 안전.
마지막: 5분 다운의 원인은 도구가 아니었다. ALTER COLUMN ... SET NOT NULL을 4억 행 테이블에 한 트랜잭션으로 돌린 패턴이 원인. 도구는 그대로 두고, 패턴을 expand-contract로 바꿨다면 5분이 0초가 됐을 것이다.
참고 / References
- Atlas: https://atlasgo.io/
- Atlas GitHub: https://github.com/ariga/atlas
- Atlas migrate lint: https://atlasgo.io/versioned/lint
- Flyway: https://flywaydb.org/
- Flyway 11 release notes: https://documentation.red-gate.com/fd/release-notes-for-flyway-engine
- Liquibase: https://www.liquibase.com/
- Liquibase 4.30 docs: https://docs.liquibase.com/
- Sqitch: https://sqitch.org/
- Sqitch tutorial: https://sqitch.org/docs/manual/sqitchtutorial-pg/
- Bytebase: https://www.bytebase.com/
- Bytebase docs: https://www.bytebase.com/docs/
- Bytebase GitHub: https://github.com/bytebase/bytebase
- Squawk: https://squawkhq.com/
- Squawk GitHub: https://github.com/sbdchd/squawk
- pgroll: https://github.com/xataio/pgroll
- pgroll docs: https://pgroll.com/docs
- gh-ost: https://github.com/github/gh-ost
- gh-ost cheatsheet: https://github.com/github/gh-ost/blob/master/doc/cheatsheet.md
- pt-online-schema-change: https://docs.percona.com/percona-toolkit/pt-online-schema-change.html
- Reshape: https://github.com/fabianlindfors/reshape
- dbmate: https://github.com/amacneil/dbmate
- golang-migrate: https://github.com/golang-migrate/migrate
- Diesel migrations: https://diesel.rs/guides/getting-started
- Alembic: https://alembic.sqlalchemy.org/
- Prisma Migrate: https://www.prisma.io/docs/concepts/components/prisma-migrate
- Drizzle Kit: https://orm.drizzle.team/kit-docs/overview
- GitHub Engineering — gh-ost: https://github.blog/2016-08-01-gh-ost-github-s-online-migration-tool-for-mysql/
- PlanetScale schema changes: https://planetscale.com/docs/concepts/branching
- Toss SLASH talks: https://toss.tech/slash
- Mercari Engineering blog: https://engineering.mercari.com/en/blog/
- Strong Migrations (Rails 컨텍스트): https://github.com/ankane/strong_migrations
- Expand and Contract pattern (Martin Fowler): https://martinfowler.com/bliki/ParallelChange.html