261 lines
6.4 KiB
Markdown
261 lines
6.4 KiB
Markdown
---
|
|
id: cs-eventual-consistency
|
|
title: Eventual Consistency — CAP / Conflict / 보상
|
|
category: Coding
|
|
status: draft
|
|
source_trust_level: B
|
|
verification_status: conceptual
|
|
created_at: 2026-05-09
|
|
updated_at: 2026-05-09
|
|
tags: [cs, distributed, consistency, vibe-coding]
|
|
tech_stack: { language: "Concept", applicable_to: ["Backend"] }
|
|
applied_in: []
|
|
aliases: [CAP theorem, eventual consistency, strong consistency, linearizability, BASE]
|
|
---
|
|
|
|
# Eventual Consistency
|
|
|
|
> 분산 시스템의 trade-off. **CAP: Consistency / Availability / Partition tolerance**. Network partition 있을 때 둘 중 하나 포기. 대부분 system 가 partition 에서 availability 우선 = eventually consistent.
|
|
|
|
## 📖 핵심 개념
|
|
- Strong consistency: 모든 replica 즉시 같은 값.
|
|
- Eventual: 시간이 지나면 같아짐.
|
|
- Linearizability: 외부 관찰 = 한 노드 처럼.
|
|
- Causal consistency: 원인-결과 순서 보장.
|
|
|
|
## 💻 핵심 모델
|
|
|
|
### Strong vs Eventual 차이
|
|
```
|
|
Strong:
|
|
- DB transaction
|
|
- Spanner / CockroachDB
|
|
- 단일 leader (write)
|
|
- Latency / availability 한계
|
|
|
|
Eventual:
|
|
- DNS, CDN, S3 list
|
|
- Cassandra, DynamoDB (보통)
|
|
- 빠름 / 항상 OK / 큰 scale
|
|
- "방금 쓴 게 안 보일 수 있음"
|
|
```
|
|
|
|
### CAP 실제
|
|
```
|
|
"Network partition 시 CA 둘 중":
|
|
Partition 가 자주 안 일어남 — 그러나 실제 발생.
|
|
|
|
CP system: ZooKeeper, etcd, MongoDB (default)
|
|
AP system: Cassandra, DynamoDB, Couchbase
|
|
CA: 단일 노드 (no partition by definition)
|
|
```
|
|
|
|
### PACELC (확장)
|
|
```
|
|
Partition 시: A vs C
|
|
없을 때 (Else): Latency vs Consistency
|
|
|
|
ALPS (Cassandra): A + Latency
|
|
CALC (Spanner): C + Consistency (어느 때나)
|
|
```
|
|
|
|
### Read-your-writes
|
|
```
|
|
사용자가 자기 변경 즉시 봐야 = consistency.
|
|
다른 사용자에게는 100ms lag OK.
|
|
```
|
|
|
|
```ts
|
|
// Server 가 user 별 sticky → primary
|
|
// 또는 last-write timestamp track
|
|
async function getUserOrders(userId: string) {
|
|
const lastWrite = await redis.get(`recent:${userId}`);
|
|
const db = lastWrite && Date.now() - lastWrite < 5000 ? primary : replica;
|
|
return db.query('SELECT * FROM orders WHERE user_id = $1', [userId]);
|
|
}
|
|
```
|
|
|
|
### Monotonic reads
|
|
```
|
|
한 번 새 version 본 후 옛 version 안 보여야.
|
|
사용자가 page 1 → page 2 → page 1 = 같은 데이터.
|
|
```
|
|
|
|
→ Sticky session 또는 client 가 read 결과 cache.
|
|
|
|
### Causal consistency
|
|
```
|
|
A → B 라는 관계가 있으면 B 보기 전 A 봐야.
|
|
|
|
예: Comment 가 post 의존.
|
|
Post visible → Comment visible 보장.
|
|
```
|
|
|
|
→ vector clock / Lamport timestamp.
|
|
|
|
### Conflict resolution (다른 노드 동시 write)
|
|
```
|
|
LWW (Last-Write-Wins): 최신 timestamp 선택.
|
|
Merge: CRDT — 자동 merge.
|
|
Application-defined: user 가 결정 (Dropbox conflict file).
|
|
```
|
|
|
|
### LWW 위험
|
|
```
|
|
A: write at 10:00:00.001 (clock skew)
|
|
B: write at 10:00:00.000 (real later)
|
|
→ A 가 win — but B 가 진짜 마지막.
|
|
```
|
|
|
|
→ Hybrid Logical Clock 또는 vector clock.
|
|
|
|
### Vector clock
|
|
```
|
|
Node A: [A:1, B:0] write x=1
|
|
Node B: [A:0, B:1] write x=2 (without seeing A)
|
|
|
|
→ Concurrent writes — application 이 결정 (또는 둘 다 보존).
|
|
```
|
|
|
|
### CRDT (위 CRDT 문서)
|
|
```
|
|
G-Counter, PN-Counter (counter)
|
|
LWW-Set, OR-Set (set)
|
|
RGA, LSEQ (sequence — text)
|
|
|
|
→ 자동 merge. Yjs / Automerge.
|
|
```
|
|
|
|
### Quorum
|
|
```
|
|
N replicas, write to W, read from R:
|
|
W + R > N = read-your-writes (strong).
|
|
|
|
Cassandra: ONE / QUORUM / ALL.
|
|
DynamoDB: eventual / strongly consistent read.
|
|
```
|
|
|
|
```
|
|
N=3:
|
|
W=2, R=2 → quorum (W+R=4 > 3 = strong)
|
|
W=1, R=1 → fast 그러나 stale 가능
|
|
```
|
|
|
|
### BASE 원칙
|
|
```
|
|
Basically Available
|
|
Soft state
|
|
Eventually consistent
|
|
|
|
→ ACID 의 분산 trade-off.
|
|
```
|
|
|
|
### Read repair (Cassandra)
|
|
```
|
|
Read 시 여러 replica 비교 → 차이 있으면 가장 최근 winner.
|
|
Background 가 다른 replica update.
|
|
```
|
|
|
|
### Anti-entropy / hinted handoff
|
|
```
|
|
Anti-entropy: 주기적 replica 비교 (Merkle tree).
|
|
Hinted handoff: down 노드 hint 다른 노드 보관 → 복구 시 전달.
|
|
```
|
|
|
|
→ Cassandra / DynamoDB 자동.
|
|
|
|
### App 측 — 사용자에 stale 표시
|
|
```tsx
|
|
const r = await fetch('/api/orders', { headers: { 'consistency': 'eventual' } });
|
|
// 응답 헤더
|
|
// 'X-Stale-Seconds': 30
|
|
|
|
if (response.headers.get('X-Stale-Seconds') > 60) {
|
|
showWarning('Showing data from 1 minute ago');
|
|
}
|
|
```
|
|
|
|
### Write order (causal)
|
|
```ts
|
|
// 1. Post 를 먼저 (commit 보장)
|
|
const post = await db.posts.create({...});
|
|
|
|
// 2. 그 후 알림 보냄 (post 가 visible 후)
|
|
await redis.publish('post:created', post.id);
|
|
|
|
// 다른 service 가 post 안 보일 위험 → 주기적 retry 또는 lookup
|
|
```
|
|
|
|
### Read after write 명시 API
|
|
```
|
|
DynamoDB:
|
|
GetItem(... ConsistentRead=true) // strongly consistent
|
|
GetItem(... ConsistentRead=false) // eventual (default, cheaper)
|
|
|
|
S3 (2020+ 모든 region):
|
|
PUT 후 GET = read-after-write strong (이전 LIST 만 eventual).
|
|
```
|
|
|
|
### Eventual consistency 적합 use case
|
|
```
|
|
- DNS (TTL 분 단위)
|
|
- CDN (cache invalidation)
|
|
- Like / view count (eventual OK)
|
|
- Activity feed (몇 초 lag OK)
|
|
- Search index (CDC + lag)
|
|
```
|
|
|
|
### 부적합
|
|
```
|
|
- 결제 (account balance — strong)
|
|
- Inventory (stock — strong + lock)
|
|
- Booking (seat reservation — strong)
|
|
- Auth state (recent password change)
|
|
```
|
|
|
|
### Saga (eventual + 보상)
|
|
```
|
|
1. Service A: do X (commit local)
|
|
2. Service B: do Y (commit local)
|
|
3. 둘 다 fail 가능 — eventually 같은 결과 (compensating tx)
|
|
```
|
|
|
|
→ [[Backend_Saga_Patterns]].
|
|
|
|
### Compensating transaction
|
|
```
|
|
Forward: charge → reserve → ship
|
|
Failure: reserve fail → refund (compensate)
|
|
```
|
|
|
|
## 🤔 의사결정 기준
|
|
| 데이터 | 일관성 |
|
|
|---|---|
|
|
| Money / billing | Strong |
|
|
| Inventory | Strong + lock |
|
|
| User profile | Read-your-writes |
|
|
| Like / view | Eventual |
|
|
| Search | Eventual (CDC) |
|
|
| Activity feed | Eventual |
|
|
| Cache | Eventual + TTL |
|
|
|
|
## ❌ 안티패턴
|
|
- **모든 거 strong 가정**: 비싸 / 느림.
|
|
- **Eventual + 사용자 자기 거 못 봄**: read-your-writes 패턴.
|
|
- **LWW + clock skew 무시**: 데이터 잃음.
|
|
- **Conflict resolution 없음**: 마지막 wins — 데이터 손실.
|
|
- **Cache TTL 없음**: 영원 stale.
|
|
- **CAP 가정 +항상 partition 없음**: 진짜 partition 시 깨짐.
|
|
- **Quorum 안 자동**: app 측 manual.
|
|
|
|
## 🤖 LLM 활용 힌트
|
|
- 대부분 system = eventual + read-your-writes 보강.
|
|
- Strong 은 단일 leader / quorum / Spanner.
|
|
- CRDT = 자동 merge.
|
|
- Saga + 보상 = 분산 transaction.
|
|
|
|
## 🔗 관련 문서
|
|
- [[Backend_Saga_Patterns]]
|
|
- [[Backend_Geo_Replication]]
|
|
- [[CS_CRDT_Patterns]]
|