분산 일관성과 합의, 무엇부터 봐야 할까?

분산 시스템에서 여러 노드의 상태를 정확히 맞추려면 통신 오버헤드와 지연이 증가한다. 이 트레이드오프를 판단하는 기준은 세 가지다: (1) 얼마나 강한 일관성이 필요한가? — 강한 일관성은 높은 지연을, 최종 일관성은 복잡한 충돌 처리를 요구한다. (2) 노드 간 합의를 어떻게 이룰 것인가? — Raft, Paxos 같은 프로토콜의 선택이 성능과 운영 복잡도를 결정한다. (3) 부분 장애 상황에서 어떤 값을 우선할 것인가? — 가용성과 일관성 중 무엇을 먼저 보호할지의 선택이 전체 아키텍처를 좌우한다.

강한 일관성과 최종 일관성 사이의 시간·처리량 트레이드오프는 얼마나 되는가?

강한 일관성(linearizability 또는 serializability)을 보장하려면 쓰기 작업이 대다수 노드에 동기 복제되어야 하고, 이는 지연을 증가시킨다. 반대로 최종 일관성은 쓰기 직후 즉시 반환하지만, 모든 노드가 같은 상태에 도달할 때까지 시간이 걸린다.

실제 시스템들의 관찰 결과:

  • 동기 복제 (강한 일관성): 3개 노드 클러스터, 같은 데이터센터 내 네트워크 왕복 지연 1ms 기준, 쓰기 응답 시간 35ms 증가. 처리량은 약 3050% 감소.
  • 비동기 복제 (최종 일관성): 쓰기 반환은 즉시지만, 모든 노드 동기화까지 네트워크 지연 + 리플리카 처리 시간으로 수십~수백 밀리초 소요.

이 차이는 노드 간 거리가 늘어날수록 심화된다. 다중 리전 환경에서는 강한 일관성의 지연이 수백 밀리초대로 뛸 수 있고, 이 때문에 많은 시스템이 지역별 강한 일관성 + 리전 간 최종 일관성 구조를 택한다.

합의 프로토콜 선택(Raft vs Paxos vs 다른 것들)은 어떤 기준으로 하는가?

대부분의 실무 분산 시스템은 Raft를 택한다. 이유는 구현의 단순성과 정확성 증명의 명확함 때문이다.

Raft의 특징:

  • 리더 선출(leader election) 기반. 한 번에 하나의 리더만 존재하고, 리더가 모든 쓰기를 직렬화한다.
  • 로그 복제(log replication): 리더의 로그 항목이 대다수 노드에 복제되면 커밋된 것으로 간주한다.
  • 구현 예: etcd, Consul, CockroachDB의 기본 레이어.
  • 지연: 정상 상황에서 리더로의 RTT 1회 + 복제 노드들의 동기화 시간. 보통 5~20ms.

Paxos의 특징:

  • 리더 없이 모든 노드가 대등하게 합의에 참여할 수 있다 (이론상).
  • 구현이 훨씬 복잡하고, 실무에서는 거의 쓰이지 않는다. (Google Chubby는 예외)
  • 지연이나 성능상 Raft에 비해 이점이 뚜렷하지 않다.

그 외:

  • PBFT (Practical Byzantine Fault Tolerance): 악의적 노드도 견뎌야 할 때만. 오버헤드가 크다.
  • Viewstamped Replication, Zookeeper의 ZAB: 특정 시스템에 내장되어 있고, 원리는 Raft와 유사하다.

선택 기준은 명확하다: 일반적으로는 Raft, 기존 인프라와의 호환성이 중요하면 그 시스템에 내장된 것을 따른다.

격리 수준(Isolation Level) 선택이 분산 일관성에 미치는 영향은?

격리 수준은 동시 트랜잭션들이 서로 어느 정도로 간섭하는지를 제어한다. 분산 시스템에서는 강한 격리 수준을 보장하기가 더 어렵다.

주요 격리 수준 (ISO SQL 표준):

  • Read Uncommitted: 커밋되지 않은 데이터도 읽음. 분산 환경에서는 거의 쓰이지 않음.
  • Read Committed: 커밋된 데이터만 읽음. 가장 일반적. 분산 DB의 기본값(예: PostgreSQL 기본).
  • Repeatable Read: 트랜잭션 내 같은 데이터를 여러 번 읽으면 같은 값을 본다. MySQL의 InnoDB 기본값.
  • Serializable: 완벽한 직렬화. 구현 방식(2PL, MVCC, OCC)에 따라 지연 증가율이 다르다.

분산 환경의 추가 개념:

  • Snapshot Isolation (SI): 각 트랜잭션이 시작 시점의 일관된 스냅샷을 본다. 많은 NoSQL과 NewSQL DB의 기본 격리 수준(예: CockroachDB, TiDB).
  • Linearizable Reads: 모든 읽기가 가장 최신의 커밋된 상태를 본다. 강하지만 지연 증가.

지연 영향:
2PL(Two-Phase Locking) 기반 Serializable은 잠금 대기로 인해 지연이 크게 증가한다. MVCC(Multi-Version Concurrency Control) 기반은 읽기 지연을 낮출 수 있지만, 쓰기 충돌 해소가 복잡해진다.

분산 트랜잭션에서 강한 격리를 원하면 분산 잠금(예: ZooKeeper, etcd)이나 timestamp ordering 기법이 필요하고, 이들은 모두 추가 통신 오버헤드를 낳는다.

클럭과 순서 보장: 물리 시간과 논리 시간 중 무엇을 쓸 것인가?

분산 시스템의 노드들은 물리적 시간을 정확히 맞출 수 없다. 이것이 순서 보장을 어렵게 만든다.

물리 시간(Physical Clock):

  • NTP(Network Time Protocol) 등으로 시간을 동기화하려 해도, 네트워크 지연과 하드웨어 드리프트로 인해 정확도는 제한된다.
  • 일반적인 NTP 동기화 정확도: 수십~수백 밀리초.
  • Google Spanner는 GPS 수신기와 원자 시계로 마이크로초 수준의 정확도를 달성하지만, 일반 인프라에서는 불가능하다.

논리 시간(Logical Clock):

  • Lamport Clock: 각 노드가 로컬 카운터를 유지하고, 메시지 수신 시 값을 갱신한다. 인과 관계만 보장한다 (전체 순서 X).
  • Vector Clock: 모든 노드의 카운터를 벡터로 유지. 인과 관계 추적은 정확하지만, 메모리 오버헤드가 노드 수에 비례한다.
  • HybridTime (Spanner, CockroachDB): 물리 시간과 논리 시간을 섞어서, 물리 시간 정확도 범위 내에서는 물리 시간을 쓰고, 그 외엔 논리 시간으로 순서를 정한다.

실무 선택:

  • 단일 리더 시스템(Raft 기반): 리더가 쓰기를 직렬화하므로 물리 시간이 필요 없다. 로그 인덱스(= 논리 시간)로 순서를 정한다.
  • 다중 리더 시스템: 논리 시간 또는 HybridTime 필요. 구현 복잡도 ↑.

2024년~2026년 기준으로, 대부분의 신규 분산 DB는 단일 리더(Raft) + 논리 순서를 택한다. 이것이 일관성 증명과 운영이 모두 가장 단순하기 때문이다.

파티셔닝과 리밸런싱 중 일관성을 유지하기는 어떻게 하는가?

데이터를 여러 노드에 분산할 때(파티셔닝), 키 범위가 변하거나 노드가 추가·제거될 때(리밸런싱) 일관성을 깨지 않으려면 특별한 조치가 필요하다.

기본 방식:

  • 2-Phase Commit (2PC): 파티션 소유권 변경 시, 데이터 이동과 라우팅 테이블 갱신을 두 단계로 나눈다.

    • 1단계: 새 파티션 리더에게 데이터 복사 요청.
    • 2단계: 라우팅 테이블 갱신(원자성 보장).
    • 문제: 2PC 자체가 높은 지연 + 장애 시 복구 복잡.
  • 세 번째 단계(Split/Merge): 데이터 이동을 명확히 세 단계로 나눈다.

    • Phase 1 (Prepare): 새 노드 준비, 초기 데이터 복사.
    • Phase 2 (Catch-up): 리밸런싱 중 변경사항 추적, 새 노드에 적용.
    • Phase 3 (Switch): 라우팅 전환, 이제 새 노드가 파티션 소유권을 가짐.
    • 예: CockroachDB, Spanner의 방식.

리밸런싱 중 가용성:

  • 파티션이 한 번에 한 노드에만 쓰기를 받으면, 리밸런싱 중 해당 파티션은 읽기만 가능 또는 일시적으로 불가.
  • 일부 시스템(예: Kafka)은 리더 지정(preferred leader)을 통해 읽기는 계속 허용하지만 쓰기는 제한.

성능 영향:

  • 리밸런싱은 배경 작업이므로 기존 트래픽에 경합 발생. 대역폭 제한과 I/O 우선순위 제어로 완화.
  • 파티션 개수가 많을수록 리밸런싱 시간 증가 (이동할 청크가 많음). 반대로 너무 적으면 핫스팟 위험.

부분 장애 상황에서 일관성과 가용성 중 무엇을 택할 것인가?

CAP 정리: Consistency (일관성) + Availability (가용성) + Partition tolerance (분할 내성)은 동시에 만족 불가.

분산 시스템은 필연적으로 네트워크 분할(partition)이 일어날 수 있으므로, 일관성과 가용성 중 하나를 포기해야 한다.

CP 선택 (Consistency, Partition tolerance):

  • 일관성 우선. 분할 시 일부 노드는 읽기/쓰기를 거부.
  • 강한 일관성이 중요한 시스템(금융 원장, 원본 소유권 기록).
  • 지연: 리더십 선출 후 정상화까지 수~수십 초.
  • 예: Raft 기반 etcd, Consul.

AP 선택 (Availability, Partition tolerance):

  • 가용성 우선. 분할 시에도 모든 노드가 읽기/쓰기 가능.
  • 최종 일관성 허용. 분할 해제 후 충돌 조정(merging) 필요.
  • 지연: 즉시 반환, 하지만 충돌 처리 복잡도 증가.
  • 예: Dynamo, Riak, Cassandra (AP로 설정 시).

실무 선택:

  • 단일 데이터센터 내: CP로 설정해도 분할 빈도가 낮다.
  • 다중 리전: 리전 간 분할 가능성 때문에 정책을 명확히 하거나, 리전별로 다르게 설정.
  • 하이브리드: 프라이머리 리전에서는 CP(강한 일관성), 보조 리전에서는 AP(최종 일관성) 허용.

유형별로 어떤 구조를 선택하는가?

고가용성 트랜잭션 시스템 (금융, 주문 관리):

  • Raft 기반 단일 리더 + 동기 복제.
  • Snapshot Isolation 또는 Serializable 격리 수준.
  • 지연 허용 범위: 100ms~1s.
  • 구현 예: CockroachDB, TiDB.

대규모 조회 시스템 (분석, 로그 저장):

  • 비동기 복제, 다중 파티션 + 리드-레플리카.
  • Read Committed 이상의 격리 수준.
  • 지연 허용: 수초~분 단위.
  • 구현 예: BigQuery, Snowflake.

실시간 협업 서비스 (문서 편집, 메시징):

  • 최종 일관성 + CRDT(Conflict-free Replicated Data Type) 또는 OT(Operational Transform).
  • 각 노드가 독립적으로 쓰기 가능, 자동 병합.
  • 지연: 수십ms~수백ms, 사용자 인식 가능하지만 블로킹 없음.
  • 구현 예: Figma의 내부 아키텍처, Automerge 같은 라이브러리.

흔하게 간과되는 지점: 장애 감지와 회복의 시간을 예산화해야 한다

분산 시스템의 지연은 정상 경로(happy path)의 네트워크 RTT만이 아니다. 노드 장애 감지와 리더 선출(failover) 시간이 전체 지연 변동성(tail latency)을 좌우한다.

Raft 기반 시스템의 장애 감지:

  • 리더가 추종자(follower)로부터 심박(heartbeat) 응답이 없으면 장애로 판단.
  • Heartbeat 간격: 보통 50~150ms. 이 간격 동안 응답 없으면 장애 의심.
  • 장애 선언까지: 최소 heartbeat interval × 몇 배 (보통 25배), 즉 **100750ms**.
  • 새 리더 선출: 추가 50~150ms.
  • 합계: 대략 150ms~1s가 추가 지연 가능.

실무에서의 영향:

  • 강한 일관성을 요구하는 시스템이라도 장애 시에는 이 정도의 unavailability 창이 발생한다.
  • SLA 99.9%(월간 가동 시간 99.9%)를 계획할 때, 이 장애 감지 시간을 고려해야 한다.

완화 방법:

  • Heartbeat 간격을 짧게 설정 → 빠른 감지, 하지만 네트워크 트래픽 증가.
  • 외부 타이밍 서비스(예: Zookeeper watch) 활용 → 추가 구성 요소.
  • 대부분의 시스템은 이 타이밍을 명시하지 않아, 튜닝과 테스트가 필수다.

핵심 정리

  • 강한 일관성은 지연을, 최종 일관성은 복잡도를 낳는다. 동기 복제 오버헤드(3~5ms 증가)와 비동기 복제의 충돌 해소 비용을 함께 평가해야 한다.

  • Raft는 사실상 표준이다. 구현 단순성과 정확성 증명이 명확해, 신규 분산 시스템 대부분이 채택한다. Paxos나 Byzantine 프로토콜은 특수 목적만 해당.

  • 격리 수준 선택은 지연과 동시성에 직결된다. 2PL은 지연 증가, MVCC는 읽기 성능 향상 대신 쓰기 충돌 처리 추가.

  • 클럭은 논리(인덱스)로 순서를 정한다. 물리 시간 동기화 오버헤드(수십~수백ms)를 피하려면, 단일 리더가 로그 인덱스로 직렬화하는 구조가 가장 단순하다.

  • 리밸런싱 중 일관성 유지는 3단계(Prepare-Catch-up-Switch)를 거친다. 2PC는 지연이 크고, 세 단계 프로토콜이 표준.

  • CAP 정리는 선택이지 우회 불가능하다. 분할 시 일관성(CP) 또는 가용성(AP) 중 하나를 명확히 선택하고, 그 선택의 대가를 아키텍처에 반영해야 한다.

  • 장애 감지 시간(100~750ms)은 SLA 계산에 포함된다. 정상 경로 지연뿐 아니라, 리더 선출 시간을 명시하고 테스트해야 한다.

자주 묻는 질문

Raft와 Paxos를 두고 고민할 이유가 있나?

아니다. 성능이나 일관성 측면에서 Paxos가 Raft보다 우월한 점이 없다. Paxos의 유일한 이점은 리더 선출 불필요(모든 노드가 동등)라는 것인데, 이는 구현 복잡도 증가와 실제 성능 이득의 불균형을 만든다. Raft를 선택하고, 기존 시스템(Consul, etcd)과 호환성이 있는지만 확인하면 된다.

동기 복제의 지연 증가가 정말 3~5ms인가, 더 클 수 있나?

그렇다. 같은 데이터센터, 저지연 네트워크 기준이 35ms다. 다중 리전(지역 간 거리 수십수백 km)이면 왕복 지연 자체가 수십~수백ms이므로, 동기 복제 오버헤드는 이 기본 지연을 단순히 더한 값이 된다. 또한 리플리카의 디스크 I/O, 복제 큐 경합 등이 추가되면 더 커질 수 있다.

격리 수준 Read Committed가 분산 환경에서 정말 안전한가?

Read Committed만으로는 부족한 경우가 많다. 예를 들어 계좌 이체(A에서 B로)에서, 한 트랜잭션은 A의 감소를 읽고, 다른 트랜잭션은 B의 증가를 읽으면서 동시에 진행되면 불일치가 발생할 수 있다(write skew). 따라서 금융 시스템은 보통 Snapshot Isolation 이상을 요구한다.

최종 일관성 시스템에서 충돌이 발생했을 때 어떻게 푸는가?

자동 푸는 방법은 CRDT(수학적으로 병합 가능한 데이터 구조)와 OT(작업 변환) 두 가지다. CRDT는 인과 관계 추적으로 자동 병합하고(예: Automerge), OT는 사용자의 동시 편집을 각 클라이언트가 독립적으로 변환해 일관성을 유지한다(예: Google Docs). 하지만 이 둘도 모든 충돌을 자동으로 풀 수 없고, 애플리케이션 수준의 해소가 필요한 경우가 있다(예: 같은 셀을 다르게 수정했을 때의 최종 선택).

Vector Clock이 왜 실무에서 많이 안 쓰이나?

메모리 오버헤드가 노드 수에 비례하기 때문이다. 100개 노드 클러스터에서 모든 메시지에 100 크기의 벡터를 붙이면, 네트워크와 저장소 비용이 선형으로 증가한다. 단일 리더(Raft) 시스템에서는 리더의 로그 인덱스 하나면 충분하므로, Vector Clock은 다중 리더 최종 일관성 시스템에서만 가치가 있다.

CAP 정리에서 "Partition Tolerance"를 포기할 수 있나?

이론적으로는 가능하지만 실무에서는 불가능하다. 네트워크 분할은 항상 일어날 수 있기 때문이다(하드웨어 장애, 케이블 손상, 스위치 재부팅 등). CAP를 "분할이 일어나지 않는 시스템에서는 일관성과 가용성을 함께 보장할 수 있다"로 읽으면 이해하기 쉽다. 따라서 실제로는 CP(가용성 포기) 또는 AP(일관성 포기) 중 선택한다.

장애 감지 150ms~1s 지연을 줄일 수 있나?

완전히 제거는 불가능하지만, 완화 방법이 있다: (1) Heartbeat 간격을 30~50ms 수준으로 짧게 설정하면 감지 시간을 100ms 대로 줄일 수 있으나, 네트워크 트래픽이 증가한다. (2) 외부 관찰자(external observer) 또는 lease 메커니즘으로 더 빠른 감지가 가능하지만, 구현 복잡도가 올라간다. 대부분의 시스템은 이 트레이드오프를 명시하지 않으므로, 프로토타입과 부하 테스트로 실제 동작을 검증하는 것이 필수다.

<<FIELD:END>>