NeedU V1 → V2 마이그레이션 1~6차 비교 및 개선 결과 - 100-hours-a-week/KTB4-4th-wiki GitHub Wiki
목차
- 개요
- 회차별 개선 결과
- 최종 Production Cutover 실행 결과
- V1 → V2 트래픽 전환 검증
- Grafana 모니터링 결과
- 데이터 검증 결과
- 복구 절차
- 핵심 개선 성과
- 결론
1. 개요
2026년 10월 17일부터 19일까지 총 6차례의 마이그레이션 리허설 및 Production Cutover를 수행했다.
V1 EC2 환경에서 V2 ECS·RDS 환경으로 전환하는 과정에서 다음 항목을 순차적으로 개선했다.
- DNS 기반 전환을 ALB Listener Rule 기반 전환으로 변경
- Nginx Write Freeze를 ALB Write Gate로 변경
- PostgreSQL 데이터 사전 적재
- V2 ECS 및 ALB Target 사전 기동·검증
- DMS 중지, 데이터 검증, ALB 전환 및 Gate 해제 자동화
- GitHub Actions 기반 Preflight 및 Cutover Workflow 구축
- CloudWatch, CloudTrail, ECS Container Insights 및 Grafana 기반 검증
1.1 최종 Production 결과
| 항목 | 결과 |
|---|---|
| Production Preflight | 20/20 PASS |
| Production Cutover | SUCCESS |
| Preflight + Cutover 전체 Workflow | 281초 |
| Cutover Job | 157초 |
| 일반 쓰기 Gate Rule 유지 시간 | 102초 |
| 인증 Callback Gate Rule 유지 시간 | 103초 |
| Backend ALB 전환 | 15:55:04 KST |
| Frontend ALB 전환 | 15:55:06 KST |
| DMS Full Load | 100% |
| DMS Validation Pending/Failed | 0건 |
| PostgreSQL Catalog 검증 | PASS |
| 1분 단위 ALB 요청 공백 | 0구간 |
| V1·V2 Unhealthy Target | 0개 |
| V1·V2 Target 5xx | 관측되지 않음 |
| Target 연결 오류 | 관측되지 않음 |
| ALB 연결 거부 | 관측되지 않음 |
1.2 측정 기준
| 용어 | 기준 |
|---|---|
| Cutover | Write Gate 생성, DMS 중지, 데이터 검증·변환, ALB 전환 및 Gate 해제를 포함한 절차 |
| 트래픽 전환 | ALB Listener Rule의 Target Weight를 V1=0, V2=100으로 변경한 작업 |
| 쓰기 제한 시간 | CloudTrail의 Write Gate Rule 생성부터 삭제까지의 시간 |
| Workflow 실행 시간 | GitHub Actions Run 및 Job 기록의 시작부터 종료까지의 시간 |
| 관측 구간 | 2026-10-19 15:48~16:01 KST |
1.3 V2 설계 기준과 Cutover 방식 선정
1.3.1 V2 목표 사용자 및 트래픽
V2는 서비스 출시 약 14개월 후의 성장 규모를 다음과 같이 설정했다.
| 항목 | V2 설계값 |
|---|---|
| 목표 시점 | 서비스 출시 약 14개월 후 |
| MAU | 약 291,900명 |
| DAU | 약 72,975명 |
| Peak API Traffic | 약 101~122 RPS |
| Peak Write | 약 49 QPS |
| AI Inference Traffic | 약 4.87 RPS |
| 서비스 가용성 목표 | 월간 99% 이상 |
이에 맞춰 ECS 다중 Task, ALB, Rolling Deployment 및 Auto Scaling 구조를 설계했다. Production Cutover에서는 실제 요청 흐름, Target Health, 오류 지표와 Write Gate 시간을 확인했다.
1.3.2 평가 기준
V2의 월간 가용성 SLO는 99% 이상이다. Cutover에서는 이를 다음 운영 지표로 확인했다.
| 구분 | 기준 |
|---|---|
| 읽기 연속성 | 전환 전후 ALB 요청 유입 및 V1 → V2 처리 대상 이동 |
| 처리 용량 | Healthy Target 유지 및 서비스별 Running Task 2개 |
| 오류 | Target 5xx, Target 연결 오류, ALB 연결 거부 |
| 쓰기 제한 | Write Gate 120초 이하 |
| 데이터 | DMS Validation Pending/Failed 0건 및 DB 검증 PASS |
1.3.3 SLO와 이번 Cutover 결과
30일을 720시간으로 계산하면 99% SLO의 월간 오류 예산은 다음과 같다.
720시간 × 1%
= 7.2시간
= 7시간 12분
= 25,920초
이번 Production Cutover의 AWS Rule 유지 시간은 일반 쓰기 102초, 인증 Callback 103초였다. 103초를 전체 서비스 비가용 시간으로 보수적으로 환산하면 다음과 같다.
월간 시간 기반 가용성
= (2,592,000초 - 103초) ÷ 2,592,000초 × 100
= 약 99.996%
99% 월간 오류 예산 사용률
= 103초 ÷ 25,920초 × 100
= 약 0.4%
실제 전환에서는 읽기 요청을 계속 처리하고 쓰기와 인증만 제한했다. 따라서 103초를 전체 중단으로 보는 위 계산은 실제 영향보다 보수적인 값이며, V2의 99% SLO 오류 예산 중 약 0.4%에 해당한다.
1.3.4 방식 선정과 중단 시간 개선
Application은 V1과 V2를 ALB에 함께 준비한 뒤 트래픽을 전환했고, 데이터 정합성 확보를 위해 전환 전에 짧은 Write Gate를 적용했다.
V1·V2 Target 사전 연결
→ V1 100 / V2 0
→ Write Gate ON
→ DMS 중지 및 데이터 검증
→ V1 0 / V2 100
→ V2 Target Health 확인
→ Write Gate OFF
전환 구간의 쓰기 처리 방식으로 다음 대안을 검토했다.
| 방식 | 전환 중 쓰기 처리 | 고려사항 |
|---|---|---|
| Write Freeze | 짧은 시간 쓰기를 503으로 차단 | 구현이 단순하고 데이터 기준점을 명확히 만들 수 있음 |
| Queue | 요청을 Queue에 저장하고 전환 후 V2 DB에 반영 | 비동기 응답, 순서·중복·재시도·DLQ 및 멱등성 처리 필요 |
| Dual Write | V1·V2 DB에 동시에 저장 | 한쪽만 성공할 때 데이터 불일치와 복구 처리 필요 |
| ProxySQL | Query Rule로 쓰기 DB를 V1에서 V2로 변경 | 라우팅 도구이며 두 DB의 동기화와 트랜잭션 정합성을 해결하지 않음 |
| Transactional Outbox | DB 변경과 Outbox Event를 같은 Transaction으로 저장한 뒤 V2에 전달 | 모든 쓰기 경로에 Outbox·Relay·Consumer와 중복 처리 로직 필요 |
Queue 방식은 Write Gate 동안 요청을 잃지 않고 전환 후 처리할 수 있지만 즉시 처리 API를 비동기 방식으로 바꿔야 한다. Amazon SQS Standard Queue는 메시지를 한 번 이상 전달할 수 있으므로 Consumer의 멱등성 처리도 필요하다.
ProxySQL은 Query Rule과 Hostgroup으로 쓰기 대상을 바꿀 수 있지만, 진행 중인 Transaction과 V1·V2 데이터 동기화는 별도로 처리해야 한다. 따라서 DMS CDC나 Dual Write를 대체하지 않는다.
Transactional Outbox는 하나의 DB 변경과 Event 발행을 원자적으로 묶는 방식이다. 이번 마이그레이션에 적용하려면 V1의 모든 쓰기 경로를 변경하고 Relay와 V2 Consumer를 새로 운영해야 하므로, 한 번의 Cutover를 위해 도입하지 않았다.
NeedU는 V2의 99% SLO와 데이터 정합성을 기준으로 DMS CDC + 120초 이내 Write Freeze를 선택했다.
동시에 쓰기 제한을 최소화하기 위해 PostgreSQL 사전 적재, V2 ECS 사전 기동, Health Check, DMS 중지, 데이터 검증, ALB 전환 및 Gate 해제를 반복적으로 개선하고 자동화했다. 그 결과 쓰기 제한 시간은 1차 최소 46분 35초에서 최종 Production 일반 쓰기 102초, 인증 Callback 103초로 단축됐다.
- AWS Prescriptive Guidance: Cutover
- AWS Prescriptive Guidance: Transactional Outbox Pattern
- Amazon SQS: At-least-once Delivery
- ProxySQL: Read/Write Split
2. 회차별 개선 결과
2.1 전체 비교
| 항목 | 1차 | 2차 | 3차 | 4차 | 5차 | 6차 |
|---|---|---|---|---|---|---|
| 일자 | 10/17 | 10/17 | 10/18 | 10/18 | 10/19 | 10/19 |
| 환경 | Staging | Staging | Staging | Staging | Production | Production |
| 트래픽 전환 | DNS | ALB | ALB | ALB | ALB | ALB |
| 실행 방식 | 수동 | 수동 | 수동 | 자동 | 자동 | 자동 |
| 쓰기 제어 | Nginx Freeze | ALB Gate | ALB Gate | ALB Gate | ALB Gate | ALB Gate |
| 사전 검증 | 수동 | 수동 | Smoke 7/7 | 자동 검증 | 20/20 PASS | 20/20 PASS |
| 기록된 쓰기 제한 시간 | 46분 35초 이상 | 630초 | 327~349초 | 59초 | 129초 | 93초 |
| Cutover Job | - | - | - | - | 192초 | 157초 |
| 전체 Workflow | - | - | - | - | 261초 | 281초 |
| 결과 | DNS 전환 | ALB 전환 | 절차 단축 | 자동화 | Production 적용 | Production 전환 성공 |
6차의 93초는 Workflow가 Gate의 HTTP 503을 확인한 시점부터 Gate OFF를 확인한 시점까지의 시간이다. CloudTrail의 AWS Rule 생성·삭제 시각으로 계산한 실제 Rule 유지 시간은 일반 쓰기 102초, 인증 Callback 103초다.
2.2 1차 → 2차: DNS에서 ALB로 전환
1차에서는 Nginx Write Freeze 이후 PostgreSQL Dump/Restore, DMS 중지, ECS 기동, Smoke Test 및 DNS 전환을 순서대로 수행했다.
| 단계 | 시각(KST) |
|---|---|
| Write Freeze ON | 16:58:25 |
| PostgreSQL 최종 Dump 완료 | 17:03:22 |
| DMS 중지 | 17:06:20 |
| ECS Health Check 완료 | 약 17:16 |
| DNS 전환 완료 | 약 17:22 |
| Smoke Test 완료 | 약 17:24 |
| 완료 보고 | 17:45 |
2차부터 다음 구조를 적용했다.
- ALB Listener Rule Target Weight로 V1/V2 전환
- ALB Write Gate로 쓰기 요청 제어
- 트래픽 전환과 쓰기 제어 분리
- DMS 중지와 데이터 확인 순서 정리
2차 Write Gate는 22:41:30부터 22:52:00까지 630초 유지됐다. Gate 해제 시점에 ALB V1=0/V2=100, Backend Target 2/2 Healthy, FE·BE·AI ECS 각각 2/2 Running이 확인됐다.
2.3 2차 → 3차: 수동 절차 단축
3차에서는 PostgreSQL 사전 복원, ECS 사전 기동, Target Health 확인 및 Cutover 이전 Smoke Test를 적용했다.
| 단계 | 시각(KST) |
|---|---|
| Write Gate ON | 13:19:00~13:19:22 |
| PostgreSQL 활성 트랜잭션 0 확인 | 13:19:22 |
| DMS 중지 | 13:20:05 |
| MySQL 활성 트랜잭션 0 확인 | 13:20:27 |
| MySQL 정책 변경 완료 | 13:21:22 |
| ALB 전환 완료 | 13:22:30 |
| Write Gate OFF | 13:24:49 |
| HTTP 200 확인 | 13:26:17~13:26:23 |
결과:
- Write Gate 327~349초
- DMS 중지부터 ALB 전환 완료까지 145초
- ALB 전환 완료부터 Gate OFF까지 139초
- Smoke Test 7/7 PASS
2.4 3차 → 4차: GitHub Actions 자동화
4차에서는 다음 작업을 GitHub Actions로 자동화했다.
- DMS 중지
- 데이터 확인
- ALB Listener Rule 변경
- ECS 및 Target Health 확인
- Write Gate 해제
결과:
- Write Gate 59초
- Cutover 전 k6 3,001회 반복에서 HTTP 실패 0건
- Cutover 후 읽기 검증 8/8 확인
2.5 5차 Production 적용
| 항목 | 결과 |
|---|---|
| Preflight | 20/20 PASS |
| Preflight Job | 62초 |
| Cutover Job | 192초 |
| 전체 Workflow | 261초 |
| Write Gate | 129초 |
5차에서 V1/V2 JWT 서명 키 불일치로 HTTP 401이 발생했고, 이후 V2 Production JWT 설정을 V1과 일치시켰다.
2.6 6차 Production Cutover
-
Preflight 완료: 2026-10-19 15:52:28 KST
-
Preflight: 20 PASS / 0 FAIL
-
Cutover: SUCCESS
-
Cutover Job: 157초
-
전체 Workflow: 281초
-
Workflow Gate 측정값: 93초
-
AWS Rule 기준 최대 Gate 유지 시간: 103초
3. 최종 Production Cutover 실행 결과
3.1 Preflight 검증
최종 Cutover 전에 다음 항목이 모두 통과했다.
- DNS 및 Route53 레코드
- ALB V1=100, V2=0
- V1 Healthy Target
- Write Gate Disabled
- ECS FE·BE·AI Running Task 각각 2개 이상
- V2 FE·BE ALB Target Healthy
- RDS MySQL 및 PostgreSQL Available
- DMS Running 및 Full Load 100%
- DMS Validation Pending/Failed 0건
- MySQL 테이블 및 주요 데이터 행 수
- PostgreSQL Catalog 행 수, vector 타입 및 HNSW Index
3.2 초 단위 타임라인
| 시각(KST) | 이벤트 | 근거 |
|---|---|---|
| 15:53:37 | POST·PUT·PATCH·DELETE Gate Rule 생성 | CloudTrail CreateRule |
| 15:53:38 | 인증 Callback Gate Rule 생성 | CloudTrail CreateRule |
| 15:53:49 | Gate HTTP 503 검증 완료 | GitHub Actions |
| 15:54:12~15:54:34 | DMS 중지 및 stopped 확인 | GitHub Actions |
| 15:54:34~15:54:46 | DMS 중지 후 DB 재검증 | GitHub Actions |
| 15:54:46~15:54:51 | V2 데이터 변환 | GitHub Actions |
| 15:55:04 | Backend API V1=0, V2=100 | CloudTrail ModifyRule |
| 15:55:06 | Frontend V1=0, V2=100 | CloudTrail ModifyRule |
| 15:55:08~15:55:12 | V2 FE·BE Target Health 검증 | GitHub Actions |
| 15:55:19 | 일반 쓰기 Gate Rule 삭제 | CloudTrail DeleteRule |
| 15:55:21 | 인증 Callback Gate Rule 삭제 | CloudTrail DeleteRule |
| 15:55:22 | Gate OFF 확인 | GitHub Actions |
3.3 Write Gate 시간
| 대상 | 시작 | 종료 | 유지 시간 |
|---|---|---|---|
| POST·PUT·PATCH·DELETE | 15:53:37 | 15:55:19 | 102초 |
| 인증 Callback | 15:53:38 | 15:55:21 | 103초 |
| Workflow 확인 구간 | 15:53:49 | 15:55:22 | 93초 |
4. V1 → V2 트래픽 전환 검증
4.1 ALB Listener Rule 변경
CloudTrail에서 다음 ModifyRule 이벤트를 확인했다.
| 대상 | 시각(KST) | 변경 결과 | CloudTrail Event ID |
|---|---|---|---|
| Backend API | 15:55:04 | V1=0, V2 Backend=100 | e71f1f9d-1d65-43e6-af69-b5d7829cc88a |
| Frontend | 15:55:06 | V1=0, V2 Frontend=100 | 4499ad53-e7cc-40b5-a3c3-ff5e54b3653e |
두 이벤트의 responseElements에 변경된 Target Weight가 기록됐다.
4.2 ALB 요청 유입
CloudWatch AWS/ApplicationELB RequestCount, Period 60초, Statistic Sum 조회 결과다.
| 시각(KST) | 요청 수 |
|---|---|
| 15:48 | 2 |
| 15:49 | 2 |
| 15:50 | 17 |
| 15:51 | 65 |
| 15:52 | 242 |
| 15:53 | 247 |
| 15:54 | 242 |
| 15:55 | 265 |
| 15:56 | 301 |
| 15:57 | 368 |
| 15:58 | 252 |
| 15:59 | 302 |
| 16:00 | 243 |
| 16:01 | 179 |
- 전체 요청: 2,727건
- 요청 수가 0인 1분 구간: 0개
- Backend와 Frontend가 전환된 15:55 요청: 265건
4.3 요청 처리 대상 이동
CloudWatch RequestCountPerTarget 조회 결과다.
| 시각(KST) | V1 | V2 Frontend | V2 Backend |
|---|---|---|---|
| 15:54 | 242 | 0 | 0 |
| 15:55 | 67 | 12 | 87 |
| 15:56 | 0 | 20.5 | 130 |
| 15:57 | 0 | 37 | 147 |
15:54에는 V1에서 요청이 처리됐고, 전환 시각이 포함된 15:55에는 V1과 V2 양쪽에서 요청이 처리됐다. 15:56부터 V1 지표는 0이고 V2 Frontend와 Backend에서 요청이 처리됐다.
RequestCountPerTarget은 Target Group의 요청 수를 Healthy Target 수로 나눈 값이므로 세 값을 합산하지 않는다.
4.4 Target Health
CloudWatch HealthyHostCount Minimum과 UnHealthyHostCount Maximum 조회 결과다.
| Target Group | Healthy 최솟값 | Unhealthy 최댓값 |
|---|---|---|
| V1 | 1 | 0 |
| V2 Frontend | 2 | 0 |
| V2 Backend | 2 | 0 |
V1 요청 처리 구간에는 V1 Healthy Target이 유지됐고, V2 전환 전후에는 V2 Frontend와 Backend Target이 각각 2개씩 Healthy 상태를 유지했다.
4.5 ECS 실행 용량
ECS Container Insights RunningTaskCount Minimum 조회 결과다.
| ECS 서비스 | 최소 Running Task |
|---|---|
| Frontend | 2 |
| Backend | 2 |
| AI | 2 |
같은 구간의 CloudTrail 조회에서 UpdateService와 StopTask 이벤트는 각각 0건이었다.
4.6 Target 및 연결 오류
15:48~16:01 KST 구간 조회 결과다.
| 지표 | 결과 |
|---|---|
V1 HTTPCode_Target_5XX_Count |
오류 Datapoint 없음 |
V2 FE HTTPCode_Target_5XX_Count |
오류 Datapoint 없음 |
V2 BE HTTPCode_Target_5XX_Count |
오류 Datapoint 없음 |
V1 TargetConnectionErrorCount |
오류 Datapoint 없음 |
V2 FE TargetConnectionErrorCount |
오류 Datapoint 없음 |
V2 BE TargetConnectionErrorCount |
오류 Datapoint 없음 |
ALB RejectedConnectionCount |
오류 Datapoint 없음 |
4.7 ALB 연결 활동
| 시각(KST) | ActiveConnectionCount | NewConnectionCount |
|---|---|---|
| 15:52 | 11 | 246 |
| 15:53 | 20 | 256 |
| 15:54 | 10 | 245 |
| 15:55 | 36 | 268 |
| 15:56 | 30 | 271 |
| 15:57 | 45 | 285 |
ALB Rule 변경 시각이 포함된 15:55에도 활성 연결과 신규 연결이 기록됐다.
4.8 Target 응답시간 p95
| 시각(KST) | V1 p95 | V2 FE p95 | V2 BE p95 |
|---|---|---|---|
| 15:52 | 9.5ms | - | - |
| 15:53 | 10.0ms | - | - |
| 15:54 | 8.1ms | - | - |
| 15:55 | 19.4ms | 416.1ms | 50.4ms |
| 15:56 | - | 383.7ms | 39.1ms |
| 15:57 | - | 71.4ms | 25.4ms |
V2 Frontend p95는 전환 직후 416.1ms였고 15:57에는 71.4ms로 낮아졌다. 같은 구간에 V2 Target 5xx, Target 연결 오류 및 ALB 연결 거부 Datapoint는 없었다.
4.9 GitHub Actions 실행 결과
| 항목 | 결과 |
|---|---|
| Workflow | Prod Migration Cutover |
| 전체 결과 | success |
| Preflight Job | success |
| Cutover Job | success |
| Switch API and frontend to V2 | success |
| Verify V2 target health before releasing gate | success |
| Disable Write Gate | success |
| Recover V1 and release Write Gate | skipped |
| Restore V2 onboarding flags after service recovery | skipped |
전환, V2 Target Health 확인 및 Gate 해제 단계가 성공했고 V1 복구 단계는 실행되지 않았다.
4.10 DNS 변경 여부
15:48~16:01 KST CloudTrail 조회에서 ChangeResourceRecordSets 이벤트는 0건이었다. 최종 전환은 DNS 변경 없이 기존 Production ALB Listener Rule의 Target Weight를 변경하는 방식으로 수행됐다.
4.11 V2 애플리케이션 로그
15:52~15:58 KST 구간에 다음 Log Group을 검색했다.
/needu/prod/v2/frontend/needu/prod/v2/backend/needu/prod/v2/ai
검색어는 error, exception, timeout, failed, connection refused, outofmemory였으며, 검색된 27개 레코드에서 일치 항목은 0건이었다.
4.12 확인된 별도 오류 지표
| 시각(KST) | 지표 | 건수 | 전환 시각과의 관계 |
|---|---|---|---|
| 15:53 | HTTPCode_ELB_5XX_Count |
1 | ALB 전환 2분 전 |
| 15:53 | HTTP_Fixed_Response_Count |
1 | Write Gate 생성·검증 구간 |
| 15:53 | ClientTLSNegotiationErrorCount |
5 | ALB 전환 전 |
| 15:54 | ClientTLSNegotiationErrorCount |
1 | ALB 전환 전 |
Backend 및 Frontend ALB ModifyRule은 15:55:04와 15:55:06에 실행됐다. 위 지표는 두 ModifyRule 이전에 기록됐다. 실제 전환 시각이 포함된 15:55 이후에는 V1·V2 Target 5xx, Target 연결 오류 및 ALB 연결 거부가 기록되지 않았다.
4.13 검증 결과
| 검증 항목 | 결과 | 근거 |
|---|---|---|
| ALB V1=0/V2=100 전환 | 확인 | CloudTrail ModifyRule |
| V1 → V2 요청 처리 대상 이동 | 확인 | RequestCountPerTarget |
| 1분 단위 요청 유입 공백 | 0구간 | RequestCount |
| V1·V2 Healthy Target 유지 | 확인 | HealthyHostCount |
| Unhealthy Target | 0개 | UnHealthyHostCount |
| FE·BE·AI Running Task | 각각 최소 2개 | RunningTaskCount |
| Target 5xx | 전환 구간 미관측 | HTTPCode_Target_5XX_Count |
| Target 연결 오류 | 전환 구간 미관측 | TargetConnectionErrorCount |
| ALB 연결 거부 | 전환 구간 미관측 | RejectedConnectionCount |
| ALB 연결 활동 | 유지 | Active/NewConnectionCount |
| V1 자동 복구 실행 | 실행되지 않음 | GitHub Actions skipped |
CloudTrail과 CloudWatch 기록에서 V1 요청 처리 후 V2 요청 처리가 시작됐고, 전환 전후 1분 단위 요청 유입과 Healthy Target 및 ECS Running Task가 유지됐다. 전환 구간의 Target 5xx, Target 연결 오류 및 ALB 연결 거부는 관측되지 않았다.
따라서 AWS 관측 지표에서 V1 EC2 → V2 ECS 트래픽 전환 중 서비스 중단은 관측되지 않았다. 쓰기 및 인증 요청은 데이터 정합성 확보를 위해 각각 102초와 103초 동안 Write Gate로 제한했다.
5. Grafana 모니터링 결과
5.1 Production ALB 요청률
| 시각(KST) | 요청률 |
|---|---|
| 15:45~15:49 | 약 0 RPS |
| 15:51 | 약 1 RPS |
| 15:52 | 약 4 RPS |
| 15:55 | 약 5 RPS |
| 15:57 | 약 6.1 RPS |
| 16:01 | 약 3 RPS |
전환 시각이 포함된 15:55와 전환 이후 구간에서 Production ALB 요청률이 계속 기록됐다.
5.2 ALB HTTP 5xx
- 15:53 KST ALB 5xx: 1건
- 15:53 KST Fixed Response: 1건
- Write Gate Rule 생성: 15:53:37~15:53:38
- Backend 및 Frontend ALB 전환: 15:55:04~15:55:06
- ALB 전환 시각 이후 Target 5xx: 관측되지 않음
5.3 ALB Target Response Time p95
- Grafana 15:55 KST 응답시간 상승 관측
- CloudWatch V2 FE p95: 416.1ms
- CloudWatch V2 BE p95: 50.4ms
- 15:57 V2 FE p95: 71.4ms
- 15:57 V2 BE p95: 25.4ms
5.4 ECS 및 ALB Target Health
- FE·BE·AI Running Task: 각각 최소 2개
- V2 FE·BE Healthy Target: 각각 2개
- Unhealthy Target: 0개
- Cutover 전후 실행 용량 유지
5.5 DMS CDC Latency
- CDC 지연이 약 4초에서 0초로 감소한 구간 확인
- DMS Full Load 100%
- DMS Validation Pending/Failed 0건
- Cutover Workflow에서 DMS stopped 상태 확인
5.6 V1 Nginx 요청률
- V1 Nginx 요청률 약 0.2 RPS 기록
- ALB
RequestCountPerTarget에서는 15:56부터 V1 요청 처리량 0 기록 - 15:56부터 V2 Frontend와 Backend 요청 처리량 기록
6. 데이터 검증 결과
6.1 MySQL
| 검증 대상 | 검증값 | 결과 |
|---|---|---|
| 전체 테이블 수 | 22 | PASS |
| 친구 관계 데이터 | 438 | PASS |
| 인증 세션 데이터 | 155 | PASS |
| 취향 분석 데이터 | 0 | PASS |
| 상품 데이터 | 7,969 | PASS |
| AI 대화방 데이터 | 230 | PASS |
| 사용자 데이터 | 73 | PASS |
| 오류 보고 데이터 | 2 | PASS |
추가 확인 결과:
- DMS Running
- Full Load 100%
- Validation Pending/Failed 0건
- Preflight
Data reconciliation: PASS
6.2 PostgreSQL
| 검증 대상 | 검증값 | 결과 |
|---|---|---|
| 임베딩 모델 메타데이터 | 1 | PASS |
| 상품 문서 데이터 | 20,289 | PASS |
| 상품 Catalog 데이터 | 7,969 | PASS |
| Catalog 동기화 기록 | 0 | PASS |
| 전체 행 수 | 28,259 | PASS |
| 임베딩 타입 | vector(1024) |
PASS |
| HNSW Cosine Index | 1 | PASS |
6.3 데이터 이전 방식
| 데이터베이스 | 이전 방식 | Cutover 검증 |
|---|---|---|
| MySQL | AWS DMS Full Load + CDC | Full Load 100%, Validation 오류 0건, 주요 테이블 행 수 PASS |
| PostgreSQL | Dump/Restore 및 Catalog 적재 | Catalog 행 수, vector 타입, HNSW Index PASS |
이 문서의 데이터 결과는 Cutover Workflow와 Preflight가 실행한 검증 항목의 통과 결과를 기록한다.
7. 복구 절차
6차 Cutover Workflow에는 Gate 해제 전 Cutover가 실패하거나 취소될 경우 다음 순서가 정의돼 있다.
- ALB Listener Rule을 V1으로 복원
- V1 Healthy Target 최소 1개 확인
- Write Gate 해제
- V2 사용자 상태 플래그 복원 Step 실행
최종 6차 Run에서는 Cutover가 성공하여 Recover V1 and release Write Gate와 Restore V2 onboarding flags after service recovery Step이 skipped로 기록됐다.
8. 핵심 개선 성과
8.1 쓰기 제한 시간
| 회차 | 기록된 시간 | 방식 |
|---|---|---|
| 1차 | 46분 35초 이상 | Nginx Write Freeze |
| 2차 | 630초 | 수동 ALB Gate |
| 3차 | 327~349초 | 수동 ALB Gate |
| 4차 | 59초 | Staging 자동 Cutover |
| 5차 | 129초 | Production Workflow |
| 6차 | 93초 | Production Workflow |
기존 Workflow 기록 기준:
- 2차 630초 → 6차 93초: 537초 단축, 85.2% 감소
- 5차 129초 → 6차 93초: 36초 단축, 27.9% 감소
6차 CloudTrail Rule 수명 기준:
- 일반 쓰기: 102초
- 인증 Callback: 103초
8.2 자동화 구조
| 영역 | 초기 방식 | 최종 방식 |
|---|---|---|
| 트래픽 전환 | Route53 DNS 변경 | ALB Listener Rule Weight 변경 |
| 실행 | 수동 명령 | GitHub Actions Workflow |
| 사전 검증 | 수동 | Preflight 20/20 |
| 쓰기 제어 | Nginx Write Freeze | ALB Write Gate |
| Target 검증 | 수동 확인 | Workflow 자동 검증 |
| 데이터 검증 | 개별 확인 | DMS·MySQL·PostgreSQL 검증 |
| 복구 | 수동 | V1 복원 및 Gate 해제 Step |
| 관측 | 운영 로그 | CloudWatch·CloudTrail·Grafana |
8.3 5차 대비 6차
| 지표 | 5차 | 6차 | 변화 |
|---|---|---|---|
| Cutover Job | 192초 | 157초 | 35초 단축 |
| Workflow Gate | 129초 | 93초 | 36초 단축 |
| 전체 Workflow | 261초 | 281초 | 20초 증가 |
| Preflight | 20/20 PASS | 20/20 PASS | 동일 |
| Cutover | SUCCESS | SUCCESS | 동일 |
9. 결론
NeedU V1 → V2 마이그레이션은 6차례의 실행을 통해 DNS 기반 수동 전환에서 ALB 기반 자동 Cutover로 개선됐다.
최종 6차 Production Cutover에서 확인된 결과는 다음과 같다.
- Preflight 20/20 PASS
- Cutover Workflow SUCCESS
- Backend ALB V1=0/V2=100 변경: 15:55:04 KST
- Frontend ALB V1=0/V2=100 변경: 15:55:06 KST
- 관측 구간 전체 ALB 요청: 2,727건
- 요청 수가 0인 1분 구간: 0개
- 전환 시각이 포함된 15:55 요청: 265건
- V1에서 V2로 요청 처리 대상 이동 확인
- V1·V2 Unhealthy Target: 0개
- FE·BE·AI Running Task: 각각 최소 2개
- 전환 구간 Target 5xx: 관측되지 않음
- Target 연결 오류: 관측되지 않음
- ALB 연결 거부: 관측되지 않음
- 일반 쓰기 제한: 102초
- 인증 Callback 제한: 103초
- DMS Full Load 100% 및 Validation 오류 0건
- MySQL 및 PostgreSQL 정의 검증 항목 PASS
CloudTrail과 CloudWatch 기록에서 V1 요청 처리 이후 V2 요청 처리가 시작됐고, 전환 전후 요청 유입, Healthy Target, ECS Running Task 및 ALB 연결 활동이 유지됐다.
AWS 관측 지표에서 V1 EC2 → V2 ECS 트래픽 전환 중 서비스 중단은 관측되지 않았다. 데이터 정합성 확보를 위한 쓰기 및 인증 요청은 Write Gate를 통해 제한한 뒤 V2 Target Health를 확인하고 해제했다.