Split View: 분산 시스템 핵심 개념 완전 정리: CAP 정리부터 합의 알고리즘, 일관성 모델까지
분산 시스템 핵심 개념 완전 정리: CAP 정리부터 합의 알고리즘, 일관성 모델까지
목차
1. 왜 분산 시스템인가
1.1 분산 시스템의 필요성
단일 서버로는 현대 서비스의 요구를 충족할 수 없습니다. 분산 시스템이 필요한 세 가지 핵심 이유가 있습니다.
확장성(Scalability): 트래픽이 증가하면 단일 서버의 CPU, 메모리, 디스크에는 한계가 있습니다. 수평 확장(Scale-Out)으로 여러 서버에 부하를 분산합니다.
가용성(Availability): 서버 하나가 죽어도 서비스가 계속 동작해야 합니다. 넷플릭스, 구글 같은 서비스는 99.99% 이상의 가용성을 목표로 합니다.
내결함성(Fault Tolerance): 네트워크 단절, 디스크 장애, 데이터센터 화재 등 장애는 반드시 발생합니다. 분산 시스템은 이런 장애를 견딜 수 있도록 설계됩니다.
1.2 분산 시스템의 도전 과제
단일 서버 분산 시스템
+-----------+ +-----+ +-----+ +-----+
| App +DB | vs | N1 |--| N2 |--| N3 |
+-----------+ +-----+ +-----+ +-----+
\ | /
네트워크(불안정)
분산 시스템은 다음과 같은 고유한 문제를 가집니다:
- 네트워크 지연: 노드 간 통신은 ms~s 단위의 지연이 발생
- 부분 장애: 일부 노드만 죽고 나머지는 정상 동작
- 시계 동기화 불가: 각 노드의 물리적 시계가 미세하게 다름
- 비잔틴 장애: 노드가 잘못된 데이터를 보낼 수 있음
2. CAP 정리
2.1 CAP 정리란?
2000년 Eric Brewer가 제안하고, 2002년 Seth Gilbert와 Nancy Lynch가 증명한 정리입니다.
분산 시스템은 다음 세 가지 속성 중 최대 두 가지만 동시에 보장할 수 있습니다:
| 속성 | 설명 |
|---|---|
| Consistency (일관성) | 모든 노드가 같은 시점에 같은 데이터를 봄 |
| Availability (가용성) | 모든 요청이 (성공/실패) 응답을 받음 |
| Partition Tolerance (분할내성) | 네트워크 분할이 발생해도 시스템이 동작 |
2.2 왜 셋 다 못 가지나 — 직관적 증명
네트워크 분할 발생!
[Node A] ----X---- [Node B]
data=1 data=1
Client가 Node A에 write(data=2) 요청
선택지 1 (CP): Node B와 동기화 불가 → 요청 거부 (가용성 포기)
선택지 2 (AP): 일단 Node A만 업데이트 → 불일치 허용 (일관성 포기)
네트워크 분할(P)은 분산 시스템에서 피할 수 없으므로, 실질적으로 CP vs AP 중 선택해야 합니다.
2.3 CP 시스템 vs AP 시스템
| 특성 | CP 시스템 | AP 시스템 |
|---|---|---|
| 분할 시 동작 | 쓰기 거부 또는 대기 | 쓰기 허용, 나중에 병합 |
| 예시 | ZooKeeper, etcd, Spanner | Cassandra, DynamoDB, Riak |
| 적합 사례 | 금융 거래, 리더 선출 | 소셜미디어, 쇼핑 카트 |
2.4 PACELC 확장
CAP의 한계를 보완하는 PACELC 모델:
if (Partition) then
Availability vs Consistency
else
Latency vs Consistency
| 시스템 | P 발생 시 | 정상 시 |
|---|---|---|
| Cassandra | PA (가용성) | EL (낮은 지연) |
| Spanner | PC (일관성) | EC (일관성) |
| MongoDB | PA (가용성) | EC (일관성) |
3. 일관성 모델
3.1 일관성 수준 비교
강한 순서대로:
엄격한 일관성 (Strict)
↓
선형화 가능성 (Linearizability)
↓
순차 일관성 (Sequential)
↓
인과 일관성 (Causal)
↓
최종 일관성 (Eventual)
3.2 Strong Consistency (강한 일관성)
모든 읽기가 가장 최근 쓰기의 결과를 반환합니다.
시간 →
Client A: write(x=1) ------>
Client B: read(x) → 반드시 1 반환
- 구현: 모든 노드에 동기 복제 후 응답
- 장점: 프로그래밍 모델이 단순
- 단점: 높은 지연, 낮은 가용성
3.3 Sequential Consistency (순차 일관성)
모든 노드가 동일한 순서로 연산을 봅니다. 단, 실시간 순서와 다를 수 있습니다.
실제 시간: A:write(x=1) B:write(x=2)
순차 일관성: B:write(x=2), A:write(x=1) 도 허용
(모든 노드가 이 순서에 동의하면)
3.4 Causal Consistency (인과 일관성)
인과 관계가 있는 연산은 순서가 보장됩니다. 인과 관계가 없는 연산은 순서가 다를 수 있습니다.
A가 글 작성 → B가 댓글 작성 (인과 관계 있음)
→ 모든 노드에서 글이 댓글보다 먼저 보임
C가 글 작성 / D가 별도 글 작성 (인과 관계 없음)
→ 노드마다 순서가 다를 수 있음
3.5 Eventual Consistency (최종 일관성)
업데이트가 중단되면 최종적으로 모든 노드가 같은 값을 갖게 됩니다.
시간 →
Node A: x=1 ......... x=1 (변경 전파) → x=1
Node B: x=0 ......... x=0 → x=1 (약간의 지연 후)
Node C: x=0 ......... x=0 → x=1
- DNS, CDN 캐시, Amazon S3 등에서 사용
- 충돌 해결 전략 필요: Last-Write-Wins, CRDT, 애플리케이션 수준 병합
3.6 일관성 모델 비교표
| 모델 | 지연 | 가용성 | 프로그래밍 난이도 | 사용 사례 |
|---|---|---|---|---|
| Strong | 높음 | 낮음 | 쉬움 | 금융, 재고 |
| Sequential | 중간 | 중간 | 중간 | 분산 락 |
| Causal | 낮음 | 높음 | 중간 | 소셜 피드 |
| Eventual | 매우 낮음 | 매우 높음 | 어려움 | DNS, 캐시 |
4. 복제 (Replication)
4.1 Leader-Follower 복제
가장 일반적인 복제 방식입니다.
쓰기
Client → [Leader] → 복제 → [Follower 1]
→ [Follower 2]
→ [Follower 3]
읽기
Client → [Follower 1, 2, 3 중 아무나]
동기 복제: 리더가 모든 팔로워의 확인을 받은 후 응답. 강한 일관성, 높은 지연.
비동기 복제: 리더가 즉시 응답, 팔로워는 나중에 복제. 낮은 지연, 데이터 손실 가능.
반동기 복제: 최소 1개 팔로워의 확인 후 응답. MySQL, PostgreSQL 기본 방식.
# 반동기 복제 의사코드
def write(data):
leader.write(data)
ack_count = 1 # 리더 자신
for follower in followers:
if follower.replicate(data):
ack_count += 1
if ack_count >= 2: # 최소 1개 팔로워 확인
return SUCCESS
return FAILURE
4.2 Multi-Leader 복제
여러 노드가 동시에 쓰기를 받을 수 있습니다.
[Leader A] ←→ [Leader B] ←→ [Leader C]
↓ ↓ ↓
[Follower] [Follower] [Follower]
- 사용 사례: 다중 데이터센터, 오프라인 작업(Google Docs)
- 핵심 문제: 쓰기 충돌 해결
- Last-Write-Wins (LWW): 타임스탬프가 큰 쓰기가 승리
- 병합: CRDT(Conflict-free Replicated Data Type) 사용
- 사용자 해결: 충돌을 사용자에게 보여주고 선택하게 함
4.3 Leaderless 복제 (Quorum)
리더가 없고, 클라이언트가 여러 노드에 동시에 읽기/쓰기합니다.
write(x=1)
Client → [Node A] ✓
→ [Node B] ✓
→ [Node C] ✗ (장애)
W=2 성공 → 쓰기 성공!
read(x)
Client → [Node A] x=1
→ [Node B] x=1
→ [Node C] x=0 (오래된 값)
R=2 → 최신 값(x=1) 반환
Quorum 공식: W + R > N이면 최신 데이터 읽기 보장
| 설정 | N | W | R | 특성 |
|---|---|---|---|---|
| 강한 일관성 | 3 | 2 | 2 | 일관성 보장 |
| 빠른 쓰기 | 3 | 1 | 3 | 쓰기 빠름, 읽기 느림 |
| 빠른 읽기 | 3 | 3 | 1 | 쓰기 느림, 읽기 빠름 |
- Cassandra, DynamoDB, Riak에서 사용
- 안티 엔트로피(Anti-entropy), 읽기 복구(Read Repair)로 불일치 해결
5. 파티셔닝 (Partitioning / Sharding)
5.1 왜 파티셔닝인가
데이터가 단일 노드 용량을 초과하면 여러 노드에 분산 저장합니다.
5.2 해시 파티셔닝
hash(key) mod N = partition_number
key="user_123" → hash → 7 → 7 mod 3 = 1 → Partition 1
key="user_456" → hash → 2 → 2 mod 3 = 2 → Partition 2
- 장점: 데이터 균등 분배
- 단점: 범위 쿼리 비효율적, 노드 추가/제거 시 대량 재배치
5.3 범위 파티셔닝
Partition 0: A-F
Partition 1: G-M
Partition 2: N-S
Partition 3: T-Z
- 장점: 범위 쿼리 효율적
- 단점: 핫스팟 발생 가능 (특정 범위에 데이터 집중)
5.4 Consistent Hashing
노드 추가/제거 시 최소한의 키만 재배치합니다.
0
/ \
330 30
/ \
300 60
| Ring |
270 90
\ /
240 120
\ /
180
Node A: 0-90
Node B: 90-180
Node C: 180-270
Node D: 270-360
Node E 추가(135 위치) → Node B의 90-135만 Node E로 이동
- 가상 노드(Virtual Node)로 불균형 해소
- Cassandra, DynamoDB, Memcached에서 사용
5.5 리밸런싱 전략
| 전략 | 설명 | 예시 |
|---|---|---|
| 고정 파티션 수 | 처음부터 파티션 수 고정, 노드에 분배 | Elasticsearch, Riak |
| 동적 분할 | 파티션 크기 기준으로 분할/병합 | HBase, RethinkDB |
| 노드 비례 | 노드 수에 비례하여 파티션 수 결정 | Cassandra |
6. 합의 알고리즘 (Consensus)
6.1 합의 문제란?
분산 시스템의 모든 노드가 하나의 값에 합의하는 것. 리더 선출, 원자적 커밋, 분산 락 등에 필수.
6.2 Paxos 기초
Leslie Lamport가 1989년 제안. 이해하기 어렵기로 유명합니다.
세 가지 역할:
- Proposer: 값을 제안
- Acceptor: 값을 수락/거부
- Learner: 합의된 값을 학습
두 단계:
Phase 1: Prepare
Proposer → Acceptor: "제안 번호 n을 준비해주세요"
Acceptor → Proposer: "OK" (이전에 수락한 값이 있으면 함께 전달)
Phase 2: Accept
Proposer → Acceptor: "제안 번호 n, 값 v를 수락해주세요"
Acceptor → Proposer: "수락" (과반수가 수락하면 합의 완료)
- 문제점: 라이브락 가능, Multi-Paxos로 해결
6.3 Raft 상세
Diego Ongaro와 John Ousterhout가 2014년 제안. Paxos보다 이해하기 쉽도록 설계.
핵심 개념: 리더 기반 합의
노드 상태: Leader, Follower, Candidate
6.3.1 리더 선출 (Leader Election)
시간 →
Term 1 Term 2
Follower: [Heartbeat]... [Timeout!] → Candidate
Candidate: 자신에게 투표 + 다른 노드에 RequestVote
과반수 득표 → Leader 당선!
Leader: [Heartbeat]→[Heartbeat]→[Heartbeat]→...
# Raft 리더 선출 의사코드
class RaftNode:
def __init__(self):
self.state = "follower"
self.current_term = 0
self.voted_for = None
self.election_timeout = random(150, 300) # ms
def on_timeout(self):
self.state = "candidate"
self.current_term += 1
self.voted_for = self.id
votes = 1 # 자기 자신
for peer in self.peers:
if peer.request_vote(self.current_term, self.log):
votes += 1
if votes > len(self.peers) // 2:
self.state = "leader"
self.send_heartbeats()
6.3.2 로그 복제 (Log Replication)
Client → Leader: write(x=5)
Leader Log: [Term1:x=3] [Term1:y=7] [Term2:x=5]
↓ AppendEntries
Follower1: [Term1:x=3] [Term1:y=7] [Term2:x=5] ✓
Follower2: [Term1:x=3] [Term1:y=7] [Term2:x=5] ✓
Follower3: [Term1:x=3] [Term1:y=7] (네트워크 지연)
과반수(3/5) 복제 완료 → 커밋! → 클라이언트에 응답
6.3.3 안전성 보장 (Safety)
- Election Safety: 한 Term에 최대 한 명의 리더
- Leader Append-Only: 리더는 로그를 덮어쓰거나 삭제하지 않음
- Log Matching: 두 로그가 같은 인덱스에 같은 Term의 엔트리면, 그 이전 엔트리도 모두 동일
- State Machine Safety: 커밋된 엔트리는 모든 노드에서 같은 순서로 적용
6.4 Raft 사용 사례
| 시스템 | 용도 |
|---|---|
| etcd | Kubernetes 클러스터 상태 저장 |
| Consul | 서비스 디스커버리, KV 저장소 |
| CockroachDB | 분산 SQL 데이터베이스 |
| TiKV | TiDB의 분산 KV 저장소 |
7. 분산 트랜잭션
7.1 2PC (Two-Phase Commit)
Phase 1: Prepare (투표)
Coordinator → Participant A: "커밋 가능?"
Coordinator → Participant B: "커밋 가능?"
A → Coordinator: "Yes"
B → Coordinator: "Yes"
Phase 2: Commit (실행)
Coordinator → A: "커밋!"
Coordinator → B: "커밋!"
문제점:
- 코디네이터 단일 장애점 (SPOF)
- 블로킹: Prepare 후 코디네이터 장애 시 참여자가 무한 대기
- 성능: 동기 프로토콜이라 느림
7.2 3PC (Three-Phase Commit)
2PC의 블로킹 문제를 해결하기 위해 Pre-Commit 단계를 추가합니다.
Phase 1: CanCommit
Phase 2: PreCommit (타임아웃 설정)
Phase 3: DoCommit
- 네트워크 분할 시 여전히 불일치 가능
- 실무에서 거의 사용되지 않음
7.3 Saga 패턴
긴 트랜잭션을 여러 로컬 트랜잭션으로 분할하고, 실패 시 보상 트랜잭션을 실행합니다.
주문 생성 Saga:
T1: 주문 생성 → T2: 결제 처리 → T3: 재고 차감 → T4: 배송 시작
↓ 실패!
C2: 결제 취소 ← C1: 주문 취소
두 가지 구현 방식:
| 방식 | 설명 | 장단점 |
|---|---|---|
| Choreography | 이벤트 기반, 각 서비스가 다음 이벤트 발행 | 단순, 결합도 낮음. 추적 어려움 |
| Orchestration | 중앙 오케스트레이터가 순서 제어 | 추적 쉬움, 중앙 집중 위험 |
# Saga Orchestrator 의사코드
class OrderSaga:
steps = [
("create_order", "cancel_order"),
("process_payment", "refund_payment"),
("update_inventory", "restore_inventory"),
("arrange_shipping", "cancel_shipping"),
]
def execute(self):
completed = []
for action, compensation in self.steps:
try:
getattr(self, action)()
completed.append(compensation)
except Exception:
# 보상 트랜잭션 역순 실행
for comp in reversed(completed):
getattr(self, comp)()
raise SagaFailed()
8. 분산 시계 (Distributed Clocks)
8.1 물리적 시계의 문제
- NTP(Network Time Protocol) 동기화 오차: 수 ms ~ 수백 ms
- 시계 드리프트(Clock Drift): 시간이 지남에 따라 점점 벌어짐
- 윤초(Leap Second): 가끔 1초가 추가되거나 삭제됨
결론: 물리적 시계만으로는 이벤트 순서를 정확히 판단할 수 없습니다.
8.2 Lamport Clock (논리적 시계)
Leslie Lamport가 1978년 제안. 각 프로세스가 카운터를 유지합니다.
규칙:
1. 이벤트 발생 시 카운터 증가
2. 메시지 전송 시 자신의 카운터를 포함
3. 메시지 수신 시 max(자신 카운터, 받은 카운터) + 1
Process A: (1)→ (2)→ (3)────→ (6)
send recv
Process B: (1)→ (2)→ (4)→ (5)
recv send
- 한계: 인과 관계는 알 수 있지만, 두 이벤트가 동시(concurrent)인지는 알 수 없음
8.3 Vector Clock (벡터 시계)
각 노드가 모든 노드의 카운터를 벡터로 유지합니다.
3개 노드 A, B, C
A: [1,0,0] → [2,0,0] → [2,0,0] send to B
B: [0,0,0] → [0,1,0] → [2,2,0] recv from A → [2,3,0] send to C
C: [0,0,0] → [0,0,1] → [2,3,2] recv from B
동시성 판단:
V1 <= V2: V1의 모든 요소가 V2 이하 — V1이 V2보다 먼저(happens-before)- V1과 V2가 비교 불가 — 동시(concurrent) 이벤트
def compare_vector_clocks(vc1, vc2):
"""벡터 시계 비교"""
less = False
greater = False
for a, b in zip(vc1, vc2):
if a < b:
less = True
elif a > b:
greater = True
if less and not greater:
return "BEFORE" # vc1 < vc2
elif greater and not less:
return "AFTER" # vc1 > vc2
elif not less and not greater:
return "EQUAL"
else:
return "CONCURRENT" # 동시 이벤트!
8.4 Hybrid Logical Clock (HLC)
물리적 시계 + 논리적 시계의 조합. CockroachDB, MongoDB에서 사용합니다.
HLC = (물리적 시간, 논리적 카운터)
물리적 시간이 같을 때 논리적 카운터로 순서 결정
→ NTP 오차 범위 내에서 인과 관계 보장
9. 장애 감지 (Failure Detection)
9.1 Heartbeat 방식
Node A → "살아있어?" → Node B
Node A ← "응!" ← Node B
... 시간 초과 ...
Node A → "살아있어?" → Node B
Node A ← (무응답) ← Node B
→ Node B 장애로 판단
- 단순하지만, 네트워크 지연 시 오탐(False Positive) 발생
- 타임아웃 설정이 중요: 짧으면 오탐, 길면 감지 지연
9.2 Phi Accrual Failure Detector
하트비트 도착 시간의 분포를 학습하여 장애 확률을 계산합니다.
Phi(φ) 값:
φ = 1 → 10% 확률로 장애
φ = 2 → 1% 확률로 장애
φ = 3 → 0.1% 확률로 장애
임계값(예: φ > 8)을 넘으면 장애로 판단
- Cassandra, Akka에서 사용
- 네트워크 상태에 적응적으로 반응
9.3 SWIM Protocol
Scalable Weakly-consistent Infection-style Membership. 효율적인 그룹 멤버십 프로토콜입니다.
1. Node A가 Node B에 ping
2. 응답 없으면 → Node C, D에 "B에 indirect ping 해줘" 요청
3. C, D가 B에 ping → 응답 없으면 B를 의심(suspect)
4. 일정 시간 후 여전히 응답 없으면 B를 제거
5. 가십(gossip)으로 멤버십 변경을 전파
- O(log N) 라운드에 모든 노드에 전파
- HashiCorp Memberlist, Consul에서 사용
10. 실제 시스템 분석
10.1 Apache Cassandra (AP 시스템)
아키텍처:
- Leaderless, Consistent Hashing
- Quorum 읽기/쓰기 (W + R > N)
- 가십 프로토콜로 멤버십 관리
- Last-Write-Wins 충돌 해결
- SSTable + Memtable 저장 구조
PACELC: PA/EL
- 분할 시: 가용성 우선
- 정상 시: 낮은 지연 우선
10.2 Google Spanner (CP 시스템)
아키텍처:
- 전 세계 분산, 강한 일관성
- TrueTime API: GPS + 원자시계로 시간 불확실성 구간 제공
- Paxos 기반 복제
- 외부 일관성(External Consistency) 보장
TrueTime:
TT.now() → [earliest, latest]
불확실성 구간이 보통 1-7ms
트랜잭션 커밋 시:
1. 타임스탬프 s 할당
2. commit-wait: TT.after(s)가 true가 될 때까지 대기
3. 불확실성 구간만큼 대기하여 순서 보장
10.3 Amazon DynamoDB (AP 시스템)
아키텍처:
- Consistent Hashing + 가상 노드
- Sloppy Quorum: 장애 시 다른 노드가 대신 응답
- Hinted Handoff: 복구 후 원래 노드에 데이터 전달
- Vector Clock으로 버전 관리
- Merkle Tree로 안티 엔트로피
10.4 시스템 비교표
| 특성 | Cassandra | Spanner | DynamoDB |
|---|---|---|---|
| CAP 분류 | AP | CP | AP |
| 일관성 | Tunable | Strong | Tunable |
| 복제 | Leaderless | Paxos | Leaderless |
| 파티셔닝 | Consistent Hash | Range | Consistent Hash |
| 시계 | NTP | TrueTime | Vector Clock |
| 쿼리 언어 | CQL | SQL | PartiQL |
11. DDIA 핵심 요약
Martin Kleppmann의 "Designing Data-Intensive Applications" 핵심 내용:
Part 1: 데이터 시스템의 기초
| 장 | 핵심 키워드 |
|---|---|
| 1장 | 신뢰성, 확장성, 유지보수성 |
| 2장 | 관계형 vs 문서 vs 그래프 모델 |
| 3장 | 저장 엔진: B-Tree vs LSM-Tree |
| 4장 | 인코딩: JSON, Protobuf, Avro |
Part 2: 분산 데이터
| 장 | 핵심 키워드 |
|---|---|
| 5장 | 복제: Leader-Follower, Leaderless |
| 6장 | 파티셔닝: Hash, Range |
| 7장 | 트랜잭션: ACID, 격리 수준 |
| 8장 | 분산 시스템의 어려움 |
| 9장 | 일관성과 합의 |
Part 3: 파생 데이터
| 장 | 핵심 키워드 |
|---|---|
| 10장 | 배치 처리: MapReduce |
| 11장 | 스트림 처리: 이벤트 소싱 |
| 12장 | 데이터 시스템의 미래 |
12. 면접 질문 15선
Q1. CAP 정리를 설명하고, 실제 시스템에서 어떻게 적용되는지 예를 들어주세요.
CAP 정리는 분산 시스템에서 일관성(Consistency), 가용성(Availability), 분할내성(Partition Tolerance) 중 최대 두 가지만 동시에 보장할 수 있다는 정리입니다. 네트워크 분할은 피할 수 없으므로 실질적으로 CP(일관성 우선)와 AP(가용성 우선) 중 선택합니다. ZooKeeper는 CP 시스템으로 리더 선출과 분산 락에 적합하고, Cassandra는 AP 시스템으로 높은 쓰기 처리량이 필요한 경우에 적합합니다.
Q2. Raft 합의 알고리즘의 리더 선출 과정을 설명하세요.
Raft에서 팔로워가 election timeout 내에 리더의 하트비트를 받지 못하면 후보(Candidate)가 됩니다. 후보는 Term을 증가시키고, 자신에게 투표한 뒤, 다른 노드에 RequestVote RPC를 보냅니다. 과반수의 투표를 받으면 리더가 되고, 즉시 하트비트를 보내 자신의 리더십을 알립니다. 각 Term에 최대 한 명의 리더만 존재할 수 있습니다.
Q3. Eventual Consistency와 Strong Consistency의 차이를 설명하세요.
Strong Consistency는 모든 읽기가 가장 최근 쓰기의 결과를 반환합니다. 동기 복제가 필요하여 지연이 높고 가용성이 낮습니다. Eventual Consistency는 업데이트 중단 후 최종적으로 모든 복제본이 동일한 값을 갖게 됩니다. 중간에 오래된 데이터를 읽을 수 있지만 지연이 낮고 가용성이 높습니다. DNS, 캐시 시스템에서 주로 사용됩니다.
Q4. Consistent Hashing을 설명하고, 가상 노드가 왜 필요한지 말해주세요.
Consistent Hashing은 해시 공간을 원(Ring)으로 구성하여 노드 추가/제거 시 최소한의 키만 재배치하는 방식입니다. 키와 노드를 같은 해시 함수로 매핑하고, 키는 시계 방향으로 가장 가까운 노드에 할당됩니다. 가상 노드(Virtual Node)는 물리 노드가 여러 해시 위치를 가지게 하여 데이터 분포의 불균형을 해소합니다.
Q5. 2PC와 Saga 패턴의 차이를 설명하세요.
2PC(Two-Phase Commit)는 코디네이터가 모든 참여자에게 prepare/commit을 지시하여 원자적 커밋을 보장합니다. 동기적이고 블로킹이며 코디네이터가 SPOF입니다. Saga 패턴은 긴 트랜잭션을 로컬 트랜잭션 체인으로 분할하고, 실패 시 보상 트랜잭션을 역순 실행합니다. 비동기적이고 확장성이 좋지만 격리성이 보장되지 않습니다.
Q6. Vector Clock이 Lamport Clock보다 나은 점은 무엇인가요?
Lamport Clock은 이벤트 순서(happens-before)를 판단할 수 있지만, 두 이벤트가 동시(concurrent)인지 알 수 없습니다. Vector Clock은 각 노드가 모든 노드의 카운터를 벡터로 유지하여 동시성을 정확히 판단할 수 있습니다. 벡터 비교가 불가능하면 두 이벤트는 동시적이며, 충돌 해결이 필요합니다.
Q7. Leader-Follower, Multi-Leader, Leaderless 복제의 장단점을 비교하세요.
Leader-Follower는 단일 쓰기 지점으로 충돌이 없지만 리더가 SPOF이고 쓰기 확장이 어렵습니다. Multi-Leader는 다중 데이터센터에서 쓰기 지연이 낮지만 충돌 해결이 복잡합니다. Leaderless는 높은 가용성과 쓰기 처리량을 제공하지만 Quorum 설정과 충돌 해결이 필요합니다.
Q8. PACELC 모델이 CAP을 어떻게 확장하나요?
PACELC는 네트워크 분할(P) 시 가용성(A) vs 일관성(C) 선택에 더해, 정상(E: else) 상태에서 지연(L) vs 일관성(C) 트레이드오프를 추가합니다. Cassandra는 PA/EL(분할 시 가용성, 정상 시 낮은 지연)이고, Spanner는 PC/EC(항상 일관성)입니다.
Q9. Google Spanner의 TrueTime이 강한 일관성을 어떻게 보장하나요?
TrueTime은 GPS와 원자시계를 사용해 시간 불확실성 구간을 제공합니다. 트랜잭션 커밋 시 타임스탬프를 할당하고, commit-wait로 불확실성 구간만큼 대기합니다. 이렇게 하면 나중에 커밋된 트랜잭션이 반드시 더 큰 타임스탬프를 가지게 되어, 전 세계적으로 외부 일관성이 보장됩니다.
Q10. Quorum 읽기/쓰기에서 W + R > N이 왜 일관성을 보장하나요?
N개 노드에서 W개에 쓰고 R개에서 읽으면, W + R > N일 때 쓰기 집합과 읽기 집합에 반드시 교집합이 있습니다. 이 교집합 노드가 최신 데이터를 가지고 있으므로 읽기 시 최신 값을 반환할 수 있습니다. 예를 들어 N=3, W=2, R=2이면 최소 1개 노드가 공통됩니다.
Q11. 분산 시스템에서 Split-Brain 문제를 어떻게 해결하나요?
Split-Brain은 네트워크 분할로 두 파티션이 각각 자신이 리더라고 판단하는 상황입니다. 해결 방법: Quorum 기반 리더 선출(과반수 없는 파티션은 리더를 선출할 수 없음), Fencing Token(이전 리더의 요청을 거부), STONITH(Shoot The Other Node In The Head, 의심스러운 노드를 강제 종료) 등이 있습니다.
Q12. LSM-Tree와 B-Tree의 차이를 분산 시스템 관점에서 설명하세요.
LSM-Tree는 쓰기 최적화 구조로 모든 쓰기가 순차적이며, Compaction으로 정리합니다. 쓰기 집중 워크로드(Cassandra, HBase)에 적합합니다. B-Tree는 읽기 최적화 구조로 제자리 업데이트(in-place update)를 합니다. 읽기 집중 워크로드(전통 RDBMS)에 적합합니다. 분산 환경에서 LSM-Tree는 복제 지연이 적고 높은 쓰기 처리량을 제공합니다.
Q13. Phi Accrual Failure Detector가 단순 타임아웃보다 좋은 이유는?
단순 타임아웃은 고정된 시간 내에 응답이 없으면 장애로 판단합니다. 네트워크 지연이 변동하면 오탐이 많습니다. Phi Accrual Failure Detector는 하트비트 도착 시간의 통계적 분포를 학습하여, 현재 도착 시간이 분포에서 얼마나 이탈했는지(phi 값)로 장애 확률을 계산합니다. 네트워크 상태 변화에 적응적입니다.
Q14. Saga 패턴에서 Choreography와 Orchestration의 차이는?
Choreography는 각 서비스가 이벤트를 발행하고 다른 서비스가 구독하여 다음 단계를 수행합니다. 중앙 제어가 없어 결합도가 낮지만 흐름 추적이 어렵습니다. Orchestration은 중앙 오케스트레이터가 전체 흐름을 제어합니다. 추적과 디버깅이 쉽지만 오케스트레이터가 SPOF가 될 수 있습니다.
Q15. 분산 시스템에서 멱등성(Idempotency)이 왜 중요한가요?
네트워크 장애로 요청이 재전송될 수 있으므로, 같은 요청을 여러 번 수행해도 결과가 동일해야 합니다. 멱등키(Idempotency Key)를 사용하여 중복 요청을 감지하고, 결제나 재고 차감 같은 부작용 있는 연산에서 중복 처리를 방지합니다.
13. 퀴즈 5선
퀴즈 1. N=5인 분산 시스템에서 W=3, R=2일 때 일관된 읽기가 보장되는가?
보장됩니다. W + R = 3 + 2 = 5 > N(5)이므로 쓰기 집합과 읽기 집합이 반드시 겹칩니다. 단, W + R = N인 경우는 경계값이므로 모든 노드가 정상일 때만 보장됩니다.
퀴즈 2. Raft에서 Term 5의 리더가 있을 때, Term 3의 RequestVote를 받으면 어떻게 하나요?
거부합니다. Raft에서 노드는 자신의 현재 Term보다 작은 Term의 요청을 항상 거부합니다. 이를 통해 오래된 리더의 간섭을 방지합니다.
퀴즈 3. Vector Clock V1=[2,3,1]과 V2=[3,2,1]의 관계는?
동시(Concurrent) 이벤트입니다. V1의 첫 번째 요소(2)가 V2(3)보다 작지만, 두 번째 요소(3)가 V2(2)보다 큽니다. 어느 한쪽이 완전히 크거나 같지 않으므로 비교 불가능하며, 이는 동시 이벤트를 의미합니다.
퀴즈 4. Cassandra에서 ONE, QUORUM, ALL 일관성 수준의 차이는?
- ONE: 1개 노드 응답으로 충분. 가장 빠르지만 오래된 데이터를 읽을 수 있음
- QUORUM: 과반수(N/2+1) 노드 응답 필요. 균형 잡힌 선택
- ALL: 모든 노드 응답 필요. 가장 느리지만 강한 일관성. 하나라도 장애면 실패
퀴즈 5. 2PC에서 Prepare 후 코디네이터가 장애 나면 어떻게 되나요?
참여자들이 블로킹 됩니다. Prepare에 Yes로 응답한 참여자는 커밋도 중단도 할 수 없어 리소스 잠금 상태로 무한 대기합니다. 이것이 2PC의 가장 큰 문제점이며, 3PC나 코디네이터 로그 복구로 해결합니다.
14. 참고 자료
도서
- Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
- Maarten van Steen, Andrew S. Tanenbaum, "Distributed Systems" (4th ed., 2023)
- Mikito Takada, "Distributed Systems for Fun and Profit" (온라인 무료)
논문
- Brewer, E., "CAP Twelve Years Later" (IEEE Computer, 2012)
- Lamport, L., "The Part-Time Parliament" (1998) - Paxos 원본 논문
- Ongaro, D. & Ousterhout, J., "In Search of an Understandable Consensus Algorithm" (2014) - Raft 논문
- Lamport, L., "Time, Clocks, and the Ordering of Events in a Distributed System" (1978)
- DeCandia, G. et al., "Dynamo: Amazon's Highly Available Key-value Store" (2007)
- Corbett, J.C. et al., "Spanner: Google's Globally-Distributed Database" (2012)
웹 자료
- Jepsen - 분산 시스템 안전성 테스트: https://jepsen.io/
- The Raft Consensus Algorithm 시각화: https://raft.github.io/
- Aphyr의 분산 시스템 강의: https://github.com/aphyr/distsys-class
- AWS re:Invent 분산 시스템 발표 영상 시리즈
- Martin Kleppmann 블로그: https://martin.kleppmann.com/
- Marc Brooker 블로그: https://brooker.co.za/blog/
- Distributed Systems Reading Group: http://dsrg.pdos.csail.mit.edu/
Distributed Systems Fundamentals: From CAP Theorem to Consensus Algorithms and Consistency Models
Table of Contents
1. Why Distributed Systems
1.1 The Need for Distributed Systems
A single server cannot meet the demands of modern services. There are three core reasons why distributed systems are essential.
Scalability: As traffic grows, a single server's CPU, memory, and disk hit their limits. Horizontal scaling (scale-out) distributes the load across multiple servers.
Availability: Even if one server dies, the service must continue running. Services like Netflix and Google target 99.99%+ availability.
Fault Tolerance: Network partitions, disk failures, data center fires — failures are inevitable. Distributed systems are designed to withstand them.
1.2 Challenges of Distributed Systems
Single Server Distributed System
+-----------+ +-----+ +-----+ +-----+
| App + DB | vs | N1 |--| N2 |--| N3 |
+-----------+ +-----+ +-----+ +-----+
\ | /
Network (unreliable)
Distributed systems have unique challenges:
- Network latency: Communication between nodes takes ms to seconds
- Partial failures: Some nodes crash while others remain healthy
- Clock synchronization: Physical clocks on each node differ slightly
- Byzantine failures: Nodes may send incorrect data
2. CAP Theorem
2.1 What Is the CAP Theorem?
Proposed by Eric Brewer in 2000 and proven by Seth Gilbert and Nancy Lynch in 2002.
A distributed system can guarantee at most two of three properties simultaneously:
| Property | Description |
|---|---|
| Consistency | Every read receives the most recent write |
| Availability | Every request receives a response (success or failure) |
| Partition Tolerance | System continues to operate despite network partitions |
2.2 Why You Cannot Have All Three — Intuitive Proof
Network partition occurs!
[Node A] ----X---- [Node B]
data=1 data=1
Client sends write(data=2) to Node A
Option 1 (CP): Cannot sync with Node B → Reject request (sacrifice availability)
Option 2 (AP): Update Node A only → Allow inconsistency (sacrifice consistency)
Since network partitions (P) are unavoidable in distributed systems, the real choice is CP vs AP.
2.3 CP Systems vs AP Systems
| Aspect | CP Systems | AP Systems |
|---|---|---|
| Behavior during partition | Reject writes or wait | Accept writes, merge later |
| Examples | ZooKeeper, etcd, Spanner | Cassandra, DynamoDB, Riak |
| Best for | Financial transactions, leader election | Social media, shopping carts |
2.4 PACELC Extension
The PACELC model addresses CAP's limitations:
if (Partition) then
Availability vs Consistency
else
Latency vs Consistency
| System | During Partition | Normal Operation |
|---|---|---|
| Cassandra | PA (availability) | EL (low latency) |
| Spanner | PC (consistency) | EC (consistency) |
| MongoDB | PA (availability) | EC (consistency) |
3. Consistency Models
3.1 Consistency Levels Compared
From strongest to weakest:
Strict Consistency
|
Linearizability
|
Sequential Consistency
|
Causal Consistency
|
Eventual Consistency
3.2 Strong Consistency
Every read returns the result of the most recent write.
Time -->
Client A: write(x=1) ------>
Client B: read(x) --> must return 1
- Implementation: Synchronous replication to all nodes before responding
- Pro: Simple programming model
- Con: High latency, low availability
3.3 Sequential Consistency
All nodes see operations in the same order, but the order may differ from real-time.
Real time: A:write(x=1) B:write(x=2)
Sequential OK: B:write(x=2), A:write(x=1)
(as long as all nodes agree on this order)
3.4 Causal Consistency
Operations with causal relationships are ordered. Unrelated operations may be seen in different orders.
A writes a post --> B writes a comment (causally related)
--> All nodes see the post before the comment
C writes a post / D writes another post (no causal relation)
--> Different nodes may see them in different orders
3.5 Eventual Consistency
If updates stop, eventually all nodes will converge to the same value.
Time -->
Node A: x=1 ......... x=1 (propagation) --> x=1
Node B: x=0 ......... x=0 --> x=1 (slight delay)
Node C: x=0 ......... x=0 --> x=1
- Used by DNS, CDN caches, Amazon S3
- Requires conflict resolution: Last-Write-Wins, CRDTs, application-level merging
3.6 Consistency Model Comparison
| Model | Latency | Availability | Programming Difficulty | Use Cases |
|---|---|---|---|---|
| Strong | High | Low | Easy | Finance, inventory |
| Sequential | Medium | Medium | Medium | Distributed locks |
| Causal | Low | High | Medium | Social feeds |
| Eventual | Very low | Very high | Hard | DNS, caches |
4. Replication
4.1 Leader-Follower Replication
The most common replication strategy.
writes
Client --> [Leader] --> replication --> [Follower 1]
--> [Follower 2]
--> [Follower 3]
reads
Client --> [any Follower 1, 2, or 3]
Synchronous replication: Leader waits for all followers to acknowledge. Strong consistency, high latency.
Asynchronous replication: Leader responds immediately, followers replicate later. Low latency, potential data loss.
Semi-synchronous replication: Leader waits for at least one follower. Default approach in MySQL, PostgreSQL.
# Semi-synchronous replication pseudocode
def write(data):
leader.write(data)
ack_count = 1 # leader itself
for follower in followers:
if follower.replicate(data):
ack_count += 1
if ack_count >= 2: # at least 1 follower confirmed
return SUCCESS
return FAILURE
4.2 Multi-Leader Replication
Multiple nodes accept writes simultaneously.
[Leader A] <--> [Leader B] <--> [Leader C]
| | |
[Follower] [Follower] [Follower]
- Use cases: Multi-datacenter, offline work (Google Docs)
- Core challenge: Write conflict resolution
- Last-Write-Wins (LWW): Write with largest timestamp wins
- Merge: Use CRDTs (Conflict-free Replicated Data Types)
- User resolution: Show conflicts to users for manual choice
4.3 Leaderless Replication (Quorum)
No leader; clients read/write to multiple nodes simultaneously.
write(x=1)
Client --> [Node A] OK
--> [Node B] OK
--> [Node C] FAIL (down)
W=2 success --> Write succeeds!
read(x)
Client --> [Node A] x=1
--> [Node B] x=1
--> [Node C] x=0 (stale)
R=2 --> return latest value (x=1)
Quorum formula: W + R > N guarantees reading the latest data
| Config | N | W | R | Characteristics |
|---|---|---|---|---|
| Strong consistency | 3 | 2 | 2 | Consistency guaranteed |
| Fast writes | 3 | 1 | 3 | Fast writes, slow reads |
| Fast reads | 3 | 3 | 1 | Slow writes, fast reads |
- Used by Cassandra, DynamoDB, Riak
- Anti-entropy and read repair fix inconsistencies
5. Partitioning (Sharding)
5.1 Why Partition
When data exceeds a single node's capacity, distribute it across multiple nodes.
5.2 Hash Partitioning
hash(key) mod N = partition_number
key="user_123" --> hash --> 7 --> 7 mod 3 = 1 --> Partition 1
key="user_456" --> hash --> 2 --> 2 mod 3 = 2 --> Partition 2
- Pro: Even data distribution
- Con: Inefficient range queries, massive redistribution on node changes
5.3 Range Partitioning
Partition 0: A-F
Partition 1: G-M
Partition 2: N-S
Partition 3: T-Z
- Pro: Efficient range queries
- Con: Hotspots possible (data concentrated in certain ranges)
5.4 Consistent Hashing
Minimizes key redistribution when nodes are added or removed.
0
/ \
330 30
/ \
300 60
| Ring |
270 90
\ /
240 120
\ /
180
Node A: 0-90
Node B: 90-180
Node C: 180-270
Node D: 270-360
Add Node E at position 135 --> only keys 90-135 move from B to E
- Virtual nodes eliminate imbalances
- Used by Cassandra, DynamoDB, Memcached
5.5 Rebalancing Strategies
| Strategy | Description | Examples |
|---|---|---|
| Fixed partition count | Set partition count upfront, assign to nodes | Elasticsearch, Riak |
| Dynamic splitting | Split/merge based on partition size | HBase, RethinkDB |
| Proportional to nodes | Partition count scales with node count | Cassandra |
6. Consensus Algorithms
6.1 The Consensus Problem
All nodes in a distributed system agreeing on a single value. Essential for leader election, atomic commits, and distributed locks.
6.2 Paxos Basics
Proposed by Leslie Lamport in 1989. Notoriously difficult to understand.
Three roles:
- Proposer: Proposes a value
- Acceptor: Accepts or rejects values
- Learner: Learns the agreed-upon value
Two phases:
Phase 1: Prepare
Proposer --> Acceptor: "Please prepare for proposal number n"
Acceptor --> Proposer: "OK" (includes previously accepted value if any)
Phase 2: Accept
Proposer --> Acceptor: "Please accept proposal n with value v"
Acceptor --> Proposer: "Accepted" (consensus reached if majority accepts)
- Problem: Livelock possible; solved by Multi-Paxos
6.3 Raft in Detail
Proposed by Diego Ongaro and John Ousterhout in 2014. Designed to be more understandable than Paxos.
Core idea: Leader-based consensus
Node states: Leader, Follower, Candidate
6.3.1 Leader Election
Time -->
Term 1 Term 2
Follower: [Heartbeat]... [Timeout!] --> Candidate
Candidate: Votes for self + sends RequestVote to others
Gets majority --> Elected as Leader!
Leader: [Heartbeat]-->[Heartbeat]-->[Heartbeat]-->...
# Raft leader election pseudocode
class RaftNode:
def __init__(self):
self.state = "follower"
self.current_term = 0
self.voted_for = None
self.election_timeout = random(150, 300) # ms
def on_timeout(self):
self.state = "candidate"
self.current_term += 1
self.voted_for = self.id
votes = 1 # self-vote
for peer in self.peers:
if peer.request_vote(self.current_term, self.log):
votes += 1
if votes > len(self.peers) // 2:
self.state = "leader"
self.send_heartbeats()
6.3.2 Log Replication
Client --> Leader: write(x=5)
Leader Log: [Term1:x=3] [Term1:y=7] [Term2:x=5]
| AppendEntries
Follower1: [Term1:x=3] [Term1:y=7] [Term2:x=5] OK
Follower2: [Term1:x=3] [Term1:y=7] [Term2:x=5] OK
Follower3: [Term1:x=3] [Term1:y=7] (network delay)
Majority (3/5) replicated --> Commit! --> Respond to client
6.3.3 Safety Properties
- Election Safety: At most one leader per term
- Leader Append-Only: Leader never overwrites or deletes log entries
- Log Matching: If two logs have entries with the same index and term, all preceding entries are identical
- State Machine Safety: Committed entries are applied in the same order on all nodes
6.4 Raft in Production
| System | Use |
|---|---|
| etcd | Kubernetes cluster state storage |
| Consul | Service discovery, KV store |
| CockroachDB | Distributed SQL database |
| TiKV | Distributed KV engine for TiDB |
7. Distributed Transactions
7.1 Two-Phase Commit (2PC)
Phase 1: Prepare (Vote)
Coordinator --> Participant A: "Can you commit?"
Coordinator --> Participant B: "Can you commit?"
A --> Coordinator: "Yes"
B --> Coordinator: "Yes"
Phase 2: Commit (Execute)
Coordinator --> A: "Commit!"
Coordinator --> B: "Commit!"
Problems:
- Coordinator is a single point of failure (SPOF)
- Blocking: If coordinator fails after Prepare, participants wait indefinitely
- Performance: Synchronous protocol, so it is slow
7.2 Three-Phase Commit (3PC)
Adds a Pre-Commit phase to solve 2PC's blocking problem.
Phase 1: CanCommit
Phase 2: PreCommit (with timeout)
Phase 3: DoCommit
- Still inconsistent under network partitions
- Rarely used in practice
7.3 Saga Pattern
Splits a long transaction into local transactions, with compensating transactions on failure.
Order Creation Saga:
T1: Create Order --> T2: Process Payment --> T3: Deduct Inventory --> T4: Start Shipping
| FAIL!
C2: Refund Payment <-- C1: Cancel Order
Two implementation styles:
| Style | Description | Trade-offs |
|---|---|---|
| Choreography | Event-driven, each service publishes next event | Simple, low coupling. Hard to trace |
| Orchestration | Central orchestrator controls the sequence | Easy to trace, centralization risk |
# Saga Orchestrator pseudocode
class OrderSaga:
steps = [
("create_order", "cancel_order"),
("process_payment", "refund_payment"),
("update_inventory", "restore_inventory"),
("arrange_shipping", "cancel_shipping"),
]
def execute(self):
completed = []
for action, compensation in self.steps:
try:
getattr(self, action)()
completed.append(compensation)
except Exception:
# Run compensations in reverse
for comp in reversed(completed):
getattr(self, comp)()
raise SagaFailed()
8. Distributed Clocks
8.1 The Problem with Physical Clocks
- NTP synchronization error: milliseconds to hundreds of milliseconds
- Clock drift: Diverges over time
- Leap seconds: Occasionally an extra second is added or removed
Conclusion: Physical clocks alone cannot determine event ordering accurately.
8.2 Lamport Clock (Logical Clock)
Proposed by Leslie Lamport in 1978. Each process maintains a counter.
Rules:
1. Increment counter on each event
2. Include counter when sending messages
3. On receive: max(own counter, received counter) + 1
Process A: (1)--> (2)--> (3)---------> (6)
send recv
Process B: (1)--> (2)--> (4)--> (5)
recv send
- Limitation: Can determine causality but not whether two events are concurrent
8.3 Vector Clock
Each node maintains a vector of counters for all nodes.
3 nodes: A, B, C
A: [1,0,0] --> [2,0,0] --> [2,0,0] send to B
B: [0,0,0] --> [0,1,0] --> [2,2,0] recv from A --> [2,3,0] send to C
C: [0,0,0] --> [0,0,1] --> [2,3,2] recv from B
Determining concurrency:
V1 <= V2: Every element of V1 is less than or equal to V2 — V1 happens-before V2- V1 and V2 are incomparable — Concurrent events
def compare_vector_clocks(vc1, vc2):
"""Compare two vector clocks"""
less = False
greater = False
for a, b in zip(vc1, vc2):
if a < b:
less = True
elif a > b:
greater = True
if less and not greater:
return "BEFORE" # vc1 < vc2
elif greater and not less:
return "AFTER" # vc1 > vc2
elif not less and not greater:
return "EQUAL"
else:
return "CONCURRENT" # concurrent events!
8.4 Hybrid Logical Clock (HLC)
Combines physical clock + logical clock. Used by CockroachDB and MongoDB.
HLC = (physical_time, logical_counter)
When physical times are equal, the logical counter determines ordering
--> Guarantees causal ordering within NTP error bounds
9. Failure Detection
9.1 Heartbeat Approach
Node A --> "Are you alive?" --> Node B
Node A <-- "Yes!" <-- Node B
... timeout expires ...
Node A --> "Are you alive?" --> Node B
Node A <-- (no response) <-- Node B
--> Consider Node B failed
- Simple but causes false positives during network delays
- Timeout setting is critical: too short means false positives, too long means slow detection
9.2 Phi Accrual Failure Detector
Learns the distribution of heartbeat arrival times and calculates failure probability.
Phi value:
phi = 1 --> 10% chance of failure
phi = 2 --> 1% chance of failure
phi = 3 --> 0.1% chance of failure
If above threshold (e.g., phi > 8) --> consider node failed
- Used by Cassandra and Akka
- Adapts to changing network conditions
9.3 SWIM Protocol
Scalable Weakly-consistent Infection-style Membership. An efficient group membership protocol.
1. Node A pings Node B
2. No response --> A asks Nodes C, D to "indirect ping B"
3. C, D ping B --> still no response, B is suspected
4. After timeout, if still no response, B is removed
5. Gossip protocol spreads membership changes
- Propagates to all nodes in O(log N) rounds
- Used by HashiCorp Memberlist and Consul
10. Real-World System Analysis
10.1 Apache Cassandra (AP System)
Architecture:
- Leaderless, Consistent Hashing
- Quorum reads/writes (W + R > N)
- Gossip protocol for membership
- Last-Write-Wins conflict resolution
- SSTable + Memtable storage
PACELC: PA/EL
- During partition: Availability first
- Normal operation: Low latency first
10.2 Google Spanner (CP System)
Architecture:
- Globally distributed, strong consistency
- TrueTime API: GPS + atomic clocks provide time uncertainty intervals
- Paxos-based replication
- External consistency guarantee
TrueTime:
TT.now() --> [earliest, latest]
Uncertainty interval typically 1-7ms
On transaction commit:
1. Assign timestamp s
2. commit-wait: wait until TT.after(s) is true
3. Wait for uncertainty interval to guarantee ordering
10.3 Amazon DynamoDB (AP System)
Architecture:
- Consistent Hashing + virtual nodes
- Sloppy Quorum: other nodes respond during failures
- Hinted Handoff: data delivered to original node after recovery
- Vector Clocks for versioning
- Merkle Trees for anti-entropy
10.4 System Comparison
| Feature | Cassandra | Spanner | DynamoDB |
|---|---|---|---|
| CAP Classification | AP | CP | AP |
| Consistency | Tunable | Strong | Tunable |
| Replication | Leaderless | Paxos | Leaderless |
| Partitioning | Consistent Hash | Range | Consistent Hash |
| Clocks | NTP | TrueTime | Vector Clock |
| Query Language | CQL | SQL | PartiQL |
11. DDIA Key Takeaways
Key insights from Martin Kleppmann's "Designing Data-Intensive Applications":
Part 1: Foundations of Data Systems
| Chapter | Key Concepts |
|---|---|
| Ch. 1 | Reliability, scalability, maintainability |
| Ch. 2 | Relational vs document vs graph models |
| Ch. 3 | Storage engines: B-Tree vs LSM-Tree |
| Ch. 4 | Encoding: JSON, Protobuf, Avro |
Part 2: Distributed Data
| Chapter | Key Concepts |
|---|---|
| Ch. 5 | Replication: Leader-Follower, Leaderless |
| Ch. 6 | Partitioning: Hash, Range |
| Ch. 7 | Transactions: ACID, isolation levels |
| Ch. 8 | Challenges of distributed systems |
| Ch. 9 | Consistency and consensus |
Part 3: Derived Data
| Chapter | Key Concepts |
|---|---|
| Ch. 10 | Batch processing: MapReduce |
| Ch. 11 | Stream processing: Event sourcing |
| Ch. 12 | The future of data systems |
12. Interview Questions (15)
Q1. Explain the CAP theorem and give real-world examples.
The CAP theorem states that a distributed system can guarantee at most two of three properties: Consistency, Availability, and Partition Tolerance. Since network partitions are unavoidable, the practical choice is between CP (consistency first) and AP (availability first). ZooKeeper is a CP system suitable for leader election and distributed locks, while Cassandra is an AP system suited for high write throughput workloads.
Q2. Describe the leader election process in Raft.
In Raft, when a follower does not receive a heartbeat within its election timeout, it becomes a candidate. It increments its term, votes for itself, and sends RequestVote RPCs to other nodes. If it receives votes from a majority, it becomes the leader and immediately sends heartbeats. At most one leader can exist per term.
Q3. Explain the difference between Eventual and Strong Consistency.
Strong Consistency guarantees every read returns the most recent write. It requires synchronous replication, leading to high latency and lower availability. Eventual Consistency guarantees that if updates stop, all replicas will eventually converge to the same value. Intermediate reads may return stale data, but latency is low and availability is high. Used by DNS and cache systems.
Q4. Explain Consistent Hashing and why virtual nodes are needed.
Consistent Hashing maps the hash space onto a ring, so adding or removing nodes only redistributes a minimal number of keys. Keys and nodes are mapped using the same hash function, and each key is assigned to the nearest node clockwise. Virtual nodes give each physical node multiple hash positions, smoothing out data distribution imbalances.
Q5. Compare 2PC and the Saga pattern.
2PC (Two-Phase Commit) has a coordinator that instructs all participants to prepare/commit, ensuring atomic commits. It is synchronous, blocking, and the coordinator is a SPOF. The Saga pattern breaks a long transaction into a chain of local transactions, running compensating transactions in reverse on failure. It is asynchronous and scales well but does not guarantee isolation.
Q6. What advantage does a Vector Clock have over a Lamport Clock?
A Lamport Clock can determine happens-before ordering but cannot tell if two events are concurrent. A Vector Clock maintains a vector of counters for every node, enabling accurate detection of concurrency. When two vectors are incomparable, the events are concurrent and require conflict resolution.
Q7. Compare Leader-Follower, Multi-Leader, and Leaderless replication.
Leader-Follower has a single write point with no conflicts, but the leader is a SPOF and write scaling is limited. Multi-Leader offers low write latency across datacenters but requires complex conflict resolution. Leaderless provides high availability and write throughput but needs quorum configuration and conflict resolution strategies.
Q8. How does PACELC extend CAP?
PACELC adds a tradeoff for normal operation on top of CAP. During a partition (P), you choose between availability (A) and consistency (C). Otherwise (E: else), you choose between latency (L) and consistency (C). Cassandra is PA/EL (availability during partition, low latency normally), while Spanner is PC/EC (consistency always).
Q9. How does Google Spanner's TrueTime guarantee strong consistency?
TrueTime uses GPS and atomic clocks to provide a time uncertainty interval. On transaction commit, a timestamp is assigned and commit-wait pauses for the uncertainty interval. This ensures later commits always receive larger timestamps, guaranteeing external consistency globally.
Q10. Why does W + R > N guarantee consistency in quorum reads/writes?
With N nodes, writing to W and reading from R, when W + R > N, the write set and read set always overlap. The overlapping node holds the latest data, so reads are guaranteed to return the most recent value. For example, with N=3, W=2, R=2, at least one node is shared.
Q11. How do you resolve a split-brain problem in distributed systems?
Split-brain occurs when a network partition causes two partitions to each believe they have a leader. Solutions include quorum-based leader election (partitions without a majority cannot elect a leader), fencing tokens (rejecting requests from old leaders), and STONITH (Shoot The Other Node In The Head — forcibly shutting down suspect nodes).
Q12. Compare LSM-Tree and B-Tree from a distributed systems perspective.
LSM-Tree is write-optimized with all writes being sequential, cleaned up via compaction. Ideal for write-heavy workloads (Cassandra, HBase). B-Tree is read-optimized with in-place updates, suitable for read-heavy workloads (traditional RDBMS). In distributed environments, LSM-Trees offer lower replication lag and higher write throughput.
Q13. Why is a Phi Accrual Failure Detector better than simple timeouts?
Simple timeouts declare failure if no response arrives within a fixed time, causing many false positives when network latency varies. The Phi Accrual Failure Detector learns the statistical distribution of heartbeat arrival times and calculates a phi value representing how much the current arrival deviates from the distribution. It adapts to changing network conditions.
Q14. What is the difference between Choreography and Orchestration in the Saga pattern?
In Choreography, each service publishes events and other services subscribe to trigger the next step. No central control, low coupling, but hard to trace. In Orchestration, a central orchestrator controls the entire flow. Easier to trace and debug, but the orchestrator can become a SPOF.
Q15. Why is idempotency important in distributed systems?
Network failures can cause requests to be retried, so performing the same request multiple times must produce the same result. Idempotency keys detect duplicate requests, preventing double processing in operations with side effects like payments or inventory deductions.
13. Quiz (5)
Quiz 1. With N=5, W=3, R=2 — is consistent reading guaranteed?
Yes. W + R = 3 + 2 = 5 > N(5), so the write set and read set always overlap. However, since W + R equals N exactly, this is a boundary case that only holds when all nodes are operational.
Quiz 2. In Raft, a leader at Term 5 receives a RequestVote for Term 3. What happens?
It rejects the request. In Raft, a node always rejects requests with a term lower than its current term. This prevents interference from stale leaders.
Quiz 3. What is the relationship between Vector Clock V1=[2,3,1] and V2=[3,2,1]?
They are concurrent. V1's first element (2) is less than V2's (3), but V1's second element (3) is greater than V2's (2). Since neither vector is fully less than or equal to the other, they are incomparable, indicating concurrent events.
Quiz 4. What is the difference between ONE, QUORUM, and ALL consistency levels in Cassandra?
- ONE: One node response suffices. Fastest but may read stale data
- QUORUM: Majority (N/2+1) nodes must respond. Balanced choice
- ALL: All nodes must respond. Slowest but strong consistency. Fails if any node is down
Quiz 5. What happens if the coordinator crashes after Prepare in 2PC?
Participants are blocked. Participants that responded "Yes" to Prepare can neither commit nor abort, waiting indefinitely with resources locked. This is the biggest drawback of 2PC, addressed by 3PC or coordinator log recovery.
14. References
Books
- Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
- Maarten van Steen, Andrew S. Tanenbaum, "Distributed Systems" (4th ed., 2023)
- Mikito Takada, "Distributed Systems for Fun and Profit" (free online)
Papers
- Brewer, E., "CAP Twelve Years Later" (IEEE Computer, 2012)
- Lamport, L., "The Part-Time Parliament" (1998) — Original Paxos paper
- Ongaro, D. & Ousterhout, J., "In Search of an Understandable Consensus Algorithm" (2014) — Raft paper
- Lamport, L., "Time, Clocks, and the Ordering of Events in a Distributed System" (1978)
- DeCandia, G. et al., "Dynamo: Amazon's Highly Available Key-value Store" (2007)
- Corbett, J.C. et al., "Spanner: Google's Globally-Distributed Database" (2012)
Web Resources
- Jepsen — Distributed systems safety testing: https://jepsen.io/
- The Raft Consensus Algorithm visualization: https://raft.github.io/
- Aphyr's distributed systems class: https://github.com/aphyr/distsys-class
- AWS re:Invent distributed systems talk series
- Martin Kleppmann's blog: https://martin.kleppmann.com/
- Marc Brooker's blog: https://brooker.co.za/blog/
- Distributed Systems Reading Group: http://dsrg.pdos.csail.mit.edu/