Software Architecture/Distributed Systems

Distributed Systems - 03. 일관성 모델

일관성 모델 — 강한 일관성과 최종 일관성 사이의 스펙트럼

2018년 한 SaaS 회사에서 사용자가 프로필을 수정한 직후 "저장됐다"는 메시지를 받았다. 새로고침하니 예전 프로필이 떴다. 사용자는 "저장이 안 됐나?"라고 생각해 다시 저장했다. 같은 데이터가 두 번 쓰였고, 결제 주소까지 두 번 변경돼 뒷배송 처리에 며칠이 걸렸다.

원인은 — 쓰기 요청이 마스터 DB에 갔고, 읽기 요청이 읽기 복제본(비동기 복제)에 갔다. 복제 지연으로 복제본엔 아직 새 데이터가 안 반영됐기 때문이다. 시스템은 "저장됐다"고 했지만, 사용자가 보는 화면은 그게 아니었다.

이 사태를 "강한 일관성 vs 최종 일관성"이라는 이분법으로 설명하면 핵심이 빠진다. 강도의 스펙트럼이 있고, 그 사이에 여러 모델이 있다. 이 글은 그 스펙트럼을 차례로 밟는다.

비유로 감 잡기 — 사건을 사람들이 어떻게 아나

한 사건이 일어났다고 하자. 누군가가 그 사건을 알게 되는 경로는 여럿이다. 어떻게 알았느냐에 따라 "내가 아는 게 다른 사람이 아는 것과 같나"가 달라진다.

첫째, 현장에 있었다. 사건을 직접 봤다. 옆에 있던 다른 사람도 같은 순서로 봤다. 우리가 아는 내용은 일치한다. 이게 가장 강한 형태다.

둘째, 친구에게 전해 들었다. 사건을 직접 목격한 친구가 말해줬다. 친구가 말한 순서대로 알게 된다. 친구에게 들은 내용은 정확하지만, 다른 친구가 따로 전한 소식과 섞일 수 있다. 다른 사람이 나보다 먼저 알았을 수도, 나중에 알았을 수도 있다.

셋째, 뉴스로 봤다. 편집된 순서대로 뉴스가 나간다. 모든 사람이 같은 순서로 뉴스를 보지만, 사건이 실제 일어난 시간과 뉴스가 보도한 시간이 어긋날 수 있다.

넷째, 소문으로 들었다. 누가 먼저 말했는지 모른다. 결국 다들 알게 되지만, 그 전에 서로 다른 이야기를 들었을 수 있다.

이 네 형태가 바로 분산 시스템의 일관성 모델에 대응한다. 각 형태가 어떤 모델인지는 뒤에 짝지어 보자. 핵심은 — "강하다 vs 약하다" 이분법이 아니라 스펙트럼이라는 것. 어느 정도의 보장을 주느냐에 따라 여러 형태가 있다.

비유의 한계 — 뉴스 비유는 '인간 인지'를 가정한다. 인간은 맥락으로 모순을 알아채지만, 프로그램은 명시적 검사 없이 모순을 못 알아챈다. 또한 사건은 한 번 일어나지만, 분산 시스템에선 데이터가 연속적으로 바뀐다. 비유는 '연속적 변경'에서 무너진다.

"일관성"이란 — 왜 여러 모델이 필요한가

일관성(consistency)이란 — 여러 노드(또는 복제본)가 데이터를 같은 상태로 보느냐의 문제다. 한 노드에서 잔액이 100만 원이면 다른 노드에서도 100만 원이어야 한다. 이게 "일관적"이라는 뜻이다.

분산 시스템에선 이게 단순하지 않다. 데이터가 여러 노드에 복사돼 있고, 한 노드에서 변경이 일어나면 다른 노드에도 전달해야 한다. 전달은 시간이 걸린다. 그 사이에 다른 노드에서 읽으면, 낡은 값이 보일 수 있다.

이 문제를 완전히 해결하려면 — 한 노드에서 쓰기가 일어났을 때 모든 복제본이 그 쓰기를 반영할 때까지 기다려야 한다. 이건 강한 일관성을 주지만 느리다. 복제본 중 하나가 느려지면 전체가 느려진다.

반대로 "어차피 결국 같아진다"며 기다리지 않으면 — 빠르지만, 중간에 낡은 값이 보일 수 있다. 이게 약한 일관성.

양극단 사이에 여러 타협 지점이 있다. 어느 정도 강하게, 어느 정도 느슨하게. 그 타협 지점들을 체계화한 게 일관성 모델이다. 각 모델은 특정 보장을 특정 비용에 제공한다. 비유의 네 형태가 바로 네 가지 모델이다.

가장 강한 모델 — Linearizability

가장 강한 일관성 모델. 한 클라이언트가 쓰기를 완료하면, 그 이후 다른 클라이언트의 읽기는 항상 새 값을 봐야 한다. "가장 최근 쓰기"가 모든 노드에 즉시 보인다.

비유에서 "현장에 있었다"가 바로 이 모델이다. 현장 목격자들은 같은 순서로 같은 걸 본다. linearizability 시스템에선 모든 노드가 같은 순서로 같은 값을 본다.

시간 t1: Client A가 x를 1로 설정
시간 t2: Client B가 x 읽기 → 무조건 1이어야 함
시간 t3: Client C가 x 읽기 → 무조건 1이어야 함

왜 비싼가. 한 노드에 쓰기가 일어나면 다른 모든 노드에 즉시 전달돼야 한다. 그 전엔 그 노드에서 읽을 수 없다 (낡은 값이 나올 수 있으니). 노드 중 하나가 느려지면 전체가 그 노드를 기다린다. 그래서 빠르거나 가용성 높은 시스템에 부적합. 02편(CAP)에서 CP 시스템이 이 모델을 제공한다.

etcd, Zookeeper, HBase가 linearizability를 제공하는 대표적 시스템이다.

가장 약은 모델 — Eventual Consistency

반대 극단. 새 쓰기가 멈추면 결국 모든 복제본이 같은 값에 수렴한다. "결국"이 언제인지는 보장 안 됨. 그 전에는 각 복제본이 다른 값을 가질 수 있다.

비유에서 "소문"이 eventual consistency다. 결국 모두가 알게 되지만, 그 전에 서로 다른 이야기를 들었을 수 있다.

쓰기 중: 각 복제본이 다른 값을 가질 수 있음
쓰기 멈춘 후: 결국 모든 복제본이 같은 값으로 수렴 (언제? 보장 없음)

왜 쓰는가. 가장 빠르고 가용성이 높다. 한 노드에 쓰고 즉시 응답. 다른 노드엔 나중에 비동기로 전달. 노드 하나가 느려도 전체가 안 느려진다.

왜 위험한가. 중간 상태에서 사용자가 일관되지 않은 값을 본다. 잔액이 100만 원인데 다른 복제본에선 아직 50만 원으로 보일 수 있다. 오버셀 같은 비즈니스 사고의 원인이 될 수 있다.

이 모델이 적합한 데이터 — 소셜 피드, 조회수, 추천, 분석 데이터. 약간 낡아도 자연스러운 데이터. 부적합한 데이터 — 잔액, 재고, 결제 상태. 정확성이 필수인 데이터.

DynamoDB(기본 모드), Cassandra, Riak이 eventual consistency를 기본으로 쓴다.

그 사이의 모델들

양극단만 보면 "강 vs 약" 이분법으로 보인다. 하지만 그 사이에 여러 타협 지점이 있다. 각각 특정 상황을 다룬다.

Read-Your-Writes — 내가 쓴 건 내가 본다

한 클라이언트가 쓴 것을 같은 클라이언트가 다시 읽으면 새 값이 보인다. 다른 클라이언트에게는 보장 없다.

이 글 도입부의 프로필 수정 사례가 read-your-writes가 위반된 경우였다. 사용자는 자기가 방금 수정한 걸 바로 봐야 한다. 그게 안 되면 사용자는 "버그다"라고 느낀다. 사용자 경험과 직결된 가장 실용적 보장.

Monotonic Reads — 과거로 안 돌아간다

한 클라이언트가 한 번 본 데이터는 그 후에 "더 과거" 데이터를 다시 안 본다. 읽을수록 데이터는 더 최신이거나 같지, 과거로 돌아가지 않는다.

이게 없으면 새로고침할 때마다 데이터가 앞뒤로 왔다 갔다 한다 — "방금 본 게 어제 것으로 되돌아간" 현상. 사용자는 혼란스럽다.

Causal Consistency — 인과 순서는 보장

한 쓰기를 본 클라이언트의 후속 행위는 그 쓰기 이후로 정렬된다. "A가 쓰고, B가 그걸 본 뒤 새로 쓰면, 다른 클라이언트는 A의 쓰기를 B의 쓰기보다 먼저 본다." 인과적 순서는 보장되지만, 인과 관계 없는 두 쓰기는 다른 클라이언트가 다른 순서로 봐도 된다.

비유에서 "친구에게 전해 들었다"가 이에 해당. 친구가 말한 순서는 보장되지만, 다른 친구가 따로 말한 내용과 섞일 수 있다.

Sequential Consistency — 전체 순서는 보장, 시간 무관

모든 연산이 어떤 순서로 실행되는 것처럼 보인다. 모든 클라이언트가 그 순서를 본다. 단, 그 순서가 실제 시간과 일치할 필요는 없다. linearizability보다 약하다 — 실제 시간 순서를 안 따져도 되니까.

비유에서 "뉴스"가 이에 해당. 모든 사람이 같은 순서로 뉴스를 보지만, 사건 실제 시간과 어긋날 수 있다.

위계 한눈에 보기

flowchart TD
    L[Linearizability<br/>실제 시간 순서, 가장 최근 값] --> S[Sequential<br/>전체 순서, 시간 무관]
    S --> C[Causal<br/>인과 순서 보장]
    C --> RY[Read-Your-Writes<br/>자기 쓰기 자기가 읽음]
    RY --> MR[Monotonic Reads<br/>더 과거로 안 돌아감]
    MR --> E[Eventual<br/>결국 수렴]

위로 갈수록 강한 보장, 비용도 큼. 아래로 갈수록 느슨하지만 빠르고 가용성 높음. 어느 쪽이 맞나 — 데이터마다 다르다.

모델 보장 비용 대표 시스템
Linearizability 실제 시간 순서, 가장 최근 값 가장 높음 etcd, Zookeeper, HBase
Sequential 전체 순서 (시간 무관) 높음 멀티코어 CPU 메모리 모델
Causal 인과 순서 보장 중간 일부 분산 저장소
Read-Your-Writes 자기 쓰기 자기가 읽음 낮음 세션 일관성 옵션
Monotonic Reads 더 과거로 안 돌아감 낮음 DynamoDB 읽기 모드
Eventual 결국 수렴 가장 낮음 DynamoDB, Cassandra, Riak

Linearizability vs Serializability — 이름이 비슷해서 헷갈리는 것

두 용어가 "강한 일관성"을 가리키는 것처럼 보이지만, 다른 축이다. 이 둘을 섞어 쓰면 혼란이 생긴다.

  • Linearizability — 한 객체(또는 한 키)의 읽기/쓰기 순서에 관한 보장. "가장 최근 쓰기"를 본다. CAP의 'C'가 이거다.
  • Serializability — 여러 객체에 걸친 트랜잭션 격리에 관한 보장. 한 트랜잭션이 여러 객체를 바꿀 때, 마치 순차적으로 실행된 것처럼 보임. ACID의 'I'에 가깝다.

한 시스템이 linearizable하더라도 serializable하지 않을 수 있다 (한 키는 강한 일관성인데 여러 키 트랜잭션은 안 됨). 반대도 성립. 두 축을 합쳐야 진짜 "강한 일관성 + 트랜잭션"이다. Google Spanner, CockroachDB 같은 분산 SQL DB가 둘 다 제공한다.

시스템이 "강한 일관성을 제공한다"고 할 때 어느 쪽인지 확인해야 한다. linearizable인지, serializable인지, 둘 다인지.

설계 사례 — 같은 시스템 안에서 데이터마다 다르게

모든 데이터를 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;
}

재고는 오버셀을 막기 위해 linearizable. 주문은 트랜잭션까지 필요하니 linearizable + serializable. 반면 리뷰나 조회수는 eventual로 충분하다.

Read-Your-Writes 보장 구현 — 세션 고정

도입부의 프로필 수정 사례를 어떻게 막나. 가장 흔한 패턴은 세션 고정이다. 사용자가 쓰기를 한 뒤 일정 시간 동안은 마스터(원본)에서 읽는다. 복제본으로부터 낡은 값을 안 읽게.

// 안티패턴 — 읽기 복제본에서 바로 읽기
class BadReadService {
    public Profile getProfile(UserId id) {
        return readReplica.find(id);
        // 사용자가 방금 수정한 데이터가 안 보일 수 있음
    }
}

// 개선 — 쓰기 후 일정 시간 마스터에서 읽기
class ReadYourWritesService {
    private final Cache<SessionId, Instant> recentWrites;

    public void updateProfile(UserId id, Profile profile) {
        master.write(id, profile);
        // 이 세션의 최근 쓰기 시간 기록
        recentWrites.put(currentSession(), Instant.now());
    }

    public Profile getProfile(UserId id) {
        Instant lastWrite = recentWrites.getIfPresent(currentSession());
        if (lastWrite != null && lastWrite.isAfter(Instant.now().minus(Duration.ofSeconds(30)))) {
            return master.find(id);  // 최근 쓰기가 있으면 마스터에서 (정확하지만 느림)
        }
        return readReplica.find(id);  // 쓰기 없으면 복제본에서 (빠름)
    }
}

쓰기 후 30초 동안은 마스터에서 읽는다. 비싸지만 정확하다. 30초가 지나면 다시 빠른 복제본으로. 사용자는 자기 쓰기를 잃지 않으면서 대부분 빠른 읽기를 누린다.

일관성 위반 감지 — 운영에서 어떻게 알아내나

신호 위반된 모델
사용자가 방금 쓴 데이터가 안 보임 Read-Your-Writes
새로고침 시 데이터가 과거로 돌아감 Monotonic Reads
두 사용자가 같은 시각 다른 최신값을 봄 Linearizability
동시 쓰기에서 일관성 없는 결과 Serializability
복제본이 영원히 수렴 안 함 Eventual Consistency
쓰기 순서가 인과 순서를 안 지킴 Causal

이 표의 신호가 발생하면, 시스템이 "공식적으로" 제공한다는 일관성과 현실이 어긋나는 것이다. 사용자 경험으로 드러나는 버그의 대부분은 이 표의 한 항목이다.

데이터 종류별 추천 모델

데이터 유형 추천 모델 근거
금융 원장, 재고 Linearizable + Serializable 강한 정확성 필수
사용자 세션, 프로필 Read-Your-Writes 사용자 경험 보호
상품 카탈로그 Eventual (몇 분 지연 허용) 자주 안 바뀜, 약간 낡아도 OK
소셜 피드, 알림 Eventual 약간 늦어도 자연스러움
카운터 (조회수, 좋아요) Eventual + CRDT 수렴형 자료구조로 충돌 회피
분석 데이터 Eventual 배치성, 실시간 불필요

CRDT(Conflict-free Replicated Data Type)는 eventual consistency에서 자동 수렴을 보장하는 자료구조다 — 동시 쓰기 충돌을 자동으로 해결. 카운터, 집합 같은 단순 데이터에 적합. Marc Shapiro 등이 2011년에 정리했고, Riak, Redis CRDT, Yjs(협업 편집)가 구현했다.

다음으로 — 합의

linearizability를 보장하려면 노드들이 "어떤 값이 최근인지"에 동의해야 한다. 이 동의를 이끌어내는 알고리즘이 합의(consensus)다. 04편에서 Paxos와 Raft를 다룬다 — 분단과 지연이 있는 환경에서 어떻게 노드들이 하나의 값을 정하는가.

일관성 모델의 핵심 통찰 — "강 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)