아키텍처 케이스 · 2
읽기 부하로 데이터베이스가 죽었다
시험이 가장 집요하게 노리는 혼동 쌍이 여기에 있습니다 — Multi-AZ는 가용성을 위한 것이고, 읽기 전용 복제본은 읽기 확장을 위한 것입니다. 둘은 서로를 대체하지 않습니다. 이 케이스는 같은 장애에 네 가지 처방을 대 보고, 어느 것이 실제로 부하를 덜어 주는지 판정합니다.
학습 목표
- Multi-AZ 배포가 읽기 부하를 덜어 주지 않는 이유를 복제 방식과 엔드포인트로 설명할 수 있습니다.
- 읽기 전용 복제본과 캐시 계층 중 어느 것이 근본 처방인지 쿼리 패턴으로 판단할 수 있습니다.
- 복제 지연과 캐시 신선도가 허용되는 쿼리와 허용되지 않는 쿼리를 구분해 경로를 나눌 수 있습니다.
- 연결 수 고갈이 원인일 때 처방이 달라진다는 점을 설명할 수 있습니다.
- 시험 문항에서 "가용성" 신호와 "읽기 성능" 신호를 구분해 선택지를 소거할 수 있습니다.
상황
전자상거래 서비스의 상품 상세 페이지가 Amazon RDS for MySQL 단일 인스턴스를 씁니다. Multi-AZ는 켜져 있지 않고, 읽기 전용 복제본도 없습니다. 애플리케이션 서버는 데이터베이스 엔드포인트 하나만 알고 있으며, 모든 쿼리가 그 엔드포인트로 갑니다.
지난 금요일 프로모션이 시작되자 다음 순서로 장애가 진행됐습니다.
- 상품 상세 페이지 조회가 평소의 여러 배로 늘었습니다.
- 데이터베이스 CPU 사용률이 100%에 붙고 쿼리 지연이 올라갔습니다.
- 애플리케이션의 커넥션 풀이 반환되지 않은 연결로 가득 찼습니다.
- 새 요청이 연결을 얻지 못해 쓰기 경로(주문 생성)까지 함께 실패했습니다.
쿼리 로그를 보면 읽기가 전체 요청의 대부분이고, 그중에서도 같은 상품 몇 백 건에 대한 동일한 조회가 반복됩니다. 쓰기는 주문과 재고 차감 정도이며 양이 많지 않습니다.
요구사항과 제약
| 구분 | 내용 | 후보 필터 |
|---|---|---|
| 요구 1 | 읽기 처리량을 수 배 이상 늘린다 | 프라이머리에 도달하는 요청 자체를 줄이는 안만 통과 |
| 요구 2 | 읽기 폭주가 쓰기 경로를 무너뜨리지 않게 한다 | 읽기와 쓰기의 자원을 분리하는 안이 유리 |
| 요구 3 | 단일 인스턴스 구성의 가용 영역 장애 취약성도 해소한다 | 가용성 처방이 별도로 필요하다 — 읽기 처방과 섞지 않는다 |
| 제약 1 | 상품 정보는 수 초의 지연을 허용한다 | 복제 지연과 캐시 신선도를 감수할 수 있는 쿼리가 있다 |
| 제약 2 | 재고 수량과 주문 조회는 지연을 허용하지 않는다 | 이 쿼리들은 프라이머리로 고정해야 한다 |
| 제약 3 | 다음 프로모션까지 시간이 없다. 큰 리팩터링은 어렵다 | 코드 변경량이 판단에 들어간다 |
배경 개념은 10장 RDS와 관계형 데이터베이스와 11장 NoSQL · 캐시 · 분석 DB에 있습니다.
후보 설계안 비교
| 항목 | A · 인스턴스 클래스 확대 | B · Multi-AZ 활성화 | C · 읽기 전용 복제본 | D · ElastiCache 캐시 계층 |
|---|---|---|---|---|
| 복제 방식 | 해당 없음 | 동기(synchronous) | 비동기(asynchronous) | 복제가 아니라 별도 저장소 |
| 읽기 트래픽을 받는가 | 같은 인스턴스가 다 받는다 | 대기 인스턴스는 받지 않는다 | 받는다 — 별도 읽기 엔드포인트 | 받는다 — 데이터베이스에 도달하기 전에 응답 |
| 읽기 부하 경감 | 상한이 인스턴스 크기에 묶인다 | 없음 | 복제본 수만큼 분산 | 반복 조회가 많을수록 가장 큼 |
| 가용성 기여 | 없음 | 가용 영역 장애 시 자동 장애 조치 | 수동 승격이 필요하다 | 데이터베이스 가용성과 무관 |
| 데이터 신선도 | 항상 최신 | 항상 최신 | 복제 지연만큼 뒤처질 수 있다 | TTL과 무효화 전략에 달렸다 |
| 코드 변경 | 없음 | 없음 — 엔드포인트가 그대로 | 읽기 쿼리를 읽기 엔드포인트로 보내는 분기 | 캐시 조회·적재 로직 추가 |
| 판정 | 임시방편 — 상한이 곧 다시 온다 | 채택하되 요구 3(가용성)에 대해서만 | 채택 — 요구 1·2의 1차 처방 | 채택 — 반복 조회에 대한 근본 처방 |
선택과 근거
B + C + D를 서로 다른 요구에 각각 배정합니다. 하나로 다 해결하려는 시도가 이 케이스에서 가장 흔한 오답입니다.
| 순서 | 처방 | 해결하는 요구 | 왜 이 순서인가 |
|---|---|---|---|
| 1 | Multi-AZ 활성화 | 요구 3 (가용 영역 장애) | 코드 변경이 없고 엔드포인트가 유지되므로 가장 먼저 넣어도 위험이 없다 |
| 2 | 읽기 전용 복제본 추가 + 지연 허용 읽기를 읽기 엔드포인트로 | 요구 1·2 | 애플리케이션 변경이 "엔드포인트 분기" 한 곳으로 끝난다. 제약 3에 맞는다 |
| 3 | 반복 조회를 ElastiCache로 흡수 | 요구 1의 근본 처방 | 효과가 가장 크지만 캐시 무효화 설계가 필요하므로 프로모션 이후에 넣는다 |
| 4 | 연결이 다시 병목이면 RDS Proxy 검토 | 요구 2의 잔여 위험 | CPU를 먼저 해결한 뒤에도 연결 수가 문제인지 확인하고 판단한다 |
쿼리를 두 갈래로 나눈다
제약 1과 제약 2가 이 설계의 핵심입니다. 모든 읽기를 복제본으로 보내면 안 됩니다. 읽기 전용 복제본은 비동기로 복제되므로 프라이머리보다 뒤처진 데이터를 반환할 수 있습니다.
| 쿼리 | 지연 허용 | 경로 |
|---|---|---|
| 상품 상세 · 카테고리 목록 · 리뷰 | 허용 | 캐시 → 없으면 읽기 엔드포인트 |
| 재고 수량 확인 | 불가 | 프라이머리(기본 엔드포인트) 고정 |
| 방금 생성한 주문 조회 | 불가 | 프라이머리 고정 — 쓰기 직후 읽기는 복제 지연에 그대로 노출된다 |
| 관리자 리포트 · 집계 | 허용 | 읽기 엔드포인트 — 무거운 쿼리를 프라이머리에서 떼어 낸다 |
1단계 · Multi-AZ 활성화
aws rds modify-db-instance \
--db-instance-identifier shop-prod \
--multi-az \
--apply-immediately
# 적용 상태와 대기 가용 영역을 확인합니다.
aws rds describe-db-instances \
--db-instance-identifier shop-prod \
--query 'DBInstances[0].[MultiAZ,AvailabilityZone,SecondaryAvailabilityZone,Endpoint.Address]' \
--output table
엔드포인트 주소는 바뀌지 않습니다. 장애 조치가 일어나면 같은 이름이 대기 인스턴스를 가리키도록 DNS가 전환됩니다. 그래서 애플리케이션 설정을 고칠 필요가 없습니다.
2단계 · 읽기 전용 복제본
aws rds create-db-instance-read-replica \
--db-instance-identifier shop-prod-read-1 \
--source-db-instance-identifier shop-prod
# 복제본은 자기만의 엔드포인트를 가집니다. 이 주소로 읽기 쿼리를 보냅니다.
aws rds describe-db-instances \
--db-instance-identifier shop-prod-read-1 \
--query 'DBInstances[0].[DBInstanceStatus,Endpoint.Address]' \
--output table
복제본 개수에는 상한이 있습니다. MySQL · MariaDB · PostgreSQL은 인스턴스당 최대 15개, Oracle과 SQL Server는 5개, Db2는 3개입니다. 복제본은 다른 가용 영역이나 다른 리전에도 만들 수 있어 재해 복구 구성의 재료로도 쓰입니다 (케이스 8 · 리전 장애에 대비한 DR을 설계하라).
3단계 · 캐시 계층
반복 조회가 지배적이라는 관측이 캐시를 근본 처방으로 만듭니다. 같은 상품 한 건을 1초에 수백 번 조회한다면, 복제본을 몇 개 붙이든 같은 결과를 매번 다시 계산하고 있는 셈입니다.
aws elasticache create-replication-group \
--replication-group-id shop-cache \
--replication-group-description "product detail cache" \
--cache-node-type cache.t3.micro \
--num-cache-clusters 2 \
--automatic-failover-enabled \
--multi-az-enabled \
--cache-subnet-group-name shop-cache-subnets \
--security-group-ids sg-0123456789abcdef0
엔진 선택 기준은 단순합니다. 복제 · 자동 장애 조치 · 스냅샷이 필요하면 복제 그룹을 만들 수 있는 엔진(Valkey 또는 Redis OSS)을 고릅니다. Memcached는 다중 스레드로 단순한 키-값 캐시를 처리하지만 복제 그룹과 자동 장애 조치를 제공하지 않습니다. 시험에서 "캐시가 죽어도 데이터가 남아야 한다"거나 "정렬된 자료 구조가 필요하다"는 신호가 있으면 Memcached는 소거 대상입니다.
캐시 채우기 전략과 무효화
| 전략 | 동작 | 장점 | 단점 |
|---|---|---|---|
| 지연 로딩(lazy loading) | 캐시에 없으면 데이터베이스에서 읽어 캐시에 넣는다 | 실제로 요청된 데이터만 캐시에 남는다 | 첫 요청은 항상 느리고, 캐시가 비면 부하가 한꺼번에 몰린다 |
| 라이트 스루(write-through) | 쓰기 시점에 데이터베이스와 캐시를 함께 갱신한다 | 캐시가 항상 최신이다 | 읽히지 않는 데이터까지 캐시에 쌓이고, 쓰기 경로가 느려진다 |
| TTL 병행 | 두 전략 어느 쪽이든 만료 시간을 함께 둔다 | 무효화를 놓쳐도 언젠가 정정된다 | TTL 동안은 낡은 값이 보인다 |
이 케이스에서는 상품 정보에 지연 로딩과 짧은 TTL을 함께 쓰고, 가격 변경처럼 즉시 반영이 필요한 쓰기에서만 해당 키를 명시적으로 삭제합니다. 재고 수량은 캐시에 넣지 않습니다 — 제약 2 때문입니다.
비용과 운영에 미치는 영향
| 처방 | 추가되는 비용 항목 | 추가되는 운영 항목 | 얻는 것 |
|---|---|---|---|
| 인스턴스 클래스 확대 | 더 큰 인스턴스 요금으로 대체 | 변경 시 재시작 계획 | 일시적 여유. 상한은 그대로 |
| Multi-AZ | 대기 인스턴스와 가용 영역 간 복제 트래픽 | 장애 조치 시나리오 점검 | 가용성. 읽기 처리량은 그대로 |
| 읽기 전용 복제본 | 복제본 인스턴스 수만큼. 교차 리전이면 리전 간 전송 | 복제 지연 감시, 승격 절차 문서화 | 읽기 처리량 분산 |
| ElastiCache | 캐시 노드 | 무효화 설계, 캐시 적중률 감시, 콜드 스타트 대비 | 가장 큰 부하 경감과 가장 낮은 응답 지연 |
| RDS Proxy | 프록시 사용 항목 | 연결 고정(pinning) 여부 확인 | 연결 수 병목 완화, 장애 조치 시 연결 전환 단축 |
설계 체크리스트
진단할 때
설계할 때
관련 시험 포인트
| 문제문의 신호 | 고르는 것 | 소거하는 것 |
|---|---|---|
| 읽기가 많아 느리다 / 리포팅 쿼리가 무겁다 | 읽기 전용 복제본 | Multi-AZ |
| 가용 영역 장애를 견뎌야 한다 / 계획된 유지 관리 중단을 줄여야 한다 | Multi-AZ | 읽기 전용 복제본 |
| 같은 데이터를 반복 조회한다 / 밀리초 단위 응답이 필요하다 | ElastiCache | 인스턴스 클래스 확대 |
without changing application code |
Multi-AZ, RDS Proxy, 인스턴스 확대 | 캐시 계층 도입(코드 변경이 필요하다) |
| Lambda 동시 실행이 늘어 연결이 고갈된다 | Amazon RDS Proxy | 복제본 추가(연결 문제를 풀지 못한다) |
| DynamoDB 앞에 캐시가 필요하다 | DynamoDB Accelerator(DAX) | ElastiCache(가능하지만 코드 변경이 더 크다) |
| 다른 리전에서도 읽어야 한다 | 교차 리전 읽기 전용 복제본, Aurora 글로벌 데이터베이스 | Multi-AZ(같은 리전 안의 구성이다) |
이어서 볼 곳
- 10장 · RDS와 관계형 데이터베이스 Multi-AZ와 읽기 전용 복제본의 복제 방식, 엔드포인트 차이, Aurora 클러스터 구조를 다룹니다.
- 11장 · NoSQL · 캐시 · 분석 DB ElastiCache 엔진 선택과 캐시 채우기 전략, DAX와의 역할 구분을 정리합니다.
- 실습 4 · RDS Multi-AZ와 읽기 전용 복제본 장애 조치를 직접 일으켜 엔드포인트가 어떻게 움직이는지 관찰합니다.
- 케이스 7 · 트래픽 급증에 스케일아웃이 못 따라갔다 같은 프로모션 트래픽이 애플리케이션 계층에서 어떻게 실패하는지 이어서 봅니다.
- 함정 사전 Multi-AZ와 읽기 전용 복제본을 포함한 헷갈리는 쌍을 한 자리에 모았습니다.
- 복원력 있는 아키텍처 설계 (26%) 과제 진술 2개가 어떻게 촘촘하게 출제되는지와 출제 포인트를 정리했습니다.
공식 문서 출처
- Amazon RDS 사용 설명서 — 단일 대기 인스턴스를 사용하는 Multi-AZ 배포 — 대기 복제본으로 동기식 복제한다는 서술
- Amazon RDS 사용 설명서 — 읽기 전용 복제본 사용 — 비동기식 복제, 별도 엔드포인트, 승격 동작
- Amazon RDS API 레퍼런스 — CreateDBInstanceReadReplica — 엔진별 읽기 전용 복제본 개수 상한(기본 15개, Oracle · SQL Server 5개, Db2 3개)과 지원 엔진 목록
- Amazon RDS API 레퍼런스 — ModifyDBInstance
—
MultiAZ설정과 즉시 적용 옵션 - Amazon RDS 사용 설명서 — Amazon RDS Proxy — 연결 풀링과 장애 조치 시 연결 전환에 대한 설명
- Amazon ElastiCache API 레퍼런스 — CreateReplicationGroup — 복제 그룹의 자동 장애 조치와 Multi-AZ 설정, 지원 엔진
- Amazon ElastiCache 개발자 안내서 — 자동 장애 조치 — 프라이머리 장애 시 읽기 전용 복제본이 승격되는 조건
- Amazon ElastiCache 개발자 안내서 — 엔진 선택 — 복제와 스냅샷을 지원하는 엔진과 Memcached의 차이
- Amazon RDS 사용 설명서 — 모범 사례 — 연결 수와 인스턴스 크기, 모니터링 지표에 대한 권고