PostgreSQL 스키마를 Git처럼 관리하기 — Atlas와 GitHub Actions로 선언적 Diff, 린팅, Drift 감지, 자동 롤백까지
"야, 누가 프로덕션 DB에서 컬럼 직접 지웠어?" — 팀 슬랙에 이런 메시지가 뜨는 순간, 그날의 배포 계획은 전부 날아갑니다. 마이그레이션 파일은 충실히 쌓여 있는데, 누군가 핫픽스랍시고 프로덕션 DB에서 직접 ALTER TABLE을 날리면 파일과 실제 스키마가 조용히 어긋나기 시작합니다. 그 어긋남은 다음 배포 때까지 모르다가 폭탄이 됩니다.
이 문제의 본질은 스키마의 "의도된 상태"와 "실제 상태"를 추적하는 단일 소스가 없다는 것입니다. Atlas는 바로 이 지점을 파고듭니다. Terraform이 인프라를 선언적으로 관리하듯, Atlas는 데이터베이스 스키마를 선언적으로 관리합니다. 개발자가 원하는 스키마 상태를 정의하면 Atlas가 현재 DB 상태와 비교해 실행할 SQL을 자동으로 계산합니다.
이 글에서는 Atlas를 GitHub Actions와 연결해 PR 단계의 마이그레이션 린팅, 프로덕션 Drift 감지, 배포 실패 시 자동 롤백까지 이어지는 파이프라인을 어떻게 구성하는지 살펴봅니다. 도구 소개보다는 "왜 이 구조인가"에 더 집중해서, 현업에서 실제로 판단이 필요한 지점들을 함께 짚어봅니다.
핵심 개념
이 글에서 다루는 방식: 버전드 마이그레이션
Atlas는 두 가지 워크플로우를 지원합니다. 선언적 방식은 atlas schema apply를 사용해 schema.hcl에 정의된 목표 상태를 현재 DB와 직접 비교·적용합니다. 별도의 마이그레이션 파일 없이 Atlas가 diff를 계산해 바로 실행합니다. 버전드 방식은 atlas migrate diff로 변경을 감지해 번호가 붙은 마이그레이션 파일을 생성하고, atlas migrate apply로 순서대로 실행합니다.
이 글은 버전드 방식을 기준으로 설명합니다. 변경 이력이 Git 히스토리에 남아 감사 추적이 가능하고, PR 리뷰에서 정확히 어떤 SQL이 실행될지 검토할 수 있으며, 특정 버전으로 정확하게 롤백할 수 있기 때문입니다. 기존 Flyway·Liquibase 팀이 Atlas로 이전하는 경우에도 자연스러운 중간 지점이 됩니다.
선언적 스키마 vs 버전 기반 마이그레이션
핵심 차이는 "현재 스키마가 무엇인가"를 누가, 어떻게 알고 있느냐입니다. 버전 기반 도구는 V001__init.sql, V002__add_column.sql처럼 변경 이력을 파일로 쌓습니다. 현재 스키마를 알려면 이 파일들을 처음부터 재생(replay)해야 하고, 누군가 중간에 직접 DB를 건드렸다면 파일과 실제 DB가 어긋나도 도구가 감지하지 못합니다.
Atlas는 스키마 자체를 소스 오브 트루스(source of truth)로 삼습니다. schema.hcl에 원하는 최종 상태를 정의하면, Atlas가 현재 DB를 직접 인트로스펙트해서 diff를 계산하고 필요한 SQL만 생성합니다.
버전드 방식에서도 Atlas의 diff 계산 능력을 활용할 수 있습니다. schema.hcl에 목표 상태를 정의하고 atlas migrate diff를 실행하면 변경 사항을 자동으로 감지해 번호가 붙은 마이그레이션 파일을 생성합니다. 이렇게 schema.hcl은 "의도된 최종 상태"를, migrations/ 디렉토리는 "변경 이력"을 담당하는 구조가 됩니다.
마이그레이션 린팅
atlas migrate lint는 마이그레이션 파일에 대한 정적 분석 도구입니다. 파괴적 변경·락 위험·보안 패턴 등을 탐지하는 다수의 내장 분석기가 포함되어 있습니다(전체 분석기 목록 참조).
| 분석기 카테고리 | 탐지 예시 |
|---|---|
| 파괴적 변경 | 테이블·컬럼·인덱스 삭제 |
| 데이터 의존 수정 | 기본값 없는 NOT NULL 컬럼 추가 |
| 락 위험 | 테이블 재작성을 유발하는 ALTER |
| SQL 인젝션 패턴 | 동적 쿼리 내 인젝션 취약점 |
| 트랜잭션 중첩 오류 | 잘못된 BEGIN/COMMIT 중첩 |
GitHub Actions와 연동하면 PR이 열릴 때 린팅이 자동으로 실행되고 결과가 PR 코멘트로 달립니다. 코드 리뷰에서 사람이 놓치기 쉬운 DB 레벨 위험을 CI가 대신 잡아주는 역할입니다.
팀 컨벤션을 강제하는 커스텀 린트 규칙도 정의할 수 있습니다 — "외래 키에는 반드시 ON DELETE 옵션을 명시", "인덱스 없는 FK 컬럼 금지" 같은 규칙이요. 다만 이 기능은 현재 엔터프라이즈·유료 플랜 전용입니다. 내장 분석기는 무료 티어에서도 계속 쓸 수 있으니, 팀 예산에 따라 판단이 필요한 부분입니다.
Drift 감지
Drift(드리프트)는 표준 마이그레이션 프로세스 바깥에서 스키마가 변경되는 현상입니다. "누가 핫픽스로 직접 ALTER TABLE을 날렸다"가 대표적인 케이스입니다. 이 변경은 Git에 없고, 마이그레이션 이력에도 없습니다.
Atlas의 Drift 감지는 주기적으로 실제 DB 스키마를 인트로스펙트해서 의도된 상태(스키마 파일)와 비교합니다. 차이가 발견되면 HCL/SQL 형식의 diff를 출력합니다.
ERD 시각화 대시보드 등 고급 드리프트 모니터링 기능은 Atlas Cloud(SaaS) 연동이 필요합니다. CLI만으로도 지속적인 드리프트 감지 파이프라인을 구성할 수 있고, 이 글에서는 그 방법을 다룹니다.
롤백: 동적 역방향 SQL 계산
전통적인 도구에서 롤백하려면 미리 down 파일을 작성해둬야 합니다. V003__add_column.sql을 만들면 V003__add_column_down.sql도 같이 만들어야 하는 식이죠. 이걸 깜빡하거나 잘못 작성하면 롤백 자체가 실패합니다.
Atlas는 다른 방향으로 접근합니다. atlas migrate down이 실행될 때 현재 DB 상태를 기반으로 역방향 SQL을 동적으로 계산합니다. 미리 작성된 down 파일이 필요 없습니다. 이 동적 계산 능력을 GitHub Actions의 if: failure() 조건과 결합하면, 배포 실패 시 자동으로 롤백이 트리거되는 파이프라인을 만들 수 있습니다.
PostgreSQL은 트랜잭션 DDL을 지원하기 때문에 Atlas가 전체 롤백을 단일 트랜잭션으로 감쌉니다. 중간에 실패하면 전체가 롤백되므로, 반쯤 적용된 스키마 변경이 남는 상황을 피할 수 있습니다.
단, 롤백은 구조적으로 복잡합니다. 특히 데이터 변환(backfill)이 포함된 마이그레이션이라면 스키마 롤백만으로는 부족하고, 데이터 복구 로직도 함께 설계해야 합니다. Atlas 공식 블로그의 "The Myth of Down Migrations"와 "GitOps 롤백의 현실적 고려사항"을 참고하면 도움이 됩니다.
실전 적용
1단계: atlas.hcl 설정
프로젝트 루트에 atlas.hcl 설정 파일을 만들어 데이터 소스와 환경을 정의합니다.
# atlas.hcl
variable "db_url" {
type = string
default = getenv("DATABASE_URL")
}
env "local" {
src = "file://schema.hcl"
url = var.db_url
dev = "docker://postgres/15/dev"
migration {
dir = "file://migrations"
}
}
env "production" {
url = var.db_url
migration {
dir = "file://migrations"
}
}dev에 Docker URL을 지정하면 Atlas가 린팅이나 diff 계산 시 임시 컨테이너를 스핀업해서 사용합니다. 실제 개발 DB를 건드리지 않고 안전하게 계산할 수 있는 설정입니다.
2단계: 스키마 정의
스키마 변경은 schema.hcl로 정의하고 atlas migrate diff로 마이그레이션 파일을 자동 생성하는 흐름입니다.
# schema.hcl
table "users" {
schema = schema.public
column "id" {
type = bigserial
}
column "email" {
type = varchar(255)
null = false
}
column "created_at" {
type = timestamptz
default = sql("now()")
}
primary_key {
columns = [column.id]
}
index "users_email_idx" {
columns = [column.email]
unique = true
}
}이 파일을 수정하고 아래 명령을 실행하면 migrations/ 디렉토리에 타임스탬프가 붙은 SQL 파일이 자동으로 생성됩니다.
atlas migrate diff --env local --name add_users_table3단계: GitHub Actions 파이프라인 구성
PR 오픈부터 머지 후 배포, 실패 시 자동 롤백까지의 전체 흐름입니다. 여기서 중요한 아키텍처 포인트가 하나 있습니다. GitHub Actions Runner가 린팅과 마이그레이션 적용을 직접 실행하며, Atlas Cloud는 마이그레이션 버전의 레지스트리 역할만 담당합니다. 프로덕션 DB에는 Runner가 직접 접속하는 것이지, Atlas Cloud가 중개하는 구조가 아닙니다.
이 흐름을 구현하는 워크플로우는 두 파일로 분리합니다.
PR 검증용 워크플로우:
# .github/workflows/atlas-ci.yml
name: Atlas CI
on:
pull_request:
paths:
- 'migrations/**'
- 'schema.hcl'
jobs:
lint:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: dev
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: ariga/setup-atlas@v0
with:
cloud-token: ${{ secrets.ATLAS_CLOUD_TOKEN }}
- uses: ariga/atlas-action/migrate/lint@v1
with:
dir: 'file://migrations'
dev-url: 'postgres://postgres:postgres@localhost:5432/dev?sslmode=disable'
config: './atlas.hcl'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}GITHUB_TOKEN을 전달해야 린팅 결과가 PR 코멘트로 자동으로 달립니다.
배포 워크플로우 (자동 롤백 포함):
# .github/workflows/atlas-deploy.yml
name: Atlas Deploy
on:
push:
branches:
- main
paths:
- 'migrations/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ariga/setup-atlas@v0
with:
cloud-token: ${{ secrets.ATLAS_CLOUD_TOKEN }}
- name: Push migration to Atlas Cloud
uses: ariga/atlas-action/migrate/push@v1
with:
dir: 'file://migrations'
dir-name: 'myapp'
config: './atlas.hcl'
- name: Apply to production
id: apply
uses: ariga/atlas-action/migrate/apply@v1
with:
url: ${{ secrets.DATABASE_URL }}
dir-name: 'myapp'
config: './atlas.hcl'
- name: Auto rollback on failure
if: failure() && steps.apply.outcome == 'failure'
run: |
echo "배포 실패 감지 — 마지막 마이그레이션을 롤백합니다"
atlas migrate down \
--url "${{ secrets.DATABASE_URL }}" \
--dir "file://migrations" \
--amount 1apply 스텝이 실패하면 if: failure() 조건이 충족되어 롤백 스텝이 자동으로 실행됩니다. 다만 이 롤백은 스키마 변경만 되돌리므로, 데이터 변환(backfill)이 포함된 마이그레이션에서는 별도의 데이터 복구 절차도 함께 준비해야 합니다.
4단계: Drift 감지 스케줄 설정
atlas schema diff는 diff가 존재해도 기본적으로 exit code 0으로 종료합니다. 즉, 아무 처리 없이 이 명령만 실행하면 드리프트가 있어도 워크플로우가 실패하지 않습니다. 드리프트 발견 시 실제로 파이프라인이 실패하도록 만들려면, 출력값을 캡처해서 비어 있지 않을 때 직접 exit 1을 호출해야 합니다.
또한 atlas schema diff --to "file://schema.hcl" 형식으로 HCL 파일과 비교할 때는 HCL 스키마를 정규화할 dev DB가 필요합니다. 아래 예시는 서비스 컨테이너를 띄워 이 역할을 맡깁니다.
# .github/workflows/atlas-drift.yml
name: Drift Detection
on:
schedule:
- cron: '0 * * * *' # 매 시간 실행
jobs:
detect:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: dev
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
- uses: ariga/setup-atlas@v0
with:
cloud-token: ${{ secrets.ATLAS_CLOUD_TOKEN }}
- name: Run drift detection
run: |
DIFF=$(atlas schema diff \
--config ./atlas.hcl \
--from "${{ secrets.DATABASE_URL }}" \
--to "file://schema.hcl" \
--dev-url "postgres://postgres:postgres@localhost:5432/dev?sslmode=disable")
if [ -n "$DIFF" ]; then
echo "스키마 드리프트가 감지되었습니다:"
echo "$DIFF"
exit 1
fi드리프트가 발견되면 exit 1로 워크플로우가 실패하고 GitHub에서 알림이 옵니다. Slack 연동 스텝을 추가하면 팀 채널로 바로 메시지를 보낼 수도 있습니다.
5단계: 롤백 시나리오
배포 파이프라인 밖에서 수동으로 롤백이 필요한 경우에는 atlas migrate down을 직접 실행합니다.
# 실행 전 어떤 SQL이 적용될지 먼저 확인
atlas migrate down --dry-run \
--url "$DATABASE_URL" \
--dir "file://migrations" \
--amount 1
# 확인 후 실제 롤백 실행
atlas migrate down \
--url "$DATABASE_URL" \
--dir "file://migrations" \
--amount 1
# 특정 버전까지 롤백
atlas migrate down \
--url "$DATABASE_URL" \
--dir "file://migrations" \
--to-version "20250601120000"--dry-run으로 계획을 먼저 확인한 뒤 진행하는 것을 권장합니다. Atlas는 현재 DB 상태를 기반으로 역방향 SQL을 동적으로 계산하므로, 미리 작성해둔 down 파일이 없어도 됩니다.
장단점 분석
장점
| 항목 | 설명 |
|---|---|
| 선언적 워크플로우 | 목표 상태만 정의하면 마이그레이션 SQL 자동 생성 |
| 동적 롤백 | 미리 작성된 down 파일 없이 역방향 SQL 자동 계산 |
| 내장 분석기 | 파괴적 변경·락 위험·보안 패턴 등 CI에서 자동 탐지 |
| GitHub Actions 네이티브 | PR 코멘트 자동화, pre-approval 플로우 내장 |
| PostgreSQL 트랜잭션 DDL | 실패 시 전체 마이그레이션 자동 롤백 보장 |
| 다중 ORM 지원 | Ent, GORM, SQLAlchemy 스키마에서 직접 diff 계산 |
| Drift 감지 | 의도치 않은 스키마 변경 즉각 알림 |
| pgvector 지원 | LLM 생태계 벡터 컬럼 네이티브 관리 |
단점 및 고려사항
| 항목 | 설명 |
|---|---|
| 현재 유료 전용 기능 | 커스텀 린트 규칙, 고급 드리프트 모니터링은 엔터프라이즈·유료 플랜 전용 |
| 학습 곡선 | HCL DSL과 선언적 사고방식 전환에 초기 비용 필요 |
| 데이터 마이그레이션 미지원 | 스키마 변경은 자동화되지만 backfill 로직은 직접 작성 필요 |
| Atlas Cloud 의존성 | 드리프트 대시보드, Registry 등 고급 기능은 SaaS 연동 필요 |
| 프로덕션 롤백의 현실 | 데이터 변환 포함 시 스키마 롤백만으로 불충분한 경우 있음 |
실무에서 자주 보이는 실수
1. dev-url 없이 린팅 시도
atlas migrate lint는 마이그레이션 계획을 검증하기 위해 임시 DB가 필요합니다. dev-url을 지정하지 않으면 일부 분석기가 동작하지 않습니다. CI에서 PostgreSQL 서비스 컨테이너를 띄우거나 docker://postgres/15/dev 형식의 Docker URL을 넘겨주는 것이 안정적입니다.
2. Atlas가 생성한 마이그레이션 파일을 직접 수동 편집
수동으로 내용을 바꾸면 migrations/atlas.sum 체크섬 파일과 불일치가 발생해 검증이 실패합니다. 변경이 필요하다면 해당 마이그레이션 파일과 이후 파일을 모두 삭제하고 atlas migrate diff로 새로 생성하는 것이 가장 안전합니다.
불가피하게 수동 편집을 했다면 atlas migrate hash --force를 실행하면 됩니다. 이 명령은 현재 파일 상태를 기준으로 migrations/atlas.sum 파일을 강제로 재작성합니다. --force 없이 atlas migrate hash만 실행하면 불일치 여부만 확인하고 파일은 변경하지 않습니다. 단, --force로 체크섬을 덮어쓰면 이전 상태 검증이 불가능해지므로, 팀 전체가 변경 사실을 인지한 상태에서 사용해야 합니다.
3. schema.hcl과 마이그레이션 파일의 동기화 실패
schema.hcl을 수정하고 atlas migrate diff를 실행하지 않으면 두 소스가 어긋납니다. CI에서 atlas migrate diff로 신규 변경사항이 없는지 검증하는 스텝을 추가해두면 이 문제를 예방할 수 있습니다.
마치며
**Atlas + GitHub Actions 조합의 핵심 가치는 "스키마 변경을 코드 변경과 동일한 검증 프로세스로 통과시키는 것"**입니다. PR이 열리면 린터가 파괴적 변경을 잡고, 머지되면 버전이 Atlas Cloud에 등록되고, 배포되면 프로덕션에 적용됩니다. 배포가 실패하면 자동으로 롤백이 트리거됩니다. 그리고 누군가 몰래 DB를 건드리면 Drift 감지가 팀에 알려줍니다.
모든 기능을 한 번에 도입할 필요는 없습니다. 린팅 파이프라인부터 시작해서 안정화되면 Drift 감지를 추가하고, 그다음에 자동 롤백 스텝을 연결하는 단계적 접근이 현실적입니다. 가장 빠른 시작점은 atlas schema inspect --url "$DATABASE_URL" > schema.hcl로 현재 스키마를 파일로 가져오는 것입니다. 이 한 줄이 모든 자동화의 출발점이 됩니다.
참고 자료
- Atlas 공식 문서
- GitHub Actions 통합 공식 가이드
- 선언적 방식 CI/CD 단계별 가이드
- Schema Drift Detection 공식 문서
- Down Migrations 공식 문서
- 마이그레이션 린트 공식 문서
- Migration Analyzers 목록
- v0.31 릴리스 노트: Custom schema rules, pgvector
- v0.38 릴리스 노트: Linting Analyzers, PII Detection, Migration Hooks
- 동적 롤백 설계 배경: The Myth of Down Migrations
- GitOps 롤백의 현실적 고려사항
- PR 사전 승인 워크플로우
- Atlas vs Flyway, Liquibase, ORM 도구 비교
- GitHub — ariga/atlas
- Palark의 실제 운영 사례 리뷰
- 2026년 데이터베이스 CI/CD 도구 트렌드