Migration Strategy¶
Umbra 의 DB 스키마 마이그레이션은 Atlas 선언형 방식으로 관리한다.
db/schema/가 desired state source of truth 이며, Atlas 가 마이그레이션을 자동 생성한다. 이 문서는 실무 절차, drift detection, 위험한 마이그레이션의 수동 승인 게이트를 정리한다.
Why this approach¶
Atlas 는 선언형 스키마를 입력 받아 현재 DB 와의 diff 를 자동 생성한다 (ADR-0006). 이 방식이 주는 이점:
- Source of truth 단일화 —
db/schema/*.sql만 보면 "이 시스템이 어떤 상태를 원하는가" 가 명확 - Migration 자동 생성 — 수동 작성 오류 방지
- Drift detection — CI 가 "선언 스키마 ≠ 실제 DB" 를 감지
- Neon branching 과 통합 — PR 별 DB 브랜치에 마이그레이션 자동 적용 검증
Repository layout¶
db/
├─ schema/ # Source of truth (declarative DDL)
│ ├─ 00_schemas.sql # CREATE SCHEMA ...
│ ├─ identity.sql # identity schema 의 모든 테이블
│ ├─ guild.sql
│ ├─ member.sql
│ ├─ licensing.sql
│ ├─ billing.sql
│ ├─ recovery.sql
│ ├─ notification.sql
│ ├─ audit.sql
│ ├─ webhook.sql
│ └─ events.sql
│
├─ migrations/ # Auto-generated by Atlas
│ ├─ 20260401000000_init.sql
│ ├─ 20260415123045_add_guild_config.sql
│ └─ atlas.sum # checksum (integrity)
│
├─ queries/ # sqlc source
│ ├─ identity/
│ ├─ guild/
│ └─ ...
│
├─ atlas.hcl # Atlas 설정
└─ sqlc.yaml # sqlc 설정
Atlas config¶
db/atlas.hcl:
env "local" {
src = "file://schema"
url = "postgres://localhost:5432/umbra_dev?sslmode=disable"
dev = "docker://postgres/16/dev"
migration {
dir = "file://migrations"
}
}
env "preview" {
src = "file://schema"
url = env.PREVIEW_DATABASE_URL # Neon PR 브랜치
dev = "docker://postgres/16/dev"
migration {
dir = "file://migrations"
}
}
env "prod" {
src = "file://schema"
url = env.PROD_DATABASE_URL
dev = "docker://postgres/16/dev"
migration {
dir = "file://migrations"
}
}
Standard workflow¶
1. 스키마 변경 (declarative)¶
개발자는 db/schema/{domain}.sql 을 수정한다.
아니다. 선언형이라 변경이 아니라 최종 상태를 적는다:
-- db/schema/licensing.sql
CREATE TABLE licensing.licenses (
id UUID PRIMARY KEY,
guild_id UUID NOT NULL REFERENCES guild.guilds(id),
plan_id UUID NOT NULL REFERENCES licensing.plans(id),
status TEXT NOT NULL CHECK (status IN ('active', 'suspended', 'canceled')),
granted_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ,
suspended_at TIMESTAMPTZ,
suspended_reason TEXT, -- 이 줄 추가
canceled_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
2. Migration 자동 생성¶
Atlas 가 현재 DB 와 선언 스키마를 비교해 필요한 마이그레이션 SQL 을 db/migrations/20260415_add_suspended_reason.sql 로 생성.
3. 로컬 적용¶
4. Commit¶
db/schema/licensing.sql(수정됨)db/migrations/20260415_add_suspended_reason.sql(생성됨)db/migrations/atlas.sum(체크섬 갱신)
세 파일 모두 PR 에 포함.
5. CI 검증¶
GitHub Actions 가 PR 마다:
- Neon PR 브랜치 DB 에 마이그레이션 적용
atlas migrate lint로 위험 변경 감지atlas schema diff로 drift 재확인 (선언 스키마와 적용 결과 일치 확인)- drift 있으면 PR 실패
6. Merge → Production apply¶
main merge 시 GitHub Actions 가 production DB 에 적용:
7. 자동 생성 파일과 함께 Commit¶
changed files:
db/schema/licensing.sql
db/migrations/20260415_add_suspended_reason.sql
db/migrations/atlas.sum
# sqlc 생성 파일 (새 컬럼 추가 시)
engine/licensing/adapter/persistence/sqlc/models.go
engine/licensing/adapter/persistence/sqlc/queries.sql.go
PR review checklist¶
| Check | Responsibility |
|---|---|
| 선언 스키마 파일 변경이 의도와 일치하는가 | Author |
| 자동 생성된 migration SQL 이 예상과 일치하는가 | Reviewer |
| Destructive 변경 포함 여부 | Reviewer |
| CI 의 Atlas lint 통과 | CI |
| Neon PR 브랜치에서 적용 성공 | CI |
| sqlc 재생성 commit 여부 | Author |
Destructive migrations¶
위험한 변경 은 수동 승인 게이트.
분류¶
| Change | Risk | Required gate |
|---|---|---|
| ADD COLUMN (nullable) | Low | Auto |
| ADD COLUMN (NOT NULL with DEFAULT) | Low | Auto |
| ADD COLUMN (NOT NULL no DEFAULT) | High | Manual approval |
| DROP COLUMN | High | Manual approval |
| ALTER COLUMN TYPE | Medium~High | Manual review |
| DROP TABLE | Critical | Manual approval + owner sign-off |
| ALTER CONSTRAINT | Medium | Manual review |
| RENAME | High | Multi-step migration 권장 |
| Large UPDATE/BACKFILL | High | 별도 script, 본 마이그레이션과 분리 |
Gate 설정¶
GitHub Actions approval 또는 Atlas Cloud 의 policy 적용:
Multi-step rename 패턴¶
컬럼 이름 변경은 단일 마이그레이션으로 하지 않는다.
- Step 1 — 새 이름의 컬럼 추가 + 데이터 복사
- Step 2 — 애플리케이션 코드를 새 컬럼 사용하도록 배포
- Step 3 — 기존 컬럼 삭제 (다음 PR)
이 과정은 단일 PR 로 합쳐서는 안 된다. 배포 순서 깨질 위험.
Backfill scripts¶
데이터 이관이 필요한 경우 마이그레이션 SQL 과 분리. Atlas 마이그레이션은 schema 만, backfill 은 별도 Go script (cmd/backfill/).
db/migrations/20260415_add_col.sql # ADD COLUMN nullable
cmd/backfill/20260415_populate_col.go # 데이터 채우기
db/migrations/20260501_set_not_null.sql # NOT NULL 제약 추가 (다음 PR)
3-step 배포:
- Schema 변경 1 (nullable)
- Backfill 실행
- Schema 변경 2 (NOT NULL)
Neon branching integration¶
Neon 의 DB branching 을 활용.
PR branch¶
PR 생성 시 GitHub Actions 가:
- Neon 에 PR 번호로 branch 생성 (
pr-123) - main branch 의 snapshot 복제
- 해당 branch 에 마이그레이션 적용
- CI 테스트 실행
- 결과를 PR 코멘트로 게시
Schema drift detection¶
atlas schema inspect --url "$PREVIEW_DATABASE_URL" > actual.hcl
atlas schema inspect --url "file://schema" > desired.hcl
diff actual.hcl desired.hcl
일치하지 않으면 CI 실패.
Merge 후 정리¶
PR merge 시 Neon PR branch 는 자동 삭제 (cron 또는 webhook).
Rollback strategy¶
Atlas 는 down migration 을 기본 생성하지 않는다. 롤백은 다음 중 하나:
Option 1: New forward migration¶
가장 안전. 문제를 복구하는 새 마이그레이션 작성:
Option 2: Neon PITR (point-in-time recovery)¶
Neon 이 제공하는 PITR 로 특정 시점으로 복구. 장점: 빠름. 단점: 복구 시점 이후 데이터 손실.
Production 롤백은 반드시 팀 논의 후. 데이터 손실 risk 분석 필수.
Seed data¶
초기 데이터 (Plan 마스터 등) 는 migration 에 포함:
-- db/migrations/20260401000001_seed_plans.sql
INSERT INTO licensing.plans (id, code, name, ...) VALUES
('018f...', 'FREE', ...),
('018f...', 'PRO', ...),
('018f...', 'ENTERPRISE', ...)
ON CONFLICT (code) DO NOTHING;
Plan 가격 변경은 별도 migration 으로 UPDATE.
Dev environment setup¶
신규 개발자의 로컬 환경:
# Docker PostgreSQL 시작
docker compose up -d postgres
# Schema 적용
atlas migrate apply --env local
# Seed data 확인
psql $LOCAL_DATABASE_URL -c "SELECT code FROM licensing.plans;"
Production apply order¶
Fly.io 배포 파이프라인에서:
- Migration 먼저 적용
- 성공 시 애플리케이션 배포
- 실패 시 전체 배포 중단
이유: 새 컬럼을 참조하는 코드가 먼저 배포되면 런타임 에러.
Backwards-compatible migration 원칙:
- 새 컬럼은 항상 nullable 또는 DEFAULT 로 추가 (기존 코드가 모른 채 돌아감)
- 기존 컬럼 제거는 코드에서 사용 중단 후 다음 PR 에서
Tooling summary¶
| Tool | Purpose |
|---|---|
| Atlas CLI | Migration 생성 및 적용 |
atlas migrate diff | 선언 스키마와 DB 비교 |
atlas migrate apply | 마이그레이션 실행 |
atlas migrate lint | 위험 변경 감지 |
atlas schema inspect | 현재 DB schema dump |
| Neon branching | PR 별 DB 격리 |
| sqlc | 마이그레이션 후 Go 코드 재생성 |
See also¶
adr/0006-migration-atlas.md— Atlas 선택 근거adr/0020-postgres-schema-per-domain.md— schema 분리guides/database-conventions.md— SQL 작성 컨벤션data/schema-overview.md— 전체 schema- Atlas Documentation