[V2] DB 무중단 마이그레이션 설계 - 100-hours-a-week/KTB4-15th-CLOUD GitHub Wiki
DB 무중단 마이그레이션 설계 (초안)
| 구분 | V1 출발점 | V2 도착점 |
|---|---|---|
| MySQL | RDS MySQL db.t4g.micro |
Multi-AZ RDS MySQL db.t4g.medium |
| PostgreSQL | EC2 내부 PostgreSQL 컨테이너 | Multi-AZ RDS PostgreSQL db.t4g.small |
- DB Migration — V1 데이터를 V2 RDS로 안전하게 옮기고 전환
- 고가용성 검증 — V2 RDS 장애 조치 후 애플리케이션이 다시 연결되는지 확인
설계 순서
- MySQL 마이그레이션 설계
- RDS Blue/Green 방식 결정 (엔진은 8.4.11로 동일 — 사양·Multi-AZ만 변경)
- GTID 적용으로 롤백(역방향 복제) 단순화
- 복제 지연, 전환, 롤백 검증
- PostgreSQL 마이그레이션 설계
- 컨테이너 PostgreSQL 버전·확장·스키마 확인
- 네이티브 논리 복제 + PgBouncer 방식 결정
- 쓰기 중단 범위와 전환 방법 결정
- V2 Multi-AZ 검증
- 두 RDS 모두 애플리케이션 연결 확인
- 강제 Failover 실행
- Spring/FastAPI/Crawler의 재연결 및 연결 풀 회복 확인
개념정리
무중단 Data Migration의 핵심 원리
Step1. 기존 데이터 1차 복사(Initial Load) — 마이그레이션 시작 시 저장되어 있던 과거 데이터를 신규 DB로 전체 복사
Step2. 실시간 변경 내역 감지 및 동기화 — DB 트랜잭션 로그를 실시간으로 감지해, 1차 복사 이후 새로 발생한 변경 데이터를 계속 반영
Step3. 정합성 검증 및 즉각적인 switch over — 정합성 확보 시, 트래픽의 연결 지점을 신규 시스템으로 순간 전환
세 가지 기술적 요건
V1 DB는 계속 운영
↓
기존 데이터를 V2 DB로 전체 복사
↓
복사 중 발생한 변경분도 계속 동기화
↓
V1과 V2의 차이가 거의 없어짐
↓
잠시 쓰기 중단
↓
마지막 변경분 반영
↓
애플리케이션을 V2 DB에 연결
- Blue/Green
현재 운영 환경과 새 환경을 동시에 준비한 뒤, 새 환경의 검증이 끝나면 트래픽을 전환하는 방식
구분 의미 현재 프로젝트 Blue 현재 사용 중인 운영 환경 V1 DB Green 새로 구축하고 검증할 환경 V2 RDS Switchover Green을 새로운 운영 환경으로 만드는 전환 애플리케이션 DB 연결을 V2로 전환
Blue는 계속 사용자 요청을 처리하고, Green은 Blue의 데이터를 따라가면서 새 사양과 애플리케이션 호환성을 테스트한다.
- 복제 & CDC 복제 — 한 DB의 데이터를 다른 DB로 계속 복사해서 두 DB의 상태를 비슷하게 유지하는 것
V1 DB에 데이터 입력
│
▼
복제 도구가 변경 내용 전달
│
▼
V2 DB에도 동일한 내용 반영
CDC(Change Data Capture) — 전체 테이블을 계속 복사하는 것이 아니라, 최초 복사가 끝난 후 발생한 변경 사항만 찾아서 V2에 적용
- Replication Lag V1에서 발생한 변경이 V2에 아직 반영되지 않은 차이를 의미한다.
10:00:00 V1에 주문 생성
10:00:03 V2에 주문 반영
이 경우 복제 지연은 약 3초다. V1의 쓰기가 너무 많거나, V2 인스턴스 성능이 부족하거나, 네트워크가 느리면 발생한다.
Switchover 전에 Replication Lag이 충분히 줄어든 것을 확인하지 않으면 전환 중 데이터가 누락될 수 있다. 그래서 보통 다음 순서로 진행한다.
1. V1의 새로운 쓰기를 잠시 중단
2. 남아 있는 변경분이 V2에 모두 반영되기를 기다림
3. Replication Lag이 0 또는 허용 기준 이하인지 확인
4. 데이터 검증 후 V2로 전환
- Switchover와 Rollback Switchover — 기존 V1 DB를 사용하던 애플리케이션이 V2 DB를 사용하도록 계획적으로 전환하는 작업
1. V1 쓰기 중단
2. Replication Lag 0 확인
3. V1과 V2 데이터 검증
4. 애플리케이션 DB 주소를 V2 엔드포인트로 변경
5. 애플리케이션 재연결
6. 기능 검증
7. V2 쓰기 재개
**Rollback** — Switchover 후 문제가 발견됐을 때 다시 V1으로 돌아가는 계획
V2에 쓰기가 일어나기 전이라면 다시 V1으로 연결하면 된다. 하지만 V2에 이미 쓰기가 발생했다면 그 데이터를 V1에도 반영해야 한다. 따라서 다음을 정해 두어야 한다.
- V2 쓰기를 언제부터 허용할 것인지
- 전환 후 검증 시간을 얼마나 둘 것인지
- 어떤 문제가 발생하면 되돌릴 것인지
- V2에서 발생한 데이터를 V1으로 어떻게 되돌릴 것인지
- 되돌리기 어려우면 V2에서 문제를 수정할 것인지
- V1을 언제까지 삭제하지 않고 보존할 것인지
참고자료
- https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/UserGuide/blue-green-deployments-overview.html
- https://aws.amazon.com/ko/blogs/tech/lg-electronics-blue-green-deployments-in-amazon-aurora-and-amazon-rds/
- https://aws.amazon.com/ko/blogs/tech/amazon-aurora-postgres-blue-green-with-dms/
- https://www.samsungsds.com/kr/insights/migration_cdc.html
- https://sbtglobal.tistory.com/97
작업 순서
| 단계 | 작업 | 핵심 내용 | 사용자 트래픽 |
|---|---|---|---|
| 1. 현황 조사·목표 확정 | 현재 DB와 연결 프로그램 조사 | MySQL/PostgreSQL 버전, 용량, 스키마, 모든 Writer, 목표 사양, 허용 중단 시간 확정 | V1 |
| 2. V1 Dev 구축 | 기존 환경을 연습용으로 복제 | Dev VPC의 EC2에 V1 전체 Docker Compose를 구성하고, 운영 스냅샷으로 만든 Dev MySQL RDS와 EC2 PostgreSQL 컨테이너 연결 | V1 |
| 3. V2 Dev 구축 | 새로운 목적지 환경 준비 | 같은 Dev VPC에 Dev ECS, ALB, MySQL RDS, PostgreSQL RDS+pgvector 구축 | V1 |
| 4. Dev 마이그레이션 리허설 | 실제 이전 절차 반복 테스트 | MySQL GTID 활성화·Blue/Green 전환, PostgreSQL 복제·연결 변경·롤백 연습 (합성 데이터 확대 + k6 부하) | V1 |
| 5. 운영 절차 확정 | 검증·전환·롤백 Runbook 작성 | 소요 시간, 검증 쿼리, Writer 중단 방법, 실패 조건, 담당자와 명령어 확정 | V1 |
| 6. V2 Prod 사전 구축 | 운영용 목적지 준비 | Prod ECS·ALB·PostgreSQL RDS를 만들고 외부 트래픽 없이 내부 테스트 (운영 DB에 테스트 데이터 쓰기 금지) | V1 |
| 6-1. MySQL GTID 활성화 | 운영 MySQL 정비 | 경고 모드로 하루 확인 → GTID ON + 재부팅 (트래픽 최저 시간, 사전 공지) | V1 (짧은 중단) |
| 7. MySQL Blue/Green 전환 | MySQL 사양 변경 | Green 생성(t4g.medium·Multi-AZ, GTID 동일) → Switchover → GTID로 역복제 연결 | V1 |
| 8. PostgreSQL 복제·전환 | EC2 PostgreSQL을 RDS로 이전 | 초기 복사 → 실시간 동기화 → PgBouncer PAUSE → 지연 0·시퀀스 맞춤 → RDS로 연결 변경 → RESUME | V1 |
| 9. V2 트래픽 전환·안정화 | 애플리케이션을 V2로 점진 전환 | ALB 가중치를 V1에서 V2로 이동하고, 모니터링 후 V1 DB·서버를 나중에 정리 | V1 → V2 |
리허설 공통 방식 (Dev)
1. 환경
- Dev VPC = V1 dev + V2 dev / Prod VPC = V1 prod + V2 prod
- 리허설은 Dev VPC에서 하고, 합격한 절차만 Prod VPC에서 그대로 실행한다.
- Dev VPC의 CIDR은 운영과 겹치지 않게 잡는다 (CIDR이 겹치면 나중에 VPC 피어링이 불가능).
2. 데이터 확대
운영 데이터는 작아서(PostgreSQL 43MB, 상품 3,000건) 그대로 리허설하면 초기 복사 시간·복제 지연 같은 문제가 드러나지 않는다. 따라서 Dev에서만 합성 데이터로 규모를 키운다. 운영에는 합성 데이터를 절대 넣지 않는다.
3. k6 부하 주입
k6는 트래픽을 주는 것 자체가 목적이 아니라, 전환 순간에 무슨 일이 일어났는지를 숫자로 재는 계측기다.
| 목적 | 방법 |
|---|---|
| 끊김을 숫자로 만들기 | 일정 간격으로 요청을 보내고, 요청마다 시각·성공 여부·지연을 기록 |
| 복제 지연이 생길 조건 만들기 | 운영보다 높은 쓰기량을 보냄 |
| 위험한 경로 지나가기 | MySQL: 찜 쓰기, AI 결과 비동기 저장 / PostgreSQL: 채팅, 대화 이어가기, 사용량 기록, 벡터 검색 |
| 유실·중복 확인 장부 | 쓰기 요청마다 고유 ID를 붙이고, 성공한 ID를 전환 후 DB와 대조 |
| 연결 풀 회복 시간 재기 | 전환 직후 지연이 튀는 구간의 길이를 잼 |
설정
- 고정 도착률(constant-arrival-rate)로 보낸다. 응답이 느려져도 정해진 속도로 요청을 보내므로, DB가 끊긴 순간에도 요청이 들어가 장애가 작게 측정되지 않는다.
- 시간: 전환 전 10분 → 전환 → 전환 후 10분
- AI 호출은 stub으로 대체한다. MySQL 리허설은 FastAPI 자체를 stub으로 교체하고, PostgreSQL 리허설은 FastAPI를 진짜로 띄우되 OpenAI·Runware만 stub으로 바꾼다.
1. MySQL Migration(RDS→RDS) 설계
1-1. MySQL 현황
| 항목 | V1 운영 MySQL 현황 |
|---|---|
| 서비스 | Amazon RDS for MySQL |
| 엔진 버전 | MySQL 8.4.11 |
| 인스턴스 클래스 | db.t4g.micro |
| 가용성 구성 | Single-AZ |
| 스토리지 | gp3 20GiB |
| 자동 백업 | 활성화, 보존 기간 7일 |
| 마스터 비밀번호 관리 | Secrets Manager 미사용 |
| 읽기 복제본 | 없음 |
| RDS Proxy | 없음 |
| 주요 사용 주체 | Spring 애플리케이션 |
| 조사 시점 부하 | 전반적으로 낮은 수준 |
1-2. 마이그레이션 방식
다음 다섯 가지 방식을 비교했다.
| 층 | 방식 | 해결하는 문제 |
|---|---|---|
| 인프라 (DB를 통째로 복제·교체) | Blue/Green, CDC 복제 | 인스턴스 사양, 엔진, 위치 변경 |
| 애플리케이션 (코드가 두 저장소나 두 스키마를 다룸) | Dual Write, Shadow, Expand/Contract | 스키마·데이터 모델 변경 |
a. RDS Blue/Green
AWS는 RDS 간 Blue/Green 전환을 서비스로 지원한다. 따라서 V1 RDS MySQL → V2 RDS MySQL에 적용할 수 있다. (전체 단계는 1-3 참고)
동작 방식
- 현재 DB를 Blue로 지정
- AWS가 Green DB 생성
- Blue의 snapshot과 설정을 기반으로 생성
- 미리 만든 RDS를 Green으로 지정하는 것이 아니라, AWS가 새로 만든다.
- Green을 V2 사양으로 변경
- Blue가 운영되는 동안 변경 사항이 Green으로 계속 복제되고, 그동안 Green을 검사
- Switchover
1. Blue와 Green 상태 점검
2. 양쪽의 신규 쓰기 잠시 중단
3. 기존 DB 연결 종료
4. Blue의 마지막 변경까지 Green에 반영
5. Blue와 Green의 이름·엔드포인트 교환
6. 새 Green을 운영 DB로 승격
7. 연결과 쓰기 다시 허용
장점
-
Green이 Blue의 엔드포인트 이름을 넘겨받기 때문에 애플리케이션을 고칠 필요가 없다 (Parameter Store DB 주소 변경 불필요). 단점
-
다운타임이 보통 1분 미만으로 짧지만, 완전한 무중단은 아니다.
-
자동 롤백 기능이 없으므로, 역방향 복제 같은 별도 복구 계획이 필요하다. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-green-deployments-overview.html https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/UserGuide/blue-green-deployments-switching.html
b. CDC
기존 데이터를 통째로 복사하는 동안 새로 생긴 변경 사항을 따로 기록해 뒀다가, 전체 복사가 끝나면 그 변경 사항까지 차례로 적용하는 방식이다. INSERT, UPDATE, DELETE 같은 변경을 DB의 변경 기록에서 읽어 V2에 반영한다.
| DB | 변경 기록 |
|---|---|
| MySQL | Binlog |
| PostgreSQL | WAL |
절차
- Full Load — V1 데이터를 V2로 전체 복사
- 복사 중 변경 사항 보관 — Binlog나 WAL에서 변경 사항을 읽어 임시 보관
- 지속적 변경 복제 — 이후 새 변경 사항을 계속 V2에 적용 (약간의 시간 차이가 생기며, 이것이 Replication Lag) https://docs.aws.amazon.com/ko_kr/dms/latest/userguide/CHAP_Introduction.HighLevelView.html
CDC 도구
- AWS DMS — AWS가 운영하는 데이터 복사·CDC 서비스
모드 동작 Full Load 기존 데이터만 한 번 복사 CDC only 특정 시점 이후 변경분만 복제 Full Load + CDC 기존 데이터 복사 후 지속 복제 https://docs.aws.amazon.com/ko_kr/dms/latest/userguide/CHAP_Tasks.Creating.html - Debezium — DB 변경을 이벤트로 바꿔 실시간으로 내보내는 오픈소스 CDC 플랫폼. 보통 Kafka와 함께 쓰며, 대상 DB에 반영하려면 별도 Sink가 필요하다. https://debezium.io/documentation/reference/stable/connectors/mysql.html
| 항목 | AWS DMS | Debezium |
|---|---|---|
| 주요 목적 | DB 마이그레이션 | 변경 이벤트 스트리밍 |
| 운영 주체 | AWS 관리형 | 직접 운영 |
| Kafka 필요 | 없음 | 일반적으로 필요 |
| 구축 난이도 | 비교적 낮음 | 높음 |
| 일회성 마이그레이션 | 적합 | 다소 무거움 |
RDS Blue/Green vs DMS CDC
| 항목 | RDS Blue/Green | DMS CDC |
|---|---|---|
| 대상 DB 생성 | RDS가 Green 생성 | 사용자가 미리 생성 |
| 데이터 동기화 | RDS가 자동 구성 | DMS Task 구성 |
| 엔드포인트 전환 | RDS가 이름 교환 | 직접 Parameter 변경 |
| EC2 PostgreSQL → RDS | 사용 불가 | 가능 |
| RDS MySQL → RDS MySQL | 적합 | 가능 |
c. RDS Modify
기존 RDS 설정을 직접 수정하는 방식이다. 새 DB나 복제를 만들지 않아 비용은 아끼지만, 바뀐 환경을 미리 테스트할 수 없고 운영 DB를 바로 수정해야 하는 위험이 있다.
d. Dual Write
애플리케이션이 저장할 때마다 기존 DB와 새 DB에 모두 쓰는 방식이다. Spring 등의 코드 수정과 장애 복구 로직이 필요하며, 두 DB를 오래 함께 운영하거나 스키마가 크게 달라질 때 효과적이다.
- 두 DB가 달라졌을 때 대조·보정 작업이 필요하다.
- DB에 쓰는 모든 프로그램을 찾아서 수정해야 한다.
e. Shadow DB
운영 DB와 동기화된 사본에 실제 읽기 요청도 함께 보내서, 새 DB가 같은 결과를 내는지 확인하는 방식이다.
사용자 요청
├─ 기존 DB 조회 → 사용자에게 응답
└─ 새 DB 조회 → 결과와 성능만 기록
새 DB의 결과는 사용자에게 보여주지 않으므로 문제가 생겨도 서비스에 영향이 없다.
RDS Blue/Green 채택
-
문제 상황과 맞는다 이번에 바뀌는 것은 인스턴스 사양과 가용성(Multi-AZ)뿐이다. 엔진은 8.4.11로 같다. Green은 만들어진 뒤 추가로 수정할 수 있고, Multi-AZ로 바꾸는 것도 지원한다. 전환 시 Green이 기존 엔드포인트 이름을 넘겨받으므로 애플리케이션 설정을 바꿀 필요가 없다. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-green-deployments-switching.html https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-green-deployments-creating.html
-
스키마 변경이 잦아도 Dual Write까지는 필요하지 않다 Blue/Green으로 전환한 뒤에도 V1과 V2는 MySQL 하나를 같이 쓴다. 그래서 스키마 변경은 "DB 두 개를 맞추는 문제"가 아니라 "한 DB 안에서 구버전 코드와 신버전 코드가 함께 살 수 있게 바꾸는 문제"이며, Expand/Contract 패턴으로 해결한다. V2의 찜·랭킹 등은 기존 테이블 변경보다 새 테이블 추가가 중심이라 Blue/Green과 충돌하지 않는다. DMS CDC도 가능하지만, 같은 MySQL의 사양 업그레이드에 앱 접속 주소를 직접 바꾸는 작업까지 더할 이유가 없다고 판단했다.
MySQL 인프라 이전은 Blue/Green으로 수행하고, 애플리케이션 스키마 변경은 Expand/Contract 방식으로 별도 관리한다.
GTID 적용
GTID는 모든 트랜잭션에 고유 번호를 붙이는 방식이다. 복제할 때 "binlog 파일 몇 번, 위치 몇 번"을 사람이 지정하지 않고, DB가 "이미 가진 트랜잭션"을 비교해 스스로 이어 받는다.
적용 이유
- 역복제(롤백 경로)를 binlog 위치 대신 자동 위치 지정으로 연결한다
- 두 DB가 같은지 GTID 목록 비교로 확인한다 - "데이터 유실 0"을 증명하기 쉽다.
규칙
- Blue와 Green의 GTID 설정을 반드시 똑같이 유지한다. 한쪽만 켜면 역복제가 실패해 롤백 경로가 사라진다. https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/mysql-replication-gtid.html https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/blue-green-deployments-best-practices.html
1-3. 작업 순서
0. GTID 활성화 (별도 정비일, 재부팅)
1. V1 RDS MySQL을 Blue로 선택
2. AWS가 Green DB 생성
3. Blue → Green 자동 복제 시작
4. Green을 db.t4g.medium으로 변경
5. Green을 Multi-AZ DB 인스턴스로 변경
6. Green 전용 Parameter Group 적용 (GTID 설정 Blue와 동일)
7. 데이터·성능·Spring 호환성 검증
8. ReplicaLag이 거의 0인지 확인
9. Switchover
10. 기존 엔드포인트로 새 DB 연결
11. Spring 재연결과 핵심 기능 검증
12. GTID 자동 위치로 역복제 연결 (새 DB → old1)
13. 롤백 기간 동안 old1 유지
1. 사전 준비
-
GTID 활성화 (별도 정비일)
enforce_gtid_consistency = WARN→ 평소처럼 운영하며 하루 동안 경고 로그 확인gtid_mode = ON_PERMISSIVE,enforce_gtid_consistency = ONgtid_mode = ON→ 재부팅
- 1단계에서 경고가 나오면 다음 단계로 가지 않고, 앱을 고친 뒤 다시 시도한다.
-
Green 전용 Parameter Group에도
gtid_mode = ON,enforce_gtid_consistency = ON설정 -
V1이 기본 Parameter Group을 쓰는지 확인 — 쓴다면 사용자 정의 그룹으로 바꾸는 것도 재부팅이 필요하므로 GTID 재부팅과 함께 처리
-
수동 스냅샷 생성 (출발점 snapshot)
-
binlog 보존 기간 설정 — 48시간 (롤백 보장 기간 24시간)
-
Green 전용 Parameter Group 생성 — 롤백과 튜닝 작업이 서로 영향을 주지 않게 하기 위함
-
JVM DNS 캐시 TTL을 5초 이하로 조정 — 전환 후 앱이 이전 DB에 쓰지 않게 하기 위함
-
역방향 복제를 위한 보안 그룹 설정 — 두 DB가 3306으로 서로 통신할 수 있어야 함
DB Security Group → 동일 DB Security Group
Inbound TCP 3306
Outbound TCP 3306
- Terraform Provider와 State 복구 가능 여부 확인 https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/mysql-replication-gtid.configuring-existing-read-replicas.html
2. V1 Dev 환경에서 리허설
운영 스냅샷 복원 → V1 Dev MySQL (t4g.micro, Single-AZ, 8.4.11)
│ GTID 활성화 → Blue/Green
▼
V2 Dev MySQL (t4g.medium, Multi-AZ)
1-3의 0~13 과정을 Dev 서버 간에 모두 수행하며 다음을 확인한다.
- V1 Spring이 Green으로 정상 접속하는가
- 전환할 때 요청 오류가 몇 초 동안 발생하는가
- HikariCP 연결이 자동으로 다시 만들어지는가
- 비동기 작업이 유실되지 않는가
- GTID 자동 위치로 역방향 복제를 구성할 수 있는가
- 실제 롤백이 가능한가
- 장기 트랜잭션이 있으면 전환이 어떻게 실패하는가
| 회차 | 내용 | 확인할 것 |
|---|---|---|
| R0 | GTID 활성화 | 재부팅 횟수와 끊긴 시간, Spring 자동 재연결 |
| R1 | 기본 전환 | 전체 절차와 단계별 소요 시간 |
| R2 | 쓰기 부하를 걸면서 전환 | 쓰기 차단 시간, 실패 요청 수, Spring 회복 시간 |
| R2-확장 | 확대 데이터 + k6 부하 단계적 증가 | 부하 단계별 쓰기 차단 시간, 복제 지연 |
| R3 | 롤백 드릴 | 역방향 복제(GTID 자동 위치) → 옛 DB로 되돌리기 |
| R4 | 실패 주입 | 전환 중 긴 트랜잭션을 걸었을 때 가드레일과 타임아웃이 자동 원복하는지 |
규칙
- V1 Dev는 운영의 복제본이며, 리허설 후 삭제한다.
- 데이터 확대와 k6 부하는 「리허설 공통 방식」을 따른다.
- AI 호출은 stub으로 대체한다 (정상 응답 / 지연 / 실패 응답 선택 가능). FastAPI 컨테이너를 stub으로 교체하며, 원본 docker-compose는 그대로 두고 덮어쓰기 파일을 사용한다.
3. 운영 Blue/Green 생성
Blue
- db.t4g.micro
- Single-AZ
- MySQL 8.4.11
- GTID ON
Green
- db.t4g.medium
- Multi-AZ
- MySQL 8.4.11 (같은 버전)
- GTID ON (Blue와 동일)
- Green 전용 Parameter Group
4. Green 워밍업 및 검증
- 로그인·사용자 조회
- 상품 목록과 상세 조회
- 채팅 및 피팅 결과 조회
- 주요 인덱스를 사용하는 검색
- 자주 사용하는 JOIN과 집계 쿼리 | 검증 | 확인 내용 | | --- | --- | | 데이터 | 행 수, PK 범위, 핵심 집계, 샘플 데이터 | | 구조 | 테이블, 컬럼, 타입, 인덱스, 제약조건 | | 애플리케이션 | 로그인, 상품, 채팅, 피팅 API 및 쿼리 시간 |
5. 운영 Switchover
전환 직전에는 새 비동기 작업이 시작되지 않도록 막고, 이미 실행 중인 작업이 끝날 때까지 기다린다.
현재 AI 채팅·피팅 결과는 비동기로 처리되어 MySQL에 기록되며, 작업을 최대 120초간 기다리도록 설정되어 있다. 그런데 컨테이너 종료 유예 시간은 30초이므로, 다음 전환 조건을 둔다.
- 새로운 AI 요청 접수 중단
- 실행 중인 AI 작업 0개
- 내부 큐 대기 작업 0개
- 장기 DB 트랜잭션 없음
- Green replication lag 0
- RDS pending modification 없음
- Blue와 Green 모두
available
6. 전환 직후 검증 및 역방향 복제
전환이 끝나면 새 DB가 운영 DB가 되고, 기존 DB는 -old1 같은 이름으로 바뀌며 읽기 전용이 된다. 전환 후 생길 문제와 롤백에 대비해 역방향 복제를 건다.
역방향 복제는 GTID 자동 위치 지정(mysql.rds_set_external_source_with_auto_position)으로 연결한다. 전환 이벤트에서 binlog 파일·위치를 찾아 옮겨 적을 필요가 없다.
롤백 절차
전환 후 24시간은 다음 상태를 유지한다.
- 옛 DB 삭제 금지
- 역방향 복제 유지
- 파괴적인 스키마 변경 금지
- Terraform Apply 제한
- 새 DB 애플리케이션·DB 지표 모니터링 순서
- 새 요청과 AI 비동기 작업을 중단
- 새 DB와 old1의 GTID 목록(
gtid_executed)이 같은지 확인 - 역방향 복제를 중단
- 옛 DB를 쓰기 가능한 상태로 변경 — 필요하면 reboot
- Parameter Store의 RDS host를 옛 endpoint로 변경
- Spring 재시작
- API와 데이터 검증
2. Postgres Migration(container → RDS) 설계
2-1. Postgres 현황
| 항목 | 현재 운영 환경 |
|---|---|
| EC2 이름 | lookddak-app-v1 |
| EC2 사양 | t4g.medium, ARM64 |
| 운영체제 | Ubuntu 24.04 |
| 가용 영역 | ap-northeast-2a |
| PostgreSQL 실행 방식 | Docker 컨테이너 |
| PostgreSQL 데이터 위치 | /srv/lookddak/postgresql |
| 데이터 저장 디스크 | EC2 Root EBS gp3 |
| EBS 용량 | 30GiB |
| 데이터 백업 | 검증된 DB 백업 없음 |
| EBS Snapshot | 없음 |
| AWS Backup | 없음 |
| 항목 | 현재 설정 |
|---|---|
| 컨테이너 이름 | lookddak-postgresql |
| PostgreSQL 버전 | 16.15 |
| 컨테이너 이미지 | pgvector/pgvector:0.8.6-pg16-bookworm |
| pgvector 버전 | 0.8.6 |
| CPU 제한 | 1 vCPU |
| 메모리 제한 | 512MiB |
| 최대 연결 수 | 100 |
| SSL | 비활성화 |
| 포트 공개 | EC2 외부에 공개되지 않음 |
| 접근 주체 | 같은 Docker Network의 FastAPI |
주요 테이블
| 테이블 | 용도 | 데이터 |
|---|---|---|
products |
상품 정보 및 임베딩 | 3,000건 |
usage_amount |
AI 사용량 기록 | 11건 |
checkpoints |
LangGraph 대화 상태 | 2,387건 |
checkpoint_blobs |
LangGraph 체크포인트 데이터 | 3,022건 |
checkpoint_writes |
LangGraph 중간 쓰기 | 6,438건 |
checkpoint_migrations |
LangGraph 스키마 버전 | 10건 |
2-2. 마이그레이션 방식
PostgreSQL 네이티브 논리 복제 + PgBouncer 전환 방식을 채택한다.
- 논리 복제: 기존 컨테이너 PostgreSQL의 데이터를 RDS로 실시간 전달 — 데이터 이동 담당
- PgBouncer: 전환 순간 PostgreSQL 쿼리를 잠시 대기시키고 접속 대상을 RDS로 교체 — 연결 전환 담당 PostgreSQL 논리 복제 동작 방식
INSERT·UPDATE·DELETE 발생
↓
PostgreSQL WAL에 변경 기록
↓
Publication이 변경사항 발행
↓
Subscription이 변경사항 수신
↓
RDS PostgreSQL에 적용
논리 복제는 처음에 기존 데이터를 전체 복사하고, 이후 발생하는 변경을 WAL에서 읽어 계속 전달한다.
https://www.postgresql.org/docs/16/logical-replication-architecture.html
PgBouncer는 PostgreSQL용 경량 오픈소스 커넥션 풀러다. FastAPI는 항상 PgBouncer만 바라보게 하고, 뒤쪽 목적지만 바꾼다.
전환 전
FastAPI ──▶ PgBouncer ──▶ EC2 PostgreSQL 컨테이너
전환 후
FastAPI ──▶ PgBouncer ──▶ RDS PostgreSQL
PAUSE: PostgreSQL을 사용하는 요청을 잠시 대기시킨다.- 진행 중인 트랜잭션: 강제로 끊지 않고 완료될 때까지 기다린다.
RELOAD: PgBouncer의 목적지를 기존 DB에서 RDS로 변경한다.RESUME: 대기 중이던 쿼리를 RDS에서 처리한다. https://www.pgbouncer.org/usage.html
다음 조건을 모두 만족하면 최종 채택한다.
- LangGraph를 포함한 전체 기능이 트랜잭션 풀링에서 정상 동작하는가
- 가장 긴 트랜잭션이 있어도 PAUSE가 10초(리허설 후 재조정) 이내에 끝나는가
- PAUSE 중 들어온 요청이 실패하지 않고 RESUME 후 정상 완료되는가
후보 비교
데이터 이전 후보
| 방식 | 판단 | 이유 |
|---|---|---|
| 네이티브 논리 복제 | ◎ | 출발지·도착지 모두 PostgreSQL(16.15)이라 내장 복제 기능으로 충분. 정방향과 역방향(롤백)을 같은 도구로 구성 가능 |
| DMS Homogeneous | ○ | 관리형이지만 도착지가 RDS·Aurora만 가능해 롤백 방향 복제를 할 수 없음. 실행 시 도착지 DB 객체를 지우고 다시 만듦 |
| DMS 일반 CDC | △ | pgvector 같은 사용자 정의 타입을 반드시 별도 검증해야 함 |
| Dump/Restore | ✕ 무중단 목표와 불일치 | Dump·Restore·검증 중 쓰기 중단 필요 |
| 앱 Dual Write | ✕ | LangGraph 저장 로직까지 양쪽 DB에 쓰도록 수정해야 함 |
- Dump/Restore — DB가 작아 기술적으로는 가장 간단하지만, 최종 Dump 이후 RDS 복원과 검증이 끝날 때까지 쓰기를 막아야 한다. 무중단이라고 하기 어렵다.
- DMS Homogeneous — 도착지로 RDS나 Aurora만 지원한다. 롤백은 RDS에서 EC2 컨테이너로 거꾸로 복제하는 것이라 DMS 범위 밖이다. DMS를 쓰더라도 롤백용 복제를 따로 구성해야 해서 도구 두 개를 운영하게 된다.
https://docs.aws.amazon.com/dms/latest/userguide/CHAP_Introduction.Targets.html
연결 전환 후보
| 방식 | 판단 | 이유 |
|---|---|---|
| PgBouncer PAUSE | ◎ | 진행 중 트랜잭션은 끝까지 처리하고(Drain), 새 쿼리는 대기시킬 수 있음 |
| HAProxy | ○ | PostgreSQL 트랜잭션을 이해하지 못해 기존 TCP 연결을 직접 끊어야 함 |
| FastAPI Blue/Green | ○ | 연결 상태는 명확하지만 FastAPI 두 개와 내부 라우팅이 필요 |
| RDS Proxy | ✕ | EC2 PostgreSQL과 RDS를 함께 전환하는 입구로 쓸 수 없음 |
선택 근거
- 전환 중인 DB 쿼리를 실패시키지 않고 기다리게 할 수 있다
HAProxy는 TCP 연결만 전달해 PostgreSQL 트랜잭션을 이해하지 못한다. PgBouncer는 서버 연결을 트랜잭션 단위로 관리하므로,
PAUSE로 진행 중 트랜잭션은 마치고 새 쿼리는 대기시킬 수 있다. - 전환 시간이 전체 데이터 크기에 영향을 받지 않는다 논리 복제는 처음에 한 번 전체를 복사하고, 이후 변경 사항만 계속 전달한다. 전환 때 전체 데이터를 다시 복사하지 않는다.
- 최종 전환에 FastAPI 프로세스를 재시작할 필요가 없다 FastAPI의 접속 주소는 계속 PgBouncer를 가리킨다. 바뀌는 것은 PgBouncer 뒤의 실제 목적지다.
DMS 대신 PostgreSQL 논리 복제를 선택한 근거
- 같은 PostgreSQL 16.15끼리 이전한다 — DMS의 데이터 변환 기능까지 필요하지 않고, PostgreSQL 자체 기능으로 충분하다.
- pgvector 처리 — 일반 DMS가 대상 테이블을 직접 만들면
vector를varchar로 만들 수 있어 별도 검증이 필요하다. 네이티브 논리 복제는 스키마를 우리가 먼저 만들고 데이터만 받으므로 통제하기 쉽다. - 롤백 방향 — 역방향(RDS → 컨테이너) 복제까지 같은 도구로 구성할 수 있다.
2-3. 작업 순서
핵심 원칙
- 한 번에 하나만 바꾼다. PgBouncer 도입, 데이터 복제, 연결 전환, V2 투입을 서로 다른 시점에 진행한다.
- Dev에서 합격한 절차만 운영에서 실행한다.
- 진행 여부를 가장 먼저 판별한다. PgBouncer 방식으로 갈 수 있는지부터 확인하고, 안 되면 HAProxy 방식으로 바꾼다.
[지금] 0 운영 PG 백업 + 복원 검증
↓
[판별] 1 PgBouncer 호환성 판별 ──실패──▶ HAProxy + 강제 재연결로 변경
↓ 통과
[Dev] 2 리허설 환경 구축 → 3 리허설 R0~R5 (R2·R3 연속 2회 합격까지 반복)
↓
[운영] 4 RDS 생성·스키마 → 5 사전 정비(PgBouncer 도입) + 하루 관찰
→ 6 정방향 복제 + 역방향 준비 + 동결 → 7 전환(PAUSE)
→ 8 롤백 대기 → 9 정리 → V2 FastAPI 투입
1. 운영 PostgreSQL 백업 + 복원 검증 및 사전 준비
백업은 S3에 저장하고, 별도 DB에 실제로 복원해 확인한다.
Source PostgreSQL에서 확인
├─ PostgreSQL 버전
├─ pgvector 버전
├─ 테이블
├─ 컬럼과 타입
├─ PK·제약조건
├─ 인덱스
└─ Sequence
Target RDS에 사전 생성
├─ Extension 설치
├─ Schema 생성
├─ Table 생성
├─ Index 생성
└─ 사용자 및 권한 생성
논리 복제는 데이터 변경만 복제하고, 테이블 정의 같은 DDL은 복제하지 않는다. CREATE TABLE, ALTER TABLE, 인덱스, Sequence 등은 따로 맞춰야 한다.
2. PgBouncer 호환성 검증
- 구성: 운영과 같은 FastAPI 이미지 → PgBouncer → PostgreSQL (로컬 Docker Compose)
- 확인: 트랜잭션 풀링에서 채팅(새 대화·이어가기·삭제), 상품 추천, 사용량 기록이 모두 에러 없이 동작하는지
- 실패하면 HAProxy 방식으로 변경
3. Dev 리허설(초안)
| 회차 | 하는 일 | 확인할 것 |
|---|---|---|
| R0 준비 공사 | DB를 한 번 재시작하면서 복제 기능을 켜고, PgBouncer 세팅 | AI가 몇 초나 끊기는지, FastAPI가 알아서 다시 붙는지 |
| R1 복사 시작 | 컨테이너 DB를 RDS로 통째로 복사한 뒤, 이후 변경을 실시간으로 따라가게 하기 | 두 DB의 데이터가 똑같은지 (건수, 검색 결과), 얼마나 늦게 따라오는지 |
| R2 실전 전환 | 요청이 들어오는 중에 PAUSE → 연결 주소를 RDS로 바꾸기 → RESUME | 몇 초 멈췄는지, 실패한 요청이 0건인지, 기존 대화가 이어지는지 (k6 기준은 「리허설 공통 방식」 참고) |
| R2-확장 | 상품 5만·체크포인트 100만으로 늘린 상태에서, k6 쓰기 부하를 단계적으로 올리며 다시 전환 | 부하 단계별 PAUSE 시간, 복제 지연, 초기 복사 시간 |
| R3 되돌리기 연습 | RDS의 변경을 컨테이너로 거꾸로 복사해 두다가, 같은 방법으로 주소를 되돌리기 | 데이터 유실이 없는지 |
| R4 사고 연습 | 일부러 문제 만들기 (오래 걸리는 작업, 스키마 변경, 디스크 차오름) | 취소 장치와 경보가 제대로 작동하는지 |
| R5 V2 연결 | V2 FastAPI를 RDS에 바로 붙이고, RDS 장애 전환을 일부러 일으키기 | 다시 잘 붙는지, 연결 수가 넘치지 않는지 |
4. 운영 인프라 구축 및 스키마 준비
- RDS PostgreSQL 16.15,
db.t4g.small, Multi-AZ, pgvector 0.8.2 - 롤백(역방향 복제)용 설정
rds.logical_replication = 1은 생성할 때 미리 켠다 (재부팅이 필요한 설정) - 스키마는 저장소 SQL이 아니라 운영 DB에서 추출해 반영하고, 앱·마이그레이션·복제 계정을 분리한다
5. 사전 정비 + 하루 관찰
- 컨테이너
wal_level = logical변경 + PgBouncer 삽입 → PostgreSQL 재시작 1회 - 최소 하루는 PgBouncer만 넣은 상태로 운영해, 문제를 데이터 이전과 분리해서 본다
6. 정방향 복제 시작 + 역방향 준비 + 동결
- 정방향 복제(컨테이너 → RDS) 시작 후 데이터 검증
- 역방향 구독(RDS → 컨테이너)을 미리 만들어 둔다
- 동결: FastAPI·LangGraph 버전 변경, 스키마 변경, Crawler 실행 금지
7. 운영 전환 (트래픽 최저 시간대)
1. PAUSE
2. 정방향 복제 지연 0 확인
3. RDS의 모든 시퀀스를 실제 최댓값으로 맞춤 (setval)
4. PgBouncer 목적지를 RDS로 → RELOAD
5. RESUME
6. 검증: 벡터 검색, 새 대화·기존 대화 이어쓰기, 사용량 기록
롤백
- 역방향 복제(RDS → 컨테이너)를 미리 걸어 두어, 컨테이너가 계속 최신 상태를 따라오도록 한다.
- 롤백 대기 기간에는 V2 FastAPI를 RDS에 붙이지 않는다 (PgBouncer로 되돌리면 V1만 돌아가므로).
순서
- PAUSE
- 역방향 복제 지연이 0인지 확인 (RDS에만 있던 데이터가 컨테이너에 다 도착했는지)
- 컨테이너 쪽 시퀀스 맞추기 (
setval) - PgBouncer 주소를 컨테이너로 바꾸고 RELOAD
- RESUME