--- 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]]