⏱️ 예상 읽기 시간: 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 접근법

  1. Expand 단계: 기존 스키마에 새로운 요소 추가
  2. Dual Schema 운영: 구버전과 신버전 동시 서비스
  3. 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에서도 다음과 같은 방식으로 무중단을 보장합니다:

  1. 새로운 description 컬럼 생성
  2. 기존 bio 데이터를 description으로 백필
  3. 트리거 설정으로 양방향 동기화
  4. 구버전 스키마에서는 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 패턴과 가상 스키마를 통해 기존 마이그레이션의 한계를 극복하고, 안전하고 빠른 스키마 변경을 가능하게 합니다.

핵심 장점 요약

  1. 제로 다운타임: 서비스 중단 없는 스키마 변경
  2. 안전한 롤백: 문제 발생시 즉시 복원
  3. 점진적 전환: 애플리케이션의 단계적 업그레이드
  4. 운영 친화적: 간단한 명령어와 명확한 상태 관리

다음 단계

  1. 학습 심화: pgroll 공식 문서 참고
  2. 커뮤니티 참여: Xata Discord 가입
  3. 실무 적용: 테스트 환경에서 점진적 도입
  4. 모니터링 구축: 운영 환경 적용시 충분한 모니터링 준비

테스트 환경:

  • OS: macOS
  • PostgreSQL: 15.0
  • pgroll: v0.14.1
  • Docker: 활용한 격리 환경
  • 테스트 시간: 2025-08-18

pgroll을 통해 더 안전하고 효율적인 데이터베이스 운영을 시작해보세요!


참고 자료