[V2] Postgres 운영환경 DB 무중단 마이그레이션 수행 결과 - 100-hours-a-week/KTB4-15th-CLOUD GitHub Wiki

1. 운영 환경 세팅

세팅 작업 순서

순서 작업 내용
1 운영 사전 점검(완료) 컨테이너 상태, DB 용량, 디스크 여유, 최신 백업, 현재 연결 계정·설정 확인
2 운영 Compose 구성(완료) PgBouncer·pgbench·PostgreSQL 사설 포트를 운영 overlay에 추가
3 운영 자동화 스크립트 작성(완료) 설치·점검·재시작·부하·복제·전환·복구 스크립트 구성
4 DB 공통 계정 준비(완료) EC2와 RDS에서 모두 사용할 lookddak_app 계정과 권한 준비
5 PgBouncer 사전 기동(완료) 아직 FastAPI는 건드리지 않고 PgBouncer→EC2 PostgreSQL 연결 검증
6 FastAPI 연결 전환(완료) FastAPI DB 주소를 PgBouncer로 변경하고 FastAPI만 재기동
7 PostgreSQL 논리 복제 설정(완료) wal_level, Slot·Sender·WAL 보관 한도를 적용하고 PAUSE 상태에서 재시작
8 pgbench·관찰 도구 준비(완료) 소량 쓰기 부하, 복제 상태, 정합성 및 HTTP 요청 관찰 구성
9 정·역방향 복제 구성(완료) EC2→RDS 복제 실행, RDS→EC2 롤백 복제는 비활성 상태로 생성
10 전환 스크립트 사전 검증(완료) 실제 DB 전환은 하지 않고 check 모드와 복구 절차까지만 확인

1. 운영 스크립트 배치(로컬)

scripts/
├── v1/                                             # 기존 V1 배포 공통 영역
│   ├── docker-compose.yaml                         # 기존 기본 Compose — 위치 유지
│   ├── docker-compose.dev.yaml                     # V1 Dev overlay — 위치 유지
│   └── scripts/
│       ├── bootstrap.sh                            # 기존 배포 진입점 — 위치 유지
│       ├── deploy.sh                               # 기존 배포 진입점 — 위치 유지
│       ├── load-parameters.sh                      # Prod 공통 Parameter 로더 — 위치 유지
│       └── load-parameters-dev.sh                  # Dev 공통 Parameter 로더 — 위치 유지
│
└── migration/
    └── postgres/                                   # PostgreSQL 마이그레이션 전용 최상위
        ├── README.md                               # 전체 구조, Dev/Prod 구분, 실행 순서
        │
        ├── rehearsal/                              # Dev에서 수행하는 리허설 전용
        │   ├── README.md                           # 리허설 준비·실행·검증 순서
        │   │
        │   ├── ec2-runtime/                        # Dev EC2에 배치되는 설정 파일
        │   │   └── pgbouncer/
        │   │       ├── pgbouncer.ini               # Dev PgBouncer 연결·Pool 설정
        │   │       ├── pg_hba.conf                 # Dev PgBouncer 인증 규칙
        │   │       ├── pg_ident.conf               # Dev 관리 사용자 매핑
        │   │       └── .gitignore                  # userlist.txt 등 인증 파일 제외
        │   │
        │   ├── automation/                         # Dev 리허설 실행 명령
        │   │   ├── prepare-pgbouncer.sh            # Dev PgBouncer 인증 파일 준비
        │   │   ├── prepare-replication.py          # Dev 논리 복제 사전 구성
        │   │   ├── rehearsal-control.sh            # 부하·상태·복구·종료 통합 제어
        │   │   └── switchover-to-rds.sh            # 검증된 Dev EC2→RDS 전환 스크립트
        │   │
        │   ├── workload/                           # 리허설 중 실행하는 테스트 부하
        │   │   ├── pgbench/
        │   │   │   └── ledger.sql                 # 연속 쓰기 및 유실 검증
        │   │   └── k6/
        │   │       └── read-observer.js            # 전환 중 읽기 요청 관찰
        │   │
        │   ├── sql/                                # Dev 리허설용 SQL
        │   │   ├── data-consistency.sql            # EC2·RDS 데이터 비교
        │   │   ├── replication-status.sql           # 복제 상태 조회
        │   │   └── setup-ec2-app-role.sql           # Dev 공통 앱 계정 준비
        │   │
        │   └── legacy/                             # 이전 리허설 이력, 실제 실행 대상 아님
        │       └── r1-r3/
        │           ├── README.md                   # 이력 보존용이며 실행 금지임을 명시
        │           ├── switchover-to-rds-r1-3.sh   # 기존 R1~R3 전환본
        │           ├── ledger-r1-3.sql             # 기존 R1~R3 부하 SQL
        │           └── data-consistency-r1-3.sql   # 기존 R1~R3 검증 SQL
        │
        └── production/                             # V1 Prod→V2 Prod 실제 마이그레이션
            ├── README.md                           # 운영 실행 순서와 안전 조건
            ├── .gitignore                         # 결과·인증정보·임시 파일 제외
            │
            ├── ec2-runtime/                        # Prod EC2에 배치되는 설정 파일
            │   ├── docker-compose.migration.yaml   # PgBouncer·pgbench·사설 5432 overlay
            │   └── pgbouncer/
            │       ├── ec2.ini                     # V1 EC2 PostgreSQL 연결 설정
            │       ├── rds.ini.template            # V2 RDS 연결 설정 템플릿
            │       ├── pg_hba.conf                 # 운영 PgBouncer 인증 규칙
            │       ├── pg_ident.conf               # 운영 관리 사용자 매핑
            │       └── .gitignore                  # userlist·렌더링 설정 제외
            │
            ├── automation/                         # 운영 작업자가 실행하는 명령
            │   ├── common-functions.sh             # 잠금·로그·SSM·DB 접속 공통 함수
            │   ├── preflight.sh                    # 운영 상태와 최신 백업 점검
            │   ├── backup-postgresql.sh            # PostgreSQL 운영 백업 생성
            │   ├── prepare-runtime.sh              # 계정·PgBouncer 런타임 파일 준비
            │   ├── restart-postgresql-logical.sh   # 논리 복제 설정 적용 및 안전 재시작
            │   ├── pgbench-control.sh              # 운영 저부하 테스트 제어
            │   ├── prepare-replication.py           # Publication·Subscription 준비
            │   ├── start-forward-replication.sh    # EC2→RDS 정방향 복제 시작
            │   ├── replication-status.sh           # 실시간 복제 상태 조회
            │   ├── consistency-check.sh            # EC2·RDS 데이터 정합성 비교
            │   ├── switchover-to-rds.sh            # 실제 writer를 RDS로 전환
            │   └── rollback-to-ec2.sh               # 장애 발생 시 EC2로 복구
            │
            ├── workload/
            │   └── pgbench/
            │       └── ledger.sql                  # 운영 전환 중 연속 쓰기 확인
            │
            └── sql/
                ├── create-migration-test-events.sql # 운영 검증용 장부 테이블
                ├── setup-ec2-app-role.sql           # 운영 공통 앱 계정 구성
                ├── setup-ec2-migration-role.sql     # 운영 논리 복제 계정 구성
                └── data-consistency.sql             # 운영 테이블 정합성 조회

1-1. 운영 k6 자동 로그인 스크립트 추가

scripts/migration/postgres/production/
├── automation/
│   └── k6-read-control.sh                         # k6 이미지 준비·설정 검사·시작·상태 조회
├── workload/
│   └── k6/
│       └── read-observer.js                       # 운영 테스트 계정 자동 로그인 후 10 RPS 읽기 관찰
└── README.md                                      # 자동 로그인 설정·실행 방법
/opt/lookddak/scripts/migration/postgres/production/
├── automation/
│   └── k6-read-control.sh
├── workload/
│   └── k6/
│       └── read-observer.js
└── README.md

/opt/lookddak/k6.prod.credentials.json             # 운영 테스트 계정 인증정보, root:root·0600
  • 기존 운영 테스트 계정 1개를 사용
  • k6 setup() 단계에서 /api/v1/auth/login을 한 번 호출
  • 로그인 응답의 accessToken, refreshToken 쿠키를 읽기 요청에 사용
  • 회원가입이나 신규 테스트 계정 생성 없음
  • 인증정보는 스크립트나 Git에 저장하지 않고 /opt/lookddak/k6.prod.credentials.json에만 저장
  • 인증정보 파일 형식:
{
  "email": "운영 테스트 계정 이메일",
  "password": "운영 테스트 계정 비밀번호"
}
  • k6-read-control.sh check에서 인증정보 형식, 대상 URL, 10 RPS, 10분 실행 설정 확인
  • 운영 전환 중 https://lookddak.com/api/v1/chat-rooms를 10 RPS로 호출해 요청 실패 여부 관찰
  • 로컬 파일과 운영 EC2 파일의 SHA-256 checksum 일치 확인
  • 테스트 종료 후 /opt/lookddak/k6.prod.credentials.json 삭제

2. 운영 스크립트 배치(EC2)

/opt/lookddak/
├── docker-compose.yaml                           # 기존 V1 Prod 기본 Compose
├── docker-compose.postgres-migration.yaml        # 마이그레이션용 운영 overlay
├── pgbouncer.prod/                               # PgBouncer 운영 설정
└── scripts/
    ├── load-parameters.sh                        # Parameter Store 로딩 및 DB 연결정보 구성
    └── migration/
        └── postgres/
            └── prod/                             # 운영 마이그레이션 자동화 스크립트 전체

3. DB 공통 계정 준비 + V2 RDS Schema 세팅

FastAPI
   └─ lookddak_app
        ├─ 현재: V1 EC2 PostgreSQL
        └─ 전환 후: V2 RDS PostgreSQL

논리 복제
   └─ lookddak_migration
        └─ EC2 PostgreSQL ↔ RDS PostgreSQL

관리자
   ├─ V1 EC2: 기존 관리자/소유자 계정
   └─ V2 RDS: lookddak_admin
V1 EC2 PostgreSQL
├── 운영 테이블 7개
├── migration_test_events 신규 생성
├── products_backup_20261015 유지·복제 제외
├── lookddak_app 객체 권한 확인
└── lookddak_migration 객체·Sequence 권한 확인

V2 PostgreSQL RDS
├── 운영 테이블 7개 생성
├── products_id_seq 생성
├── 제약조건·인덱스 생성
├── lookddak_app CRUD·Sequence 권한 부여
├── lookddak_migration SELECT·복제 권한 부여
└── 데이터 0건 유지
  • V1·V2 컬럼 구조 일치
  • 제약조건·인덱스 일치
  • Sequence 구조 일치
  • lookddak_app RDS 로그인 및 CRUD 권한 확인
  • lookddak_migration RDS 로그인·SELECT·rds_replication 권한 확인
  • RDS 데이터 건수 0
  • 논리 복제 시작하지 않음
  • FastAPI·PgBouncer·PostgreSQL 모두 healthy

4. PostgreSQL 논리 복제 설정 및 pgbench 세팅 & 초기 복제

  • 신규 운영 백업 생성:

    • postgresql/prod/20261018T055302Z
    • dump 약 196MB
    • KMS 암호화
    • pg_restore --list 검증 통과
  • V1 PostgreSQL 설정 적용

    • wal_level=logical
    • max_replication_slots=10
    • max_wal_senders=10
    • max_slot_wal_keep_size=2GB
  • 10.20.1.203:5432 사설 IP에만 포트 개방

    • 인터넷 공개 없음
    • RDS 보안그룹과의 연결만 허용
  • EC2→RDS 초기 복사 완료

    • 대상 7개 테이블 모두 ready
    • 검증 행 RDS 도착 확인
    • 최종 WAL 지연 0 bytes
    • apply/sync 오류 0
  • RDS→EC2 롤백 Subscription:

    • 생성 완료
    • 비활성 상태 유지
    • 아직 롤백 Slot과 worker는 시작하지 않음
  • PostgreSQL·PgBouncer·FastAPI·pgbench 모두 healthy

  • FastAPI는 계속 PgBouncer → V1 PostgreSQL을 사용 중

2. 수행 결과

  • 실행일: 2026-10-19
  • 실행 ID: prod-pg-cutover-20261019T060151Z
  • 대상: V1 Prod EC2 PostgreSQL → V2 Prod PostgreSQL RDS
  • 전환 방식: PostgreSQL 논리 복제 + PgBouncer PAUSE/RESUME
  • 최종 판정: 성공

2-1. 작업 목적

V1 Prod EC2에서 실행 중인 FastAPI의 PostgreSQL 연결을 V2 Prod PostgreSQL RDS로 전환했다. 전환 중 실제 운영 읽기 요청과 별도의 테스트 쓰기 부하를 유지해 서비스 요청 실패와 데이터 유실 여부를 확인했다.

전환 후에는 RDS를 새로운 writer로 사용하고, RDS에서 발생한 변경을 기존 EC2 PostgreSQL로 복제해 즉시 롤백 가능한 상태를 유지하는 것을 목표로 했다.

2-2. 전환 전 상태

항목 상태
서비스 DB 경로 FastAPI → PgBouncer → EC2 PostgreSQL
정방향 복제 EC2 → RDS 활성화
역방향 복제 RDS → EC2 비활성 상태로 준비
정방향 복제 지연 0 byte
복제 오류 0건
장기 트랜잭션 0건
PostgreSQL 설정 wal_level=logical, slot/sender 각 10개
주요 컨테이너 모두 Healthy
EC2 디스크 사용량 46%

전환 직전에 운영 PostgreSQL 백업을 새로 생성했다.

백업 항목 결과
S3 객체 수 3개
전체 크기 196,156,289 bytes
업로드 검증 성공
백업 결과 BACKUP_SUCCEEDED

2-3. 작업 절차

  1. 운영 EC2에 SSM Session Manager로 접속
  2. 운영 테스트 계정을 이용한 k6 자동 로그인 설정 검증
  3. 운영 PostgreSQL 최신 백업 생성 및 S3 업로드 검증
  4. 컨테이너, 디스크, PostgreSQL 설정, DB 연결 경로 사전 점검
  5. EC2 → RDS 정방향 논리 복제 상태와 지연 확인
  6. k6 읽기 관찰 부하 10 RPS 시작
  7. pgbench 테스트 쓰기 부하 30 TPS 시작
  8. PgBouncer PAUSE
  9. EC2의 마지막 WAL이 RDS에 반영된 것을 확인
  10. 정방향 복제를 중지하고 RDS → EC2 역방향 복제 활성화
  11. PgBouncer 연결 대상을 RDS로 변경
  12. PgBouncer RESUME
  13. FastAPI 요청, pgbench 쓰기, 역방향 복제 및 롤백 준비 상태 검증

2-4. 스위치오버 결과

전환 자동화는 정상적으로 완료됐다.

SWITCHOVER_SUCCEEDED target_lsn=0/13D65548 pause_seconds=2 forward=false reverse=true
확인 항목 결과
PgBouncer PAUSE 2초
PgBouncer 전환 대상 V2 Prod PostgreSQL RDS
FastAPI DB 접속 경로 FastAPI → PgBouncer → RDS
EC2 → RDS 정방향 Subscription 비활성화
RDS → EC2 역방향 Subscription 활성화
역방향 복제 Slot 활성화
복제 적용 오류 0건
복제 동기화 오류 0건
롤백 자동화 사전 점검 통과

전환 직후에도 PostgreSQL, FastAPI, PgBouncer, pgbench 컨테이너는 모두 Healthy 상태를 유지했다.

2-5. 서비스 읽기 검증

k6는 기존 운영 테스트 계정으로 한 번 자동 로그인한 뒤 인증 쿠키를 사용해 읽기 API를 10분 동안 호출했다.

지표 결과
실행 시간 10분
목표 요청률 10 RPS
읽기 요청 6,000건
전체 HTTP 요청 6,001건
실패 요청 0건
서버 오류 0건
전송 오류 0건
중단된 반복 0건
누락된 반복 0건
평균 응답 시간 10.62ms
p95 응답 시간 14.83ms
p99 응답 시간 63.05ms
최대 응답 시간 727.22ms

PgBouncer 전환 전후를 포함한 전체 실행에서 HTTP 실패가 발생하지 않았다.

2-6. 최종 구성

운영 사용자 요청
    → V1 Prod FastAPI
    → PgBouncer
    → V2 Prod PostgreSQL RDS (현재 writer)
    → 논리 복제
    → V1 Prod EC2 PostgreSQL (롤백 대기)
구성 요소 최종 상태
현재 writer V2 Prod PostgreSQL RDS
V1 Prod FastAPI PgBouncer를 통해 RDS 사용
V2 Prod FastAPI ECS 동일 RDS를 사용하도록 구성
정방향 복제 중지
역방향 복제 활성 상태 유지
기존 EC2 PostgreSQL 롤백 대상 DB로 유지
DNS 이번 DB 전환에서 변경하지 않음

3. 결론

운영 PostgreSQL의 EC2 → RDS 스위치오버가 정상 완료됐다. 실제 운영 읽기 요청 6,000건과 테스트 쓰기 2,981건이 전환 전후에 실패 없이 처리됐으며, PgBouncer PAUSE는 2초였다. 테스트 이벤트 수가 EC2와 RDS에서 일치했고 역방향 복제와 롤백 절차도 준비된 상태다.

따라서 현재 V2 Prod PostgreSQL RDS를 운영 writer로 사용하는 상태이며, 기존 EC2 PostgreSQL은 역방향 복제를 받는 롤백 대기 환경으로 유지한다.