가상 면접 사례로 배우는 대규모 시스템 설계 기초 6장: 키-값 저장소 설계

송현진·2025년 8월 18일
0

System Design

목록 보기
5/11

키-값 저장소(Key-Value Store)는 를 통해 을 빠르게 찾는 단순한 구조를 갖는다. 하지만 대규모 분산 환경에서는 단순히 HashMap처럼 동작하지 않는다. 수십~수천 대 서버에서 안정적이고 빠른 응답을 제공하려면 파티셔닝, 복제, 일관성 모델, 장애 복구 전략이 필수다. 이번 장은 Dynamo, Cassandra, Redis 같은 실제 분산 키-값 저장소의 원리를 이해하는 데 초점을 둔다.

설계 목표와 이상적 상태 (Ideal State)

키-값 저장소의 이상적인 특성은 다음과 같다.

  • 고가용성(Availability): 일부 서버가 다운되어도 서비스는 계속 동작해야 한다. 사용자는 서버 장애를 거의 느끼지 못해야 한다.
  • 수평 확장성(Scalability): 서버 수를 늘리면 처리량도 선형적으로 확장되어야 한다. 작은 규모에서 수백만 QPS까지 확장 가능해야 한다.
  • 내구성(Durability): 저장된 데이터는 절대 손실되지 않아야 하며 장애 후에도 영구적으로 복구 가능해야 한다.
  • 일관성(Consistency): 사용자가 데이터를 읽을 때 항상 최신 값을 얻는 것이 이상적이다. 하지만 CAP 정리에 따라 가용성과의 균형을 고려해야 하므로 “어느 수준까지 일관성을 보장할 것인가”가 핵심 설계 포인트가 된다.

데이터 파티셔닝

대규모 환경에서는 하나의 서버에 모든 데이터를 저장할 수 없기 때문에 데이터를 파티셔닝(Sharding) 해야 한다. 가장 널리 쓰이는 방식이 Consistent Hashing(안정 해시) 이다.

안정 해시는 서버를 원형 해시 링에 배치하고 각 데이터의 키를 해시 값으로 변환해 링 상의 특정 서버에 매핑한다. 이 방식의 장점은 서버가 추가되거나 제거되더라도 전체 데이터의 일부분만 재배치되므로 확장성과 안정성이 뛰어나다. 즉, “규모가 커져도 데이터 이동 비용이 최소화된다”는 점이 핵심이다.

데이터 복제와 가용성

복제(Replication)

데이터가 단일 서버에만 존재하면 그 서버 장애 시 데이터가 유실된다. 이를 막기 위해 각 데이터는 N개의 복제본(replica) 으로 저장된다. 보통은 리더-팔로워 구조 또는 멀티리더 구조를 사용한다.

복제는 단순히 백업이 아니라 읽기/쓰기 요청을 분산해 처리율을 높이고 가용성을 보장하는 핵심 메커니즘이다. 예를 들어 Dynamo나 Cassandra는 각 키를 N개의 노드에 저장하고 읽기/쓰기 시 몇 개의 노드에서 응답을 확인할지를 조절할 수 있다.

쓰기 전략

대표적인 전략은 Quorum 방식이다.

  • W = 쓰기 성공을 확인해야 하는 복제본 수
  • R = 읽기 시 확인하는 복제본 수
  • N = 전체 복제본 수

만약 W + R > N 조건을 만족하면 읽기 시 반드시 최신 쓰기 결과가 포함되므로 일관성과 가용성의 균형을 잡을 수 있다. 예를 들어 N=3, W=2, R=2라면, 어떤 읽기 연산도 최소 1개의 최신 복제본을 참조하게 된다.

데이터 버저닝과 충돌 해결

분산 환경에서는 같은 키에 대해 여러 클라이언트가 동시에 쓰기를 요청할 수 있다. 이때는 충돌(conflict) 이 발생한다.

이를 다루는 방식이 버저닝(Versioning) 이다. Dynamo 같은 시스템에서는 벡터 클락(Vector Clock) 을 사용한다. 각 쓰기 연산마다 “버전 메타데이터”를 함께 저장해 충돌이 발생했을 때 어떤 버전이 최신인지, 또는 병합이 필요한지를 알 수 있다.

충돌을 해결하는 방식은 다양하다.

  • Last Write Wins (LWW): 타임스탬프가 가장 최신인 값을 선택한다. 단순하지만 오래된 쓰기가 덮어씌워지면서 데이터 손실 가능성이 있다.
  • Client-side Resolution: 충돌된 모든 버전을 클라이언트에 반환하고 클라이언트가 병합한다. 애플리케이션 레벨에서 복잡하지만 유연성이 크다.
  • Server-side Resolution: 사전에 정의된 규칙에 따라 서버가 자동으로 병합한다. 예를 들어 “재고 수량은 합산, 문자열은 최신값 선택” 같은 정책을 둘 수 있다.

데이터 동기화와 이상적 상태 복원

분산 키-값 저장소는 네트워크 지연, 장애, 파티션 분리 등으로 인해 복제본 간 불일치가 발생할 수 있다. 이를 복원하기 위한 전략은 다음과 같다.

  • Hinted Handoff
    특정 노드가 다운되었을 때 다른 노드가 해당 데이터를 임시로 저장하고 장애가 복구되면 원래 노드에 데이터를 전달한다. 이로써 쓰기 요청이 손실되지 않는다.

  • Anti-Entropy
    장기적으로는 머클 트리(Merkle Tree) 같은 데이터 구조를 사용해 복제본 간 차이를 비교하고 동기화한다. 단순 전체 복사보다 효율적이다.

  • Eventual Consistency (최종적 일관성)
    모든 노드가 즉각적으로 동일한 값을 반환하지 않더라도 시간이 지나면 결국 일관된 상태에 도달하도록 설계한다. 이는 강한 일관성을 포기하는 대신 높은 가용성과 성능을 확보할 수 있게 해준다. Dynamo, Cassandra 같은 시스템이 이 모델을 채택한다.

📝 느낀점

이번 장을 보면서 “HashMap처럼 단순해 보이는 키-값 저장소가 실제로는 얼마나 복잡한 분산 시스템 위에 구현되는지”를 알 수 있었다. 단일 노드에서 put/get 만 잘 동작하면 될 것 같지만 실제 서비스 환경에서는 노드 추가/삭제, 장애, 네트워크 분리, 동시 쓰기 같은 수많은 문제가 발생한다. 이를 해결하기 위해 파티셔닝, 복제, 버저닝, 충돌 해결, 데이터 동기화 같은 다양한 기술이 조합되어야 한다.

특히 Dynamo가 선택한 최종적 일관성(Eventual Consistency) 은 인상적이었다. 강한 일관성을 포기하는 대신 가용성과 확장성을 얻는 트레이드오프는 분산 시스템 설계의 현실적인 고민을 잘 보여준다. 이 개념을 이해하면 CAP 정리와 PACELC 정리가 실제 시스템에 어떻게 적용되는지도 더 잘 와닿는다.

profile
개발자가 되고 싶은 취준생

0개의 댓글