일관된 해싱이란? 서버 추가 시 캐시 키 이동을 최소화하는 방법

@JavaPark · 2026년 9월 4일 · 10 min read

서버 추가 시 모듈러 해싱과 Hash Ring의 키 재배치량을 비교한 그림
서버 추가 시 모듈러 해싱과 Hash Ring의 키 재배치량을 비교한 그림

안녕하세요. 자바파커입니다.

분산 캐시에 서버가 세 대 있다고 가정해 보겠습니다. 가장 단순한 배치식은 다음과 같습니다.

server = hash(key) % 3

이 방식은 빠르고 이해하기 쉽습니다. 하지만 네 번째 서버를 추가하는 순간 식이 % 4로 바뀝니다. 같은 키의 해시값은 그대로인데 나누는 수가 달라져 기존 키 대부분의 목적지가 달라질 수 있습니다.

일관된 해싱의 목적은 데이터를 완벽히 균등하게 나누는 것이 아니라, 노드가 바뀌어도 배치를 덜 흔들리게 만드는 것입니다.


왜 모듈러 해싱은 크게 흔들릴까

예제 해시값 0~11을 서버 세 대에 배치해 보겠습니다.

hash % 3
A: 0, 3, 6, 9
B: 1, 4, 7, 10
C: 2, 5, 8, 11

서버 D를 추가해 % 4로 바꾸면 결과는 다음과 같습니다.

hash % 4
A: 0, 4, 8
B: 1, 5, 9
C: 2, 6, 10
D: 3, 7, 11

이 작은 예제에서는 12개 중 9개의 목적지가 바뀝니다. 캐시라면 대량의 cache miss가 발생하고, 샤드라면 데이터 이동과 네트워크 트래픽이 한꺼번에 늘어납니다.


Hash Ring은 어떻게 이동 범위를 줄일까

일관된 해싱은 해시 공간을 원형으로 연결합니다.

0 ─────────────── MAX_HASH
└────── 다시 0으로 연결 ──────┘

서버와 키를 모두 같은 링 위에 해싱하고, 키 위치에서 시계 방향으로 처음 만나는 서버를 소유자로 정합니다.

hash(key) → 링 위 위치 → 시계 방향 첫 서버

새 서버 D가 B와 C 사이에 들어오면 D가 새로 담당하는 구간의 키만 C에서 D로 이동합니다. 다른 구간의 소유 관계는 그대로 남습니다. Cassandra 공식 문서도 단순 해싱은 노드 추가 시 거의 모든 매핑을 무효화할 수 있지만, 일관된 해싱은 작은 일부만 옮긴다고 설명합니다.


실전에서는 Virtual Node가 필요하다

물리 서버 한 대를 링의 한 지점에만 놓으면 구간 크기가 고르지 않을 수 있습니다. 큰 구간을 맡은 서버에 데이터와 요청이 몰립니다.

가상 노드(vnode)는 물리 서버 하나를 링의 여러 위치에 배치합니다.

physical A → A1, A2, A3, A4
physical B → B1, B2, B3, B4

서버를 추가하면 여러 작은 구간을 기존 서버들로부터 나눠 받으므로 부하 편차가 줄어듭니다. 다만 vnode 수가 늘면 링 메타데이터와 재조정 단위도 늘어납니다. 무조건 많게 잡기보다 데이터 크기, 노드 수, 장애 복구 시간을 함께 측정해야 합니다.


간단한 JavaScript 구현

아래 코드는 원리를 보여주는 최소 예제입니다. 실제 시스템에서는 균일한 해시 함수, 정렬된 자료구조, 복제 정책이 추가로 필요합니다.

class HashRing {
  constructor() {
    this.points = []
  }

  addNode(node, virtualNodes = 64) {
    for (let i = 0; i < virtualNodes; i++) {
      this.points.push({ hash: hash(`${node}#${i}`), node })
    }
    this.points.sort((a, b) => a.hash - b.hash)
  }

  getNode(key) {
    const h = hash(key)
    const point = this.points.find(p => p.hash >= h)
    return (point ?? this.points[0]).node
  }
}

조회는 정렬 배열에서 선형 탐색하지 않고 이진 탐색을 사용해야 합니다. 노드 목록이 바뀌는 순간 모든 애플리케이션 인스턴스가 같은 링 버전을 보도록 하는 것도 중요합니다.


복제는 별도의 문제다

일관된 해싱은 기본적으로 키의 주 소유자를 정합니다. 장애를 견디려면 복제본도 선택해야 합니다.

primary  = 시계 방향 첫 번째 물리 노드
replica1 = 다음 물리 노드
replica2 = 그다음 물리 노드

여기서 vnode만 보고 같은 물리 서버를 두 번 고르면 복제 의미가 사라집니다. 반드시 서로 다른 장애 도메인의 물리 노드를 선택해야 합니다.


운영에서 자주 놓치는 함정

1. 재배치가 적다고 비용이 0은 아니다

새 서버가 담당할 데이터를 실제로 복사하고, 복사 중 발생한 쓰기를 따라잡고, 트래픽을 전환해야 합니다. 링 계산만 구현하고 마이그레이션 상태를 관리하지 않으면 읽기 누락이 발생합니다.

2. Hot Key는 해결하지 못한다

키 하나에 트래픽이 집중되면 그 키의 소유 서버가 계속 뜨겁습니다. 요청 병합, 로컬 캐시, 키 분할 같은 별도 전략이 필요합니다.

3. 노드 용량이 다르면 가중치가 필요하다

메모리 64GB 서버와 256GB 서버에 같은 수의 vnode를 주면 큰 서버의 용량을 활용하지 못합니다. 용량 비율에 맞춰 토큰 수를 조정해야 합니다.

4. 멤버십 정보가 갈리면 같은 키가 다른 곳으로 간다

클라이언트마다 서로 다른 노드 목록을 사용하면 동일한 키의 목적지가 달라집니다. 링 버전, 배포 순서, 롤백 절차를 명시해야 합니다.


어디에 사용할까

  • 분산 캐시의 키 배치
  • NoSQL 데이터베이스의 파티셔닝
  • 상태 저장 서비스의 샤드 선택
  • 세션이나 테넌트를 셀에 안정적으로 매핑
  • 동일 키 요청을 같은 upstream으로 보내는 라우팅

반대로 서버 수가 거의 바뀌지 않고 전체 데이터가 작다면 단순 모듈러 방식이 더 쉽습니다. 일관된 해싱은 재배치 비용이 실제 운영 문제일 때 가치가 있습니다.


마무리

Modulo Hashing    → 서버 수가 바뀌면 배치 전체가 흔들림
Consistent Hashing → 새 서버가 맡은 구간만 이동
Virtual Nodes      → 구간과 부하의 편차를 완화

일관된 해싱을 도입할 때는 링 그림만 보지 말고 재배치량, 데이터 이동 시간, 복제, 핫키, 멤버십 버전을 함께 설계해야 합니다.

자주 묻는 질문

일관된 해싱을 쓰면 데이터가 완벽히 균등해지나요?

아닙니다. 핵심 보장은 노드 변경 시 이동 범위를 줄이는 것입니다. 균등성은 해시 품질, vnode 수, 노드별 가중치의 영향을 받습니다.

서버를 제거할 때도 일부 키만 이동하나요?

네. 제거된 서버가 소유하던 구간이 시계 방향의 다음 서버로 넘어갑니다. 다만 복제본과 장애 도메인을 함께 고려해야 합니다.

Rendezvous Hashing과 무엇이 다른가요?

Rendezvous Hashing은 각 키에 대해 모든 노드의 점수를 계산해 가장 높은 노드를 선택합니다. 링 관리가 필요 없다는 장점이 있지만 노드 수가 클 때 계산 비용과 최적화 방법이 달라집니다.

로드 밸런싱의 Round Robin을 대체하나요?

목적이 다릅니다. Round Robin은 요청을 순환 배분하고, 일관된 해싱은 같은 키가 가능한 한 같은 목적지에 남도록 합니다. 상태나 캐시 지역성이 필요할 때 사용합니다.

참고 자료

퀴즈

서버 수가 3대에서 4대로 바뀔 때 단순 hash(key) % N 방식의 가장 큰 문제는 무엇일까요?

백엔드 CS 문제 더 풀기
@JavaPark
AI 시대의 개발자 도구, 실전 경험을 공유합니다