NoSQL

레디스 클러스터와 Consistent Hashing

sstdio.h·2019년 12월 3일·조회 8,807

1. 개요

레디스(Redis)를 운영 환경에서 사용할 때는 장애 조치, 데이터 분산, 노드 증설 시 데이터 이동 범위를 함께 고려해야 한다. 이 글에서는 레디스 고가용성 구성과 클러스터의 슬롯 방식, 그리고 일반적인 해싱 방식에서 노드 수가 바뀔 때 발생하는 문제를 Consistent Hashing 관점에서 정리한다.


2. 레디스 고가용성

2.1. Failover

기본적인 자동 장애 조치(Auto failover)는 센티넬(Sentinel)이 처리할 수 있다. 레디스 노드 프로세스나 컨테이너 수준의 재시작은 쿠버네티스 같은 오케스트레이션 도구로도 처리할 수 있지만, 어떤 노드를 마스터로 승격할지 판단하고 클라이언트가 새 마스터를 바라보게 하는 역할은 별도로 고려해야 한다.

2.2. 클러스터

레디스 클러스터를 반드시 사용해야 하는가? 꼭 그렇지는 않다. 데이터 규모, 처리량, 운영 복잡도에 따라 센티넬 기반의 마스터-레플리카 구성이 더 단순하고 충분한 경우도 있다. 반대로 여러 노드에 데이터를 샤딩해야 하거나 수평 확장이 필요하다면 클러스터 구성이 적합하다.

2.3. 레디스 클러스터의 키-슬롯-노드 할당

레디스 클러스터는 0부터 16383까지, 총 16384개의 슬롯을 사용한다. 이 슬롯들은 클러스터 초기 구성 시 각 마스터 노드에 적절히 할당된다.

  • Redis#1 : 0 ~ 5460
  • Redis#2 : 5461 ~ 10922
  • Redis#3 : 10923 ~ 16383

데이터가 어떤 슬롯에 저장되어야 하는지는 HASH_SLOT = CRC16(key) mod 16384라는 정해진 식에 의해 결정되며, 각 노드는 다른 노드가 어떤 슬롯을 담당하는지 알고 있다. 따라서 요청을 받은 노드가 해당 키의 슬롯을 관리하지 않는다면, 그 슬롯을 담당하는 노드로 클라이언트를 리다이렉션한다.

HASH_SLOT = CRC16(key) mod 16384는 CRC16 함수로 계산한 해시 값을 전체 슬롯 개수인 16384로 나누는 방식이다. 그러면 0부터 16383 사이의 나머지가 나오고, 그 값이 키에 해당하는 슬롯 번호가 된다. 예를 들어 aaa라는 키를 저장한다고 하면 CRC16("aaa") mod 16384를 수행한다. 그 결과가 1000이라고 할 때, 1000은 0 ~ 5460 범위에 속하므로 Redis#1에 저장된다.

만약 노드가 추가되거나 삭제되면 슬롯과 데이터를 재분배해야 하는 상황이 발생한다. 이때 Reshard 또는 Rebalance 기능을 사용해 일부 슬롯을 다른 노드로 이동시킨다. 클러스터는 키를 노드 수로 직접 나누는 것이 아니라 고정된 16384개 슬롯을 한 번 더 거치기 때문에, 노드 변경 시에도 전체 키를 무조건 다시 계산해 옮기는 방식과는 다르게 동작한다.

2.4. 노드 추가 시 고려할 점

일반적인 해싱 방식에서는 키 값이 변하지 않으면 해시 값도 변하지 않는다. 예를 들어 해시 값을 노드 수로 나눈 나머지로 저장 위치를 결정한다면, 노드가 3개일 때는 hash(key) mod 3으로 노드를 고르게 된다.

그런데 3개 노드에서 4개 노드로 증가한다고 가정하면, 이전에는 해시 값을 3으로 나누었지만 노드 증가 후에는 4로 나누어야 한다. 이 경우 같은 키라도 나머지 값이 달라질 수 있으므로 최종적으로 저장되는 노드가 크게 바뀐다. 결과적으로 캐시 재구축을 위한 데이터의 대이동이 발생할 수 있고, 이미 운영 시스템에서 캐시가 활발히 사용 중이라면 성능 저하나 캐시 미스 증가로 이어질 수 있다.

레디스 클러스터의 슬롯 방식은 이런 문제를 완화하기 위한 구조에 가깝다. 다만 노드를 추가한다고 해서 데이터 이동이 전혀 없는 것은 아니며, 새 노드에 슬롯을 넘기는 만큼 해당 슬롯에 속한 키들은 이동해야 한다.


3. Consistent Hashing

Consistent Hashing은 1997년 제안된 알고리즘이다. 처음에는 웹 캐시를 구현하기 위해 개발되었으며, 캐시 노드의 추가/삭제와 무관하게 높은 캐시 히트율을 유지하는 것을 목표로 한다.

앞서 설명한 것처럼 단순히 hash(key) mod node_count 방식으로 노드를 결정하면 노드 수가 바뀔 때 많은 키가 다시 매핑된다. Consistent Hashing은 이 문제를 줄이기 위한 방법으로 사용된다. 해시 공간을 원형 링처럼 보고, 키와 노드를 같은 해시 공간에 배치한 뒤 키가 링을 따라 만나는 노드에 저장되는 방식이다.

Consistent Hashing을 적용하지 않으면 노드 수 변경 시 많은 키가 재매핑될 수 있다. 반면 Consistent Hashing을 적용하면 해시 테이블의 크기, 즉 노드 구성이 변할 때 평균적으로 K/n개의 키만 재매핑된다고 볼 수 있다. 여기서 K는 아이템 수, n은 노드 수다.

예를 들어 캐시 노드가 3개인 상태에서 1개를 추가하면, 새 노드가 담당하게 되는 일부 구간의 키만 이동하면 된다. 나머지 구간의 키들은 기존 노드에 그대로 남기 때문에 전체 캐시를 다시 채우는 부담을 줄일 수 있다. 실제 구현에서는 노드 간 데이터 분포를 더 고르게 만들기 위해 하나의 물리 노드를 여러 개의 가상 노드로 표현하는 방식도 함께 사용된다.

댓글 0

로그인 후 댓글을 남길 수 있습니다.

아직 댓글이 없습니다.