양파개발자 실바의 블로그

AWS RDS 확장 시나리오: 다운타임 기준으로 고르는 스케일업·스케일아웃·DB 분리

서비스가 커지면 반드시 “DB가 못 버틴다”는 순간이 옵니다. 이때 쓸 수 있는 카드는 사실 세 장뿐입니다.

  1. 위로 키우기(Scale-Up) — 인스턴스를 더 좋은 사양으로 교체
  2. 옆으로 늘리기(Scale-Out) — 읽기 전용 복제본을 추가해 조회 부하 분산
  3. 쪼개기(Decoupling) — 특정 서비스의 DB를 별도 RDS로 분리

문제는 “어떤 카드를 쓸까”가 아니라 “얼마의 서비스 중단을 감당할 수 있나” 입니다. 같은 스케일업도 점검 공지를 걸 수 있으면 5분짜리 작업이지만, 상시 가동 서비스라면 Multi-AZ나 Blue/Green을 동원해 절차가 완전히 달라집니다. 이 글은 세 가지 방식을 다운타임 기준으로 정리한 실전 시나리오집입니다.


1. 먼저 병목의 종류를 구분한다

방식을 고르기 전에 지금 부족한 게 무엇인지 확정해야 합니다. 여기서 틀리면 돈만 쓰고 성능은 그대로입니다.

증상 확인 지표 (CloudWatch) 처방
CPU가 상시 70~80% 이상 CPUUtilization 스케일업
메모리 부족으로 디스크 I/O 폭증 FreeableMemory 하락 + ReadIOPS 상승 스케일업
디스크 처리량이 인스턴스 한계에 붙음 ReadThroughput / WriteThroughput 스케일업 (EBS 대역폭은 인스턴스 타입에 종속)
조회 쿼리는 많은데 쓰기는 여유 ReadIOPSWriteIOPS 스케일아웃 (Read Replica)
특정 서비스가 커넥션·자원을 독점 DatabaseConnections, Performance Insights의 DB별 부하 DB 분리

두 가지 흔한 착각을 먼저 걷어냅니다.

  • 스토리지 용량 부족은 스케일업이 아닙니다. gp3 볼륨 크기 확장은 온라인으로 처리되며 재부팅이 필요 없습니다. 인스턴스 타입 변경과 완전히 별개 작업입니다.
  • 쿼리 튜닝이 먼저입니다. 인덱스 하나 없어서 Full Scan 도는 쿼리 때문에 CPU가 100%인 상황이라면, 인스턴스를 두 배로 키워도 몇 주 뒤 같은 문제가 반복됩니다. 확장은 튜닝으로 더 이상 줄일 게 없을 때 쓰는 카드입니다.

2. 공용 RDS 스케일업 (Scale-Up)

대상: CPU, RAM, EBS 대역폭 등 단일 인스턴스의 컴퓨팅 자원이 부족할 때 (예: db.t4g.smalldb.m6g.large)

인스턴스 타입 변경은 다른 하드웨어로 DB를 옮겨 태우는 작업입니다. 그래서 원칙적으로 재부팅이 발생하고, 다운타임을 어디까지 줄일 수 있는지가 시나리오를 갈라놓습니다.

2-1. Scenario A. 단기 다운타임 허용 (약 5~10분 중단)

  • 적용 시점: 서비스 초기이거나, 야간 정기 점검 시간을 확보할 수 있을 때
  • 절차:
    1. 테라폼 코드의 instance_class 변수 수정
    2. 점검 공지 후 트래픽 차단, 또는 유지관리 시간대(maintenance window)에 terraform apply 실행
    3. 인스턴스 상태가 available로 돌아온 뒤 애플리케이션 커넥션 풀 재기동
  • 다운타임 원인: 인스턴스 타입 변경 시 재부팅이 발생합니다. 실제 소요는 보통 5~10분이며, DB 크기보다 인스턴스 프로비저닝 시간에 좌우됩니다.

💡 3단계를 빼먹으면 스케일업 후에도 앱이 죽은 커넥션을 붙잡고 에러를 내는 경우가 있습니다. 커넥션 풀은 재기동하거나 헬스체크 옵션을 켜 두는 것이 안전합니다.

2-2. Scenario B. Multi-AZ Failover 활용 (수십 초 순간 절체)

  • 적용 시점: 운영 서비스가 상시 가동 중이라 10분 이상 중단을 허용할 수 없을 때
  • 절차:
    1. Multi-AZ 활성화: 테라폼에서 multi_az = true 적용. 대기(Standby) 인스턴스 생성은 백그라운드에서 진행되므로 서비스 영향이 없습니다.
    2. 스케일업 실행: instance_class를 상위 타입으로 수정하여 적용
    3. AWS의 순차 적용: AWS가 Standby를 먼저 스케일업한 뒤 Failover를 일으켜 새 Primary로 전환하고, 이후 구 Primary를 스케일업합니다.
  • 다운타임 원인: Failover 시 DNS 레코드가 새 인스턴스를 가리키도록 교체되면서 기존 커넥션이 전부 끊깁니다. AWS 문서 기준 Failover는 보통 60~120초가 걸립니다.

Multi-AZ가 왜 다운타임을 줄이나? Multi-AZ는 다른 가용영역에 동기 복제되는 예비 인스턴스를 항상 띄워 둡니다. 스케일업할 대상이 “지금 서비스 중인 서버”가 아니라 “놀고 있는 예비 서버”이므로, 무거운 작업을 서비스 영향 없이 끝낸 뒤 마지막에 스위치만 넘기는 구조입니다.

반드시 챙길 것:

  • 앱단 DB 재연결(Retry) 로직이 없으면 절체 순간 에러가 사용자에게 그대로 노출됩니다.
  • JVM 환경은 DNS TTL을 60초 이하로 설정해야 합니다. JVM은 DNS 조회 결과를 무기한 캐싱하는 설정이 기본인 경우가 있어, Failover 후에도 사라진 구 인스턴스 IP로 계속 접속을 시도합니다.
  • Multi-AZ는 동기 복제 비용 때문에 쓰기 지연이 다소 늘고, 요금은 약 2배가 됩니다. Standby는 읽기 트래픽을 받지 못하므로 스케일아웃 용도로 착각하면 안 됩니다.

2-3. Scenario C. Blue/Green Deployments (스위치오버 1분 이내)

인스턴스 타입만 바꾸는 게 아니라 엔진 버전 업그레이드나 파라미터 그룹 변경까지 같이 묶어야 할 때 가장 안전한 선택지입니다.

  • 개념: 운영 환경(Blue)의 복제본인 스테이징 환경(Green)을 만들고, 두 환경을 실시간 동기화한 상태로 둡니다. Green에서 변경 사항을 충분히 테스트한 뒤 스위치오버하면 Green이 그대로 운영이 됩니다. 엔드포인트 이름까지 승계되므로 앱 설정 변경이 필요 없습니다.
  • 절차:
    1. Blue/Green 배포 생성 (이 시점에 엔진 버전 업그레이드, 다른 파라미터 그룹 지정 가능)
    2. Green의 instance_class를 상위 타입으로 변경
    3. Green에서 성능·호환성 검증 (Green은 기본적으로 읽기 전용)
    4. 복제 지연 0 확인 후 스위치오버 실행
  • 다운타임: AWS 문서 기준 보통 1분 이내, 데이터 손실 없음. RDS Proxy나 smart driver를 함께 쓰면 DNS 전파를 기다리지 않고 커넥션을 재지정해 중단을 더 줄일 수 있습니다.

주의할 제약:

  • RDS for MySQL / MariaDB / PostgreSQL만 지원합니다.
  • CloudFormation과 Multi-AZ DB 클러스터, 크로스 리전 리드 리플리카는 미지원입니다. 테라폼으로 관리하는 환경이라면 스위치오버 후 인스턴스 이름이 -old1로 밀리고 리소스 ID가 바뀌므로, state 정리 계획을 미리 세워야 합니다.
  • PostgreSQL이 논리 복제(logical replication) 로 동작하는 경우 제약이 더 많습니다. CREATE TABLE 같은 DDL은 Green으로 복제되지 않고(감지되면 복제 degraded 상태가 되어 재생성 필요), Unlogged 테이블·라지 오브젝트도 복제되지 않으며, PK 없는 테이블은 UPDATE/DELETE가 막힙니다. 배포 기간 중 스키마 마이그레이션을 멈출 수 있는지 먼저 확인하세요.
  • 쓰기량이 매우 많은 워크로드는 Green의 적용 프로세스가 단일 스레드라 복제 지연이 쌓일 수 있습니다. 이 경우 AWS는 DMS 사용을 권고합니다.

3. 공용 RDS 스케일아웃 (Scale-Out)

대상: 읽기(Read) 트래픽 비중이 매우 높아 조회 성능 병목이 발생할 때 (Read Replica 추가)

3-1. Read Replica와 복제 지연 이해하기

Read Replica는 Primary DB의 변경 내용을 계속 따라 복사받는 읽기 전용 복제 인스턴스입니다. 목록 조회, 검색, 통계 같은 조회 쿼리를 여기로 넘기면 Primary는 쓰기에만 집중할 수 있습니다.

핵심은 복제 지연(Replication Lag) 입니다. 복제는 비동기이므로, Primary에 쓴 데이터가 Replica에 도착하기까지 보통 수십 밀리초, 부하가 크면 수 초까지 늦어질 수 있습니다. 그래서 이런 버그가 생깁니다.

1. 사용자가 게시글 작성  → Primary에 INSERT
2. 작성 완료 후 목록 조회 → Replica에서 SELECT
3. 방금 쓴 글이 목록에 없음 (복제가 아직 도착 안 함)

대응 원칙: 쓰기 직후 곧바로 다시 읽는 흐름(작성 후 상세 페이지 이동, 결제 직후 상태 확인 등)은 Replica로 보내지 말고 Primary로 읽습니다. ReplicaLag 지표는 항상 모니터링·알람 대상에 올려 두세요.

3-2. Scenario A. 무중단 (Zero-Downtime)

  • 적용 시점: 조회 트래픽을 분산해야 할 때
  • 절차:
    1. 테라폼 aws_db_instance 리소스에 replicate_source_db 인자를 지정해 Read Replica 생성 (기존 Primary에는 영향 없음)
    2. 애플리케이션 DataSource 설정에서 Write(Primary 엔드포인트)와 Read(Replica 엔드포인트) 분리 — TypeORM의 replication, Prisma의 read replicas extension, Django의 DB router 등
    3. 앱 무중단 롤링 배포
    4. ReplicaLag 알람 설정
  • 다운타임: 없음. Primary를 건드리지 않고 읽기 엔드포인트만 추가되는 구조입니다.

⚠️ 쓰기 스케일아웃은 안 됩니다. PostgreSQL은 데이터 정합성을 위해 쓰기를 받는 노드가 하나여야 합니다(단일 Writer). 여러 노드에서 동시에 쓰면 같은 행에 대한 충돌을 해결할 방법이 없기 때문입니다. 따라서 쓰기 병목은 Read Replica로 해결되지 않으며, 스케일업 또는 아래의 DB 분리로 풀어야 합니다.


4. 특정 서비스 별도 RDS 분리 (Database Decoupling)

대상: 특정 서비스(예: order_db, user_db)의 트래픽이 폭증해 공용 RDS 자원을 독점하거나, 보안·독립 확장 요구가 생겼을 때

4-1. 분리하면 무엇을 잃는가 (먼저 확인)

분리는 되돌리기 가장 어려운 카드입니다. 실행 전에 이 세 가지를 확인하세요.

  • DB 간 JOIN이 불가능해집니다. ordersusers가 다른 RDS로 갈라지면 JOIN으로 한 번에 조회했던 쿼리를 애플리케이션 레벨에서 두 번 조회해 조합해야 합니다. 현재 코드에서 분리 대상 테이블을 참조하는 JOIN을 전부 찾아 두세요.
  • 트랜잭션 경계가 깨집니다. 주문 생성과 사용자 포인트 차감을 한 트랜잭션으로 묶던 로직은 더 이상 원자적이지 않습니다. 실패 시 보상 처리(compensating transaction)를 직접 구현해야 합니다.
  • FK 제약이 사라집니다. DB가 보장해 줬던 참조 정합성을 애플리케이션이 책임져야 합니다.

이 비용을 감당할 수 없다면, 분리보다 스케일업이 훨씬 저렴한 해법입니다.

4-2. Scenario A. 점검을 통한 안전한 이관 (30분 ~ 2시간 중단)

  • 적용 시점: 데이터 크기가 비교적 작고, 새벽 점검 공지가 가능할 때
  • 절차:
    1. 신규 RDS 생성: 분리 대상 전용 RDS를 테라폼으로 사전 생성 (파라미터 그룹·확장 기능 동일하게 맞춤)
    2. 리허설: 스냅샷 복원본으로 덤프·복원을 한 번 연습해 실제 소요 시간을 측정하고 점검 시간을 산정
    3. 서비스 점검 전환: 대상 서비스의 Write/Read 전면 차단 (점검 페이지)
    4. 덤프 & 복원: pg_dump로 대상 DB 추출 후 신규 RDS에 pg_restore
    5. 환경변수 변경: 해당 앱의 DB_HOST·자격증명을 신규 RDS로 교체하여 재배포
    6. 검증 및 점검 해제: 주요 테이블 건수·시퀀스 값 확인 후 서비스 재개
  • 다운타임 원인: 덤프·복원 시간 + 앱 배포 시간 전체가 중단 시간입니다.
# 특정 DB만 추출 (병렬 덤프로 시간 단축)
pg_dump -h old-rds.xxx.rds.amazonaws.com -U admin \
  -Fd -j 4 -f ./order_db_dump order_db

# 신규 RDS로 복원
pg_restore -h new-rds.xxx.rds.amazonaws.com -U admin \
  -d order_db -j 4 ./order_db_dump

💡 -Fd -j 4는 디렉터리 포맷 + 4개 병렬 처리입니다. 수십 GB급에서는 단일 스레드 덤프보다 체감 속도가 크게 차이 납니다. 복원 후에는 시퀀스 현재값(setval)과 권한(GRANT)이 제대로 넘어왔는지 반드시 확인하세요.

4-3. Scenario B. AWS DMS 기반 CDC 활용 (수초 ~ 수분 중단)

  • 적용 시점: 24시간 가동되어 장시간 점검이 불가능하거나, 데이터가 커서 덤프·복원에 수 시간이 걸릴 때
  • 절차:
    1. 신규 RDS 생성: 분리 전용 독립 RDS 생성
    2. 초기 복사 & CDC 동기화: AWS DMS(Database Migration Service)로 대상 DB/테이블을 신규 RDS로 복제 (Full Load + Ongoing Replication)
    3. 동기화 지연(Lag) 0 확인: CDCLatencySource/CDCLatencyTarget 지표로 양쪽 데이터 일치 확인
    4. Cut-over(전환):
      • 대상 서비스 트래픽을 아주 짧게 차단 (수초~수분)
      • DMS Task 중지 및 잔여 변경분 완전 반영
      • 앱의 DB_HOST를 신규 RDS로 교체하여 즉시 배포
    5. 검증 후 트래픽 개방, 안정화 확인 뒤 원본 DB 정리
  • 다운타임 원인: 최종 Cut-over 순간 트래픽 정리를 위한 미세 중단만 발생합니다.

CDC란? Change Data Capture, 즉 변경 데이터 캡처입니다. 전체를 다시 복사하는 대신 원본에 발생한 INSERT/UPDATE/DELETE만 실시간으로 뽑아 대상에 반영하는 방식입니다. PostgreSQL에서는 WAL(트랜잭션 로그)을 논리 복제 슬롯으로 읽어 처리합니다. 덕분에 “며칠 전 전체 복사 → 그 뒤 변경분만 계속 따라붙이기 → 마지막에 스위치”라는 무중단 이관이 가능해집니다.

DMS 사용 시 주의:

  • 원본 RDS의 파라미터에서 rds.logical_replication = 1 설정과 재부팅이 필요합니다. 이 재부팅은 점검을 걸 수 있는 시점에 미리 처리해 두세요.
  • DMS는 데이터만 옮깁니다. 인덱스, 시퀀스, 트리거, 뷰, 권한은 온전히 넘어가지 않으므로 스키마는 pg_dump --schema-only로 미리 만들어 두는 것이 정석입니다.
  • PK가 없는 테이블은 CDC로 UPDATE/DELETE가 반영되지 않습니다. 사전에 PK 유무를 전수 점검하세요.
  • 복제 슬롯이 살아 있는 동안 원본의 WAL이 계속 쌓입니다. Task를 방치하면 원본 디스크가 차오를 수 있으니 끝난 슬롯은 반드시 정리합니다.

5. 종합 비교 가이드

시나리오 권장 방식 예상 다운타임 이럴 때는 고르지 말 것
스케일업 (점검) 점검 시간에 instance_class 변경 5~10분 점검 공지가 불가능한 상시 서비스
스케일업 (최소 중단) Multi-AZ 전환 후 타입 변경 60~120초 (Failover) 앱에 DB 재연결 로직이 없을 때
스케일업 (검증 필요) Blue/Green Deployments 1분 이내 (스위치오버) 배포 기간에 DDL을 멈출 수 없을 때, 쓰기량이 극단적으로 많을 때
스케일아웃 Read Replica 추가 무중단 (0초) 병목이 쓰기일 때는 효과 없음
DB 분리 (점검) pg_dump / pg_restore 30분 ~ 수 시간 데이터가 수백 GB 이상이라 점검 시간에 못 끝낼 때
DB 분리 (최소 중단) AWS DMS (CDC) 수초 ~ 수분 PK 없는 테이블이 많거나 사전 준비 리소스가 없을 때

의사결정 순서로 정리하면 이렇습니다.

쿼리 튜닝으로 해결되는가?  → YES: 튜닝으로 끝낸다
      ↓ NO
읽기만 부족한가?           → YES: Read Replica (무중단)
      ↓ NO
단일 인스턴스 자원 부족?   → YES: 점검 가능하면 A, 아니면 Multi-AZ 또는 Blue/Green
      ↓ NO (한 서비스가 자원 독점)
JOIN·트랜잭션 분리 비용 감당 가능? → YES: DB 분리 (점검 또는 DMS)
                                   → NO: 스케일업으로 시간 벌고 아키텍처 재설계

6. 실행 전 체크리스트

어떤 방식을 택하든 아래는 공통입니다.

  • 수동 스냅샷 생성 — 자동 백업만 믿지 말고 작업 직전 스냅샷을 남깁니다.
  • 롤백 플랜 문서화 — “실패하면 무엇을 어떤 순서로 되돌리는가”를 미리 적어 둡니다. 특히 DB 분리는 원본을 즉시 삭제하지 않고 며칠 유지합니다.
  • 리허설 — 스냅샷 복원본으로 한 번 연습해 실제 소요 시간을 측정합니다. 예상 시간은 대체로 틀립니다.
  • 커넥션 풀 재기동 계획 — 인스턴스 교체·Failover 후 죽은 커넥션이 남지 않도록 합니다.
  • 앱단 재시도 로직 확인 — 절체 시점의 일시적 커넥션 오류를 흡수할 수 있는지 확인합니다.
  • 점검 공지 및 담당자 배치 — 시작·종료 시각, 영향 범위, 롤백 판단 기준과 결정권자를 정합니다.
  • 작업 후 지표 확인CPUUtilization, ReplicaLag, DatabaseConnections를 최소 하루 이상 관찰합니다.

핵심요약

  1. 선택 기준은 기술이 아니라 허용 가능한 다운타임입니다. 병목 종류(CPU/읽기/특정 서비스 독점)를 먼저 확정하세요.
  2. 스케일업은 점검 가능하면 5~10분, 상시 서비스라면 Multi-AZ Failover(60~120초)나 Blue/Green(1분 이내)을 씁니다. 앱단 재연결 로직이 전제 조건입니다.
  3. 스케일아웃(Read Replica) 은 무중단이지만 읽기 전용입니다. 복제 지연 때문에 “쓰고 바로 읽는” 흐름은 Primary로 보내야 합니다.
  4. DB 분리pg_dump/pg_restore(30분~수 시간)와 DMS CDC(수초~수분) 중에서 고릅니다. 다만 JOIN·트랜잭션 경계를 잃는 비용이 크므로 마지막 카드로 두세요.
  5. 무엇을 하든 스냅샷·롤백 플랜·리허설 세 가지는 생략하지 않습니다.