✍️ 필사 모드: 분산 시스템 완전 가이드 — 시계·Consensus·Event Sourcing·Saga·CRDT·장애 패턴 (Season 2 Ep 12, 2025)
한국어들어가며 — 분산 시스템이 어려운 진짜 이유
Butler Lampson의 유명한 정의:
"분산 시스템이란, 네가 존재하는지도 몰랐던 컴퓨터의 고장이 네 컴퓨터를 사용할 수 없게 만드는 것이다."
분산 시스템이 어려운 이유는 다음 3가지가 동시에 일어나기 때문:
- 시계가 다르다 (Clock Drift)
- 고장이 부분적이다 (Partial Failure)
- 네트워크는 불신해야 한다 (Unreliable Network)
이 글은 이 3가지 문제에 대한 **이론적 도구(시계·합의·일관성)**와 **실전 패턴(Event Sourcing·Saga·CRDT)**을 한 번에 정리한다.
1부 — 8 Fallacies of Distributed Computing
Peter Deutsch가 1994년에 정리한 분산 시스템 8가지 거짓 가정:
- 네트워크는 신뢰할 수 있다 ❌
- 지연시간은 0이다 ❌
- 대역폭은 무한하다 ❌
- 네트워크는 안전하다 ❌
- 토폴로지는 변하지 않는다 ❌
- 관리자는 한 명이다 ❌
- 전송 비용은 0이다 ❌
- 네트워크는 균질하다 ❌
이 8가지를 어기는 모든 가정은 언젠가 프로덕션에서 터진다.
2부 — 시간의 3가지 모델
2.1 Physical Clock (물리 시계)
time.Now() — OS 시계. NTP로 동기화.
문제:
- Clock Drift (ppm 단위 오차)
- NTP 점프 (일정 조정 시 뛰어 넘음)
- Leap Second
- 데이터센터 간 ms~수십 ms 오차
2.2 Logical Clock (논리 시계)
Lamport Timestamp (1978):
이벤트 발생 시 counter++
메시지 송신: (event, counter) 전송
메시지 수신: counter = max(local, received) + 1
장점: 인과 관계 보존 (happens-before) 단점: 인과 관계를 가졌는지 구분 불가 (concurrent vs causal)
2.3 Vector Clock
노드 수 N만큼의 벡터:
A: [2, 1, 0]
B: [1, 3, 0]
- A와 B가 concurrent인지, 누가 먼저인지 판별 가능
- 크기 문제: 노드 수 많으면 메타데이터 커짐
2.4 HLC (Hybrid Logical Clock, 2014)
물리 시간 + 논리 카운터 조합:
HLC = (physical_time, logical_counter)
- 물리 시계 기준 정렬 + 논리 시계로 인과 보존
- CockroachDB, YugabyteDB 등 현대 분산 DB의 표준
2.5 TrueTime (Google Spanner)
GPS·원자시계로 ±7ms 보장 → "이 시점 이후의 이벤트는 확실히 뒤"가 가능.
- Commit Wait: 불확실성 구간(ms)만큼 대기 후 완료
- 강력한 일관성 + 성능 (Linearizable)
- 일반 기업은 구현 불가 (하드웨어 투자)
3부 — 일관성 모델 스펙트럼
3.1 주요 일관성 모델
강함 → 약함 순서:
- Linearizable: 모든 연산이 실시간 순서대로 보임
- Sequential: 모든 프로세스가 같은 순서로 보되, 실시간은 아님
- Causal: 인과 관계 있는 것만 순서 보장
- Eventual: 결국 같아짐
3.2 CAP 재방문
- Consistency: Linearizability
- Availability: 모든 요청이 답을 받음
- Partition Tolerance: 네트워크 분할 시에도 동작
네트워크 분할은 피할 수 없음 → CP or AP 선택.
3.3 PACELC (더 현실적)
- Partition: C or A
- Else (정상 운영): L(atency) or C(onsistency)
대부분의 시스템은 PA/EL (분할 시 가용성 + 정상 시 저지연).
3.4 2025 DB 일관성 선택
| DB | 모델 |
|---|---|
| PostgreSQL | 단일 노드 Serializable |
| MySQL | Read Committed (기본) |
| Spanner | Linearizable + External Consistency |
| CockroachDB | Serializable + Causal |
| Cassandra | Tunable (Quorum·Eventual) |
| DynamoDB | Eventual (옵션 Strong) |
| Redis Cluster | 단일 키 Linearizable, 다중 키 ❌ |
4부 — Consensus: 합의 알고리즘
4.1 왜 Consensus인가
분산된 여러 노드가 같은 값에 동의 — 리더 선출, 상태 복제, 트랜잭션 커밋 등.
4.2 FLP Impossibility (1985)
비동기 시스템에서 1개 노드 고장도 허용하는 deterministic consensus는 불가능.
실제 시스템은 timing 가정 (eventually synchronous) 또는 확률적 방법을 쓴다.
4.3 Paxos (1989)
Leslie Lamport가 만든 원조. 정확하지만 이해·구현 어려움으로 악명.
역할:
- Proposer (값 제안)
- Acceptor (투표)
- Learner (결정 학습)
문제: 실전에서 쓰기 어려워 Multi-Paxos, Fast Paxos, EPaxos 등 변형.
4.4 Raft (2014)
Stanford에서 "이해 가능한 Consensus"를 목표로 설계. 현재 사실상 표준.
3가지 핵심:
- Leader Election: 리더 하나 뽑기
- Log Replication: 리더가 Follower에게 로그 복제
- Safety: Committed 로그는 뒤집히지 않음
상태:
- Follower → Candidate (Election Timeout) → Leader (과반 투표)
- Leader가 heartbeat 실패 시 → 새 선출
사용처: etcd, Consul, TiKV, CockroachDB, Kafka KRaft (2024~).
4.5 PBFT (Practical Byzantine Fault Tolerance, 1999)
악의적 노드 존재 시 3f+1 노드 중 f개 고장 허용. 블록체인에서 사용.
단계: Pre-prepare → Prepare → Commit. 메시지 복잡도 O(N²).
4.6 Tendermint·HotStuff (2018~)
BFT를 블록체인에 맞게 현대화. Libra/Diem의 HotStuff, Cosmos의 Tendermint.
4.7 Nakamoto Consensus
Bitcoin의 Proof-of-Work. 확률적 합의 — 충분히 많은 블록 뒤에 있으면 confirmed.
단점: 에너지, 느림. 그래서 Ethereum이 PoS로 전환.
5부 — Replication
5.1 복제 패턴 4가지
- Single-Leader: 쓰기는 리더, 읽기는 어디서든. 단순. 대부분의 RDBMS.
- Multi-Leader: 여러 리더가 쓰기 수락. 충돌 해결 필요. 드물게 사용.
- Leaderless: 모든 노드가 동등. Dynamo-style. Cassandra, Riak, DynamoDB.
- Quorum-based: R + W > N 이면 강한 일관성.
5.2 Replication Lag
Leader → Follower 전파 지연 → 다음 문제:
- Read-your-writes: 방금 쓴 걸 못 읽음
- Monotonic reads: 이전에 본 것이 사라짐
- Consistent Prefix: 원인 없는 결과를 봄
해결: Leader에서 읽기, Sticky Session, Timestamp 기반 일관성.
5.3 Conflict Resolution (Multi-Leader/Leaderless)
- LWW (Last Writer Wins): 간단, 데이터 손실 위험
- Version Vectors: 정확, 복잡
- Application-level Merge: 사용자 정의 (CRDT)
6부 — CRDT (Conflict-free Replicated Data Type)
6.1 CRDT란
수학적으로 충돌이 불가능한 데이터 구조. 어느 순서로 합쳐도 같은 결과.
6.2 두 가지 유형
State-based (CvRDT): 상태 자체를 전송 + merge Operation-based (CmRDT): 연산을 전송 + 재적용
6.3 대표 CRDT
| CRDT | 용도 |
|---|---|
| G-Counter | 증가만 (카운터) |
| PN-Counter | 증감 |
| G-Set | 추가만 |
| OR-Set | 추가·제거 |
| LWW-Register | 마지막 쓰기 승리 |
| RGA | 순서 있는 리스트 (텍스트 편집) |
| JSON CRDT (Automerge) | 중첩 구조 |
6.4 실전 사용처
- Redis CRDT (Enterprise): Active-Active Geo
- Riak: Dynamo + CRDT
- Automerge (Ink & Switch): 협업 에디터
- Yjs: Real-time collaboration (Notion, Linear 영감)
- Figma: 디자인 협업에 자체 CRDT
6.5 한계
- 임의 불변 규칙 표현 어려움 (예: 유일성)
- 메타데이터 크기 (Tombstone)
- 작업 순서 의미를 잃음 (이게 장점이자 단점)
7부 — Event Sourcing + CQRS
7.1 Event Sourcing
상태 대신 이벤트를 저장. 상태는 이벤트 재생으로 도출.
events = [
AccountCreated(id=1, balance=0),
MoneyDeposited(account=1, amount=100),
MoneyWithdrawn(account=1, amount=30),
]
balance = fold(events, 0, apply) // 70
장점:
- 완전한 히스토리 (감사·디버깅)
- 시간 여행
- 이벤트 재생으로 새 View 생성
- 분산 친화 (이벤트는 append-only)
단점:
- 복잡도 ↑
- 스키마 진화 문제 (구 이벤트 해석)
- Snapshot 없으면 재생 느림
7.2 CQRS (Command Query Responsibility Segregation)
쓰기 모델(Command) ≠ 읽기 모델(Query) 분리.
Write: events → EventStore
↓
Projection
↓
Read: Materialized View (최적화)
장점: 읽기·쓰기 독립 최적화·확장. 단점: 일관성 지연 (Eventual).
7.3 Event Sourcing + CQRS 스택
- EventStoreDB: 전용 DB
- Kafka + Kafka Streams: 이벤트 로그 + Projection
- Axon Framework (Java): 통합
- MartenDB (.NET): Postgres 위에 구현
7.4 언제 쓰고 언제 피할까
좋을 때:
- 금융·거래처럼 감사 가능성이 중요
- 복잡한 비즈니스 로직 + 과거 재구성
- 이벤트 중심 아키텍처
피할 때:
- 단순 CRUD
- 팀이 초보자 다수
- 간단한 일관성 요구
8부 — Saga 패턴
8.1 분산 트랜잭션 문제
2PC (Two-Phase Commit): 완벽하지만 block, Coordinator 실패에 취약. 현대에 거의 안 씀.
대안: Saga — 로컬 트랜잭션 연속 + 실패 시 보상 트랜잭션.
8.2 Choreography (안무)
각 서비스가 이벤트 발행 + 구독:
OrderCreated → InventoryReserved → PaymentCharged → OrderConfirmed
↓ 실패
InventoryReleased → OrderCancelled (보상)
장점: 서비스 간 결합도 낮음. 단점: 흐름 추적 어려움.
8.3 Orchestration (지휘)
중앙 Orchestrator가 명시적 흐름 관리:
Orchestrator:
1. ReserveInventory
2. ChargePayment
3. ShipOrder
실패 시 ← CompensateAll
장점: 흐름 명시적, 디버깅 쉬움. 단점: Orchestrator가 단일 장애 지점 가능.
8.4 구현 도구
- Temporal: 워크플로우 엔진. Saga의 사실상 표준.
- AWS Step Functions: 관리형
- Camunda: BPMN 기반
- Cadence (Uber, Temporal 전신): 오픈소스
8.5 Temporal 예시
func ProcessOrder(ctx workflow.Context, order Order) error {
err := workflow.ExecuteActivity(ctx, ReserveInventory, order).Get(ctx, nil)
if err != nil { return err }
err = workflow.ExecuteActivity(ctx, ChargePayment, order).Get(ctx, nil)
if err != nil {
workflow.ExecuteActivity(ctx, ReleaseInventory, order)
return err
}
// ...
}
Temporal이 재시작 안전성·타이머·시각화를 무료로 제공.
9부 — 장애 패턴 12
9.1 장애 전파 패턴
- Cascading Failure: 한 서비스 느려짐 → 호출자 타임아웃 쌓임 → 전파
- Thundering Herd: 캐시 만료 시 모든 요청이 Origin에 몰림
- Retry Storm: 실패한 요청이 재시도로 증폭
- Metastable Failure: 일시적 문제가 정상 복귀 후에도 지속
- Split-Brain: 네트워크 분할로 두 리더 생김
- Clock Skew Bug: 시계 차이로 타임스탬프 역전
- Gray Failure: 죽지 않았지만 기능 저하
- Poison Pill: 특정 메시지가 모든 Consumer 다운
- Queue Backlog: Consumer 느려 무한 쌓임
- Hot Partition: 특정 샤드에 트래픽 집중
- Fan-out Overload: 하나의 요청이 100개 내부 요청
- Noisy Neighbor: 공유 자원에서 한 테넌트가 전체 영향
9.2 방어 패턴
| 장애 | 방어 |
|---|---|
| Cascading | Circuit Breaker, Bulkhead, Timeout |
| Thundering Herd | Request Coalescing, Jittered refresh |
| Retry Storm | Exponential Backoff + Jitter |
| Metastable | Load Shedding, Queue Depth 제한 |
| Split-Brain | Fencing Token, Quorum |
| Clock Skew | HLC, NTP 감시 |
| Gray Failure | Health Check + 적극 폐기 |
| Poison Pill | Dead Letter Queue + Alert |
| Queue Backlog | Backpressure, Auto-scale |
| Hot Partition | Consistent Hashing + replication |
| Fan-out | Timeout 깊이 고려, Async |
| Noisy Neighbor | Resource Quota, QoS |
9.3 Chaos Engineering
의도적으로 장애 주입 → 시스템 검증.
- Chaos Monkey (Netflix 2011): 인스턴스 랜덤 킬
- Chaos Mesh: K8s 네이티브
- LitmusChaos: 오픈소스
- Gremlin: 상용
원칙:
- 프로덕션에서 (처음엔 스테이징)
- Blast Radius 제한
- 자동 롤백
- 정기 실행 (Game Day)
10부 — 분산 시스템 패턴 10
- Idempotency: 같은 요청 N번 = 1번 효과
- Exactly-once is a lie: At-least-once + Idempotent Consumer
- Outbox Pattern: DB 트랜잭션 + 이벤트 발행 원자성
- Inbox Pattern: 멱등 Consumer 구현
- Circuit Breaker: 실패 감지 + 회피
- Bulkhead: 스레드풀 격리로 연쇄 실패 방지
- Leader Election: Lease 기반
- Sharding: 데이터 분할
- Sidecar: 공통 기능 (로깅, 보안)을 별도 컨테이너
- Service Mesh: Sidecar를 네트워크 레이어로
11부 — 분산 시스템 로드맵 6개월
Month 1: 이론 기초
- DDIA (Designing Data-Intensive Applications) 정독
- Lamport Paper 2편
- 8 Fallacies 사고 실험
Month 2: Consensus 실습
- Raft 직접 구현 (MIT 6.824)
- etcd·Consul 내부 읽기
- Kafka KRaft 구조 이해
Month 3: Replication + Consistency
- 일관성 모델 구분 연습
- CockroachDB·Spanner 백서
- TrueTime·HLC 구현
Month 4: Event Sourcing + Saga
- 미니 뱅킹 시스템 Event Sourcing으로
- Temporal로 Saga 구현
- Outbox 패턴 실제 적용
Month 5: CRDT + 실시간
- Automerge·Yjs 학습
- 협업 에디터 프로토타입
- Figma 기술 블로그 정독
Month 6: 장애 + 운영
- Chaos Monkey 도입
- 주요 장애 패턴 방어 구현
- Incident Postmortem 분석 10건
12부 — 분산 시스템 체크리스트 12
- 8 Fallacies를 말할 수 있다
- Lamport vs Vector vs HLC 차이를 안다
- CAP vs PACELC 차이를 안다
- Raft의 3가지 핵심을 설명할 수 있다
- FLP Impossibility의 의미를 안다
- Single-Leader vs Leaderless 트레이드오프를 안다
- CRDT가 해결하는 문제를 안다
- Event Sourcing + CQRS 장단점을 안다
- Saga Choreography vs Orchestration 차이를 안다
- Cascading Failure 방어 3가지를 말할 수 있다
- Outbox·Inbox 패턴의 역할을 안다
- Chaos Engineering 원칙을 안다
13부 — 분산 시스템 안티패턴 10
- "네트워크는 신뢰할 수 있다": 8 Fallacies 1번. Timeout·재시도 필수
- 2PC를 마이크로서비스에: Block 위험. Saga로
- 이벤트 순서를 Kafka 파티션 간 가정: 파티션별로만 보장
- Exactly-once를 DB 트랜잭션 없이: 불가능. Outbox 또는 멱등
- Clock을 신뢰:
time.Now()로 정렬 → 버그. 논리 시계 필수 - 무한 재시도: Retry Storm 유발. Backoff + Circuit Breaker
- 단일 노드 DB + 복제만으로 안전하다 가정: Split-Brain 고려 필수
- Consumer 실패 시 NACK 없이 블록: DLQ 설계 필수
- 글로벌 트랜잭션: 성능·가용성 ↓. 경계 재설계
- "Eventual Consistency는 결국 같아짐": 사용자 관점에선 문제. UX로 해결
마치며 — 분산 시스템은 "마음의 모델"이다
분산 시스템은 직관이 가장 자주 배신하는 영역이다. "직관이 강력한" 엔지니어가 가장 위험.
필요한 것:
- 의심하는 마음: 네트워크·시계·노드 전부 의심
- 보상하는 마음: 원자성 안 된다면 보상 설계
- 재현하는 마음: 이벤트로 기록 → 언제든 재현
2025년 시니어 엔지니어의 분산 역량은 연봉을 2배로 벌리는 축이다. 이 영역은 그만큼 어렵고, 그래서 그만큼 값지다.
다음 글 예고 — "DB 완전 가이드: 내부 구조·인덱스·쿼리 플래너·파티셔닝·Vector DB"
Season 2 Ep 13은 데이터의 심장, DB 심화. 다음 글은:
- B-Tree·LSM-Tree·Hash Index 내부 구조
- 쿼리 플래너와 EXPLAIN 깊이 읽기
- 트랜잭션 격리 수준 (Read Uncommitted ~ Serializable)
- 샤딩·파티셔닝 전략
- PostgreSQL의 독주 (2025)
- Vector DB와 pgvector·Qdrant·Weaviate
DB가 어떻게 쿼리를 처리하는지, 다음 글에서.
현재 단락 (1/284)
Butler Lampson의 유명한 정의: