Workspace IndexAlgorithms › Day 52

A Map of Consistency Models — Linearizable, Serializable, Causal, Eventual TODO

Algorithms · Day 52 / 100 · D. Distributed Systems & Consensus (Day 52-68)

Concept

A consistency model is a contract for how much reordering a system allows under concurrent access. Linearizability requires that each individual operation on a single object appear to take effect atomically at some point between its call and its response, in an order that respects real time. Serializability is a property of multi-object transactions: the result of concurrent execution just has to match some sequential execution — it doesn't require respecting real-time order. Causal consistency guarantees ordering only for writes that causally precede one another; concurrent writes may be seen in different orders on different nodes. Eventual consistency promises only that replicas converge at some point once updates stop arriving. Strict serializability, combining the two strongest properties, is the strongest of all — and the stronger the guarantee, the higher the cost in availability and latency, making this fundamentally a CAP/PACELC trade-off.

Bugs of the form "I read right after I wrote, but the value isn't there" usually come from a system actually being weaker than the model people assumed it guaranteed; choosing an unnecessarily strong model, conversely, just wastes latency and cost for nothing.

Code & Formula

# 일관성 모델 지도 — 선형화(linearizable) 읽기와 최종 일관성(eventual) 읽기를 토이 복제 카운터로 비교.
# 선형화는 항상 최신 쓰기를 즉시 보고, 최종 일관성은 복제 지연 동안 stale read가 가능하다.

import random

random.seed(2)

class LinearizableCounter:
    """단일 리더에게만 쓰고 읽는다 -> 항상 최신 값 (선형화 가능)"""
    def __init__(self):
        self.value = 0

    def write(self, delta):
        self.value += delta

    def read(self):
        return self.value

class EventuallyConsistentCounter:
    """리더에 쓰고, 복제본은 비동기로 지연 복제 -> replica read는 stale할 수 있다"""
    def __init__(self, replication_lag=2):
        self.leader_value = 0
        self.replica_value = 0
        self.pending = []  # (도착까지 남은 tick, delta)
        self.replication_lag = replication_lag

    def write(self, delta):
        self.leader_value += delta
        self.pending.append([self.replication_lag, delta])

    def tick(self):
        """시간 한 틱 진행: 복제 지연이 다 된 갱신을 replica에 반영"""
        still_pending = []
        for entry in self.pending:
            entry[0] -= 1
            if entry[0] <= 0:
                self.replica_value += entry[1]
            else:
                still_pending.append(entry)
        self.pending = still_pending

    def read_from_replica(self):
        return self.replica_value

lin = LinearizableCounter()
ec = EventuallyConsistentCounter(replication_lag=2)

lin.write(+1)
ec.write(+1)

print("write-then-read 직후:")
print(f"  linearizable read  = {lin.read()}   (항상 최신)")
print(f"  eventual read      = {ec.read_from_replica()}   (아직 복제 안 됨 -> stale)")

for t in range(1, 3):
    ec.tick()
    print(f"  tick {t} 후 eventual read = {ec.read_from_replica()}")

print("\n결론: 최종 일관성은 갱신이 멈추면 언젠가 수렴하지만, 그 사이엔 stale read를 감수해야 한다.")

Exercise

Reproduce a stale read for real by running a write-then-read scenario against a read replica with replication lag, then fix it to get read-your-writes via session pinning or reading from the leader.

Practical Connection

A blockchain's finalized chain effectively provides a global linearizable order, but pre-finality reorganization is possible, so an indexer or off-chain order book should treat pre-finality state as only eventually consistent and reflect it in settlement only after finality.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

일관성 모델 지도 TODO

Algorithms · Day 52 / 100 · D. 분산시스템·합의 (Day 52–68)

선형화·직렬성·인과·최종

개념

일관성 모델은 동시 접근이 있을 때 시스템이 어떤 실행 순서까지 허용하는지를 정하는 계약이다. 선형화 가능성(linearizability)은 단일 객체에 대한 개별 연산이, 각자의 호출과 응답 사이 어느 시점에 원자적으로 일어난 것처럼 보이고 그 순서가 실시간 순서를 존중할 것을 요구한다. 직렬성(serializability)은 다중 객체 트랜잭션에 대한 성질로, 동시 실행 결과가 어떤 순차 실행과 같기만 하면 되고 실시간 순서는 요구하지 않는다. 인과 일관성(causal)은 인과적으로 선행하는 쓰기들만 순서를 보장하고 동시(concurrent) 쓰기는 노드마다 다른 순서로 봐도 되며, 최종 일관성(eventual)은 새 갱신이 멈추면 언젠가 복제본이 수렴한다는 것만 약속한다. 둘을 합친 strict serializability가 가장 강하고, 강해질수록 가용성과 지연 비용이 커지므로 CAP/PACELC 관점의 선택이 된다.

"읽었는데 방금 쓴 값이 없다" 류의 버그는 대개 시스템이 약속한 모델을 실제보다 강하게 가정한 결과이고, 반대로 필요 이상으로 강한 모델을 고르면 지연과 비용을 그냥 낭비한다.

코드 · 수식

# 일관성 모델 지도 — 선형화(linearizable) 읽기와 최종 일관성(eventual) 읽기를 토이 복제 카운터로 비교.
# 선형화는 항상 최신 쓰기를 즉시 보고, 최종 일관성은 복제 지연 동안 stale read가 가능하다.

import random

random.seed(2)

class LinearizableCounter:
    """단일 리더에게만 쓰고 읽는다 -> 항상 최신 값 (선형화 가능)"""
    def __init__(self):
        self.value = 0

    def write(self, delta):
        self.value += delta

    def read(self):
        return self.value

class EventuallyConsistentCounter:
    """리더에 쓰고, 복제본은 비동기로 지연 복제 -> replica read는 stale할 수 있다"""
    def __init__(self, replication_lag=2):
        self.leader_value = 0
        self.replica_value = 0
        self.pending = []  # (도착까지 남은 tick, delta)
        self.replication_lag = replication_lag

    def write(self, delta):
        self.leader_value += delta
        self.pending.append([self.replication_lag, delta])

    def tick(self):
        """시간 한 틱 진행: 복제 지연이 다 된 갱신을 replica에 반영"""
        still_pending = []
        for entry in self.pending:
            entry[0] -= 1
            if entry[0] <= 0:
                self.replica_value += entry[1]
            else:
                still_pending.append(entry)
        self.pending = still_pending

    def read_from_replica(self):
        return self.replica_value

lin = LinearizableCounter()
ec = EventuallyConsistentCounter(replication_lag=2)

lin.write(+1)
ec.write(+1)

print("write-then-read 직후:")
print(f"  linearizable read  = {lin.read()}   (항상 최신)")
print(f"  eventual read      = {ec.read_from_replica()}   (아직 복제 안 됨 -> stale)")

for t in range(1, 3):
    ec.tick()
    print(f"  tick {t} 후 eventual read = {ec.read_from_replica()}")

print("\n결론: 최종 일관성은 갱신이 멈추면 언젠가 수렴하지만, 그 사이엔 stale read를 감수해야 한다.")

연습

복제 지연이 있는 읽기 전용 레플리카에 write-then-read 시나리오를 걸어 stale read를 실제로 재현하고, read-your-writes를 세션 고정이나 리더 읽기로 고쳐 볼 것.

실무 · Verex 연결

블록체인의 확정된 체인은 사실상 전역 선형화 순서를 제공하지만 파이널리티 이전 재조직 가능성이 있어, 인덱서·오프체인 오더북은 확정 전 상태를 최종 일관성으로 다루고 확정 후에만 정산에 반영해야 한다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 51. [복습] 성능 예산 문서 쓰기53. 논리 시계·벡터 시계·하이브리드 논리 시계(HLC) →