Cluster — 해시 슬롯 기반 수평 확장
데이터가 한 대의 Redis 메모리에 다 안 들어갈 만큼 크다면 어떻게 해야 할까요?
Redis Cluster란
Redis Cluster는 데이터를 여러 노드에 자동으로 분산 저장하는 수평 확장(horizontal scaling) 솔루션입니다. 16384개의 해시 슬롯(hash slot)을 노드에 분배하고, 각 노드는 할당받은 슬롯의 데이터만 저장합니다. 자동 페일오버도 내장되어 있어 Sentinel 없이도 고가용성을 제공합니다.
왜 필요한가
- 단일 Redis의 메모리 한계 (서버당 수십~수백 GB)를 넘어서는 데이터
- 단일 노드의 처리량(throughput) 한계를 넘어서는 트래픽
- Sentinel은 고가용성만 제공하고, 데이터 분산은 하지 않습니다
해시 슬롯 (Hash Slot)
16384개의 슬롯
Redis Cluster는 키 공간을 0~16383번까지 16384개의 슬롯으로 나눕니다.
슬롯 계산: CRC16(key) mod 16384
예:
CRC16("user:1001") mod 16384 = 5649 → 슬롯 5649
CRC16("user:1002") mod 16384 = 11298 → 슬롯 11298
슬롯 분배
3노드 클러스터:
Node A: 슬롯 0~5460 (5461개)
Node B: 슬롯 5461~10922 (5462개)
Node C: 슬롯 10923~16383 (5461개)
슬롯 정보 확인
# 클러스터 슬롯 정보
127.0.0.1:6379> CLUSTER SLOTS
1) 1) (integer) 0 # 시작 슬롯
2) (integer) 5460 # 끝 슬롯
3) 1) "192.168.1.1" # 마스터
2) (integer) 6379
4) 1) "192.168.1.4" # 레플리카
2) (integer) 6380
# 특정 키의 슬롯 확인
127.0.0.1:6379> CLUSTER KEYSLOT user:1001
(integer) 5649
Hash Tag — 같은 슬롯에 배치하기
멀티키 명령(MGET, MSET 등)은 모든 키가 같은 슬롯에 있어야 합니다. Hash Tag를 사용하면 관련 키들을 같은 슬롯에 모을 수 있습니다.
# 중괄호 {} 안의 문자열만 해시에 사용됨
{user:1001}:profile → CRC16("user:1001") → 같은 슬롯
{user:1001}:settings → CRC16("user:1001") → 같은 슬롯
{user:1001}:cart → CRC16("user:1001") → 같은 슬롯
# 이제 멀티키 명령 가능
MGET {user:1001}:profile {user:1001}:settings
# Hash Tag 없이는 에러
MGET user:1001:profile user:1001:settings
# (error) CROSSSLOT Keys in request don't hash to the same slot
Hash Tag 주의사항
같은 Hash Tag를 가진 키가 너무 많으면 핫 슬롯이 됩니다. 특정 노드에 부하가 집중될 수 있으므로 적절히 분산하세요.
MOVED와 ASK 리다이렉션
MOVED 리다이렉션
클라이언트가 잘못된 노드에 요청하면 올바른 노드를 알려줍니다.
# Node A에 요청했지만 슬롯이 Node B에 있을 때
127.0.0.1:6379> GET user:5000
(error) MOVED 11298 192.168.1.2:6379
# 클라이언트는 192.168.1.2:6379로 재요청
# 그리고 슬롯 맵을 업데이트하여 다음번엔 바로 올바른 노드에 요청
대부분의 클라이언트 라이브러리(Lettuce, Jedis, Redisson)는 MOVED를 자동으로 처리합니다.
ASK 리다이렉션
슬롯 마이그레이션 중에 발생합니다.
# 슬롯 1234가 Node A에서 Node B로 이동 중
# 키가 아직 Node A에 있으면 정상 응답
# 키가 이미 Node B로 이동했으면:
127.0.0.1:6379> GET moved-key
(error) ASK 1234 192.168.1.2:6379
# 클라이언트: Node B에 ASKING + GET 실행
# ASKING은 "마이그레이션 중인 거 알고 있다"는 의미
MOVED와의 차이: ASK는 이번 한 번만 다른 노드에 가라는 의미이고, MOVED는 슬롯 맵을 영구적으로 업데이트하라는 의미입니다.
클러스터 생성
redis-cli로 생성
# 6개 노드 시작 (3 마스터 + 3 레플리카)
# 각 노드의 redis.conf:
port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
# 클러스터 생성
redis-cli --cluster create \
192.168.1.1:6379 192.168.1.2:6379 192.168.1.3:6379 \
192.168.1.4:6379 192.168.1.5:6379 192.168.1.6:6379 \
--cluster-replicas 1
# --cluster-replicas 1: 마스터당 레플리카 1개
클러스터 상태 확인
# 클러스터 정보
127.0.0.1:6379> CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_known_nodes:6
cluster_size:3
# 노드 목록
127.0.0.1:6379> CLUSTER NODES
abc123 192.168.1.1:6379 master - 0 ... 0-5460
def456 192.168.1.2:6379 master - 0 ... 5461-10922
ghi789 192.168.1.3:6379 master - 0 ... 10923-16383
...
리샤딩 (Resharding)
노드를 추가하거나 슬롯을 재분배할 때 사용합니다.
노드 추가
# 새 노드를 클러스터에 추가
redis-cli --cluster add-node 192.168.1.7:6379 192.168.1.1:6379
# 레플리카로 추가
redis-cli --cluster add-node 192.168.1.8:6379 192.168.1.1:6379 \
--cluster-slave --cluster-master-id <master-node-id>
슬롯 재분배
# 대화형 리샤딩
redis-cli --cluster reshard 192.168.1.1:6379
# 자동 리밸런싱
redis-cli --cluster rebalance 192.168.1.1:6379
리샤딩 과정
1. 소스 노드에서 CLUSTER SETSLOT <slot> MIGRATING <target-id>
2. 타겟 노드에서 CLUSTER SETSLOT <slot> IMPORTING <source-id>
3. 소스에서 타겟으로 키를 하나씩 MIGRATE
- 이 과정에서 ASK 리다이렉션 발생
4. 모든 키 이동 완료 후 CLUSTER SETSLOT <slot> NODE <target-id>
리샤딩은 온라인으로 진행되므로 서비스 중단 없이 가능합니다.
클러스터 버스 (Cluster Bus)
노드 간 통신을 위한 내부 프로토콜입니다. 데이터 포트 + 10000번 포트를 사용합니다.
데이터 포트: 6379 (클라이언트 통신)
클러스터 버스 포트: 16379 (노드 간 통신)
클러스터 버스의 역할
- Gossip 프로토콜: 노드 상태 정보를 서로 교환
- 장애 감지: 다른 노드의 상태를 주기적으로 확인
- 페일오버 투표: 마스터 장애 시 레플리카 승격 투표
- 설정 전파: 슬롯 배치 변경 등을 모든 노드에 알림
장애 감지와 페일오버
1. Node A가 Node B에 PING → 응답 없음 (cluster-node-timeout 초과)
2. Node A가 Node B를 PFAIL(Probable Fail)로 표시
3. 다른 노드들도 B를 PFAIL로 표시 (과반수)
4. Node B가 FAIL 상태로 확정
5. Node B의 레플리카 중 하나가 마스터로 승격
- 다른 마스터들의 투표로 결정
클러스터에서의 제약사항
멀티키 명령 제약
# 같은 슬롯이면 가능
MGET {user}:1 {user}:2
# 다른 슬롯이면 에러
MGET user:1 product:2
# (error) CROSSSLOT
# 해결: Hash Tag 사용 또는 클라이언트에서 분리 실행
Lua 스크립트 제약
-- 접근하는 모든 키가 같은 슬롯에 있어야 함
-- KEYS에 명시된 키만 접근 가능
EVAL "..." 2 {user}:profile {user}:settings ... -- OK
EVAL "..." 2 user:1 product:2 ... -- CROSSSLOT 에러
데이터베이스 제약
# 클러스터 모드에서는 DB 0만 사용 가능
SELECT 1 # 에러
Spring Boot에서 Cluster 연결
# application.yml
spring:
data:
redis:
cluster:
nodes:
- 192.168.1.1:6379
- 192.168.1.2:6379
- 192.168.1.3:6379
max-redirects: 3
@Configuration
public class RedisClusterConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(
List.of("192.168.1.1:6379", "192.168.1.2:6379", "192.168.1.3:6379")
);
clusterConfig.setMaxRedirects(3);
// 레플리카에서 읽기 활성화
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(clusterConfig, clientConfig);
}
}
정리
- Redis Cluster는 16384 해시 슬롯을 기반으로 데이터를 노드에 분산합니다
CRC16(key) mod 16384로 슬롯을 결정하고, Hash Tag로 관련 키를 같은 슬롯에 모을 수 있습니다- MOVED/ASK 리다이렉션으로 클라이언트가 올바른 노드에 접근합니다
- 리샤딩은 온라인으로 진행되며, 서비스 중단 없이 노드를 추가/제거할 수 있습니다
- 멀티키 명령과 Lua 스크립트는 같은 슬롯의 키에서만 동작하는 제약이 있습니다
댓글 로딩 중...