pgroll - PostgreSQL 무중단 마이그레이션 완벽 가이드: 실무에서 안전한 스키마 변경하기
⏱️ 예상 읽기 시간: 15분
서론
현대의 웹 애플리케이션에서 데이터베이스 스키마 변경은 피할 수 없는 작업입니다. 하지만 전통적인 마이그레이션 방식은 데이터베이스 락(lock)으로 인한 서비스 중단, 롤백의 어려움, 그리고 대용량 데이터에서의 성능 문제 등 많은 위험을 내포하고 있습니다.
pgroll은 Xata에서 개발한 PostgreSQL 전용 마이그레이션 도구로, 이러한 문제들을 근본적으로 해결합니다. 가상 스키마와 expand/contract 패턴을 통해 진정한 무중단 마이그레이션을 구현하며, 언제든지 즉시 롤백할 수 있는 안전성을 제공합니다.
이 튜토리얼에서는 pgroll의 핵심 개념부터 실제 운영 환경에서의 적용 방법까지, 실무에 필요한 모든 내용을 다루겠습니다.
pgroll이란?
핵심 특징
pgroll은 PostgreSQL 데이터베이스의 제로 다운타임 마이그레이션을 실현하는 혁신적인 도구입니다:
- ✅ 무중단 마이그레이션: 데이터베이스 락 없이 스키마 변경
- ✅ 이중 스키마 지원: 기존 및 신규 스키마 동시 운영
- ✅ 자동 백필(backfill): 필요시 자동 데이터 마이그레이션
- ✅ 즉시 롤백: 문제 발생시 즉시 이전 상태로 복원
- ✅ 기존 스키마 지원: 새 프로젝트가 아닌 기존 데이터베이스에서도 사용 가능
지원 환경
- PostgreSQL 14.0 이상
- AWS RDS, Aurora 포함 모든 PostgreSQL 서비스
- Go 언어로 작성된 단일 바이너리
- Linux, macOS, Windows 크로스 플랫폼 지원
설치 및 환경 구축
1. pgroll 설치
Homebrew (macOS/Linux)
# pgroll tap 추가
brew tap xataio/pgroll
# pgroll 설치
brew install pgroll
바이너리 직접 다운로드
# macOS ARM64
curl -L -o pgroll https://github.com/xataio/pgroll/releases/download/v0.14.1/pgroll.macos.arm64
# Linux AMD64
curl -L -o pgroll https://github.com/xataio/pgroll/releases/download/v0.14.1/pgroll.linux.amd64
# 실행 권한 부여
chmod +x pgroll
Go 소스 빌드
go install github.com/xataio/pgroll@latest
2. PostgreSQL 환경 준비
Docker를 사용한 테스트 환경
# PostgreSQL 컨테이너 실행
docker run --name pgroll-postgres \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=testdb \
-p 5432:5432 \
-d postgres:15
# 연결 상태 확인
docker exec pgroll-postgres pg_isready -U postgres
3. pgroll 초기화
# pgroll 내부 상태 테이블 생성
pgroll init --postgres-url "postgres://postgres:password@localhost:5432/testdb?sslmode=disable"
실행 결과:
SUCCESS Initialization complete
pgroll이 마이그레이션 상태를 추적하기 위한 내부 테이블이 생성됩니다.
핵심 개념: Expand/Contract 패턴
전통적인 마이그레이션의 문제점
-- 위험한 전통적 방식
ALTER TABLE users ADD COLUMN email VARCHAR(255);
-- 📛 테이블 락 발생, 서비스 중단
pgroll의 Expand/Contract 접근법
- Expand 단계: 기존 스키마에 새로운 요소 추가
- Dual Schema 운영: 구버전과 신버전 동시 서비스
- Contract 단계: 더 이상 사용되지 않는 요소 제거
이 패턴을 통해 애플리케이션은 점진적으로 새로운 스키마로 전환할 수 있습니다.
첫 번째 마이그레이션: 테이블 생성
1. 마이그레이션 파일 작성
pgroll은 YAML 형식의 마이그레이션 파일을 사용합니다:
# initial_migration.yaml
operations:
- create_table:
name: customers
columns:
- name: id
type: integer
pk: true
- name: name
type: varchar(255)
unique: true
- name: bio
type: text
nullable: true
2. 마이그레이션 시작
pgroll --postgres-url "postgres://postgres:password@localhost:5432/testdb?sslmode=disable" \
start initial_migration.yaml
실행 결과:
SUCCESS New version of the schema available under the postgres "public_initial_migration" schema
3. 스키마 버전 확인
pgroll은 각 마이그레이션마다 새로운 스키마 버전을 생성합니다:
# 스키마 목록 확인
psql -U postgres -d testdb -c "\dn"
다음과 같은 스키마들이 생성됩니다:
public
: 원본 스키마public_initial_migration
: 새 버전 스키마pgroll
: pgroll 내부 상태 스키마
애플리케이션 연동
1. 기존 애플리케이션 (구버전 스키마)
import psycopg2
# 기존 애플리케이션은 원본 스키마 사용
conn = psycopg2.connect(
host="localhost",
database="testdb",
user="postgres",
password="password"
)
# 기본 search_path는 'public'
cursor = conn.cursor()
cursor.execute("SHOW search_path")
print(cursor.fetchone()) # ('public', )
2. 신규 애플리케이션 (신버전 스키마)
import psycopg2
# 새 애플리케이션은 신버전 스키마 사용
conn = psycopg2.connect(
host="localhost",
database="testdb",
user="postgres",
password="password"
)
cursor = conn.cursor()
# 새 스키마로 search_path 변경
cursor.execute("SET search_path TO 'public_initial_migration'")
# 이제 customers 테이블 사용 가능
cursor.execute("INSERT INTO customers (name, bio) VALUES (%s, %s)",
("Alice", "Software Engineer"))
conn.commit()
3. 점진적 전환 전략
class DatabaseManager:
def __init__(self, use_new_schema=False):
self.conn = psycopg2.connect(
host="localhost",
database="testdb",
user="postgres",
password="password"
)
if use_new_schema:
# 환경변수나 피쳐 플래그로 제어
cursor = self.conn.cursor()
cursor.execute("SET search_path TO 'public_initial_migration'")
def get_customer(self, customer_id):
cursor = self.conn.cursor()
cursor.execute("SELECT * FROM customers WHERE id = %s", (customer_id,))
return cursor.fetchone()
고급 마이그레이션 작업
1. 컬럼 추가 마이그레이션
# add_email_column.yaml
operations:
- add_column:
table: customers
column:
name: email
type: varchar(255)
unique: true
nullable: true
default: "'noemail@example.com'"
2. 컬럼 이름 변경 (Breaking Change)
# rename_bio_to_description.yaml
operations:
- rename_column:
table: customers
from: bio
to: description
pgroll은 이런 breaking change에서도 다음과 같은 방식으로 무중단을 보장합니다:
- 새로운
description
컬럼 생성 - 기존
bio
데이터를description
으로 백필 - 트리거 설정으로 양방향 동기화
- 구버전 스키마에서는
bio
, 신버전에서는description
제공
3. 데이터 백필이 필요한 경우
# transform_name_to_uppercase.yaml
operations:
- alter_column:
table: customers
column: name
up: "UPPER(name)" # 신버전으로 변환 함수
down: "LOWER(name)" # 구버전으로 역변환 함수
type: varchar(255)
마이그레이션 완료와 롤백
1. 마이그레이션 완료
모든 애플리케이션이 새로운 스키마로 전환되었다면:
pgroll --postgres-url "postgres://postgres:password@localhost:5432/testdb?sslmode=disable" \
complete
이 과정에서:
- 구버전 스키마 제거
- 불필요한 컬럼/테이블 정리
- 새 스키마가 기본
public
스키마가 됨
2. 롤백 수행
문제가 발생했을 때 즉시 롤백:
pgroll --postgres-url "postgres://postgres:password@localhost:5432/testdb?sslmode=disable" \
rollback
롤백은 즉시 완료되며, 모든 애플리케이션이 자동으로 이전 상태로 복원됩니다.
3. 마이그레이션 상태 확인
# 현재 마이그레이션 상태 확인
pgroll --postgres-url "postgres://postgres:password@localhost:5432/testdb?sslmode=disable" \
status
실무 활용 패턴
1. CI/CD 파이프라인 통합
# .github/workflows/deploy.yml
name: Deploy with pgroll
on:
push:
branches: [main]
jobs:
migrate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install pgroll
run: |
curl -L -o pgroll https://github.com/xataio/pgroll/releases/download/v0.14.1/pgroll.linux.amd64
chmod +x pgroll
sudo mv pgroll /usr/local/bin/
- name: Run migration
run: |
pgroll --postgres-url "$" \
start migrations/$(date +%Y%m%d)_*.yaml
- name: Deploy application
run: |
# 애플리케이션 배포 스크립트
./deploy.sh
- name: Complete migration
run: |
# 충분한 검증 후 완료
sleep 300
pgroll --postgres-url "$" complete
2. 블루-그린 배포와의 결합
#!/bin/bash
# blue-green-pgroll.sh
# 1. 마이그레이션 시작
pgroll start new_feature.yaml
# 2. 그린 환경에 새 스키마로 배포
deploy_green_with_schema "public_new_feature"
# 3. 헬스체크
if health_check_green; then
# 4. 트래픽 전환
switch_traffic_to_green
# 5. 마이그레이션 완료
pgroll complete
else
# 6. 롤백
pgroll rollback
echo "Migration failed, rolled back"
fi
3. 모니터링 및 알림
# monitoring.py
import psycopg2
import logging
class PgrollMonitor:
def __init__(self, db_url):
self.db_url = db_url
def check_migration_status(self):
"""마이그레이션 상태 모니터링"""
conn = psycopg2.connect(self.db_url)
cursor = conn.cursor()
# pgroll 내부 테이블에서 상태 확인
cursor.execute("""
SELECT version_name, started_at, completed_at
FROM pgroll.migrations
ORDER BY started_at DESC
LIMIT 1
""")
result = cursor.fetchone()
if result:
version, started, completed = result
if completed is None:
# 진행 중인 마이그레이션
duration = datetime.now() - started
if duration.total_seconds() > 1800: # 30분 초과
self.send_alert(f"Migration {version} running for {duration}")
def send_alert(self, message):
# Slack, PagerDuty 등으로 알림
logging.error(f"PGROLL ALERT: {message}")
성능 최적화
1. 백필 배치 크기 조정
# 대용량 테이블의 경우 배치 크기와 지연시간 조정
pgroll start large_table_migration.yaml \
--backfill-batch-size 5000 \
--backfill-batch-delay 100ms
2. 인덱스 생성 최적화
# concurrent_index.yaml
operations:
- create_index:
table: customers
name: idx_customers_email
columns: [email]
method: concurrent # 논블로킹 인덱스 생성
3. 파티션 테이블 지원
# partition_migration.yaml
operations:
- create_table:
name: events_2024
inherit_from: events # 기존 파티션 테이블에서 상속
columns:
- name: event_date
type: date
- name: data
type: jsonb
트러블슈팅
1. 일반적인 오류와 해결방법
마이그레이션 파일 형식 오류
Error: reading migration file: json: unknown field "name"
해결방법: JSON 대신 YAML 형식 사용
# ✅ 올바른 YAML 형식
operations:
- create_table:
name: users
# ❌ 잘못된 JSON 형식
{"name": "migration", "operations": [...]}
SSL 연결 오류
Error: pq: SSL is not enabled on the server
해결방법: 연결 문자열에 SSL 비활성화 추가
postgres://user:pass@host:port/db?sslmode=disable
권한 부족 오류
Error: pq: permission denied for schema public
해결방법: 적절한 권한을 가진 사용자 사용
GRANT CREATE ON SCHEMA public TO migration_user;
GRANT USAGE ON SCHEMA public TO migration_user;
2. 백필 성능 문제
대용량 테이블에서 백필이 느린 경우:
# 백필 설정 최적화
pgroll start migration.yaml \
--backfill-batch-size 10000 \
--backfill-batch-delay 50ms
3. 메모리 사용량 모니터링
-- 진행 중인 백필 작업 확인
SELECT
pid,
state,
query,
query_start
FROM pg_stat_activity
WHERE query LIKE '%pgroll%';
보안 고려사항
1. 데이터베이스 권한 관리
-- pgroll 전용 사용자 생성
CREATE USER pgroll_user WITH PASSWORD 'secure_password';
-- 최소 권한 부여
GRANT CONNECT ON DATABASE myapp TO pgroll_user;
GRANT USAGE ON SCHEMA public TO pgroll_user;
GRANT CREATE ON SCHEMA public TO pgroll_user;
-- 특정 테이블에만 권한 부여
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO pgroll_user;
2. 마이그레이션 파일 보안
# 마이그레이션 파일 권한 설정
chmod 600 migrations/*.yaml
# Git에서 민감한 정보 제외
echo "migrations/.env" >> .gitignore
3. 연결 문자열 보안
# 환경변수 사용
export PGROLL_DATABASE_URL="postgres://pgroll_user:password@localhost:5432/myapp"
# pgroll 실행시 환경변수 참조
pgroll --postgres-url "$PGROLL_DATABASE_URL" start migration.yaml
모범 사례
1. 마이그레이션 파일 관리
migrations/
├── 001_initial_schema.yaml
├── 002_add_users_table.yaml
├── 003_add_email_column.yaml
└── rollback/
├── 002_rollback.yaml
└── 003_rollback.yaml
2. 테스트 전략
#!/bin/bash
# test_migration.sh
# 1. 테스트 데이터베이스 생성
createdb test_migration
# 2. 프로덕션 데이터 복사
pg_dump production_db | psql test_migration
# 3. 마이그레이션 테스트
pgroll --postgres-url "postgres://localhost/test_migration" \
start new_migration.yaml
# 4. 애플리케이션 테스트
python test_with_new_schema.py
# 5. 롤백 테스트
pgroll --postgres-url "postgres://localhost/test_migration" rollback
# 6. 정리
dropdb test_migration
3. 문서화 표준
# 마이그레이션 파일 헤더
# Migration: Add user profile features
# Author: dev@company.com
# Date: 2025-08-18
# Dependencies: 002_add_users_table.yaml
# Rollback: Safe (no data loss)
# Estimated time: 5 minutes
operations:
- add_column:
table: users
column:
name: profile_image_url
type: text
nullable: true
실제 운영 사례
1. 대규모 이커머스 플랫폼
# 주문 테이블 파티셔닝 마이그레이션
operations:
- create_table:
name: orders_2024_q1
columns:
- name: id
type: bigserial
pk: true
- name: user_id
type: bigint
- name: created_at
type: timestamp
- name: total_amount
type: decimal(10,2)
partition:
type: range
key: created_at
ranges:
- start: '2024-01-01'
end: '2024-04-01'
2. SaaS 멀티테넌트 시스템
# 테넌트별 데이터 분리
operations:
- add_column:
table: all_user_tables
column:
name: tenant_id
type: uuid
nullable: false
default: "'00000000-0000-0000-0000-000000000000'"
- create_index:
table: users
name: idx_users_tenant_id
columns: [tenant_id]
대안 도구와의 비교
pgroll vs 기존 마이그레이션 도구
특징 | pgroll | Flyway | Liquibase | Prisma Migrate |
---|---|---|---|---|
무중단 마이그레이션 | ✅ | ❌ | ❌ | ❌ |
즉시 롤백 | ✅ | ❌ | ❌ | ❌ |
PostgreSQL 특화 | ✅ | ❌ | ❌ | ❌ |
러닝 커브 | 낮음 | 중간 | 높음 | 낮음 |
엔터프라이즈 지원 | 커뮤니티 | ✅ | ✅ | ❌ |
언제 pgroll을 선택해야 할까?
pgroll 추천 상황:
- PostgreSQL 전용 프로젝트
- 고가용성이 중요한 서비스
- 빈번한 스키마 변경이 필요한 경우
- 롤백 안전성이 중요한 경우
다른 도구 고려 상황:
- 멀티 데이터베이스 환경
- 복잡한 엔터프라이즈 워크플로우
- 기존 도구와의 통합이 중요한 경우
마무리
pgroll은 PostgreSQL 환경에서 진정한 무중단 마이그레이션을 실현하는 혁신적인 도구입니다. expand/contract 패턴과 가상 스키마를 통해 기존 마이그레이션의 한계를 극복하고, 안전하고 빠른 스키마 변경을 가능하게 합니다.
핵심 장점 요약
- 제로 다운타임: 서비스 중단 없는 스키마 변경
- 안전한 롤백: 문제 발생시 즉시 복원
- 점진적 전환: 애플리케이션의 단계적 업그레이드
- 운영 친화적: 간단한 명령어와 명확한 상태 관리
다음 단계
- 학습 심화: pgroll 공식 문서 참고
- 커뮤니티 참여: Xata Discord 가입
- 실무 적용: 테스트 환경에서 점진적 도입
- 모니터링 구축: 운영 환경 적용시 충분한 모니터링 준비
테스트 환경:
- OS: macOS
- PostgreSQL: 15.0
- pgroll: v0.14.1
- Docker: 활용한 격리 환경
- 테스트 시간: 2025-08-18
pgroll을 통해 더 안전하고 효율적인 데이터베이스 운영을 시작해보세요!