키-값 저장소(Key-Value Store)는 키
를 통해 값
을 빠르게 찾는 단순한 구조를 갖는다. 하지만 대규모 분산 환경에서는 단순히 HashMap
처럼 동작하지 않는다. 수십~수천 대 서버에서 안정적이고 빠른 응답을 제공하려면 파티셔닝, 복제, 일관성 모델, 장애 복구 전략이 필수다. 이번 장은 Dynamo, Cassandra, Redis 같은 실제 분산 키-값 저장소의 원리를 이해하는 데 초점을 둔다.
키-값 저장소의 이상적인 특성은 다음과 같다.
대규모 환경에서는 하나의 서버에 모든 데이터를 저장할 수 없기 때문에 데이터를 파티셔닝(Sharding) 해야 한다. 가장 널리 쓰이는 방식이 Consistent Hashing(안정 해시) 이다.
안정 해시는 서버를 원형 해시 링에 배치하고 각 데이터의 키를 해시 값으로 변환해 링 상의 특정 서버에 매핑한다. 이 방식의 장점은 서버가 추가되거나 제거되더라도 전체 데이터의 일부분만 재배치되므로 확장성과 안정성이 뛰어나다. 즉, “규모가 커져도 데이터 이동 비용이 최소화된다”는 점이 핵심이다.
데이터가 단일 서버에만 존재하면 그 서버 장애 시 데이터가 유실된다. 이를 막기 위해 각 데이터는 N개의 복제본(replica) 으로 저장된다. 보통은 리더-팔로워 구조 또는 멀티리더 구조를 사용한다.
복제는 단순히 백업이 아니라 읽기/쓰기 요청을 분산해 처리율을 높이고 가용성을 보장하는 핵심 메커니즘이다. 예를 들어 Dynamo나 Cassandra는 각 키를 N개의 노드에 저장하고 읽기/쓰기 시 몇 개의 노드에서 응답을 확인할지를 조절할 수 있다.
대표적인 전략은 Quorum 방식이다.
만약 W + R > N
조건을 만족하면 읽기 시 반드시 최신 쓰기 결과가 포함되므로 일관성과 가용성의 균형을 잡을 수 있다. 예를 들어 N=3, W=2, R=2라면, 어떤 읽기 연산도 최소 1개의 최신 복제본을 참조하게 된다.
분산 환경에서는 같은 키에 대해 여러 클라이언트가 동시에 쓰기를 요청할 수 있다. 이때는 충돌(conflict) 이 발생한다.
이를 다루는 방식이 버저닝(Versioning) 이다. Dynamo 같은 시스템에서는 벡터 클락(Vector Clock) 을 사용한다. 각 쓰기 연산마다 “버전 메타데이터”를 함께 저장해 충돌이 발생했을 때 어떤 버전이 최신인지, 또는 병합이 필요한지를 알 수 있다.
충돌을 해결하는 방식은 다양하다.
분산 키-값 저장소는 네트워크 지연, 장애, 파티션 분리 등으로 인해 복제본 간 불일치가 발생할 수 있다. 이를 복원하기 위한 전략은 다음과 같다.
Hinted Handoff
특정 노드가 다운되었을 때 다른 노드가 해당 데이터를 임시로 저장하고 장애가 복구되면 원래 노드에 데이터를 전달한다. 이로써 쓰기 요청이 손실되지 않는다.
Anti-Entropy
장기적으로는 머클 트리(Merkle Tree) 같은 데이터 구조를 사용해 복제본 간 차이를 비교하고 동기화한다. 단순 전체 복사보다 효율적이다.
Eventual Consistency (최종적 일관성)
모든 노드가 즉각적으로 동일한 값을 반환하지 않더라도 시간이 지나면 결국 일관된 상태에 도달하도록 설계한다. 이는 강한 일관성을 포기하는 대신 높은 가용성과 성능을 확보할 수 있게 해준다. Dynamo, Cassandra 같은 시스템이 이 모델을 채택한다.
이번 장을 보면서 “HashMap처럼 단순해 보이는 키-값 저장소가 실제로는 얼마나 복잡한 분산 시스템 위에 구현되는지”를 알 수 있었다. 단일 노드에서 put/get
만 잘 동작하면 될 것 같지만 실제 서비스 환경에서는 노드 추가/삭제, 장애, 네트워크 분리, 동시 쓰기 같은 수많은 문제가 발생한다. 이를 해결하기 위해 파티셔닝, 복제, 버저닝, 충돌 해결, 데이터 동기화 같은 다양한 기술이 조합되어야 한다.
특히 Dynamo가 선택한 최종적 일관성(Eventual Consistency) 은 인상적이었다. 강한 일관성을 포기하는 대신 가용성과 확장성을 얻는 트레이드오프는 분산 시스템 설계의 현실적인 고민을 잘 보여준다. 이 개념을 이해하면 CAP 정리와 PACELC 정리가 실제 시스템에 어떻게 적용되는지도 더 잘 와닿는다.