Software Architecture/Distributed Systems
Distributed Systems - 02. CAP PACELC
일관성 모델 — 강과 최종 그 사이의 스펙트럼
2018년 한 SaaS 회사에서 사용자가 프로필을 수정한 직후 "저장됐다"는 메시지를 받고, 새로고침하니 예전 프로필이 떴다. 사용자는 "저장이 안 됐나?"라고 생각하고 다시 저장했다. 같은 데이터가 두 번 쓰였다. 결제 주소까지 두 번 변경돼, 뒷배송 처리에 며칠이 걸렸다. 원인은 — 쓰기 요청이 마스터 DB에 갔고, 읽기 요청이 읽기 복제본(비동기 복제)에 갔는데, 복제 지연으로 복제본엔 아직 새 데이터가 안 반영됐기 때문이었다. 이런 현상을 "강한 일관성 vs 최종 일관성"이라는 이분법으로 설명하면 핵심이 빠진다. 강도의 스펙트럼이 있고, 그 사이에 여러 모델이 있다. 이 글은 그 스펙트럼을 정리한다.
비유로 감 잡기 — 뉴스 전파의 여러 형태
어떤 사건이 일어났다고 하자. 그 사건을 사람들이 어떻게 알게 되는가에 따라 "일관성 모델"이 다르다.
- 직접 목격 (linearizability, 강한 일관성) — 사건 현장에 있었다. 다른 목격자와 동시에 봤지만, 우리가 본 순서가 일치한다. 가장 강한 모델.
- 친구에게 전해 들음 (causal consistency) — 사건을 직접 목격한 친구가 말해줬다. 친구가 말한 순서대로 알게 된다. 인과적 순서는 보장되지만, 다른 친구가 전한 소식과 섞일 순 있다.
- 뉴스를 통해 알게 됨 (sequential consistency) — 뉴스는 편집 순서대로 나간다. 모든 사람이 같은 순서로 뉴스를 보지만, 사건 실제 시간과 뉴스 시간이 어긋날 수 있다.
- 소문 (eventual consistency) — 누군가로부터 들었는데, 누가 먼저 말했는지 모른다. 결국 모두가 알게 되지만 (eventually), 그 전에 서로 다른 이야기를 들었을 수 있다.
이 비유가 가르치는 핵심 — "강 vs 약" 이분법이 아니다. 스펙트럼이 있다. 어느 정도의 보장을 주느냐에 따라 여러 모델이 있고, 각각의 비용(성능·가용성)이 다르다.
비유의 한계 — 뉴스 비유는 '인간 인지'를 강조하지만, 분산 시스템의 일관성은 여러 노드의 메모리/저장소 상태를 다룬다. 또한 인간은 맥락으로 모순을 알아채지만, 프로그램은 명시적 검사 없이 모순을 못 알아챈다. 비유는 '모순 검사'에서 무너진다.
일관성 모델의 위계
Kleppmann이
1. Linearizability (강한 일관성)
가장 강한 모델. Maurice Herlihy와 Jeannette Wing이 1990년에 정의했다. 모든 연산이 원자적으로, 그리고 실제 시간 순서대로 실행되는 것처럼 보인다. 한 클라이언트가 쓰기를 완료하면, 그 이후 모든 클라이언트의 읽기는 새 값을 봐야 한다. "가장 최근 쓰기"가 항상 보인다.
시간 t1: Client A: write(x, 1) <- x를 1로 설정
시간 t2: Client B: read(x) -> 1이어야 함 (linearizable)
시간 t3: Client C: read(x) -> 1이어야 함
Linearizability의 비용 — 동기화가 필요하다. 모든 쓰기는 모든 복제본에 즉시 반영돼야 하고, 그 전엔 읽을 수 없다. 이건 성능과 가용성을 희생한다. 02편(CAP)에서 CP 시스템이 이 모델을 제공한다.
2. Sequential Consistency
Lamport가 1979년에 정의했다. 모든 연산이 어떤 순서로 실행되는 것처럼 보이되, 그 순서가 실제 시간과 일치할 필요는 없다. 단, 각 클라이언트가 본 순서는 보존된다 (program order).
시간 t1: Client A: write(x, 1)
시간 t2: Client B: read(x) -> 0 허용 (A의 쓰기가 아직 안 보임)
시간 t3: Client C: read(x) -> 1 허용 (A의 쓰기 보임)
Linearizability보다 약하다 — 실제 시간 순서가 안 지켜져도 된다. 하지만 모든 노드가 같은 순서를 본다는 점은 유지된다.
3. Causal Consistency
인과적 순서(happens-before)가 보장되지만, 비인과적 연산은 순서가 안 지켜져도 된다. Lamport의 happens-before 관계(09편에서 깊이)를 기반으로 한다.
시간 t1: Client A: write(x, 1)
시간 t2: Client B: read(x) -> 1 <- B는 A의 쓰기를 봄 (인과적)
시간 t3: Client B: write(y, 2) <- B의 y 쓰기는 A의 x 쓰기 이후 (인과적)
시간 t4: Client C: read(y) -> 2 허용
시간 t5: Client C: read(x) -> 0 허용 (C는 A와 인과적 관계 없음)
인과적 일관성은 — "한 클라이언트가 본 순서"와 "한 쓰기를 본 뒤의 또 다른 쓰기"를 보존한다. 비인과적 연산(독립적인 두 쓰기)은 다른 클라이언트가 다른 순서로 봐도 된다.
4. Read-Your-Writes
한 클라이언트가 쓴 것을 같은 클라이언트가 읽으면 항상 새 값이 보인다. 다른 클라이언트에게는 보장 없음. 세션 일관성(session consistency)이라고도 한다.
Client A: write(x, 1) -> A: read(x) = 1 (보장)
Client B: read(x) = 0 허용 (A의 쓰기 아직 안 보임)
이 모델이 실용적인 이유 — 사용자 경험과 직결된다. 앞서 도입부의 프로필 수정 사례는 read-your-writes가 위반된 경우다. 사용자는 자기가 방금 수정한 걸 바로 봐야 한다. 그게 안 되면 사용자는 "버그다"라고 느낀다.
5. Monotonic Reads
한 클라이언트가 한 번 본 데이터는 그 후에 "더 과거의" 데이터를 다시 안 본다. 즉, 읽을수록 데이터는 더 최신이거나 같지, 과거로 돌아가지 않는다.
Client A: read(x) = 5 <- 첫 읽기
Client A: read(x) = 5 허용
Client A: read(x) = 6 허용 (더 최신)
Client A: read(x) = 4 금지 (이전에 본 5보다 과거)
이 모델이 부족하면 사용자가 "방금 본 게 어제 것으로 되돌아간" 현상을 겪는다. 새로고침할 때마다 데이터가 앞뒤로 왔다 갔다 하는 현상이 이 위반이다.
6. Eventual Consistency
가장 약한 보장. 새 쓰기가 멈추면 결국 모든 복제본이 같은 값에 수렴한다. 얼마나 걸릴지는 보장 안 됨. DynamoDB, Cassandra, Riak의 기본 모델.
쓰기 중: 각 복제본이 다른 값을 가질 수 있음
쓰기 멈춘 후: 결국 모든 복제본이 같은 값으로 수렴
Eventual consistency는 가장 빠르고 가장 가용성이 높다. 대신 중간 상태에서 사용자는 일관되지 않은 값을 볼 수 있다. 이 모델이 적합한 데이터(소셜 피드, 조회수, 추천)와 부적합한 데이터(잔액, 재고)가 있다.
일관성 모델 위계 표
flowchart TD
L[Linearizability<br/>실제 시간 순서, 가장 최근 값] --> S[Sequential<br/>전체 순서 시간 무관]
S --> C[Causal<br/>인과 순서 보장]
C --> R[Read-Your-Writes<br/>자기 쓰기 자기가 읽음]
R --> M[Monotonic Reads<br/>더 과거로 안 돌아감]
M --> E[Eventual<br/>결국 수렴]
L -.강한 일관성.- E
E -.약한 일관성.- L
| 모델 | 보장 | 비용 | 대표 시스템 |
|---|---|---|---|
| Linearizability | 실제 시간 순서, 가장 최근 값 | 가장 높음 (동기화) | etcd, Zookeeper, HBase |
| Sequential | 전체 순서 (시간 무관) | 높음 | 일부 멀티코어 CPU,약화된 DB |
| Causal | 인과 순서 보장 | 중간 | COPS, Bolt-on Causal |
| Read-Your-Writes | 자기 쓰기 자기가 읽음 | 낮음 | 세션 일관성 (대부분의 시스템 옵션) |
| Monotonic Reads | 더 과거로 안 돌아감 | 낮음 | DynamoDB 읽기 일관성 모드 |
| Eventual | 결국 수렴 | 가장 낮음 | DynamoDB(기본), Cassandra, Riak |
Linearizability vs Serializability — 혼동되는 두 "강한 일관성"
가장 흔한 혼동. 둘 다 "강하다"지만 다른 축이다.
- Linearizability — 복제 데이터의 읽기/쓰기 순서에 관한 보장. 한 객체의 연산 순서. CAP의 'C'가 이거다.
- Serializability — 트랜잭션 격리 수준에 관한 보장. 여러 객체에 걸친 트랜잭션이 마치 순차적으로 실행된 것처럼 보임. ACID의 'I'에 가깝다.
한 시스템이 linearizable하더라도 serializable하지 않을 수 있고 (단일 객체는 강한 일관성이지만 트랜잭션은 안 됨), 반대도 성립한다. 두 축을 섞어 쓰면 "강한 일관성"이라는 모호한 말이 된다.
Linearizable + Serializable: 가장 강함. 분산 트랜잭션 시스템 (Spanner, CockroachDB).
Linearizable but not serializable: 단일 키 강한 일관성, 멀티 키 트랜잭션 안 됨.
Serializable but not linearizable: 멀티 키 트랜잭션은 되지만 읽기가 최신이 아닐 수 있음 (snapshot isolation).
둘 다 아님: eventual consistency 시스템.
이 구분이 중요한 이유 — 시스템이 "강한 일관성을 제공한다"고 할 때 어느 쪽인지 확인해야 한다. linearizable인지, serializable인지, 둘 다인지. CAP의 'C'는 linearizability 한정이다.
설계 사례 — 한 시스템 안의 일관성 다층화
같은 시스템 안에서 데이터 종류에 따라 다른 일관성 모델을 적용하는 것이 흔한 패턴. 모든 데이터를 linearizable로 하면 느리고, 모든 데이터를 eventual로 하면 비즈니스 치명적 오류가 생긴다.
사례 — 전자상거래의 데이터별 일관성 전략
class ConsistencyStrategy {
// 재고: linearizable — 오버셀 방지
Consistency inventory = Consistency.LINEARIZABLE;
// 주문 원장: linearizable + serializable — 트랜잭션 필수
Consistency orders = Consistency.LINEARIZABLE_SERIALIZABLE;
// 사용자 세션: read-your-writes — 자기 쓰기 자기가 봄
Consistency sessions = Consistency.READ_YOUR_WRITES;
// 상품 리뷰: eventual — 약간 늦어도 됨
Consistency reviews = Consistency.EVENTUAL;
// 조회수: eventual — 통계적 성격이라 부정확해도 됨
Consistency viewCount = Consistency.EVENTUAL;
}
Read-Your-Writes 보장 구현 — 세션 고정
// 안티패턴 — 읽기 복제본에서 바로 읽기 (복제 지연 시 read-your-writes 위반)
class BadReadService {
public Profile getProfile(UserId id) {
// 읽기 복제본에서 읽음 — 사용자가 방금 수정한 데이터가 안 보일 수 있음
return readReplica.find(id);
}
}
// 개선 — 세션 고정: 마스터에 쓴 뒤 일정 시간 마스터에서 읽기
class ReadYourWritesService {
private final Cache<SessionId, Instant> sessionWriteTime = CacheBuilder.newBuilder()
.expireAfterWrite(Duration.ofSeconds(30))
.build();
public void updateProfile(UserId id, Profile profile) {
master.write(id, profile);
sessionWriteTime.put(currentSession(), Instant.now()); // 쓴 시간 기록
}
public Profile getProfile(UserId id) {
Instant lastWrite = sessionWriteTime.getIfPresent(currentSession());
if (lastWrite != null && lastWrite.isAfter(Instant.now().minus(Duration.ofSeconds(30)))) {
// 최근 쓰기가 있으면 마스터에서 읽기 (비싸지만 정확)
return master.find(id);
}
// 오래됐거나 쓰기 없으면 읽기 복제본에서 (빠름)
return readReplica.find(id);
}
}
이 패턴을 세션 고정(session stickiness) 또는 read-your-writes 일관성이라 부른다. 사용자가 자기 쓰기를 잃지 않는 최소한의 보장을 주면서, 가용 시엔 빠른 읽기 복제본을 활용한다.
일관성 모델 위반 감지 — 운영에서 어떻게 알아내나
| 신호 | 위반된 모델 | 의미 |
|---|---|---|
| 사용자가 방금 쓴 데이터가 안 보임 | Read-Your-Writes | 세션 고정 부재 |
| 새로고침 시 데이터가 과거로 돌아감 | Monotonic Reads | 읽기 복제본 사이 분산 |
| 두 사용자가 같은 시각 다른 최신값을 봄 | Linearizability | 복제 지연을 고려 안 함 |
| 동시 쓰기에서 일관성 없는 결과 | Serializability | 트랜잭션 격리 부족 |
| 복제본이 영원히 수렴 안 함 | Eventual Consistency | 복제 메커니즘 고장 |
| 쓰기 순서가 인과 순서를 안 지킴 | Causal | 벡터 시계 미사용 |
이 표의 신호가 발생하면, 시스템이 "공식적으로" 제공한다는 일관성 모델과 현실이 어긋나는 것이다. 사용자 경험으로 드러나는 버그의 대부분은 이 표의 한 항목이다.
일관성 모델 선택의 트레이드오프
강한 일관성(linearizability)은 좋은 보장을 주지만 비용이 크다. 약한 일관성(eventual)은 빠르고 가용성 높지만, 사용자 경험과 비즈니스 정확성을 희생한다. 어느 쪽이 맞나 — 데이터마다 다르다.
| 데이터 유형 | 추천 모델 | 근거 |
|---|---|---|
| 금융 원장, 재고 | Linearizable + Serializable | 강한 정확성 필수 |
| 사용자 세션, 프로필 | Read-Your-Writes | 사용자 경험 보호 |
| 상품 카탈로그 | Eventual (몇 분 지연 허용) | 자주 안 바뀜, 약간 낡아도 OK |
| 소셜 피드, 알림 | Eventual | 약간 늦어도 자연스러움 |
| 카운터 (조회수, 좋아요) | Eventual + CRDT | 수렴형 자료구조로 충돌 회피 |
| 분석 데이터 | Eventual | 배치성, 실시간 불필요 |
CRDT(Conflict-free Replicated Data Type)는 eventual consistency에서 자동 수렴을 보장하는 자료구조다 — G-Counter, OR-Set 같은 형태. 동시 쓰기 충돌을 자동으로 해결한다. 카운터, 집합 같은 단순 데이터에 적합. Marc Shapiro 등이 2011년에 정리했고, Riak, Redis CRDT, Yjs(협업 편집)가 구현했다.
다음으로 — 합의
linearizability를 보장하려면 노드들이 "어떤 값이 최근인지"에 동의해야 한다. 이 동의를 이끌어내는 알고리즘이 합의(consensus)다. 04편에서 Paxos와 Raft를 다룬다 — 분단과 지연이 있는 환경에서 어떻게 노드들이 하나의 값을 정하는가. CAP 정리가 합의가 항상 가능한 건 아님을 보여주지만, 다수 결정(quorum) 기반 합의는 실용적 범위에서 작동한다. 일관성 모델의 강한 쪽(linearizability)은 합의에 의존하고, 약한 쪽(eventual)은 합의 없이 작동한다 — 그래서 비용이 다르다.
일관성 모델의 핵심 통찰 — "강한 일관성 vs 최종 일관성" 이분법이 아니다. 스펙트럼이 있고, 각 모델은 특정 보장을 특정 비용에 제공한다. 좋은 설계는 — 데이터마다 필요한 보장을 식별하고, 그에 맞는 모델을 선택한다. 모든 데이터에 같은 일관성을 적용하면 — 강하면 느리고, 약하면 치명적 오류가 생긴다.
참고
- Herlihy, Wing — "Linearizability: A Correctness Condition for Concurrent Objects" (ACM TOPLAS, 1990) — 접근 2026-07-20
- Lamport — "How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs" (IEEE TC, 1979), sequential consistency 원 제안 — 접근 2026-07-20
- Kleppmann —
(O'Reilly, 2017), Ch.5 (복제·일관성)·Ch.7 (트랜잭션)·Ch.9 (일관성과 합의) - Terry et al. — "Session Guarantees for Weakly Consistent Replicated Data" (SOSP 1994), read-your-writes·monotonic reads 원 제안 — 접근 2026-07-20
- Shapiro et al. — "Conflict-free Replicated Data Types" (SSS 2011), CRDT 원 논문 — 접근 2026-07-20
- Abadi — "Consistency Tradeoffs in Modern Distributed Database System Design" (IEEE Computer, 2012)
'Software Architecture > Distributed Systems' 카테고리의 다른 글
| Distributed Systems - 06. 복제 패턴 (0) | 2026.07.20 |
|---|---|
| Distributed Systems - 05. 파티셔닝과 샤딩 (0) | 2026.07.20 |
| Distributed Systems - 04. 합의 알고리즘 (0) | 2026.07.20 |
| Distributed Systems - 03. 일관성 모델 (0) | 2026.07.20 |
| Distributed Systems - 01. 분산의 8가지 오해 (0) | 2026.07.20 |