v2 ECS 마이그레이션 전략 - 100-hours-a-week/KTB4-17th-wiki GitHub Wiki

전체 마이그레이션 전략

전체 마이그레이션은 한 번에 V2 VPC로 이전하지 않고, 두 단계로 나누어 진행한다.


1단계. V1 EC2 내부에서 Simple Broker → RabbitMQ 전환

현재 구조

  1. V1 Spring Container = REST API + WebSocket + Spring Simple Broker
  2. V1.5 Spring Container = REST API + WebSocket + RabbitMQ
  3. 현재 메시지 전송 구조
    • Spring이 DB의 Outbox 테이블을 Polling
    • published = false인 메시지 발행
    • 발행 완료 후 published = true

1단계 전략

먼저 V2 VPC에 RabbitMQ를 구축하고, V1 VPC와 V2 VPC가 통신할 수 있도록 네트워크를 연결한다.

이후 기존 EC2 내부에 두 개의 Spring 컨테이너를 동시에 실행한다.

  • Blue: 기존 V1 코드

    • REST + WebSocket
    • Spring Simple Broker 사용
    • Outbox의 published 컬럼 사용
  • Green: V1.5 코드

    • REST + WebSocket
    • RabbitMQ 사용
    • Outbox의 rabbit_published 컬럼 사용

구조:

                    Shared DB
                   /         \
                  /           \
          published       rabbit_published
              ↓                 ↓
       Blue Container     Green Container
              ↓                 ↓
       Simple Broker         RabbitMQ
                                 ↓
                              V2 VPC

Blue/Green 공존 시 문제

Blue와 Green이 공존하는 동안 기존 WebSocket 연결은 Blue에 남아 있을 수 있다.

철수 ──→ Blue (Simple Broker)
영희 ──→ Green (RabbitMQ)

이 상태에서 하나의 published 컬럼만 사용하면 문제가 발생한다.

Blue가 Outbox 메시지를 먼저 읽어서 Simple Broker로 발행한 뒤 published = true로 변경하면 Green은 해당 메시지를 처리하지 않는다.

하지만 영희는 Green에 연결되어 있기 때문에 해당 메시지를 실시간으로 받을 수 없다.

Outbox 발행 상태 분리

이를 해결하기 위해 Outbox 발행 상태를 분리한다.

published rabbit_published
Simple Broker 발행 여부 RabbitMQ 발행 여부

Blue와 Green은 각각 자신의 컬럼을 Polling하면서 모든 메시지를 발행한다.

                   Outbox
                  /      \
                 /        \
        published      rabbit_published
            ↓                 ↓
          Blue              Green
            ↓                 ↓
     Simple Broker         RabbitMQ

Blue → Green 트래픽 전환

이 상태에서 신규 트래픽을 Blue → Green으로 전환한다.

기존 Blue WebSocket 연결은 유지하면서 자연스럽게 Drain한다.

신규 HTTP 요청 → Green
신규 WS 연결   → Green

기존 WS 연결   → Blue에서 유지

Blue에 더 이상

  • WebSocket 연결이 없고
  • 처리 중인 요청도 없으면

Blue 컨테이너를 종료한다.

1단계 완료 상태

아직 VPC나 EC2 자체는 이전하지 않는다.

V1 VPC의 기존 EC2를 그대로 사용하지만, 메시징 구조는 RabbitMQ 기반으로 전환된 상태가 된다.

VPC V1

EC2
 └─ Spring
     ├─ REST
     ├─ WebSocket
     └─ RabbitMQ
            ↓
         RabbitMQ
         (V2 VPC)

즉,

기존

EC2
└─ Spring
    └─ Simple Broker


1단계 완료

EC2
└─ Spring
    └─ RabbitMQ

2단계. V1 EC2 → V2 VPC/ECS 이전

1단계에서 메시징 구조를 RabbitMQ로 통일한 이후, 실제 서버 및 VPC 마이그레이션을 진행한다.

V2 구조

V2 VPC에는 최종 운영 환경을 미리 구축한다.

V2 VPC

                  ALB
                   │
          ┌────────┴────────┐
          │                 │
        /api               /ws
          │                 │
          ↓                 ↓
    Spring ECS       WebSocket ECS
          │                 │
          └────────┬────────┘
                   ↓
                RabbitMQ
                   │
                   ↓
               Shared DB

Spring 서버와 WebSocket 서버는 분리되어 있지만, V1 EC2의 Spring 컨테이너와 동일한 RabbitMQ와 DB를 사용한다.

따라서 마이그레이션 과정에서 V1과 V2 사용자가 공존해도 된다.

철수
 │
 ↓
V1 EC2
 │
 └──────────┐
            │
            ↓
         RabbitMQ
            ↑
            │
 ┌──────────┘
 │
V2 WebSocket ECS
 ↑
 │
영희

철수는 기존 EC2 WebSocket에 연결되어 있고, 영희는 V2 ECS WebSocket 서버에 연결되어 있어도

두 서버가 동일한 RabbitMQ를 사용하기 때문에 서로 실시간 메시지를 전달할 수 있다.

V2 트래픽 전환

V2 환경 검증이 완료되면 CloudFront의 요청 경로를 기존 EC2에서 V2 ALB로 전환한다.

전환 전

CloudFront
   │
   ├─ /api
   └─ /ws
        ↓
       EC2
        │
        ↓
     RabbitMQ

전환 후

CloudFront
   │
   ↓
  ALB
   │
   ├─ /api → Spring ECS
   │
   └─ /ws  → WebSocket ECS
                    │
                    ↓
                 RabbitMQ

전환 이후 신규 요청과 기존 연결은 다음과 같이 처리된다.

신규 REST 요청
    ↓
V2 Spring ECS


신규 WebSocket 연결
    ↓
V2 WebSocket ECS


기존 WebSocket 연결
    ↓
V1 EC2에 그대로 유지

기존 EC2와 V2 ECS가 동일한 RabbitMQ와 DB를 사용하기 때문에 V1과 V2가 공존하는 동안에도 메시지를 전달할 수 있다.

기존 EC2 Connection Drain

CloudFront 전환 이후 신규 WebSocket 연결은 V2로 전달된다.

기존 EC2에 이미 연결되어 있던 WebSocket Connection은 기존 연결을 유지하면서 자연스럽게 Drain한다.

V1 EC2 WebSocket Connection

100
 ↓
 50
 ↓
 10
 ↓
  0

기존 EC2에 더 이상

  • WebSocket 연결이 없고
  • 처리 중인 HTTP 요청이 없으면

EC2를 종료한다.


전체 흐름 요약

1단계 - 메시징 구조 전환

V1 EC2

Spring
 ├─ REST
 ├─ WebSocket
 └─ Simple Broker

        ↓

Blue / Green 전환

        ↓

Spring
 ├─ REST
 ├─ WebSocket
 └─ RabbitMQ

※ VPC와 EC2는 그대로 유지

2단계 - 인프라 이전

V1 VPC / EC2
Spring + WebSocket
        │
        │
        └──── RabbitMQ
                 ↑
                 │
V2 VPC / ECS     │
Spring + WebSocket

V1과 V2가 동일한 RabbitMQ와 DB를 사용하는 상태에서 CloudFront의 트래픽을 V2로 전환한다.

최종 순서

V1
EC2 + Spring + Simple Broker
        ↓
V2 VPC에 RabbitMQ 구축
        ↓
V1 ↔ V2 VPC 네트워크 연결
        ↓
EC2 내부 Blue / Green 구성
        ↓
Simple Broker + RabbitMQ Dual Publish
(published / rabbit_published)
        ↓
Blue → Green 트래픽 전환
        ↓
Blue WebSocket Connection Drain
        ↓
Blue 종료
        ↓
V1 EC2가 RabbitMQ 기반으로 완전히 전환
        ↓
────────────────────────────────────
          1단계 완료
────────────────────────────────────
        ↓
V2 VPC에 ECS 환경 구축 및 검증
        ↓
V1 EC2와 V2 ECS가
동일한 RabbitMQ + DB 사용
        ↓
CloudFront
EC2 → V2 ALB로 트래픽 전환
        ↓
신규 REST → V2 Spring ECS
신규 WS   → V2 WebSocket ECS
        ↓
기존 EC2 WebSocket Connection Drain
        ↓
V1 EC2 Connection = 0
V1 EC2 처리 중 요청 = 0
        ↓
V1 EC2 종료
        ↓
────────────────────────────────────
          2단계 완료
────────────────────────────────────
        ↓
V2 VPC / ECS + RabbitMQ
최종 구조로 마이그레이션 완료