Redis 클러스터, 센티넬, 싱글 모드
개요
Redis는 운영 목적과 규모에 따라 세 가지 배포 모드를 제공한다.
| 모드 | 특징 | 적합 환경 |
|---|---|---|
| 싱글(Standalone) | 단일 노드, 간단한 구성 | 개발, 소규모 서비스 |
| 센티넬(Sentinel) | 자동 페일오버, 고가용성 | 중규모, 단일 마스터 |
| 클러스터(Cluster) | 수평 확장, 데이터 분산 | 대규모, 고처리량 |
싱글 모드 (Standalone)
구성
단일 Redis 프로세스로 동작한다. 선택적으로 레플리카(Replica)를 추가할 수 있지만, 페일오버는 수동으로 처리해야 한다.
기본 설정 (redis.conf)
# 바인딩 주소
bind 0.0.0.0
# 포트
port 6379
# 백그라운드 실행
daemonize yes
# 데이터 디렉토리
dir /var/lib/redis
# RDB 스냅샷 (초:변경횟수)
save 900 1
save 300 10
save 60 10000
# AOF 활성화
appendonly yes
appendfsync everysec
# 최대 메모리 설정
maxmemory 2gb
maxmemory-policy allkeys-lru
# 로그 파일
logfile /var/log/redis/redis.log
레플리카 설정
# 레플리카 서버에서 설정
replicaof 192.168.1.10 6379
# 레플리카 읽기 전용 (기본값)
replica-read-only yes
한계
| 한계 | 설명 |
|---|---|
| 단일 장애점 | 마스터가 죽으면 서비스 중단 |
| 수동 페일오버 | 레플리카 승격을 사람이 직접 수행 |
| 단일 노드 용량 | 서버 메모리 이상으로 데이터 확장 불가 |
| 쓰기 성능 | 단일 마스터가 모든 쓰기 처리 |
센티넬 모드 (Sentinel)
아키텍처
Sentinel은 Redis 마스터/레플리카를 모니터링하고, 마스터 장애 시 자동으로 레플리카를 마스터로 승격시키는 별도 프로세스다.
장애 발생:
최소 구성: Sentinel 3개 이상 (quorum 과반수 필요)
SDOWN vs ODOWN
| 상태 | 의미 | 조건 |
|---|---|---|
| SDOWN (Subjectively Down) | 하나의 Sentinel이 마스터와 통신 실패 감지 | down-after-milliseconds 초과 |
| ODOWN (Objectively Down) | 과반수 Sentinel이 SDOWN에 동의 | quorum 수 이상 동의 |
ODOWN이 되어야 페일오버가 시작된다. 이를 통해 네트워크 일시 단절로 인한 오탐을 방지한다.
Sentinel 설정
Redis 서버 설정 (redis.conf):
# 마스터 서버
port 6379
bind 0.0.0.0
# 레플리카 서버
port 6379
replicaof 192.168.1.10 6379
Sentinel 설정 (sentinel.conf):
port 26379
daemonize yes
logfile /var/log/redis/sentinel.log
# 모니터링할 마스터 이름, 주소, 포트, quorum
# quorum: 페일오버를 선언하는 데 필요한 최소 Sentinel 수
sentinel monitor mymaster 192.168.1.10 6379 2
# 마스터와 통신 불가로 판단할 시간 (밀리초)
sentinel down-after-milliseconds mymaster 5000
# 페일오버 타임아웃
sentinel failover-timeout mymaster 60000
# 페일오버 후 동시에 마스터를 바라보게 할 레플리카 수
sentinel parallel-syncs mymaster 1
# 인증 (마스터에 requirepass 설정된 경우)
sentinel auth-pass mymaster mypassword
Sentinel 클러스터 구성 예시
자동 페일오버 흐름
Sentinel API 활용
# 마스터 주소 조회
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
# 마스터 정보
redis-cli -p 26379 SENTINEL masters
# 레플리카 목록
redis-cli -p 26379 SENTINEL replicas mymaster
# Sentinel 목록
redis-cli -p 26379 SENTINEL sentinels mymaster
클러스터 모드 (Cluster)
해시 슬롯과 데이터 분산
Redis Cluster는 16384개의 해시 슬롯을 사용해 데이터를 분산한다.
키를 저장할 때 슬롯 번호를 계산하고, 해당 슬롯을 담당하는 노드에 저장한다. 클라이언트가 잘못된 노드에 요청하면 MOVED 리다이렉션 응답을 받는다.
# 클라이언트가 노드 A에 요청
GET mykey
# 실제 슬롯은 노드 B에 있으면
-MOVED 7638 192.168.1.11:6379
# 클라이언트는 해당 노드로 재요청
클러스터 구성
최소 구성: 마스터 3개 + 레플리카 3개 (각 마스터당 1개 레플리카)
redis.conf 설정:
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf # 클러스터 상태 자동 저장 파일
cluster-node-timeout 5000 # 노드 장애 판단 시간 (밀리초)
appendonly yes
클러스터 생성:
# 6개 노드로 클러스터 생성 (마스터 3 + 레플리카 3)
redis-cli --cluster create \
192.168.1.10:7000 \
192.168.1.11:7001 \
192.168.1.12:7002 \
192.168.1.10:7003 \
192.168.1.11:7004 \
192.168.1.12:7005 \
--cluster-replicas 1 # 마스터당 레플리카 1개
클러스터 상태 확인
# 클러스터 정보
redis-cli -p 7000 cluster info
# 노드 목록 및 슬롯 분배
redis-cli -p 7000 cluster nodes
# 슬롯별 노드 확인
redis-cli -p 7000 cluster slots
리샤딩 (Resharding)
데이터를 재분배하거나 노드를 추가/제거할 때 슬롯을 이동한다.
# 리샤딩 실행
redis-cli --cluster reshard 192.168.1.10:7000
# 이동할 슬롯 수 입력
How many slots do you want to move (from 1 to 16384)? 1000
# 슬롯을 받을 노드 ID 입력
What is the receiving node ID? <node-id>
# 슬롯을 줄 노드 지정 (all 또는 특정 노드 ID)
Please enter all the source node IDs.
Type 'all' to use all nodes as source nodes for the hash slots.
Source node #1: all
리샤딩 중에도 서비스 중단 없이 진행된다. 슬롯 이동 시 ASK 리다이렉션으로 클라이언트가 정상 처리한다.
노드 추가
# 새 마스터 노드 추가
redis-cli --cluster add-node \
192.168.1.13:7006 \ # 새 노드
192.168.1.10:7000 # 기존 클러스터 노드
# 새 레플리카 노드 추가
redis-cli --cluster add-node \
192.168.1.13:7007 \
192.168.1.10:7000 \
--cluster-slave \
--cluster-master-id <master-node-id>
# 이후 리샤딩으로 슬롯 분배
노드 제거
# 제거 전 슬롯을 다른 노드로 이동 (리샤딩)
redis-cli --cluster reshard 192.168.1.10:7000
# 노드 제거
redis-cli --cluster del-node \
192.168.1.10:7000 \
<node-id-to-remove>
클러스터에서 Multi-key 명령어 제약
Redis Cluster에서는 하나의 명령어에 포함된 모든 키가 같은 슬롯에 있어야 한다.
# 에러 발생 — key1과 key2가 다른 슬롯에 있을 수 있음
MSET key1 val1 key2 val2
# → CROSSSLOT Keys in request don't hash to the same slot
SUNION set1 set2
LMOVE list1 list2 LEFT RIGHT
해시 태그로 해결
키 이름에 {태그} 형식을 사용하면, 슬롯 계산 시 {} 안의 내용만 사용한다.
# {user:1} 부분으로만 슬롯 결정 → 같은 슬롯에 배치
MSET {user:1}:name "김철수" {user:1}:email "kim@example.com"
HSET {user:1}:profile name "김철수"
SET {user:1}:session "token123"
# 이제 같은 슬롯이므로 사용 가능
MGET {user:1}:name {user:1}:email
// Spring Data Redis에서 해시 태그 사용
String userId = "1";
String nameKey = "{user:" + userId + "}:name";
String emailKey = "{user:" + userId + "}:email";
String sessionKey = "{user:" + userId + "}:session";
// MSET 사용 가능 — 모두 같은 슬롯
redisTemplate.opsForValue().multiSet(Map.of(
nameKey, "김철수",
emailKey, "kim@example.com"
));
제약이 있는 명령어:
| 명령어 | 클러스터 제약 |
|---|---|
| MSET, MGET | 모든 키가 같은 슬롯 |
| SUNION, SINTER, SDIFF | 모든 키가 같은 슬롯 |
| ZUNIONSTORE, ZINTERSTORE | 모든 키가 같은 슬롯 |
| LMOVE, RPOPLPUSH | 모든 키가 같은 슬롯 |
| EVAL (Lua) | KEYS의 모든 키가 같은 슬롯 |
클러스터 복제와 페일오버
복제 구조
자동 페일오버
페일오버 후 복구
# 다운된 마스터를 재시작하면 자동으로 레플리카로 참여
# 수동으로 마스터로 복귀시키려면
redis-cli -p 7003 cluster failover
클러스터 전체 다운 조건
한 마스터와 그 모든 레플리카가 동시에 죽으면 해당 슬롯에 접근 불가 → 클러스터 전체가 에러 상태가 된다.
# 일부 슬롯이 죽어도 나머지로 계속 서비스하려면
cluster-require-full-coverage no # 기본값: yes
세 모드 비교 표
| 항목 | 싱글 | 센티넬 | 클러스터 |
|---|---|---|---|
| 고가용성 | 없음 | 있음 (자동 페일오버) | 있음 (자동 페일오버) |
| 수평 확장 | 불가 | 불가 (단일 마스터) | 가능 (다중 마스터) |
| 데이터 분산 | 단일 노드 | 단일 마스터 | 16384 슬롯 분산 |
| 최소 노드 수 | 1 | 3 (Sentinel) + 1 (Redis) | 6 (마스터 3 + 레플리카 3) |
| Multi-key 명령어 | 제한 없음 | 제한 없음 | 해시 태그 필요 |
| Lua 스크립트 | 제한 없음 | 제한 없음 | 단일 슬롯 제약 |
| 클라이언트 복잡도 | 낮음 | 중간 (Sentinel URL) | 높음 (클러스터 클라이언트) |
| 운영 복잡도 | 낮음 | 중간 | 높음 |
| 페일오버 시간 | 수동 | 수십 초 | 수십 초 |
| 읽기 스케일 | 레플리카로 가능 | 레플리카로 가능 | 각 마스터의 레플리카 |
| 적합 환경 | 개발, 테스트 | 단일 마스터 운영 | 대규모, 고처리량 |
Spring Boot 연결 설정
싱글 모드
spring:
data:
redis:
host: localhost
port: 6379
password: yourpassword
timeout: 2000ms
lettuce:
pool:
max-active: 10
max-idle: 10
min-idle: 1
max-wait: 1000ms
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
센티넬 모드
spring:
data:
redis:
sentinel:
master: mymaster # sentinel.conf의 이름과 일치
nodes:
- 192.168.1.10:26379
- 192.168.1.11:26379
- 192.168.1.12:26379
password: sentinelpassword # Sentinel 인증 (설정된 경우)
password: redispassword # Redis 서버 인증
lettuce:
pool:
max-active: 10
// 읽기 전략 설정 (레플리카에서 읽기)
@Bean
public LettuceClientConfigurationBuilderCustomizer lettuceCustomizer() {
return builder -> builder.readFrom(ReadFrom.REPLICA_PREFERRED);
}
읽기 전략 옵션:
| 전략 | 설명 |
|---|---|
MASTER |
항상 마스터에서 읽기 (기본값) |
MASTER_PREFERRED |
마스터 우선, 불가 시 레플리카 |
REPLICA |
항상 레플리카에서 읽기 |
REPLICA_PREFERRED |
레플리카 우선, 불가 시 마스터 |
NEAREST |
가장 낮은 지연 노드 |
클러스터 모드
spring:
data:
redis:
cluster:
nodes:
- 192.168.1.10:7000
- 192.168.1.10:7003
- 192.168.1.11:7001
- 192.168.1.11:7004
- 192.168.1.12:7002
- 192.168.1.12:7005
max-redirects: 3 # MOVED 리다이렉션 최대 횟수
password: yourpassword
lettuce:
cluster:
refresh:
adaptive: true # 토폴로지 변경 시 자동 갱신
period: 60s # 주기적 토폴로지 갱신
pool:
max-active: 10
@Configuration
public class RedisClusterConfig {
// 클러스터에서도 레플리카 읽기 설정
@Bean
public LettuceClientConfigurationBuilderCustomizer lettuceCustomizer() {
return builder -> builder
.readFrom(ReadFrom.REPLICA_PREFERRED)
.commandTimeout(Duration.ofSeconds(2));
}
// 클러스터 토폴로지 갱신 설정
@Bean
public ClusterTopologyRefreshOptions clusterTopologyRefreshOptions() {
return ClusterTopologyRefreshOptions.builder()
.enableAdaptiveRefreshTrigger(
RefreshTrigger.MOVED_REDIRECT,
RefreshTrigger.PERSISTENT_RECONNECTS
)
.adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
.build();
}
}
Redisson 연결 설정
# application.yml (Redisson Spring Boot Starter)
spring:
redis:
redisson:
config: |
# 싱글 모드
singleServerConfig:
address: "redis://localhost:6379"
# 센티넬 모드
sentinelServersConfig:
masterName: mymaster
sentinelAddresses:
- "redis://192.168.1.10:26379"
- "redis://192.168.1.11:26379"
- "redis://192.168.1.12:26379"
# 클러스터 모드
clusterServersConfig:
nodeAddresses:
- "redis://192.168.1.10:7000"
- "redis://192.168.1.11:7001"
- "redis://192.168.1.12:7002"
// 코드로 설정
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 싱글
config.useSingleServer().setAddress("redis://localhost:6379");
// 센티넬
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress(
"redis://192.168.1.10:26379",
"redis://192.168.1.11:26379",
"redis://192.168.1.12:26379"
);
// 클러스터
config.useClusterServers()
.addNodeAddress(
"redis://192.168.1.10:7000",
"redis://192.168.1.11:7001",
"redis://192.168.1.12:7002"
);
return Redisson.create(config);
}
모드 선택 가이드
메모리에 충분히 들어가는가?} Q1 -->|NO| CLUSTER[클러스터 모드
수평 확장 필요] Q1 -->|YES| Q2{마스터 장애 시
자동 복구가 필요한가?} Q2 -->|YES| SENTINEL1[센티넬 모드] Q2 -->|NO| Q3{개발/테스트
환경인가?} Q3 -->|YES| SINGLE[싱글 모드] Q3 -->|NO| SENTINEL2[센티넬 모드
운영 환경 권장] style CLUSTER fill:#88f,stroke:#00c,color:#000 style SENTINEL1 fill:#8f8,stroke:#080,color:#000 style SENTINEL2 fill:#8f8,stroke:#080,color:#000 style SINGLE fill:#ff8,stroke:#880,color:#000
실무 권장:
- 개발/테스트: 싱글 모드
- 운영 (수백 GB 이하): 센티넬 모드
- 운영 (TB급, 높은 처리량): 클러스터 모드
운영 주의사항
센티넬
- Sentinel을 최소 3개 홀수로 배포해야 quorum을 만족시킬 수 있다.
- Sentinel을 마스터/레플리카와 다른 물리 서버에 배포해야 네트워크 분리 상황에서 올바르게 동작한다.
- 페일오버 후 구 마스터가 돌아오면 자동으로 레플리카로 편입된다.
클러스터
- 노드 추가 후 반드시 리샤딩으로 슬롯을 균등 분배해야 한다.
cluster-require-full-coverage yes(기본값)이면 일부 슬롯이 죽을 때 클러스터 전체가 에러 상태가 된다. 부분 서비스를 허용하려면no로 변경한다.- 클러스터 환경에서
KEYS *는 현재 노드의 키만 반환한다. 전체 키를 순회하려면 모든 노드에SCAN을 실행해야 한다. - 클라이언트는 클러스터 인식 클라이언트(Lettuce, Jedis, Redisson)를 사용해야 한다. 일반 클라이언트는
MOVED리다이렉션을 처리하지 못한다.