Consistent Hashing

일관성 해싱

🎯 일관성 해싱 (보강 버전 Part 1)

1. 왜 해싱이 필요할까?

  • 서버가 여러 대 → 데이터를 나눠 담아야 함

  • 단순 방식: key % 서버개수 (예: 손님 번호 % 마트 개수)

  • 문제: 서버 하나 추가/삭제 시 전체 데이터 재분배 발생

  • 실제로 수백만 건 데이터가 전부 흔들리면 → 서비스 멈춤/대규모 지연 위험 🚨

  • 운영 환경에서는 거의 불가능한 방식


2. 해시 링(Hash Ring) 비유

  • 원형 시계판에 서버 = 마트, 데이터 = 손님을 배치

  • 손님은 자기보다 시계방향으로 가장 가까운 마트로 이동

  • 해시 함수: 데이터 → 숫자 → 링 위 위치 변환

  • 장점: 구조가 단순하면서도 유연하고 안정적


3. 노드 추가/삭제 시 변화

  • 새 서버 등장 → 해당 구간 손님만 새 서버로 이동

  • 서버 하나 제거 → 그 서버 손님만 옆 서버로 이동

  • 전체 재분배 ❌, 일부 이동 ⭕

  • 결과: 확장성·장애 대응력이 뛰어남

  • 운영 관점: 서버 증설 시에도 서비스 중단이 거의 없음


4. 가상 노드(Virtual Node)

  • 현실: 서버 성능·용량은 제각각

  • 단순 배치 시 특정 서버에 데이터 몰려 Hotspot(핫스팟) 발생

  • 해결책: 하나의 물리 서버를 여러 가상 노드로 쪼개서 링에 흩뿌림

  • 마치 대형 마트를 작은 체인점 여러 개로 나눠 동네 곳곳에 배치하는 느낌

  • 효과: 데이터 분산 균형 ↑, 병목 ↓


5. 실제 서비스 활용

  • Redis Cluster: 키 → CRC16 해시 → 16384 슬롯 → 서버 매핑

  • Cassandra: 파티션 키 → 토큰 링 → 노드 분산

  • Memcached: 서버 증설/삭제에도 데이터 최소 이동으로 안정 운영

  • 공통점: “일관성 해싱 원리”를 각 서비스에 맞게 변형

  • 결론: 단순한 이론이 아니라 실무 핵심 기술


6. 개발자가 얻는 이점

  • 서버 확장(스케일 아웃)이 두렵지 않음

  • 장애 발생 시 전체 재분배가 아닌 일부만 이동

  • 데이터가 고르게 퍼져 특정 서버 병목 방지

  • 운영 비용 절감 + 서비스 안정성 확보

  • 실무에서 “캐시, 세션, DB 파티셔닝” 문제를 풀어주는 열쇠


7. 단점도 있다

  • 완벽히 균등 분산 ❌ → 여전히 Hotspot 가능

  • 해시 함수 선택에 따라 성능 차이 발생 (예: SHA1 vs MurmurHash)

  • 가상 노드 설계/운영이 추가 관리 포인트

  • 모니터링 없으면 특정 노드 과부하를 놓치기 쉬움

  • 즉, “도입만 하면 끝”은 아님 → 운영 지식 필요


8. 대안 알고리즘도 있다

  • Rendezvous Hashing(HRW): 키마다 모든 서버와 점수 계산 → 최고 점수 서버 선택, 구현 단순

  • Weighted Hashing: 서버 성능에 맞춰 가중치 적용 가능

  • Jump Consistent Hash: 최근 등장, 계산 빠르고 균형 분배 우수

  • 상황별로 장단점 존재 → 서비스 특성에 맞는 선택 필요


9. 그림으로 정리하기

  • 해시 링 기본 구조: 서버와 데이터가 원형에 배치

  • 노드 추가/삭제 전후: 일부만 이동하는 모습 강조

  • 가상 노드: 큰 서버가 여러 점으로 흩어져 있는 그림

  • Hotspot 예시: 특정 구간에 데이터가 몰려 과부하 발생

  • 시각 자료가 있으면 초보자도 직관적으로 이해 가능


10. 마무리: 이걸 알면 뭐가 좋을까?

  • 일관성 해싱은 “확장성과 유연성”을 제공하는 핵심 아이디어

  • 단순 수학 문제가 아니라 대규모 서비스 운영 필수 지식

  • 캐시, 세션 저장소, DB 파티셔닝, 로드밸런서 설계 등 광범위하게 활용됨

  • 실무 메시지: 규모가 커지는 순간 반드시 마주치는 문제 → 알아두면 필살기


11. Redis Cluster의 슬롯 개념

  • Redis는 데이터를 16,384개의 슬롯(slot) 으로 쪼갬

  • 키 → CRC16 해시mod 16384 → 슬롯 번호 결정

  • 각 슬롯은 특정 노드가 맡아서 저장/관리

  • 노드가 추가/삭제돼도 → 슬롯만 재배치하면 됨

  • 👉 “큰 창고를 작은 칸으로 나눠두면, 옮길 때 일부 칸만 움직이면 되는 구조”


12. 슬롯과 일관성 해싱 비교

  • Consistent Hashing: 원형 링 위에서 노드 배치 → 유연하지만 구현 복잡

  • Redis Cluster: 미리 정해진 슬롯 16,384개 → 단순, 예측 가능

  • 장점: 리밸런싱 범위가 명확, 관리 쉬움

  • 단점: 슬롯 수는 고정 → 동적 확장 불가

  • 👉 “원형 링(자유로운 지도)” vs “칸 나눠진 룰렛판(정해진 틀)”


13. 노드 추가/삭제 시 슬롯 재분배

  • 새 노드 추가 → 기존 노드가 가진 일부 슬롯만 양도

  • 노드 삭제 → 그 노드 슬롯을 다른 노드가 인수

  • 전체 데이터 이동 ❌, 슬롯 단위 일부 이동 ⭕

  • 👉 “마트 점포가 늘어나면 주변 물건 몇 박스만 새 점포로 옮기는 느낌”


14. 마스터-슬레이브 구조와 고가용성

  • 각 슬롯 = 기본적으로 마스터 노드 담당

  • 마스터마다 슬레이브 노드(복제본) 붙음

  • 마스터 장애 → 슬레이브 자동 승격

  • 데이터 손실은 최소화, 서비스는 계속 진행

  • 👉 “마트 본점(마스터) + 지점(슬레이브). 본점이 닫아도 지점이 바로 가게 열어줌”


15. 데이터 조회 시 해시 태그(Hash Tag)

  • 키 안에 {}를 쓰면 그 안 문자열만 해시 계산

  • 예: {user:1000}:name, {user:1000}:tweets → 같은 슬롯으로 묶임

  • 덕분에 멀티키 연산/트랜잭션 가능

  • 👉 “같은 박스에 담아놔야 같이 꺼낼 수 있음”


16. 클라이언트-서버 간 키 라우팅

  • 클라이언트가 직접 키 → 슬롯 → 노드 계산

  • 클러스터 구성이 바뀌면 서버가 MOVED, ASK 신호를 보내 새 노드 알려줌

  • 클라이언트는 다시 요청을 재전송 → 최신 구조 따라감

  • 👉 “택배 기사님이 이사 간 집 주소 알려주는 안내판”


17. 실제 장애 상황 시 동작

  • 노드 다운 → 해당 슬롯의 슬레이브가 자동 승격

  • 잠깐의 지연은 있을 수 있지만, 데이터 유실 최소화

  • 심각한 장애 → 관리자가 redis-cli cluster로 수동 조정

  • 👉 “자동 대체 시스템이 있지만, 최악의 경우 매니저가 직접 개입”


18. Redis Cluster의 장단점

장점

  • 구조 단순, 관리 예측 가능

  • 빠른 확장성과 높은 가용성

  • 일관성 해싱의 장점을 실제로 구현

단점

  • 슬롯 개수 고정(16384) → 동적 확장 ❌

  • 멀티키 연산은 같은 슬롯에서만 가능

  • 특정 슬롯에 트래픽 몰리면 핫스팟 문제 발생

  • 👉 “깔끔한 룰렛판이지만, 룰렛판 크기를 바꾸긴 힘듦”


19. Redis Cluster vs Consistent Hashing 총정리

  • 공통점: 노드 변경 시 전체가 아닌 일부만 이동

  • 차이점

    • Consistent Hashing → 원형 링, 유연함

    • Redis Cluster → 고정 슬롯, 단순/관리 쉬움

  • Redis가 슬롯 방식을 택한 이유: 개발자·운영자 입장에서 실용적

  • 👉 “철학은 같지만, 구현은 현실적으로 간소화”


20. 실무 적용 인사이트

  • Redis Cluster는 캐시/세션 저장소로 많이 사용

  • Hash Tag 설계를 잘못하면 → 멀티키 연산 불가 → 큰 문제 발생

  • 장애 대응, 슬롯 재분배 전략을 사전에 연습해 두는 게 중요

  • 결론: Redis Cluster는 단순 DB가 아니라 확장성과 안정성을 갖춘 분산 설계 도구

  • 👉 “그냥 창고가 아니라, 확장 계획까지 세워둔 스마트 창고”


Redis Cluster: docker-compose로 5노드 시작 → 노드 추가/삭제 튜토리얼

0) 구성 개요

  • 노드 수: 5 (마스터 3 + 슬레이브 2)로 시작

  • 포트: 7001~7005 (각 컨테이너 내부 6379에 매핑)

  • 클러스터 생성: redis-cli --cluster create ... --cluster-replicas 1

  • 확장(추가): 새 컨테이너 띄운 뒤 add-node → (원하면) 리밸런싱(reshard)

  • 축소(삭제): 대상 노드의 슬롯을 다른 노드로 옮긴 뒤 del-node


1) docker-compose.yml (5노드 시작)

Bitnami 이미지로 간단하게. 컨테이너 호스트네임을 클러스터에 알리도록 설정해 재분배/리다이렉트가 잘 동작하게 함.

참고

  • 비밀번호가 필요하면 REDIS_PASSWORD=yourpass를 모든 노드에 동일하게 지정하고, 아래 redis-cli 호출 시 -a yourpass 추가.

  • 로컬 포트를 700x로 나눈 건 보기 편하라고 한 것. 내부 포트는 항상 6379다.


2) 컨테이너 기동 & 클러스터 생성

  • 위 명령이 끝나면 마스터 3개 + 슬레이브 2개로 자동 구성된다.

  • 상태 확인:


3) (선택) 슬롯 분포/건강 체크

  • cluster slots슬롯 범위(0~16383)가 어떤 마스터에 할당됐는지 확인.

  • 마스터 각각에 5~6천 슬롯 정도가 분산돼 있으면 정상.


4) 노드 추가(확장): 5 → 6 노드

4-1) compose에 새 서비스 추가

4-2) 클러스터에 노드 합류(add-node)

  • 이 시점엔 슬롯을 아직 받지 않은 빈 마스터 상태.

4-3) 리밸런싱(슬롯 재분배, reshard)

  • 결과: 기존 마스터들 일부 슬롯이 redis-node-6으로 이동.

  • “새 노드를 슬레이브(복제본)로만 추가”하고 싶다면:

    1. add-node로 일단 추가

    2. redis-cli --cluster replicate <new-node-id> <master-node-id> 실행


5) 노드 삭제(축소): 6 → 5 노드

핵심: 해당 노드의 슬롯을 먼저 다른 마스터로 모두 옮긴 후 del-node.

5-1) 슬롯 비우기(reshard로 슬롯 이동)

5-2) del-node (클러스터에서 제거)

5-3) 컨테이너 정리

주의

  • 삭제하려는 노드가 마스터라면 반드시 슬롯 0개 상태에서 del-node.

  • 삭제하려는 노드가 슬레이브라면, 먼저 cluster nodes에서 master/slave 관계를 확인하고, 필요 시 다른 슬레이브로 재배치.


6) 장애/Failover 빠른 연습

  • 슬레이브가 같은 슬롯을 이어받으면 정상.

  • 복구 후:


7) Spring 연결 포인트(요약)

이미 스프링 설정은 진행 중이니, 클러스터 호스트는 서비스명:6379로 쓰면 깔끔.

  • 추가/삭제 후에도 Lettuce가 MOVED/ASK를 따라가며 토폴로지를 갱신.

  • 해시 태그({user:42})로 멀티키 연산을 같은 슬롯에 묶는 패턴은 그대로 유지.


8) 운영 팁 (요약 체크리스트)

  • 추가: 컨테이너 띄우기 → add-node → (필요 시) rebalance

  • 삭제: rebalance/reshard로 슬롯 0 만들기 → del-node → 컨테이너 제거

  • 모니터링: cluster info, cluster nodes, Prometheus + Grafana

  • 보안: REDIS_PASSWORD 통일 + TLS(필요 시), ACL

  • 백업: RDB/AOF, 스냅샷 경로 볼륨 마운트 권장


Spring 애플리케이션에서 Consistent Hashing/Redis Cluster 쓰는 법

1) 키 네이밍 & Hash Tag 유틸 (슬롯 고정)

  • 멀티키/트랜잭션을 쓸 가능성이 있는 도메인은 반드시 Hash Tag로 묶자: {user:42}:profile


2) RedisTemplate 기반 Repository (Value/Hash)

  • 직렬화는 StringRedisSerializer + GenericJackson2JsonRedisSerializer 추천(이미 설정했다고 가정).


3) 멀티키 조회(MGET) & 파이프라인 (같은 슬롯에 묶어야 함)

  • 같은 {user:42} 태그로 묶이면 클러스터에서도 안전하게 배치/파이프라인 가능.


4) Spring Cache (+ Hash Tag 포함 KeyGenerator)

  • 캐시 키에도 태그를 포함시켜 같은 슬롯 유지.


5) 레이트리미터(Token Bucket) – Lua (원자적)

  • 클러스터에서 Lua/EVAL은 같은 슬롯 키만 안전. 키를 태그로 묶자.


6) 분산락(선택: Redisson) – 임계 구역 보호

  • Redisson이면 코드가 간단해짐. (Lettuce로 SET NX 구현도 가능하지만 Redisson이 안전장치 풍부)


7) 세션 공유 (Spring Session)

  • Sticky Session 없이도 세션 공유. (Hash Tag는 필요 없음)


8) 멀티키 트랜잭션(동일 슬롯 전제) & TxCallback

  • SessionCallback으로 MULTI/EXEC. 키들은 같은 태그로!


9) 장애/리밸런싱 중 리다이렉션 & 재시도

  • Lettuce는 MOVED/ASK 자동 처리 + 토폴로지 리프레시.

  • 네트워크/타임아웃 재시도는 Spring Retry로 보완.


10) 관측성: 캐시 미스/Latency 메트릭

  • Micrometer로 간단히 지표 남기기.


11) 통합 테스트 (Testcontainers로 클러스터 연결 확인)

  • 단일 RedisContainer로도 연결 검증은 가능. (로컬에 이미 클러스터가 있다면 생략)


12) 성능 팁 & 체크리스트 (짧게)

  • 키 설계: 반드시 Hash Tag로 멀티키/Tx 가능한 그룹 정의

  • 직렬화 비용: 큰 객체라면 JSON 대신 바이너리(FST/Smile) 고려

  • TTL 전략: 핫키는 짧게, 백오프 재시도 시 TTL 재설정 주의

  • 파이프라인: 읽기/쓰기 벌크 처리 시 executePipelined 적극 활용

  • 핫스팟: 특정 태그에 과도한 트래픽이면 태그 스키마(샤딩 태그 추가)로 분산


끝으로

  • 핵심 1: 키를 태그로 “묶는 설계”가 클러스터에서 모든 걸 결정한다.

  • 핵심 2: Lua/멀티키/트랜잭션/락은 같은 슬롯이 전제.

  • 핵심 3: 장애/리밸런싱은 클라이언트가 따라가므로, 타임아웃/재시도/모니터링만 잘 세팅하면 실무 내구성 충분.

Last updated