포스트

분산 DB 확장 — 샤딩·분산 트랜잭션(2PC/Saga)·합의(Raft)

샤딩으로 데이터를 여러 노드에 나눌 때의 샤드 키 선택, 크로스 샤드 원자성을 다루는 2PC와 Saga 패턴, 그리고 복제 노드가 하나의 값에 합의하는 Raft까지 — CAP 이후의 실무 확장을 정리한다.

분산 DB 확장 — 샤딩·분산 트랜잭션(2PC/Saga)·합의(Raft)

CAP는 “파티션이 났을 때 C냐 A냐”라는 선택의 이론이다. 그런데 그 파티션이 의미를 가지려면 애초에 데이터가 여러 노드에 흩어져 있어야 한다. 즉 CAP는 분산이 이미 전제된 세계의 이야기다.

이 글은 그 전제를 실무로 되짚는다. 왜 데이터를 여러 노드에 나누게 되는지(샤딩), 나누고 나면 깨지는 원자성을 어떻게 다루는지(2PC·Saga), 그리고 복제된 노드들이 어떻게 하나의 진실에 동의하는지(Raft)를 순서대로 본다.

1. 왜 분산인가 — 수직 확장의 벽

단일 노드 DB는 언젠가 두 가지 한계에 부딪힌다.

  • 용량 — 디스크 하나에 담을 수 있는 데이터양의 한계.
  • 처리량 — CPU·메모리·I/O로 감당할 수 있는 초당 요청 수의 한계.

해법은 둘이다.

구분수직 확장(scale-up)수평 확장(scale-out)
방식한 노드의 사양을 키운다노드 수를 늘려 나눠 담는다
한계하드웨어 상한·비용 급증이론상 무한, 대신 복잡도 증가
장애 영향단일 실패점일부 노드만 영향
대표 기법더 큰 인스턴스샤딩·복제

수직 확장은 간단하지만 천장이 낮고 비싸며 단일 실패점이 남는다. 어느 규모를 넘으면 수평 확장이 유일한 답이 되고, 그 대표 기법이 샤딩이다.

2. 샤딩 — 데이터를 여러 노드에 쪼개기

샤딩(sharding)은 하나의 논리적 데이터셋을 샤드 키(shard key) 기준으로 여러 물리 노드(샤드)에 나눠 저장하는 것이다. 각 샤드는 전체 데이터의 일부만 갖고, 독립된 인스턴스로 동작한다.

파티셔닝과 뭐가 다른가

이름이 비슷해 헷갈리지만 위치가 다르다.

 파티셔닝(partitioning)샤딩(sharding)
분할 위치한 인스턴스 에서 테이블을 분할여러 인스턴스(노드)로 분할
목적쿼리 성능·관리 편의용량·처리량 수평 확장
노드 간 통신없음(단일 노드)있음(네트워크 경유)
CAP의 P무의미(단일 노드)유효(노드 간 단절 발생)

핵심은 샤딩은 데이터가 노드를 넘는다는 점이다. 이 순간부터 네트워크 파티션·분산 트랜잭션·합의 같은 문제가 전부 따라온다. 파티셔닝은 여전히 한 노드 안의 일이라 이런 부담이 없다.

샤드 키 선택이 전부다

어떤 컬럼을 샤드 키로 삼느냐가 샤딩의 성패를 가른다. 두 가지가 걸려 있다.

  • 핫스팟(hotspot) — 특정 샤드에 트래픽이 몰리는 현상. 예를 들어 국가 컬럼으로 샤딩하면 사용자가 몰린 나라의 샤드만 과부하가 걸린다.
  • 리밸런싱(rebalancing) — 샤드를 추가·제거할 때 데이터를 재배치하는 비용. 나이브한 방식은 거의 모든 데이터를 옮기게 만든다.

해시 샤딩 vs 범위 샤딩

샤드 키를 노드에 매핑하는 방식은 크게 둘이다.

 해시 샤딩(hash)범위 샤딩(range)
매핑hash(key)로 분산키 값 구간별로 분산
분포대체로 균등데이터 편향 시 불균등
범위 쿼리여러 샤드에 흩어짐(불리)인접 데이터가 한 샤드(유리)
핫스팟잘 안 생김최근 데이터 몰림 등 생기기 쉬움
리밸런싱단순 해시는 재배치 큼 → 일관 해싱으로 완화구간 분할/병합으로 대응

균등 분산이 목적이면 해시, 범위 스캔·시계열 조회가 잦으면 범위 샤딩이 유리하다. 단순 모듈로 해시는 노드 수가 바뀔 때 대부분의 키가 재배치되므로, 실무에서는 일관 해싱(consistent hashing)으로 재배치량을 최소화한다.

3. 샤딩의 대가 — 원자성이 깨진다

샤딩은 공짜가 아니다. 데이터가 여러 노드로 흩어지면 단일 노드에서는 당연했던 두 가지가 어려워진다.

  • 크로스 샤드 조인 — 조인 대상이 서로 다른 샤드에 있으면, DB가 알아서 못 하고 애플리케이션이 각 샤드에서 가져와 합쳐야 한다.
  • 크로스 샤드 트랜잭션 — 여러 샤드에 걸친 쓰기를 “전부 성공 아니면 전부 실패”로 묶기 어렵다. 단일 노드의 로컬 트랜잭션은 이 원자성을 공짜로 줬지만, 노드를 넘으면 그렇지 않다.

가능하면 샤드 키를 잘 골라 관련 데이터를 같은 샤드에 모으는 것(co-location)이 최선이다. 그래도 노드를 넘는 쓰기가 필요할 때, 다음 장의 분산 트랜잭션이 등장한다.

4. 분산 트랜잭션 — 여러 노드의 원자성

여러 노드에 걸친 쓰기를 원자적으로 처리하는 두 접근이 있다. 강한 일관성을 즉시 보장하는 2PC, 최종 일관성으로 완화하는 Saga다.

2PC(2단계 커밋)

2PC(Two-Phase Commit)코디네이터(coordinator)가 여러 참여자(participant)에게 커밋 여부를 조율하는 프로토콜이다. 이름 그대로 두 단계다.

1
2
3
4
5
6
7
8
[1단계 - Prepare]
Coordinator → 모든 참여자: "커밋 준비됐나?"
각 참여자: 로컬에서 준비(락 획득·로그 기록) 후
           "예(준비 완료)" 또는 "아니오(중단)" 응답

[2단계 - Commit / Abort]
모두 "예" → Coordinator: 전원에게 COMMIT 지시
하나라도 "아니오" → Coordinator: 전원에게 ABORT 지시

1단계에서 모든 참여자가 “준비 완료”를 약속했기 때문에, 2단계의 커밋은 반드시 성공한다. 이렇게 전원 합의 후에만 커밋하므로 강한 원자성을 보장한다.

대가는 두 가지다.

  • 블로킹 — 참여자는 “준비 완료”를 응답한 뒤 코디네이터의 최종 지시를 기다리며 자원을 잠근 채 대기한다. 이 사이 코디네이터가 죽으면 참여자는 커밋도 롤백도 못 하고 무한 대기(락 홀드)에 빠질 수 있다.
  • 코디네이터 단일 실패점 — 조율자가 곧 병목이자 SPOF다.

이 지연·블로킹 특성 때문에 2PC는 처리량이 중요하거나 참여 노드가 많은 시스템에는 부담이 크다.

Saga 패턴

Saga는 하나의 큰 분산 트랜잭션을, 각 노드의 로컬 트랜잭션들의 연쇄로 쪼갠다. 그리고 어느 단계가 실패하면 이미 성공한 앞 단계들을 보상 트랜잭션(compensating transaction)으로 되돌린다. 잠금을 오래 쥐지 않는 대신, 중간 상태가 잠깐 노출되는 최종 일관성(eventual consistency)을 받아들인다.

1
2
3
4
5
주문 Saga (정상)
  T1: 주문 생성  → T2: 결제 승인  → T3: 재고 차감  → 완료

T3에서 재고 부족으로 실패하면, 역순으로 보상:
  C2: 결제 취소  → C1: 주문 취소   → 원상 복구

보상은 롤백과 다르다. 이미 커밋된 로컬 트랜잭션을 되돌리는 것이므로, “결제 취소”, “재고 복원”처럼 의미적으로 상쇄하는 새 트랜잭션을 직접 정의해야 한다.

 2PCSaga
일관성강한 일관성(즉시 원자적)최종 일관성
잠금커밋까지 자원 락 유지(블로킹)각 로컬 트랜잭션만 짧게 락
실패 처리프로토콜이 abort보상 트랜잭션을 직접 작성
결합도참여자·코디네이터 강결합느슨한 결합(이벤트 기반 가능)
적합짧고 정합성이 절대적인 소수 노드장시간·다수 서비스에 걸친 흐름

강한 정합성이 반드시 필요하고 참여 노드가 적고 짧다면 2PC, 마이크로서비스처럼 오래 걸치고 가용성·처리량이 중요하며 중간 상태를 감당할 수 있으면 Saga가 맞다. 실무 백엔드에서 크로스 서비스 트랜잭션은 대개 Saga로 간다.

5. 합의(Consensus) — 복제 노드가 하나의 값에 동의하기

샤딩이 데이터를 나누는 문제라면, 합의는 데이터를 복제할 때의 문제다. 같은 데이터를 여러 노드에 복제해 두면 가용성이 오르지만, “지금 진짜 값이 뭐냐”에 노드들이 동의해야 한다. 일부 노드가 죽거나 느려도 말이다. 이게 합의(consensus) 문제다.

Raft 개괄

Raft는 이해하기 쉽도록 설계된 합의 알고리즘으로, 두 축으로 돌아간다.

  • 리더 선출(leader election) — 노드는 팔로워로 시작하고, 리더 신호가 끊기면 후보가 되어 투표를 요청한다. 과반(quorum)의 표를 얻은 노드가 리더가 된다. 리더는 하나뿐이다.
  • 로그 복제(log replication) — 모든 쓰기는 리더를 거친다. 리더는 요청을 로그 엔트리로 팔로워에게 복제하고, 과반이 기록을 확인하면 커밋한다. 이 커밋된 로그 순서가 곧 모든 노드가 동의한 하나의 진실이다.

과반 기반이라 노드 과반이 살아 있으면 동작을 이어가고, 소수 노드가 죽어도 일관성이 깨지지 않는다.

Paxos는 Raft보다 먼저 나온 고전적 합의 알고리즘으로, 이론적으로 견고하지만 이해·구현이 까다롭기로 유명하다. Raft는 같은 안전성을 제공하면서 “리더 중심”으로 구조를 단순화해, 오늘날 새 시스템에서 더 널리 채택된다.

이 합의 계층 위에서 etcd·Consul 같은 코디네이션 스토어와 여러 분산 DB가 강한 일관성을 유지한다. 샤드 하나하나가 내부적으로는 Raft로 복제되는 구조가 흔하다.

6. CAP으로 되돌아오기

이 기법들은 결국 파티션 상황에서 C와 A 중 무엇을 택하느냐로 수렴한다.

  • 2PC·Raft(합의) = CP 쪽 — 과반과 통신이 안 되거나 참여자 응답이 없으면, 틀린 답을 주느니 멈춘다. 커밋을 보류하고 리더 없이는 쓰기를 거부한다. 일관성을 위해 가용성을 양보한다.
  • Saga·AP형 복제 = A 쪽 — 파티션 중에도 각 로컬 트랜잭션은 진행되고 응답한다. 대신 전체가 일치하는 건 나중이다(최종 일관성). 가용성을 위해 즉시 일관성을 양보한다.

샤딩은 P를 실재하게 만들고, 2PC·Saga·Raft는 그 P 위에서 C와 A를 어떻게 저울질할지를 구현하는 도구다. CAP가 “무엇을 포기할지”를 말한다면, 이 장치들은 “어떻게 포기할지”를 정한다.

관련 글

관계
CAP 정리 — P는 고르는 게 아니라 주어진다이 글이 전제하는 분산 일관성·가용성 이론
테이블 파티셔닝한 인스턴스 안에서의 분할 — 샤딩과 대비
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.