Infra & Network

[Redis HA | Part 2] Redis Cluster — 샤딩 + HA

hrbds 2026. 4. 30. 21:50

목차

  • WHY — 왜 Redis Cluster가 필요한가
  • 핵심 개념 — 샤딩과 해시 슬롯
  • Redis Cluster 구성
  • 슬롯 라우팅 확인
  • Failover 실습
  • 과반수 장애 시 클러스터 중단
  • 노드 추가 (Scale-out)
  • 노드 제거 (Scale-in)
  • Sentinel vs Cluster
  • 전체 요약

1. WHY — 왜 Redis Cluster가 필요한가

Part 6에서 Sentinel로 HA를 구성했다. Sentinel은 Master 장애 시 자동 Failover를 보장하지만 한계가 있다.

Sentinel 한계
├── Master 1대에 모든 데이터가 쌓임
├── 메모리 한계 도달 시 수평 확장 불가
└── 아리 앱 카드 100만 장, 좋아요 수억 건 → 단일 Master로 감당 불가

Redis Cluster는 데이터를 여러 노드에 분산 저장(샤딩)하여 수평 확장을 가능하게 한다.


2. 핵심 개념 — 샤딩과 해시 슬롯

샤딩(Sharding)이란?

데이터를 여러 노드에 분산 저장하는 것이다.

단일 Master (Sentinel)       Redis Cluster
┌─────────────┐              ┌────┐ ┌────┐ ┌────┐
│  모든 데이터   │              │ P1 │ │ P2 │ │ P3 │
│  Master 1대  │              │33% │ │33% │ │34% │
└─────────────┘              └────┘ └────┘ └────┘

 

해시 슬롯(Hash Slot)

Redis Cluster는 16384개의 해시 슬롯으로 데이터를 분배한다.

Key → CRC16 해시 → 16384로 나눈 나머지 → 해당 슬롯의 노드로 라우팅

 

Primary 3개 기준 슬롯 배분

Primary 1 → 슬롯 0 ~ 5460
Primary 2 → 슬롯 5461 ~ 10922
Primary 3 → 슬롯 10923 ~ 16383

개발자는 그냥 set key value만 하면 된다. 어느 노드에 저장되는지는 Redis가 자동으로 처리한다.

수평 확장 가능 범위

Primary 3   → 슬롯 5461개씩
Primary 6   → 슬롯 2730개씩
Primary 12  → 슬롯 1365개씩
Primary 최대 1000개까지 확장 가능

 

해시 태그 (Hash Tag)

mset, pipeline 등 여러 키를 한 번에 처리할 때 키들이 서로 다른 노드에 있으면 Cross-slot 에러가 발생한다.

{} 안의 값이 같으면 무조건 같은 슬롯 → 같은 노드에 저장되도록 강제할 수 있다.

# Cross-slot 에러 발생 가능
mset ari:card:1 "legendary" ari:card:2 "epic"

# 해시 태그로 같은 슬롯 강제 배치
set {ari:card}:1 "legendary"
set {ari:card}:2 "epic"
# 둘 다 "ari:card" 기준으로 슬롯 계산 → 같은 노드에 저장

아리 앱 카드 10연차 뽑기처럼 여러 데이터를 한 번에 처리할 때 이 패턴이 필요하다.

실무 서버 구성

이상적인 구성

서버 1대 = 노드 1개(짝수대)가 이상적이다.

서버 1 → Primary 1
서버 2 → Primary 2
서버 3 → Primary 3
서버 4 → Replica 1 (Primary 1 백업)
서버 5 → Replica 2 (Primary 2 백업)
서버 6 → Replica 3 (Primary 3 백업)

어느 서버가 죽어도 해당 노드만 영향받고 나머지는 정상 운영된다.

 

비용 절감 구성

서버 1대에 노드 2개씩 배치하는 타협안이다.

단, Primary와 자신의 Replica는 반드시 다른 서버에 배치해야 한다.

서버 1 → Primary 1 + Replica 2 (Primary 2의 Replica)
서버 2 → Primary 2 + Replica 3 (Primary 3의 Replica)
서버 3 → Primary 3 + Replica 1 (Primary 1의 Replica)

 

서버1이 죽으면

Primary 1 죽음 → Replica 1(서버3)이 승격 ✅
Replica 2 죽음 → Primary 2의 백업 없음 ⚠️

이 상태에서 서버2까지 죽으면 Primary 2개 장애 → cluster_state:fail이 된다.

3대 구성은 이 리스크를 감수하는 비용 절감 방식이다.

비용이 허락하면 Primay, Replica를 별도 서버에 구성하도록 한다.

Stateful 시스템과 Auto Scaling

Redis Cluster에서 Auto Scaling이 어려운 이유

일반 앱 Auto Scaling (Stateless)
트래픽 증가 → 서버 추가 → 즉시 완료 (수초)

Redis Cluster Scale-out (Stateful)
트래픽 증가 → 노드 추가 → 슬롯 리밸런싱 (데이터 이동, 수분~수시간)
→ 이동 중 네트워크/CPU 부하 발생 → 완료 후 정상화

Redis, MySQL 같은 Stateful 시스템은 데이터가 특정 노드에 묶여있어 즉각적인 Auto Scaling이 위험하다.

실무 대응 방식:

  • Self-managed: 트래픽 예측 후 노드 미리 구성, 증설은 새벽 계획된 작업으로 진행
  • 관리형 서비스(ElastiCache): 슬롯 리밸런싱 자동 처리

3. Redis Cluster 구성

노드 6개 실행

for i in 1 2 3 4 5 6; do
  docker run -d \
    --name redis-cluster-$i \
    --network redis-cluster-net \
    redis:7.2 \
    redis-server \
      --cluster-enabled yes \
      --cluster-config-file nodes.conf \
      --cluster-node-timeout 5000 \
      --appendonly yes
done

 

IP 확인

docker ps -q --filter name=redis-cluster | xargs docker inspect \
  --format '{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

/redis-cluster-1 172.18.0.2
/redis-cluster-2 172.18.0.3
/redis-cluster-3 172.18.0.4
/redis-cluster-4 172.18.0.5
/redis-cluster-5 172.18.0.6
/redis-cluster-6 172.18.0.7

 

클러스터 구성

--cluster-replicas 1 — Primary 1개당 Replica 1개 자동 배정

docker exec -it redis-cluster-1 redis-cli --cluster create \
  172.18.0.2:6379 \
  172.18.0.3:6379 \
  172.18.0.4:6379 \
  172.18.0.5:6379 \
  172.18.0.6:6379 \
  172.18.0.7:6379 \
  --cluster-replicas 1

 

구성 결과

Master[0] → 슬롯 0 ~ 5460     (172.18.0.2) ← Replica: 172.18.0.6
Master[1] → 슬롯 5461 ~ 10922  (172.18.0.3) ← Replica: 172.18.0.7
Master[2] → 슬롯 10923 ~ 16383 (172.18.0.4) ← Replica: 172.18.0.5

[OK] All 16384 slots covered.

 

클러스터 상태 확인

docker exec -it redis-cluster-1 redis-cli cluster info

cluster_state:ok              → 클러스터 정상 동작
cluster_slots_assigned:16384  → 16384개 슬롯 전부 배정
cluster_slots_ok:16384        → 전부 정상
cluster_known_nodes:6         → 6개 노드 인식
cluster_size:3                → Primary 3개

4. 슬롯 라우팅 확인

Cluster 모드에서는 반드시 -c 옵션을 사용해야 한다. 없으면 다른 슬롯의 키 접근 시 에러가 발생한다.

docker exec -it redis-cluster-1 redis-cli -c set ari:card:1 "legendary"
docker exec -it redis-cluster-1 redis-cli -c set ari:card:2 "epic"
docker exec -it redis-cluster-1 redis-cli -c set ari:card:3 "rare"

 

슬롯 확인

docker exec -it redis-cluster-1 redis-cli cluster keyslot ari:card:1
# 12659 → Master[2] (10923~16383) 담당

docker exec -it redis-cluster-1 redis-cli cluster keyslot ari:card:2
# 272 → Master[0] (0~5460) 담당

docker exec -it redis-cluster-1 redis-cli cluster keyslot ari:card:3
# 4401 → Master[0] (0~5460) 담당

5. Failover 실습

Primary 노드 강제 종료

docker stop redis-cluster-3

Failover 확인 (10초 후)

docker exec -it redis-cluster-1 redis-cli cluster nodes

172.18.0.4 → master,fail  ← redis-cluster-3 장애 감지
172.18.0.5 → master       ← redis-cluster-4 자동 승격 (slave → master)
             slots 10923-16383 인수

데이터 유지 확인

docker exec -it redis-cluster-1 redis-cli -c get ari:card:1
# "legendary" ← 데이터 그대로 유지

6. 과반수 장애 시 클러스터 중단

Redis Cluster는 Primary 과반수가 죽으면 클러스터 전체가 중단된다.

docker stop redis-cluster-1 redis-cluster-2

docker exec -it redis-cluster-4 redis-cli cluster info | grep cluster_state
# cluster_state:fail

docker exec -it redis-cluster-4 redis-cli -c get ari:card:1
# (error) CLUSTERDOWN The cluster is down

복구

docker start redis-cluster-1 redis-cluster-2 redis-cluster-3

docker exec -it redis-cluster-1 redis-cli cluster info | grep cluster_state
# cluster_state:ok

docker exec -it redis-cluster-1 redis-cli -c get ari:card:1
# "legendary" ← 데이터 유지

7. 노드 추가 (Scale-out)

핵심 원칙

클러스터 서비스는 무중단, 노드 관리는 수동

무중단 ✅
├── 슬롯 이동 중에도 클러스터 정상 운영
├── 기존 데이터 접근 가능
└── 클라이언트 자동 리다이렉트

수동 관리 필요 ⚠️
├── reshard로 슬롯 직접 이동
├── add-node / del-node 직접 실행
└── 슬롯 0개 확인 후 노드 제거

슬롯 리밸런싱은 네트워크/CPU 부하가 발생하므로 트래픽이 낮은 새벽에 계획된 작업으로 진행하는 것이 원칙이다.

실습

# 1. 새 노드 실행
docker run -d \
  --name redis-cluster-7 \
  --network redis-cluster-net \
  redis:7.2 \
  redis-server \
    --cluster-enabled yes \
    --cluster-config-file nodes.conf \
    --cluster-node-timeout 5000 \
    --appendonly yes

# 2. IP 확인
docker inspect redis-cluster-7 \
  --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'
# 172.18.0.8

# 3. 클러스터에 새 노드 추가
# 두 번째 IP는 클러스터 접속 진입점 (클러스터 안의 아무 노드 IP나 가능)
docker exec -it redis-cluster-1 redis-cli --cluster add-node \
  172.18.0.8:6379 172.18.0.2:6379

 

추가 직후 상태 — 슬롯이 없는 master

172.18.0.8 → master, connected (슬롯 없음)

슬롯 리밸런싱

docker exec -it redis-cluster-1 redis-cli --cluster reshard 172.18.0.2:6379

 

입력값 설명

How many slots do you want to move?
→ 4096
  # 16384 ÷ 4 = 4096 (4개 노드 균등 분배 기준)
  # 기존 3개 노드에서 각 1365개씩 가져옴

What is the receiving node ID?
→ 새 노드(172.18.0.8)의 ID 입력
  # 슬롯을 받을 노드 지정

Source node #1:
→ all 입력
  # 기존 모든 노드에서 균등하게 가져옴
  # 특정 노드만 지정하려면 해당 노드 ID 입력 후 done

 

리밸런싱 후 슬롯 배분

Primary 1 → 1365~5460    (4096개)
Primary 2 → 6827~10922   (4096개)
Primary 3 → 12288~16383  (4096개)
Primary 4 → 0~1364 + 5461~6826 + 10923~12287 (4096개, 3개 구간으로 분리 — 정상)

슬롯이 연속적이지 않은 것은 기존 3개 노드에서 균등하게 가져왔기 때문이다.

데이터 유지 확인

docker exec -it redis-cluster-1 redis-cli -c get ari:card:1
# "legendary" ← 슬롯 이동 중에도 데이터 유지

Replica 추가

# 새 Replica 노드 실행
docker run -d \
  --name redis-cluster-8 \
  --network redis-cluster-net \
  redis:7.2 \
  redis-server \
    --cluster-enabled yes \
    --cluster-config-file nodes.conf \
    --cluster-node-timeout 5000

# Primary 4의 Replica로 추가
# 노드 ID 확인: redis-cli cluster nodes | grep 172.18.0.8
docker exec -it redis-cluster-1 redis-cli --cluster add-node \
  172.18.0.9:6379 172.18.0.2:6379 \
  --cluster-slave \
  --cluster-master-id <Primary4의노드ID>

8. 노드 제거 (Scale-in)

노드를 그냥 삭제하면 해당 노드의 슬롯이 사라져 데이터 유실이 발생한다.

반드시 Replica 먼저 제거 → 슬롯 이동 → Primary 제거 순서로 진행해야 한다.

실습

# 1. Replica 먼저 제거
docker exec -it redis-cluster-1 redis-cli --cluster del-node \
  172.18.0.2:6379 <Replica4의노드ID>

# 2. Primary 4의 슬롯을 다른 노드로 이동
docker exec -it redis-cluster-1 redis-cli --cluster reshard 172.18.0.2:6379

 

입력값 설명

How many slots do you want to move?
→ 4096  # 제거할 Primary가 가진 슬롯 수

What is the receiving node ID?
→ Primary 1의 ID
  # 슬롯을 받을 노드 지정 (균등 분배 원한다면 reshard를 3번 나눠서 진행)

Source node #1:
→ Primary 4의 ID 입력
  # 제거할 노드만 소스로 지정
Source node #2:
→ done
# 3. 슬롯 이동 후 자동으로 slave로 강등 확인
docker exec -it redis-cluster-1 redis-cli cluster nodes | grep 172.18.0.8
# 172.18.0.8 → slave (슬롯 0개, 자동 강등)

# 4. Primary 제거
docker exec -it redis-cluster-1 redis-cli --cluster del-node \
  172.18.0.2:6379 <Primary4의노드ID>

슬롯이 0개가 되면 Redis Cluster가 자동으로 해당 노드를 slave로 강등시킨다. 이후 del-node로 명시적으로 제거하면 완료된다.

데이터 유지 확인

docker exec -it redis-cluster-1 redis-cli -c get ari:card:1
docker exec -it redis-cluster-1 redis-cli -c get ari:card:2
docker exec -it redis-cluster-1 redis-cli -c get ari:card:3
# "legendary" / "epic" / "rare" ← 노드 추가/제거 전 과정에서 데이터 유실 없음

9. Sentinel vs Cluster 비교

구성 Failover 샤딩 수평확장 Auto Scaling 적합한 규모
Master-Replica 수동 개발/스테이징
Sentinel 자동 ✅ Replica만 가능 중소규모
Redis Cluster (Self-managed) 자동 ✅ ⚠️ 어려움 중대형
ElastiCache Cluster 자동 ✅ AWS 등 클라우드 환경

10. 전체 요약

Redis Cluster 핵심 원칙
├── 16384개 해시 슬롯을 Primary 수에 맞게 자동 분배
├── 키 → CRC16 → 슬롯 → 노드 라우팅 (자동, 개발자 개입 없음)
├── 해시 태그 {} → 여러 키를 같은 노드에 강제 배치 (mset, pipeline 사용 시)
├── 이상적 구성: 서버 6대 (노드 1개씩)
├── 비용 절감: 서버 3대 (노드 2개씩, 리스크 감수)
├── Primary 과반수 생존 시 클러스터 정상 유지
├── Primary 과반수 장애 시 cluster_state:fail (전체 중단)
├── 노드 추가/제거
│   ├── 클러스터 서비스는 무중단
│   ├── 슬롯 리밸런싱은 수동 (새벽 계획된 작업)
│   ├── 슬롯 0개 시 자동으로 slave 강등
│   └── del-node로 명시적 제거 필요
└── Stateful 특성상 Auto Scaling 대신 용량 계획(Capacity Planning)이 중요