콘텐츠로 이동

분산 시스템 기초 (CAP & Consistency)

분류: Layer 9 - 아키텍처 & 설계 패턴

분산 시스템 기초 (CAP & Consistency)

섹션 제목: “분산 시스템 기초 (CAP & Consistency)”

분산 시스템은 네트워크로 연결된 여러 노드가 협력하여 하나의 서비스처럼 동작하는 시스템이며, CAP 정리는 네트워크 장애 시 일관성(Consistency)과 가용성(Availability) 중 하나를 포기해야 한다는 근본적 제약이다.


프론트엔드 브릿지: 브라우저 캐시에서 stale 데이터를 보여주는 경험을 한 적 있을 것이다. Cache-Control: max-age=3600으로 캐싱된 HTML이 서버에서 이미 갱신되었는데도 1시간 동안 구버전을 보여주는 현상이다. 이것이 분산 시스템에서 말하는 Eventually Consistent 데이터와 동일한 원리다. 클라이언트(브라우저)와 서버 간 데이터 일관성 문제가 서버 노드들 사이에서도 그대로 발생한다.

BackOps 엔지니어로 성장하면서 이 지식이 필요한 이유:

  • AWS 서비스 선택 근거 설명: “왜 이 데이터는 DynamoDB에, 저 데이터는 Aurora에 넣었나?”를 CAP/PACELC로 설명할 수 있어야 한다.
  • 장애 원인 분석: Write 후 Read 시 데이터가 없는 현상이 버그인지, Eventual Consistency 특성인지 구별해야 한다.
  • 시니어 대화 가능: 시스템 설계 리뷰에서 “이 서비스는 AP 시스템인가 CP 시스템인가”라는 질문에 답해야 한다.

2.1 선행 한계: 단일 노드 ACID와 로컬 캐시가 풀지 못한 문제

섹션 제목: “2.1 선행 한계: 단일 노드 ACID와 로컬 캐시가 풀지 못한 문제”

transaction-basics에서 배운 ACID 트랜잭션은 단일 데이터베이스 안에서는 강력하다. 하지만 서비스가 여러 AZ, 여러 리전, 여러 저장소로 나뉘면 “커밋된 값은 모두가 즉시 본다”는 전제가 네트워크에 의존하게 된다. Gilbert & Lynch의 CAP 증명은 비동기 네트워크에서 일관성·가용성·파티션 허용을 동시에 만족할 수 없음을 보였고, 실무에서는 이 제약이 “장애 시 요청을 거부할 것인가, 구버전 응답을 허용할 것인가”라는 선택으로 나타난다. 출처: Gilbert & Lynch, 2002.

프론트엔드의 HTTP 캐시도 같은 모양을 가진다. max-age=3600은 원 서버 부하와 지연을 줄이는 대신 1시간 동안 stale HTML을 보여줄 수 있다. 분산 데이터베이스에서는 이 선택이 더 비싸다. Aurora는 3개 AZ에 6개 복사본을 두고 4개 쓰기 quorum 응답을 받아 커밋해 C를 지키고, DynamoDB는 기본 eventually consistent read로 지연과 가용성을 얻되 최근 쓰기가 바로 보이지 않을 수 있다. 즉 이 토픽은 “캐시 무효화”나 “DB 트랜잭션”의 확장판이 아니라, 네트워크 장애가 들어온 순간 어떤 불완전성을 제품 요구사항으로 받아들일지 정하는 설계 언어다.


2000년 Eric Brewer가 제안하고 2002년 Gilbert & Lynch가 수학적으로 증명한 정리. 분산 시스템은 아래 세 속성 중 최대 두 가지만 동시에 보장할 수 있다.

속성약어의미
ConsistencyC모든 노드가 동시에 동일한 데이터를 본다. 읽기는 항상 최신 쓰기 결과를 반환한다.
AvailabilityA모든 요청이 (최신 데이터 보장 없이) 반드시 응답을 받는다. 노드가 살아있으면 응답한다.
Partition ToleranceP네트워크 파티션(노드 간 통신 단절)이 발생해도 시스템이 계속 동작한다.

왜 셋 중 둘만 선택 가능한가:

[노드 A] ─────────────── [노드 B]
쓰기 완료 네트워크 단절
x = 10 x = 5 (구버전)
클라이언트가 노드 B에 읽기 요청:
- 응답하면(Availability 보장): 구버전 x=5 반환 → Consistency 위반
- 거부하면(Consistency 보장): 요청 실패 응답 → Availability 위반

네트워크 파티션은 분산 시스템에서 회피 불가능하다. 물리 케이블은 끊어지고, AWS AZ 간 통신 지연은 반드시 발생한다. 따라서 실질적 선택은 P가 발생했을 때 C와 A 중 무엇을 포기하느냐다.

비유:

  • CP 선택 = 은행 ATM. 네트워크 단절 시 “현재 서비스 불가” 안내를 하지만 잔액 불일치는 절대 허용하지 않는다.
  • AP 선택 = 소셜미디어 좋아요 수. 일시적으로 노드마다 다른 숫자를 보여줘도 괜찮다. 결국엔 맞춰진다.

CAP 정리가 드러나는 실제 엣지 케이스

섹션 제목: “CAP 정리가 드러나는 실제 엣지 케이스”

엣지 케이스 1: Aurora Multi-AZ에서 AZ 장애

  • 증상: ap-northeast-2a AZ 장애 → Aurora Writer가 해당 AZ에 있으면 Failover 발생
  • Failover 소요시간: 보통 60초 미만, 종종 30초 미만 (AWS 공식 문서 기준)
  • Failover 중 동작: 새 Writer 선출 전까지 모든 쓰기 요청 실패 (CP: Consistency 우선)
  • 복구 후 확인: SELECT @@hostname 로 새 Primary 확인
  • 출처: Aurora high availability, Aurora quorum design
AZ 장애 발생
├── Writer(2a) 비정상 감지
├── Aurora 스토리지: 6개 복사본 중 4개 쓰기 quorum 유지
└── 새 Writer 승격 및 클러스터 엔드포인트 전환 (보통 < 60초, 종종 < 30초)
이 구간 동안 모든 쓰기 요청 실패 → CP의 "C를 지키기 위해 A를 포기"

엣지 케이스 2: DynamoDB 핫 파티션 (Partition Skew)

  • 단일 파티션 한계: 3,000 RCU / 1,000 WCU (AWS 공식 수치)
  • 초과 시 동작: ProvisionedThroughputExceededException 발생 — AP 시스템임에도 일시적 가용성 저하
  • 발생 조건: 파티션 키가 날짜(YYYYMMDD), 고정 값, 순차 ID인 경우
  • 해결: 파티션 키에 랜덤 접미사 추가 (suffix sharding)
  • 출처: DynamoDB partition key design
파티션 키: date = "20241219" ← 하루 동안 모든 쓰기가 동일 파티션 집중
해결:
파티션 키: date + "#" + (random 0~9)
→ "20241219#3", "20241219#7" 등으로 분산
→ 조회 시 0~9 모두 병렬 조회 후 병합 (scatter-gather)

이 실패는 에러가 크게 터지기보다 p99 지연 증가와 간헐적 throttling으로 먼저 보이는 경우가 많다. 운영 중이면 aws cloudwatch get-metric-statistics --namespace AWS/DynamoDB --metric-name ThrottledRequests --dimensions Name=TableName,Value=Orders --statistics Sum --period 60 --start-time <UTC-10m> --end-time <UTC-now>로 최근 10분 throttling 합계를 확인한다. 예상 출력에서 Sum이 0이 아니면 AP 선택의 문제가 아니라 파티션 키 설계가 처리량을 한 물리 파티션에 묶은 것이다. 다음 조치는 suffix 개수를 늘리기 전에 “쓰기 경로는 분산되지만 읽기 경로가 scatter-gather 비용을 감당하는가”를 부하 테스트로 확인하는 것이다.

엣지 케이스 3: ElastiCache Primary 승격 중 Split-Brain

  • 증상: Primary 장애 → Replica 승격 전 짧은 구간에 구 Primary와 신 Primary 동시 존재
  • 소요시간: 약 10~60초 (ElastiCache failover 완료까지)
  • 이 구간 동작: AP 시스템으로 구 Primary의 구버전 데이터 반환 가능
  • 방지: 클라이언트에서 재시도 + 멱등성 보장
구 Primary (2a): 일시적으로 살아있는 것처럼 보일 수 있음 (x = 10 → 5로 롤백)
신 Primary (2b): 승격 완료 후 새 쓰기 수신 시작 (x = 10)
클라이언트가 구 Primary에 연결된 경우: stale 데이터 반환
→ DNS Failover 완료 후 자동 해소, 그 전까지 재시도 + 멱등성으로 대응

CP 시스템 (Consistency + Partition Tolerance)

섹션 제목: “CP 시스템 (Consistency + Partition Tolerance)”

파티션 발생 시 일관성을 위해 가용성을 포기한다. 일부 요청은 오류를 반환하지만 반환하는 데이터는 항상 최신이다.

CP 시스템 동작:
네트워크 파티션 발생
일관성 확인 불가 → 요청 거부 (Error 반환)
✓ 데이터 정확성 보장
✗ 일시적 서비스 중단
시스템특징
ZooKeeper분산 조율 서비스. Leader 선출 중 요청 거부. 과반수 노드 동의 후에만 쓰기 허용
HBaseHDFS 위의 CP 스토어. 리전 서버 장애 시 해당 리전 읽기/쓰기 중단
etcdKubernetes 클러스터 상태 저장소. Raft 합의 알고리즘으로 강한 일관성
Aurora (MySQL/PostgreSQL)6개 복제본 중 4개 이상 확인 후 쓰기 커밋. Read Replica 지연 있음
티켓팅 시스템같은 좌석을 두 사람이 예매하는 오버부킹을 절대 허용 불가

AP 시스템 (Availability + Partition Tolerance)

섹션 제목: “AP 시스템 (Availability + Partition Tolerance)”

파티션 발생 시 가용성을 위해 일관성을 포기한다. 구버전 데이터를 반환할 수 있지만 요청은 항상 처리한다.

AP 시스템 동작:
네트워크 파티션 발생
가능한 노드로 요청 처리 → 응답 반환 (구버전일 수 있음)
✓ 서비스 지속성 보장
✗ 데이터 일시적 불일치 허용
시스템특징
CassandraPeer-to-peer 구조. 어느 노드에나 쓰기 가능. Netflix 추천 엔진에 사용
DynamoDB기본적으로 AP. Eventually Consistent Read가 기본값
CouchDBMVCC 기반. 충돌은 애플리케이션이 해결
DNS레코드 갱신 후 전파 지연(TTL). 전 세계 가용성 우선
쇼핑몰 장바구니네트워크 장애 중에도 장바구니 담기 가능. 나중에 동기화

파티션을 허용하지 않는다는 뜻으로, 실제로는 단일 노드 또는 단일 데이터센터 시스템이다. 네트워크 파티션이 발생하면 시스템 자체가 동작 불가다.

시스템특징
단일 노드 RDB (MySQL, PostgreSQL)파티션 자체가 없어서 CA 가능
단일 AZ 배포AZ 장애 = 전체 장애 감수

실무에서 “CA 시스템을 쓰겠다”는 말은 “분산 배포를 포기하겠다”는 뜻이다. 고가용성이 필요하면 CA는 선택지가 아니다.


CAP 정리는 네트워크 파티션이 발생했을 때만 다룬다. 하지만 실제 시스템은 파티션이 없는 정상 상태에서도 성능(지연)과 일관성 간의 트레이드오프가 존재한다. Daniel Abadi는 이 한계 때문에 PACELC를 제안했다. Abadi의 표현을 실무 언어로 바꾸면, 장애 때는 “응답할 것인가, 정확할 것인가”를 고르고, 평상시에도 “빠르게 읽을 것인가, 최신 쓰기를 확인할 것인가”를 고른다는 뜻이다. 출처: Abadi, Consistency Tradeoffs in Modern Distributed Database System Design.

PACELC 공식:
if Partition:
→ Availability vs Consistency 선택
Else (정상 동작):
→ Latency vs Consistency 선택
분류의미대표 시스템선택 이유
PA/EL파티션 시 가용성, 평상시 낮은 지연 우선DynamoDB(기본), Cassandra, Riak글로벌 서비스, 높은 처리량 필요
PA/EC파티션 시 가용성, 평상시 일관성 우선Cosmos DB (일부 설정)가용성은 필수이나 정확성도 중요
PC/EL파티션 시 일관성, 평상시 낮은 지연 우선Yahoo! PNUTS드문 조합
PC/EC파티션 시 일관성, 평상시 일관성 우선ZooKeeper, etcd, CockroachDB금융, 설정 관리 등 정확성 최우선

Aurora의 PACELC 위치:

  • Partition 시: CP → 일관성 우선
  • Else: Multi-AZ 동기 복제로 EC(일관성 우선), 단 쓰기 지연이 약간 있음
  • Read Replica 사용 시: EL(낮은 지연)로 설정 가능하나 복제 지연(lag) 허용 필요

이 구분은 데이터베이스 밖에서도 반복된다. S3는 2020년 12월부터 모든 GET/PUT/LIST에 강한 read-after-write consistency를 기본 제공한다고 발표했고, 그 전에는 EMRFS Consistent View나 S3Guard처럼 애플리케이션이 최신 목록을 보강하는 층을 따로 두기도 했다. 같은 원리가 API 캐시에도 적용된다. 응답 지연을 줄이기 위해 CDN·브라우저 캐시를 쓰면 EL을 택한 것이고, 결제 완료 화면처럼 최신 상태가 더 중요하면 캐시를 우회하거나 원본 저장소를 읽어 EC에 가깝게 운영한다. 출처: Amazon S3 Strong Read-After-Write Consistency.

DynamoDB (PA/EL):
정상 상태: 클라이언트 → 가장 가까운 노드 → 즉시 응답 (EL: 낮은 지연)
파티션 시: 클라이언트 → 살아있는 노드 → 구버전 응답 (PA: 가용성)
ZooKeeper (PC/EC):
정상 상태: 클라이언트 → Leader → 과반수 동의 → 응답 (EC: 일관성, 지연 있음)
파티션 시: 과반수 달성 불가 → 요청 거부 (PC: 일관성)

“일관성”이라는 단어는 강도에 따라 여러 모델로 나뉜다. 강할수록 성능 비용이 크다.

강한 일관성 ◄────────────────────────────────► 약한 일관성
│ │
Linearizable Sequential Read-Your-Writes Eventual
│ │ │ │
최강 순서 보장 내 쓰기 보장 결국 수렴

가장 강한 일관성. 모든 읽기는 가장 최근에 완료된 쓰기 결과를 반환한다. 시스템 전체가 단일 CPU처럼 동작하는 것처럼 보인다.

  • 사용 사례: ZooKeeper의 Znode, etcd의 분산 락
  • 비용: 모든 노드 동기화 대기 → 높은 지연
Write(x=10) 완료
Read(x) → 반드시 10 반환 (어느 노드에서 읽어도)

각 프로세스의 연산 순서는 보장하되, 전역 실시간 순서는 보장하지 않는다. 내 관점에서의 순서는 맞지만 다른 노드와 실시간 동기화는 아니다.

  • 사용 사례: 멀티코어 CPU 메모리 모델

Read-Your-Writes (자신의 쓰기 읽기 보장)

섹션 제목: “Read-Your-Writes (자신의 쓰기 읽기 보장)”

내가 쓴 데이터는 내가 반드시 읽을 수 있다. 다른 클라이언트는 구버전을 볼 수 있어도, 쓴 사람은 자신의 최신 값을 본다.

  • 사용 사례: 소셜미디어 프로필 업데이트 (내가 바꾼 프로필을 나는 즉시 볼 수 있어야 함)
  • 구현: Session Token을 활용해 동일 레플리카에 라우팅
클라이언트 A: Write(profile="새 이름") → 성공
클라이언트 A: Read(profile) → "새 이름" 반환 ✓ (Read-Your-Writes 보장)
클라이언트 B: Read(profile) → "구 이름" 반환 가능 (다른 레플리카)

충분한 시간이 지나면 모든 노드가 동일한 값으로 수렴한다. 수렴 전까지는 노드마다 다른 값을 반환할 수 있다.

  • 사용 사례: DNS 전파, S3 (2020년 이전), Cassandra 기본 설정
  • 장점: 높은 가용성, 낮은 지연, 수평 확장 용이
Write(x=10) → 노드 A에 반영
├─ 노드 A: Read → 10 ✓
├─ 노드 B: Read → 5 (아직 복제 중) ← Eventual Consistency
└─ 노드 C: Read → 5 (아직 복제 중)
[복제 완료 후]
├─ 노드 A: Read → 10 ✓
├─ 노드 B: Read → 10 ✓
└─ 노드 C: Read → 10 ✓

DynamoDB Eventual Consistency가 실무에서 문제가 되는 수치

  • 복제 지연: 단일 리전 eventually consistent read는 최근 완료된 쓰기를 바로 반영하지 않을 수 있고, 잠시 뒤 재시도하면 최신 값을 반환한다. Global Tables의 리전 간 복제는 보통 1초 이내로 전파된다.
  • ConsistentRead 비용: Eventually Consistent 대비 2배 RCU 소비. 4KB 이하 항목 기준 eventually consistent read는 0.5 RCU, strongly consistent read는 1 RCU를 사용한다.
  • 실무 측정: Write 직후 500ms 이내 불일치 빈도는 트래픽·리전·키 분포에 따라 달라진다. 문서에 고정 수치로 쓰지 말고, staging에서 1,000회 이상 반복 측정해 API별 허용 여부를 판단한다.
  • 출처: DynamoDB read consistency, DynamoDB read/write capacity

Aurora Read Replica 복제 지연 임계값

구간AuroraReplicaLag판단
정상< 20ms정상 범위
주의20ms~1,000ms읽기 집중 부하 시 자연스러운 범위
경보> 1,000ms (1초)복제 지연 심각 → Writer 전용 읽기 전환 검토 필요
  • CloudWatch 지표: AuroraReplicaLag는 reader instance가 writer instance보다 뒤처진 시간을 밀리초 단위로 나타낸다. AuroraReplicaLagMaximum은 클러스터 내 최대 지연을 보여주지만, reader 삭제·이름 변경 직후에는 일시적 spike가 생길 수 있으므로 각 reader의 AuroraReplicaLag를 함께 본다. 출처: Aurora CloudWatch metrics
복제 지연이 > 1초인 경우 발생하는 문제:
사용자 프로필 수정 → Read Replica에서 조회 → 1초 이상 구버전 표시
주문 생성 → Read Replica에서 주문 목록 조회 → 새 주문 없음
대응:
CloudWatch Alarm: AuroraReplicaLag > 1000ms → SNS 알림
API 레벨: 쓰기 직후 읽기는 Writer 엔드포인트 강제 사용

Writer Instance ──── 6개 복제본 (3 AZ × 2)
└── 쓰기: 4/6 복제본 확인 후 커밋 (강한 일관성)
읽기: Writer에서 직접 → 항상 최신
읽기: Read Replica → 복제 지연 가능 (수 ms~수 초)
  • CAP: CP — 파티션 시 쓰기 중단 (최소 4/6 복제본 필요)
  • Consistency: 기본적으로 Strong (Writer 직접 읽기), Read Replica는 Eventual
  • 선택 기준: 트랜잭션 ACID 보장 필요, 복잡한 JOIN, 금융/주문 데이터
DynamoDB Table
├── Eventually Consistent Read (기본)
│ → 최근 완료된 쓰기가 반영 안 될 수 있음
│ → 4KB 이하 항목 기준 0.5 RCU 소비
└── Strongly Consistent Read (옵션)
→ 반드시 최신 값 반환
→ 4KB 이하 항목 기준 1 RCU 소비 (Eventual 대비 2배)
  • CAP: AP — 파티션 시 가용성 유지, 구버전 데이터 허용
  • Consistency: 기본 Eventual, 옵션으로 Strong (2배 비용)
  • 선택 기준: 높은 처리량(초당 수만 TPS), 글로벌 테이블, 세션/게임 상태
2020년 12월 이전: Eventual Consistency
PUT object → 즉시 GET → 구버전 반환 가능
DELETE object → 즉시 GET → 객체 여전히 반환 가능
2020년 12월 이후: Strong Consistency (추가 비용 없음)
PUT/DELETE 이후 GET → 항상 최신 상태 반환
  • AWS가 내부 아키텍처를 개선하여 성능 저하 없이 강한 일관성 달성
  • 단, S3 Replication(Cross-Region)은 여전히 Eventually Consistent
Redis Cluster Mode:
Primary ──복제──► Replica
Primary 장애 시: Replica가 Primary로 승격
승격 전 기간(수 초): 읽기는 Replica에서 구버전 반환
  • CAP: AP — 레플리카에서 읽기 허용, 구버전 데이터 가능
  • Consistency: Eventual (Primary → Replica 비동기 복제)
  • 선택 기준: 세션 캐시, 빈번한 읽기 캐싱, 속도 최우선
서비스CAPPACELC기본 Consistency강한 일관성
Aurora (Writer)CPPC/ECStrong항상
Aurora (Read Replica)CPPA/ELEventual설정 불가
DynamoDBAPPA/ELEventual옵션 (2배 비용)
S3APPA/ECStrong (2020~)기본 제공
ElastiCacheAPPA/ELEventual미지원
RDS Multi-AZCPPC/ECStrong항상

”어떤 데이터베이스를 선택할까?” 판단 흐름

섹션 제목: “”어떤 데이터베이스를 선택할까?” 판단 흐름”
Step 1: 실패 시 잘못된 응답보다 요청 실패가 나은가?
YES → Aurora / RDS (CP 시스템)
NO → Step 2로
Step 2: 쓰기 직후 같은 사용자가 반드시 최신 값을 봐야 하는가?
YES → Writer 읽기 / DynamoDB ConsistentRead / 세션 기반 read-your-writes 보장
NO → Step 3으로
Step 3: 단일 파티션 키가 3,000 RCU 또는 1,000 WCU에 접근할 수 있는가?
YES → DynamoDB를 쓰더라도 키 분산·write sharding을 먼저 설계
NO → Step 4로
Step 4: 약간의 stale 데이터가 사용자 경험으로 설명 가능한가?
YES (소셜, 로그, 캐시, 카탈로그) → AP/EL 선택 가능
NO (금융, 재고, 예약, 권한) → CP/EC 선택

결정의 핵심은 “더 좋은 데이터베이스”가 아니라 “실패했을 때 어떤 거짓말을 허용할 것인가”다. 주문 생성 직후 목록에서 주문이 빠지는 것은 UX 버그처럼 보이지만, 결제 승인 직후 잔액이 잘못 보이는 것은 금전 사고가 된다. 반대로 좋아요 수가 3초 늦게 맞춰지는 일을 막기 위해 모든 읽기를 강한 일관성으로 바꾸면, DynamoDB 기준으로 eventually consistent read 대비 읽기 비용이 2배가 되고 글로벌 사용자의 지연도 늘어난다. 출처: DynamoDB read consistency, DynamoDB read/write capacity.

시나리오선택이유
결제 처리, 잔액 차감Aurora (CP)이중 차감 절대 불가, ACID 필수
사용자 세션 관리DynamoDB (AP)세션 만료 시 재로그인 허용, 높은 TPS
상품 재고 (판매 중)Aurora (CP)오버셀링 방지
상품 조회 (카탈로그)DynamoDB (AP)읽기 많고 약간의 지연 허용
알림/이벤트 로그DynamoDB (AP)순서 느슨, 높은 쓰기 처리량
글로벌 사용자 프로필DynamoDB Global Tables (AP)멀티 리전, 낮은 지연 우선
분산 락 (선착순 이벤트)Redis (또는 DynamoDB Conditional Write)원자적 연산

예를 들어 선착순 쿠폰 발급을 DynamoDB 기본 읽기만으로 구현하면 “쿠폰 수량 확인 → 발급” 사이에 stale count를 보고 초과 발급할 수 있다. 이 경로는 ConditionExpression으로 남은 수량을 원자적으로 차감하거나 Aurora 트랜잭션으로 잠가야 한다. 반대로 상품 상세 페이지의 조회수, 추천 점수, 최근 본 상품은 구버전이 잠깐 보여도 주문 정합성을 깨지 않으므로 AP/EL 쪽으로 보내는 편이 낫다.


NestJS API + Aurora + DynamoDB를 함께 쓰는 경우의 아키텍처 판단:

[NestJS API]
├── 트랜잭션 필요한 데이터 → Aurora (CP)
│ ├── 주문 (order)
│ ├── 결제 (payment)
│ └── 재고 (inventory)
└── 높은 처리량 / 유연한 스키마 → DynamoDB (AP)
├── 사용자 세션
├── 이벤트 로그
└── 타임라인 데이터
// Aurora Read Replica에서 읽을 때 — 복제 지연 인지 필수
// 주문 생성 직후 주문 목록 조회는 Writer에서 해야 함
// 잘못된 패턴:
await orderRepository.create(orderData); // Writer에 쓰기
return await orderReadRepo.findAll(); // Read Replica에서 조회 → 새 주문 없을 수 있음
// 올바른 패턴:
await orderRepository.create(orderData); // Writer에 쓰기
return await orderRepository.findAll(); // Writer에서 조회 → 항상 최신
// 기본: Eventually Consistent (빠름, 저렴)
const result = await dynamoClient
.get({
TableName: "sessions",
Key: { userId },
})
.promise();
// 강한 일관성이 필요할 때 (2배 비용)
const result = await dynamoClient
.get({
TableName: "sessions",
Key: { userId },
ConsistentRead: true, // 읽기 용량 2배 소비
})
.promise();
// 판단 기준:
// - 로그인 직후 권한 확인 → ConsistentRead: true
// - 일반 프로필 조회 → 기본값 (Eventually Consistent)

  • CAP 정리에서 P(Partition Tolerance)가 왜 실질적으로 포기 불가능한지 설명할 수 있다
  • ZooKeeper가 CP인 이유와 파티션 발생 시 어떤 동작을 하는지 설명할 수 있다
  • DynamoDB의 기본 읽기가 Eventually Consistent인 이유와 Strongly Consistent Read와의 차이를 설명할 수 있다
  • PACELC에서 “Else” 부분(파티션 없을 때)의 Latency vs Consistency 트레이드오프를 설명할 수 있다
  • Aurora Write 후 Read Replica에서 읽을 때 데이터가 없을 수 있는 이유를 설명할 수 있다
  • “이 서비스는 AP인가 CP인가”라는 질문에 선택 근거와 함께 답할 수 있다
  • Strong Consistency와 Linearizable의 차이를 설명할 수 있다

트러블슈팅 1: Write 후 바로 Read 시 데이터가 없음 (Eventual Consistency 이해 부재)

섹션 제목: “트러블슈팅 1: Write 후 바로 Read 시 데이터가 없음 (Eventual Consistency 이해 부재)”

증상:

POST /orders → 201 Created
GET /orders → 방금 생성한 주문이 목록에 없음

원인:

  • DynamoDB의 기본 읽기는 Eventually Consistent
  • Write는 특정 노드에 완료되었으나 다른 레플리카로 전파 전에 Read가 발생
  • AWS 문서는 기본 eventually consistent read가 최근 완료된 쓰기를 바로 반영하지 않을 수 있고, 잠시 뒤 재시도하면 최신 값을 반환한다고 설명한다

해결:

// 방법 1: Strongly Consistent Read 사용
const result = await dynamoClient
.get({
Key: { orderId },
ConsistentRead: true, // 쓰기 직후 읽기 보장
})
.promise();
// 방법 2: Write 응답값을 그대로 반환 (Read 생략)
const created = await orderService.create(dto);
return created; // DB 재조회 불필요
// 방법 3: 동일 레플리카로 라우팅 (Read-Your-Writes)
// Session 기반 sticky routing 활용

예방:

  • API 설계 시 Create 후 Redirect to GET 패턴을 쓸 경우 ConsistentRead 필수
  • 단순 생성 응답은 DB 재조회 대신 생성된 객체 직접 반환

복구 runbook:

1. CloudWatch → DynamoDB → SuccessfulRequestLatency 확인 (쓰기 완료 여부 검증)
2. ConsistentRead: true로 즉시 재조회
- 데이터 있음 → 복제 지연 원인 (Eventual Consistency 정상 동작)
- 데이터 없음 → 쓰기 실패 원인 (DynamoDB SystemErrors 지표 확인)
3. 쓰기 실패 시: CloudWatch → DynamoDB → SystemErrors 지표 확인
4. 장기적 해결: Write 직후 Read 패턴은 Write 응답값 직접 반환으로 대체

트러블슈팅 2: DynamoDB Strongly Consistent Read 비용 폭증

섹션 제목: “트러블슈팅 2: DynamoDB Strongly Consistent Read 비용 폭증”

증상:

DynamoDB 비용이 예상의 2배 청구
CloudWatch: ConsumedReadCapacityUnits 급증

원인:

  • ConsistentRead: true는 읽기 용량 유닛(RCU)을 2배 소비
  • Eventually Consistent Read: 0.5 RCU per 4KB
  • Strongly Consistent Read: 1 RCU per 4KB (동일 데이터에 2배 비용)
  • 전체 API에 ConsistentRead를 일괄 적용한 경우 발생

해결:

// 비용 최적화: 필요한 경우에만 ConsistentRead 사용
class OrderRepository {
// 주문 생성 직후 확인 - Strong 필요
async findAfterCreate(orderId: string) {
return this.get(orderId, { ConsistentRead: true });
}
// 일반 목록 조회 - Eventual 충분
async list(userId: string) {
return this.query(userId, { ConsistentRead: false }); // 기본값
}
}

판단 기준:

  • 금융 트랜잭션 직후 확인 → Strong 필수
  • 대시보드, 통계, 목록 조회 → Eventual 충분 (비용 절감)
  • 2배 비용이 부담되면 Aurora 전환 고려 (쓰기 후 바로 읽기가 잦은 패턴)

복구 runbook:

1. CloudWatch → DynamoDB → ConsumedReadCapacityUnits 확인 (예상 대비 2배 여부)
2. ConsistentRead: true 사용 코드 전수 검색
grep -r "ConsistentRead: true" src/
3. 불필요한 Strong Read → Eventually Consistent로 변경
(목록 조회, 통계, 대시보드 등)
4. CloudWatch Alarm 설정:
ConsumedReadCapacityUnits > {월 예산 기반 임계값} → SNS 알림
5. 향후 예방: ConsistentRead: true는 코드 리뷰 시 이유 주석 필수화

트러블슈팅 3: Aurora Read Replica 지연으로 인한 데이터 불일치

섹션 제목: “트러블슈팅 3: Aurora Read Replica 지연으로 인한 데이터 불일치”

증상:

사용자가 프로필을 수정하고 새로고침 → 이전 데이터가 보임
주문 완료 후 주문 목록 → 새 주문 없음

원인:

  • Aurora Read Replica는 Writer와 비동기 복제
  • 일반적으로 수 ms 지연이지만 부하 시 수 초까지 증가 가능
  • replica_lag 모니터링 항목으로 확인 가능

해결:

// TypeORM 예시: 읽기 전략 선택
@Injectable()
class OrderService {
constructor(
@InjectRepository(Order) private repo: Repository<Order>,
@InjectDataSource("reader") private readerConn: DataSource,
) {}
// 쓰기 직후 조회 → Writer 사용
async createAndGet(dto: CreateOrderDto) {
const order = await this.repo.save(dto);
return this.repo.findOne({ where: { id: order.id } }); // Writer
}
// 일반 목록 조회 → Reader 사용 가능
async list(userId: string) {
return this.readerConn.getRepository(Order).find({ where: { userId } }); // Reader Replica
}
}

모니터링:

-- Aurora PostgreSQL: writer/reader 역할과 지연(ms) 확인
SELECT server_id,
CASE WHEN session_id = 'MASTER_SESSION_ID' THEN 'writer' ELSE 'reader' END AS instance_role,
replica_lag_in_msec AS replica_lag_ms
FROM aurora_replica_status()
ORDER BY replica_lag_in_msec NULLS FIRST;
-- 예상 출력: writer는 replica_lag_ms가 NULL, reader는 0에 가까운 ms 값
-- reader 값이 1000ms 이상이면 쓰기 직후 읽기 API를 Writer로 임시 전환
-- CloudWatch 지표
-- AuroraReplicaLag (ms)
-- AuroraReplicaLagMaximum

예방:

  • 쓰기 직후 읽기가 필요한 API는 Writer 엔드포인트 고정
  • Read Replica는 리포트, 검색, 집계 쿼리에만 사용
  • AuroraReplicaLag CloudWatch 알람 설정 (임계값: 1000ms)

복구 runbook:

1. CloudWatch → RDS → AuroraReplicaLag 확인
- < 1000ms: 일시적 지연, 모니터링 유지
- > 1000ms: 즉각 대응 필요
2. 즉각 대응: 해당 API의 읽기 연결을 Writer 엔드포인트로 임시 전환
3. 원인 파악:
- SHOW PROCESSLIST; → 장시간 실행 쿼리 확인
- SELECT * FROM information_schema.processlist WHERE time > 10;
4. 지연 원인 제거:
- 장시간 쿼리 종료: KILL {process_id};
- 대용량 트랜잭션이면 분할 처리로 재설계
5. CloudWatch Alarm 설정 (예방):
AuroraReplicaLag > 1000 → SNS 알림 → 자동 Writer 전환 Lambda 연동

이 문서를 이해했다면 다음 주제로 이어진다:

  1. 분산 트랜잭션 (Saga 패턴): AP 시스템에서 트랜잭션 보장하는 방법 → MSA Patterns 문서 참고
  2. Consensus 알고리즘 (Raft, Paxos): CP 시스템이 내부적으로 일관성을 유지하는 방법
  3. CRDT (Conflict-free Replicated Data Types): Eventual Consistency에서 충돌 자동 해결
  4. Vector Clock / Versioning: DynamoDB Conditional Write로 충돌 감지
  5. 분산 락: Redis SETNX, DynamoDB Conditional Write로 선착순 처리

  1. CAP Theorem Explained - AlgoMaster — 도식과 실사례가 풍부한 CAP 입문서
  2. PACELC Theorem - Wikipedia — 원 제안자 Daniel Abadi의 논리 원문 포함
  3. Amazon S3 Strong Consistency (AWS 공식) — S3가 2020년 강한 일관성으로 전환한 배경 설명
  4. Consistency and Partition Tolerance: CAP vs PACELC - ByteByteGo — 시각적 다이어그램으로 비교
  5. Why Strong Consistency? - Marc Brooker (AWS) — AWS 수석 엔지니어가 쓴 Strong Consistency의 실용적 가치

시나리오 1: DynamoDB Eventual Consistency 재현

섹션 제목: “시나리오 1: DynamoDB Eventual Consistency 재현”
// 테스트: Write 직후 Eventual Consistent Read
async function demonstrateEventualConsistency() {
const userId = "user-123";
// Write: 세션 생성
await dynamoClient
.put({
TableName: "UserSessions",
Item: { userId, status: "active", updatedAt: Date.now() },
})
.promise();
// Read (Eventually Consistent - 기본값)
const result1 = await dynamoClient
.get({
TableName: "UserSessions",
Key: { userId },
// ConsistentRead: false (기본값)
})
.promise();
// 실제 발생하는 동작:
// - 90% 이상의 경우: result1.Item.status === 'active' (운 좋게 최신 노드 할당)
// - 드물게: result1.Item === undefined (구버전 노드 할당, 아직 복제 안 됨)
// - 복제 지연이 길수록 undefined 발생 빈도 증가
console.log("Eventually Consistent Read:", result1.Item);
// Read (Strongly Consistent)
const result2 = await dynamoClient
.get({
TableName: "UserSessions",
Key: { userId },
ConsistentRead: true,
})
.promise();
// 항상 보장되는 동작:
// - result2.Item.status === 'active' (최신 쓰기 반영 보장)
console.log("Strongly Consistent Read:", result2.Item);
}

실제 발생하는 동작 설명:

  • ConsistentRead: false(기본): DynamoDB가 임의의 레플리카에서 읽는다. 쓰기 직후라면 새 값이 아직 전파 안 된 레플리카에서 읽힐 수 있다.
  • ConsistentRead: true: DynamoDB가 쓰기 완료를 확인한 노드들에서 읽는다. 항상 최신 값을 반환하지만 RCU 2배 소비.

시나리오 2: Aurora Write/Read 분리 패턴

섹션 제목: “시나리오 2: Aurora Write/Read 분리 패턴”
// Aurora Multi-AZ: Writer는 항상 최신, Reader는 지연 가능
@Injectable()
class ProductService {
// 재고 차감 (CP 필요) → Writer
async decrementInventory(productId: string, qty: number) {
return this.writerDataSource.transaction(async (em) => {
const product = await em.findOne(Product, {
where: { id: productId },
lock: { mode: "pessimistic_write" }, // 비관적 락
});
if (product.stock < qty) {
throw new BadRequestException("재고 부족");
}
product.stock -= qty;
return em.save(product);
});
}
// 상품 목록 조회 (AP 허용) → Reader
async listProducts(categoryId: string) {
// Read Replica 사용 - 수 ms 지연 허용
return this.readerDataSource
.getRepository(Product)
.find({ where: { categoryId, isActive: true } });
// 실제 발생: 재고가 실시간보다 수 ms 지연될 수 있음
// 허용 가능: 상품 목록은 약간의 stale 데이터 무방
}
}

실제 발생하는 동작 설명:

  • writerDataSource: Aurora Writer 엔드포인트. 모든 쓰기와 정확성이 중요한 읽기에 사용. 항상 최신 데이터.
  • readerDataSource: Aurora Reader 엔드포인트(Read Replica). 수 ms~수 초의 복제 지연 존재. 목록 조회, 검색 등 stale 데이터가 허용되는 곳에 사용.

개념핵심 한 줄
CAP 정리분산 시스템에서 네트워크 장애(P) 시 일관성(C)과 가용성(A) 중 하나를 포기해야 한다
CP 시스템장애 시 응답 거부, 데이터는 항상 정확. ZooKeeper, etcd, Aurora
AP 시스템장애 시도 응답, 구버전 데이터 허용. Cassandra, DynamoDB, ElastiCache
PACELCCAP를 확장: 정상 상태에서도 지연(Latency)과 일관성(Consistency) 트레이드오프 존재
Eventual Consistency시간이 지나면 수렴 보장, 즉시는 아님. DynamoDB 기본값
Strong Consistency쓰기 직후 읽기 항상 최신값. Aurora Writer, DynamoDB ConsistentRead
실무 선택ACID+복잡한 쿼리 → Aurora, 높은 TPS+유연한 스키마 → DynamoDB
핵심 함정Write 후 바로 Read Replica/DynamoDB 기본 읽기 → Eventual Consistency로 인한 불일치 주의

  • “우리 서비스가 AP인가 CP인가”를 팀원에게 설명할 수 있는가? 선택 근거는?
  • Aurora에서 Read Replica를 사용하는 API와 사용하면 안 되는 API를 각각 하나씩 들 수 있는가?
  • DynamoDB 기본 읽기가 Eventually Consistent인 이유를 CAP 정리와 연결하여 설명할 수 있는가?
  • PACELC에서 “Else” 부분이 실제 시스템 설계에 영향을 주는 상황을 하나 들 수 있는가?
  • 현재 재직 중인 회사의 주문 데이터베이스를 AP로 바꾸면 어떤 문제가 발생하는가?