AWS 환경에서 운영 중인 서비스를 새로운 VPC로 이전해야 할 때, ElastiCache RedisRDS는 특별한 주의가 필요합니다. 일반 EC2와 달리 이 두 서비스는 VPC를 변경할 수 없는 immutable 특성을 가지고 있어, 잘못 접근하면 수 시간의 다운타임이 발생할 수 있습니다. 이 글에서는 실제 운영 환경에서 검증된 무중단 VPC 전환 전략을 단계별로 설명합니다.


1. 왜 VPC 전환이 어려운가?

많은 엔지니어가 VPC 마이그레이션을 처음 시도할 때 공통적으로 마주치는 벽이 있습니다. EC2 인스턴스라면 ENI를 조작하거나 AMI를 만들어서 다른 VPC에 재배포하면 되지만, ElastiCache와 RDS는 전혀 다른 이야기입니다.

ElastiCache와 RDS의 VPC immutability

AWS 공식 문서에 따르면, ElastiCache 클러스터와 RDS 인스턴스는 한번 특정 VPC에 생성되면 해당 VPC를 변경할 수 없습니다. AWS 콘솔이나 CLI 어디에도 “VPC 변경” 옵션은 존재하지 않습니다. 이를 바꾸려면 새 인스턴스를 생성하고 데이터를 마이그레이션하는 수밖에 없습니다.

주요 마이그레이션 시나리오는 두 가지입니다:

  • Classic → VPC: AWS EC2-Classic(레거시)에서 VPC 환경으로 전환. AWS가 2022년 8월 이후 Classic을 공식 종료했기 때문에 여전히 마이그레이션이 필요한 케이스가 존재합니다.
  • VPC-A → VPC-B: 기존 VPC에서 새로운 VPC로 전환. 조직 구조 변경, 네트워크 설계 개선, 보안 정책 강화 등의 이유로 발생합니다.

다운타임이 발생하는 이유

ElastiCache는 캐시 레이어이기 때문에 캐시 미스가 발생하면 원본 DB에 부하가 집중됩니다. RDS는 말할 것도 없이 데이터 정합성이 핵심입니다. 잘못된 전환 순서나 컷오버 타이밍은 다음과 같은 장애로 이어집니다:

  • 캐시 Cold Start로 인한 DB 과부하 및 지연 급증
  • Write 요청이 구 RDS와 신 RDS에 동시에 들어가거나, 어느 쪽에도 들어가지 않는 스플릿 브레인
  • DNS 변경 후 애플리케이션이 구 엔드포인트를 캐싱해 연결 실패

2. 전체 전략 개요: Blue/Green 방식

무중단 VPC 전환의 핵심 전략은 Blue/Green 배포입니다. 기존 환경(Blue)을 유지한 채 신규 환경(Green)을 완전히 구성하고, 트래픽을 점진적으로 이동시킨 뒤 구 환경을 제거하는 방식입니다.


[구 VPC (Blue)]                    [신 VPC (Green)]
 ElastiCache Redis (old)    →→→    ElastiCache Redis (new)
 RDS Primary (old)          →→→    RDS Primary (new)
 App Server                  ↕          App Server (new)
                          VPC Peering / Transit GW

전체 흐름 요약

  1. 신규 VPC에 네트워크 인프라 구성 (서브넷, 라우팅, SG)
  2. VPC Peering 또는 Transit Gateway로 구-신 VPC 연결
  3. 신규 ElastiCache 클러스터 생성 + 캐시 워밍(dual-write)
  4. 신규 RDS 생성 + Read Replica 또는 DMS로 실시간 동기화
  5. 애플리케이션을 신규 VPC로 배포 (구 DB에 연결)
  6. Route 53 CNAME 컷오버 → 신규 DB로 완전 전환
  7. 안정성 확인 후 구 리소스 제거

3. 사전 준비

3-1. VPC Peering 또는 Transit Gateway 설정

마이그레이션 기간 동안 구 VPC와 신 VPC가 통신할 수 있어야 합니다. 규모가 작으면 VPC Peering이 간단하고 비용 효율적이며, 여러 VPC를 허브 방식으로 연결해야 한다면 Transit Gateway를 사용합니다.

# VPC Peering 생성
aws ec2 create-vpc-peering-connection 
  --vpc-id vpc-OLD123456 
  --peer-vpc-id vpc-NEW789012 
  --region ap-northeast-2

# Peering 요청 수락 (peer VPC 오너가 수락)
aws ec2 accept-vpc-peering-connection 
  --vpc-peering-connection-id pcx-xxxxxxxx 
  --region ap-northeast-2

# 라우팅 테이블에 경로 추가 (구 VPC → 신 VPC)
aws ec2 create-route 
  --route-table-id rtb-OLD123456 
  --destination-cidr-block 10.1.0.0/16 
  --vpc-peering-connection-id pcx-xxxxxxxx

# 라우팅 테이블에 경로 추가 (신 VPC → 구 VPC)
aws ec2 create-route 
  --route-table-id rtb-NEW789012 
  --destination-cidr-block 10.0.0.0/16 
  --vpc-peering-connection-id pcx-xxxxxxxx

3-2. Security Group 설정

구 VPC와 신 VPC 간 트래픽이 허용되도록 Security Group을 조정해야 합니다. 특히 ElastiCache(6379 포트)와 RDS(5432 또는 3306 포트)에 대한 인바운드 규칙을 양쪽 VPC의 CIDR에서 모두 허용해야 합니다.

# 신 VPC의 앱 서버 SG에서 구 VPC RDS로 접근 허용
aws ec2 authorize-security-group-ingress 
  --group-id sg-OLD-RDS 
  --protocol tcp 
  --port 5432 
  --cidr 10.1.0.0/16  # 신 VPC CIDR

# 신 VPC ElastiCache SG에서 구 VPC 앱 접근 허용
aws ec2 authorize-security-group-ingress 
  --group-id sg-OLD-REDIS 
  --protocol tcp 
  --port 6379 
  --cidr 10.1.0.0/16

3-3. 엔드포인트 및 DNS 확인

현재 애플리케이션이 사용 중인 모든 엔드포인트를 목록화합니다. 하드코딩된 IP 대신 DNS 이름을 사용하고 있다면 Route 53 CNAME 컷오버가 훨씬 수월합니다. 만약 IP로 직접 연결하고 있다면 이번 기회에 DNS 방식으로 전환하는 것을 강력히 권장합니다.

# 현재 엔드포인트 확인
aws elasticache describe-replication-groups 
  --replication-group-id my-redis-cluster 
  --query 'ReplicationGroups[].NodeGroups[].PrimaryEndpoint'

aws rds describe-db-instances 
  --db-instance-identifier my-db 
  --query 'DBInstances[].Endpoint'

4. ElastiCache Redis 무중단 전환

4-1. 신규 클러스터 생성

신규 VPC의 서브넷 그룹을 먼저 만들고, 동일한 스펙의 Redis 클러스터를 생성합니다. Parameter Group은 구 클러스터와 반드시 동일하게 맞춰야 합니다. maxmemory-policy, timeout, tcp-keepalive 등 설정이 다르면 캐시 동작 방식이 달라져 예측 불가능한 문제가 생깁니다.

# 신규 VPC용 서브넷 그룹 생성
aws elasticache create-cache-subnet-group 
  --cache-subnet-group-name my-redis-subnet-new 
  --cache-subnet-group-description "New VPC Redis Subnet Group" 
  --subnet-ids subnet-NEW1 subnet-NEW2

# 신규 Replication Group 생성
aws elasticache create-replication-group 
  --replication-group-id my-redis-new 
  --replication-group-description "New VPC Redis Cluster" 
  --cache-node-type cache.r6g.large 
  --engine redis 
  --engine-version 7.0.7 
  --num-cache-clusters 2 
  --cache-parameter-group-name my-redis-params 
  --cache-subnet-group-name my-redis-subnet-new 
  --security-group-ids sg-NEW-REDIS 
  --automatic-failover-enabled 
  --at-rest-encryption-enabled 
  --transit-encryption-enabled 
  --auth-token "your-auth-token" 
  --region ap-northeast-2

4-2. Dual-Write 전략으로 캐시 워밍

Redis는 영속 데이터 저장소가 아니기 때문에 “데이터 동기화”보다 캐시 워밍(Cache Warming)이 핵심입니다. 두 가지 방법을 상황에 따라 선택합니다:

방법 A: Dual-Write (권장)
애플리케이션 레벨에서 신구 Redis에 동시에 쓰는 방식입니다. 캐시가 점진적으로 채워지며 컷오버 시점에 캐시 히트율이 높아집니다.

# 애플리케이션 코드 예시 (Python)
import redis

redis_old = redis.Redis(host='old-redis.endpoint', port=6379, ssl=True)
redis_new = redis.Redis(host='new-redis.endpoint', port=6379, ssl=True)

def cache_set(key, value, ttl=3600):
    redis_old.setex(key, ttl, value)
    redis_new.setex(key, ttl, value)  # dual-write

def cache_get(key):
    # 읽기는 여전히 구 클러스터에서
    return redis_old.get(key)

방법 B: SCAN + RESTORE를 이용한 일괄 복사
Dual-write가 어려운 경우 redis-cli의 SCAN 명령으로 키를 순회하며 복사합니다. 단, 이 방법은 TTL이 만료된 키를 포함하거나 대용량 클러스터에서 성능 이슈가 있을 수 있습니다.

# redis-cli를 이용한 키 복사 (소규모 클러스터 한정)
redis-cli -h old-endpoint -a auth-token --scan --pattern "*" | while read key; do
  redis-cli -h old-endpoint -a auth-token --no-auth-warning DUMP "$key" | 
  redis-cli -h new-endpoint -a auth-token --no-auth-warning -x RESTORE "$key" 0
done

4-3. ElastiCache 컷오버

Dual-write를 충분히 운영한 뒤 (보통 24~48시간), 애플리케이션 설정을 신규 엔드포인트로 변경하고 Dual-write를 종료합니다. CloudWatch의 CacheMisses 지표를 모니터링하여 캐시 히트율이 안정적임을 확인합니다.

# CloudWatch에서 캐시 히트율 확인
aws cloudwatch get-metric-statistics 
  --namespace AWS/ElastiCache 
  --metric-name CacheHits 
  --dimensions Name=ReplicationGroupId,Value=my-redis-new 
  --start-time 2024-01-01T00:00:00Z 
  --end-time 2024-01-01T01:00:00Z 
  --period 300 
  --statistics Sum

5. RDS 무중단 전환

RDS 마이그레이션은 데이터 정합성이 생명입니다. 접근법은 크게 두 가지입니다: Read Replica PromoteAWS DMS(Database Migration Service). 각각의 선택 기준은 마지막 섹션에서 다룹니다.

RDS 마이그레이션 전, HikariCP 커넥션 풀 설정을 점검하는 것을 잊지 마세요. 컷오버 직후 신규 DB로 몰리는 연결 폭증을 커넥션 풀이 제대로 처리하지 못하면 타임아웃이 연쇄적으로 발생합니다.

5-1. 방법 A: Read Replica를 이용한 전환

동일 리전 내 VPC 간 이전이라면 Cross-VPC Read Replica를 활용할 수 있습니다. 구 RDS에서 Read Replica를 생성하되 신규 VPC의 서브넷에 배치하는 방식입니다.

# Step 1: 신규 VPC에 Read Replica 생성
aws rds create-db-instance-read-replica 
  --db-instance-identifier my-db-replica-new 
  --source-db-instance-identifier my-db-old 
  --db-instance-class db.r6g.xlarge 
  --db-subnet-group-name my-db-subnet-new 
  --vpc-security-group-ids sg-NEW-RDS 
  --publicly-accessible false 
  --region ap-northeast-2

# Step 2: Replica 동기화 상태 확인 (ReplicaLag이 0에 가까워야 함)
aws rds describe-db-instances 
  --db-instance-identifier my-db-replica-new 
  --query 'DBInstances[].StatusInfos'

# Step 3: 애플리케이션 Write 중단 또는 최소화 (짧은 maintenance window)
# Step 4: Replica Lag 0 확인 후 Promote
aws rds promote-read-replica 
  --db-instance-identifier my-db-replica-new 
  --backup-retention-period 7 
  --preferred-backup-window "03:00-04:00"

⚠️ 주의: promote-read-replica는 복제를 끊고 독립 Primary로 승격시킵니다. 이 시점 이후부터는 구 DB와 동기화가 끊기므로, Promote 전에 애플리케이션의 Write를 일시 중단하는 짧은 maintenance window가 필요합니다. 이 window를 최소화하는 것이 핵심입니다.

5-2. 방법 B: AWS DMS(Database Migration Service) 사용

DMS는 구 DB에서 신 DB로 CDC(Change Data Capture)를 이용해 실시간으로 변경분을 복제합니다. Read Replica가 불가능한 경우(예: 다른 리전, 다른 DB 엔진, RDS → Aurora 전환)에 유용합니다.

// DMS Task 생성 JSON (task-settings.json)
{
  "TargetMetadata": {
    "TargetSchema": "",
    "SupportLobs": true,
    "FullLobMode": false,
    "LobChunkSize": 64,
    "LimitedSizeLobMode": true,
    "LobMaxSize": 32768
  },
  "FullLoadSettings": {
    "TargetTablePrepMode": "DO_NOTHING",
    "CreatePkAfterFullLoad": false,
    "StopTaskCachedChangesApplied": false,
    "StopTaskCachedChangesNotApplied": false,
    "MaxFullLoadSubTasks": 8,
    "TransactionConsistencyTimeout": 600,
    "CommitRate": 50000
  },
  "CDCSettings": {
    "MessageFormat": "json"
  },
  "Logging": {
    "EnableLogging": true,
    "LogComponents": [
      {"Id": "SOURCE_UNLOAD", "Severity": "LOGGER_SEVERITY_DEFAULT"},
      {"Id": "TARGET_LOAD", "Severity": "LOGGER_SEVERITY_DEFAULT"},
      {"Id": "TASK_MANAGER", "Severity": "LOGGER_SEVERITY_DEFAULT"}
    ]
  }
}
# DMS Replication Task 생성
aws dms create-replication-task 
  --replication-task-identifier my-migration-task 
  --source-endpoint-arn arn:aws:dms:ap-northeast-2:123456789:endpoint:SOURCE 
  --target-endpoint-arn arn:aws:dms:ap-northeast-2:123456789:endpoint:TARGET 
  --replication-instance-arn arn:aws:dms:ap-northeast-2:123456789:rep:INSTANCE 
  --migration-type full-load-and-cdc 
  --table-mappings file://table-mappings.json 
  --replication-task-settings file://task-settings.json 
  --region ap-northeast-2

# Task 시작
aws dms start-replication-task 
  --replication-task-arn arn:aws:dms:ap-northeast-2:123456789:task:TASK_ARN 
  --start-replication-task-type start-replication

5-3. Route 53 CNAME 컷오버

컷오버 전에 TTL을 낮춰두는 것이 핵심입니다. 최소 24시간 전에 TTL을 60초 이하로 낮춰두면, 컷오버 후 1분 내에 대부분의 클라이언트가 새 IP를 바라보게 됩니다. 이를 통해 PostgreSQL 인덱스 쿼리 성능을 포함한 전반적인 DB 응답 지연을 최소화할 수 있습니다.

# Step 1: TTL 낮추기 (컷오버 24시간 전)
aws route53 change-resource-record-sets 
  --hosted-zone-id Z1234567890 
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "db.internal.example.com",
        "Type": "CNAME",
        "TTL": 60,
        "ResourceRecords": [{"Value": "my-db-old.xxx.ap-northeast-2.rds.amazonaws.com"}]
      }
    }]
  }'

# Step 2: 컷오버 - 신규 RDS 엔드포인트로 변경
aws route53 change-resource-record-sets 
  --hosted-zone-id Z1234567890 
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "db.internal.example.com",
        "Type": "CNAME",
        "TTL": 60,
        "ResourceRecords": [{"Value": "my-db-replica-new.xxx.ap-northeast-2.rds.amazonaws.com"}]
      }
    }]
  }'

# Step 3: 확인
aws route53 list-resource-record-sets 
  --hosted-zone-id Z1234567890 
  --query 'ResourceRecordSets[?Name==`db.internal.example.com.`]'

5-4. 제로 다운타임 체크리스트

  • ☑ Read Replica Lag이 0초 이하임을 확인 (컷오버 직전)
  • ☑ Route 53 TTL이 60초 이하로 설정됨 (최소 24시간 전)
  • ☑ 애플리케이션 커넥션 풀 설정이 신규 DB 스펙에 맞게 조정됨
  • ☑ Parameter Group이 구 DB와 동일하게 적용됨
  • ☑ CloudWatch 알람이 신규 DB에 연결됨
  • ☑ 컷오버 직후 에러율 및 지연 시간 모니터링 준비
  • ☑ 롤백 절차 팀 전체에 공유 및 드라이 런 완료

6. 롤백 플랜

아무리 준비를 잘 해도 예상치 못한 문제는 발생합니다. 롤백 절차를 미리 준비해두는 것이 진정한 무중단 마이그레이션의 완성입니다.

구 VPC 유지 기간

컷오버 후 최소 72시간은 구 리소스를 유지합니다. 이 기간 동안 신규 환경의 안정성을 면밀히 모니터링합니다. 72시간을 넘긴 후에도 이상이 없으면 구 리소스를 종료합니다.

롤백 트리거 기준

  • 에러율이 컷오버 전 대비 2배 이상 증가했을 때
  • DB 응답 시간(P99)이 500ms 이상으로 지속될 때
  • ElastiCache CacheMisses가 비정상적으로 높게 지속될 때
  • 애플리케이션 팀이 롤백을 요청할 때

롤백 절차

# Route 53 CNAME을 구 RDS 엔드포인트로 즉시 복원
aws route53 change-resource-record-sets 
  --hosted-zone-id Z1234567890 
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "db.internal.example.com",
        "Type": "CNAME",
        "TTL": 60,
        "ResourceRecords": [{"Value": "my-db-old.xxx.ap-northeast-2.rds.amazonaws.com"}]
      }
    }]
  }'

# Redis 애플리케이션 설정을 구 클러스터로 복원 (환경변수 또는 ConfigMap 변경)
kubectl set env deployment/my-app REDIS_HOST=old-redis.endpoint

7. 운영 팁

7-1. Parameter Group 동일성 보장

가장 흔하게 놓치는 부분이 Parameter Group입니다. ElastiCache의 경우 maxmemory-policy, activerehashing, hz 값이 다르면 메모리 퇴출 동작이 달라집니다. RDS의 경우 work_mem, shared_buffers, max_connections 등이 다르면 쿼리 성능과 연결 수에 차이가 생깁니다.

# ElastiCache Parameter Group 비교
aws elasticache describe-cache-parameters 
  --cache-parameter-group-name old-params 
  --query 'Parameters[].{Name:ParameterName,Value:ParameterValue}' > old_params.json

aws elasticache describe-cache-parameters 
  --cache-parameter-group-name new-params 
  --query 'Parameters[].{Name:ParameterName,Value:ParameterValue}' > new_params.json

diff old_params.json new_params.json

7-2. CloudWatch 알람 설정

컷오버 전에 신규 리소스에 대한 CloudWatch 알람을 설정합니다. 특히 DLQ(Dead Letter Queue)와 연동하면 알람 발생 시 자동 롤백 트리거를 구현할 수도 있습니다.

# RDS CPU 알람
aws cloudwatch put-metric-alarm 
  --alarm-name "rds-new-high-cpu" 
  --alarm-description "New RDS CPU over 80%" 
  --namespace AWS/RDS 
  --metric-name CPUUtilization 
  --dimensions Name=DBInstanceIdentifier,Value=my-db-replica-new 
  --statistic Average 
  --period 300 
  --threshold 80 
  --comparison-operator GreaterThanThreshold 
  --evaluation-periods 2 
  --alarm-actions arn:aws:sns:ap-northeast-2:123456789:my-alert-topic

# ElastiCache 캐시 미스 알람
aws cloudwatch put-metric-alarm 
  --alarm-name "elasticache-new-high-misses" 
  --alarm-description "New Redis Cache Miss spike" 
  --namespace AWS/ElastiCache 
  --metric-name CacheMisses 
  --dimensions Name=ReplicationGroupId,Value=my-redis-new 
  --statistic Sum 
  --period 60 
  --threshold 1000 
  --comparison-operator GreaterThanThreshold 
  --evaluation-periods 3 
  --alarm-actions arn:aws:sns:ap-northeast-2:123456789:my-alert-topic

# DLQ 메시지 수 알람 (SQS 사용 시)
aws cloudwatch put-metric-alarm 
  --alarm-name "sqs-dlq-messages" 
  --alarm-description "DLQ message count alert" 
  --namespace AWS/SQS 
  --metric-name ApproximateNumberOfMessagesVisible 
  --dimensions Name=QueueName,Value=my-dlq 
  --statistic Sum 
  --period 60 
  --threshold 1 
  --comparison-operator GreaterThanOrEqualToThreshold 
  --evaluation-periods 1 
  --alarm-actions arn:aws:sns:ap-northeast-2:123456789:my-alert-topic

7-3. 마이그레이션 전 스냅샷 백업

# RDS 수동 스냅샷 생성 (마이그레이션 직전)
aws rds create-db-snapshot 
  --db-instance-identifier my-db-old 
  --db-snapshot-identifier pre-migration-snapshot-$(date +%Y%m%d%H%M%S)

# ElastiCache 스냅샷 생성
aws elasticache create-snapshot 
  --replication-group-id my-redis-old 
  --snapshot-name pre-migration-redis-$(date +%Y%m%d%H%M%S)

8. 마무리: DMS vs Read Replica 선택 기준

RDS 마이그레이션에서 가장 많이 받는 질문이 “DMS를 써야 하나, Read Replica를 써야 하나”입니다. 다음 기준으로 판단하세요:

구분 Read Replica Promote AWS DMS
동일 엔진 (예: PostgreSQL → PostgreSQL) ✅ 권장 가능
엔진 변환 (예: MySQL → PostgreSQL) ❌ 불가 ✅ 필수
동일 리전 내 VPC 전환 ✅ 권장 (비용 저렴) 가능
리전 간 마이그레이션 Cross-Region Replica 가능 ✅ 더 유연
RDS → Aurora 전환 Aurora 전용 기능 있음 ✅ 권장
비용 Replica 인스턴스 비용 DMS 인스턴스 + 데이터 전송 비용
복잡도 낮음 높음 (설정, 모니터링)
Maintenance Window 필요 최소 (수십 초) 거의 불필요

결론: 동일 엔진, 동일 리전이라면 Read Replica Promote가 단순하고 비용 효율적입니다. 엔진이 다르거나 더 복잡한 시나리오라면 DMS가 정답입니다.


마치며

AWS ElastiCache Redis와 RDS의 VPC 전환은 분명히 까다롭지만, 올바른 전략과 충분한 사전 준비, 그리고 단계별 실행을 따른다면 서비스 중단 없이 완료할 수 있습니다. 핵심은 구 환경을 유지한 채 신규 환경을 완성하고 점진적으로 트래픽을 이동시키는 Blue/Green 접근법, 그리고 언제든 롤백할 수 있는 준비입니다.

이 가이드가 VPC 마이그레이션을 앞둔 엔지니어들에게 실질적인 도움이 되길 바랍니다. 질문이나 경험을 댓글로 공유해주세요!

관련 글