Redis 고가용성: Sentinel과 Cluster
Sentinel은 마스터 장애를 자동 failover로 넘기고, Cluster는 16,384개 해시 슬롯으로 데이터를 샤딩해 가용성과 용량을 함께 확장합니다.
Contents
Sentinel은 마스터 장애를 감지해 레플리카로 자동 전환하고, Cluster는 데이터를 16,384개 슬롯으로 나눠 여러 마스터에 분산합니다.
가용성과 확장은 다른 축
단일 인스턴스(Standalone)는 Redis 하나로 읽기와 쓰기를 모두 처리합니다. 구조가 단순해 개발과 프로토타입에는 충분하지만, 그 노드가 죽으면 전체 데이터가 함께 사라집니다. 운영 환경에서는 이 단일 장애점을 없애는 일이 먼저입니다.
여기서 풀어야 할 문제는 둘로 갈립니다. 하나는 마스터가 죽었을 때 끊기지 않게 넘기는 가용성이고, 다른 하나는 한 노드 메모리를 넘는 데이터를 담는 수평 확장입니다. Sentinel은 앞의 문제를, Cluster는 두 문제를 함께 다룹니다.
가용성과 확장을 한 번에 해결한다고 Cluster를 기본값으로 두면 운영 복잡도만 커집니다. 두 모드는 해결하는 축이 다르므로, 데이터 크기와 멀티키 연산 빈도를 기준으로 골라야 합니다.
Sentinel의 모니터링과 자동 failover
Sentinel은 마스터-레플리카 복제 위에 모니터링과 자동 장애 조치(failover)를 얹은 고가용성 구성입니다. 별도 프로세스인 Sentinel이 마스터와 레플리카를 감시하다가, 마스터가 응답하지 않으면 레플리카 하나를 새 마스터로 승격합니다. 권장 최소 구성은 마스터 1, 레플리카 2, Sentinel 3으로 모두 6개 프로세스입니다.
세 개의 Sentinel을 두는 이유는 정족수(quorum) 판정 때문입니다. 한 Sentinel만 마스터를 못 봐도 일시적 네트워크 장애일 수 있어, 여러 Sentinel의 합의로 오판을 줄입니다. 마스터 다운 판정과 승격은 다음 순서로 진행됩니다.
1. 3개 Sentinel이 마스터를 모니터링
2. 2개 Sentinel이 "마스터 다운"으로 판정
3. quorum 2 충족 → failover 승인
4. 레플리카 1을 새 마스터로 승격
5. 레플리카 2는 새 마스터로부터 복제 재개
6. 클라이언트가 새 마스터로 자동 리다이렉션판정 민감도와 속도는 sentinel.conf 몇 줄로 정해집니다. 아래는 설정 예시입니다.
port 5000
sentinel monitor mymaster 10.0.0.10 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1| 설정 | 예시 값 | 의미 |
|---|---|---|
sentinel monitor mymaster <ip> <port> 2 | quorum 2 | 다운 판정에 동의해야 하는 Sentinel 최소 수 |
down-after-milliseconds | 5000 | 응답이 없을 때 다운으로 보는 시간(밀리초) |
failover-timeout | 60000 | failover 재시도까지 기다리는 시간 |
parallel-syncs | 1 | 새 마스터에 동시에 재동기화할 레플리카 수 |
이 값들이 장애 대응의 민감도를 정합니다. down-after를 짧게 잡으면 빨리 넘기지만, 일시적 지연도 장애로 오판할 수 있습니다. parallel-syncs를 키우면 복구는 빨라지지만 재동기화 부하가 한꺼번에 몰립니다.
클라이언트는 마스터 주소를 직접 적지 않고 Sentinel에 묻습니다. redis-py에서는 Sentinel 객체로 현재 마스터를 찾고, failover로 주소가 바뀌어도 라이브러리가 다시 연결합니다.
from redis.sentinel import Sentinel
sentinel = Sentinel([('sentinel1', 5000), ('sentinel2', 5000)])
master = sentinel.master_for('mymaster', socket_timeout=0.1)
master.set('key', 'value')Sentinel의 한계는 마스터가 여전히 하나라는 점입니다. 쓰기 처리량과 데이터 용량이 단일 노드에 묶여, 가용성은 얻지만 수평 확장은 못 합니다. 그래서 데이터가 한 노드 메모리에 들어가는 중소 규모(노트 기준 100GB 미만)에 맞습니다.
Cluster의 해시 슬롯 샤딩
Cluster는 데이터를 16,384개의 해시 슬롯으로 나눠 여러 마스터에 분산합니다. 키가 어느 슬롯에 들어갈지는 CRC16(key) % 16384로 정해지고, 슬롯 범위를 마스터들이 나눠 맡습니다. 권장 최소 구성은 마스터 3, 레플리카 3으로 6노드이며, 노트 기준 최대 약 1,000 노드까지 확장합니다.
각 마스터는 자기 슬롯 범위만 담당하므로 쓰기가 세 노드로 분산됩니다. 레플리카는 짝지은 마스터의 복제본이라, 그 마스터가 죽으면 자동으로 승격됩니다. Cluster는 이 failover를 내장하므로 별도 Sentinel이 필요 없습니다.
기본적으로 키 전체가 해싱되므로, 연관된 키들이 서로 다른 슬롯으로 흩어집니다. 같은 슬롯에 묶고 싶을 때는 중괄호로 해시 태그를 지정합니다.
# 중괄호 안 {user:1001}만 해싱 → 같은 슬롯 → 같은 노드
redis.mget("{user:1001}:profile", "{user:1001}:orders")서로 다른 슬롯에 걸친 멀티키 명령은 막힙니다. 다른 노드에 흩어진 키를 한 번에 다루면 원자성을 보장할 수 없기 때문입니다.
# 다른 슬롯이면 거부됨
redis.mget("user:1001", "user:1002")
# CROSSSLOT Keys in request don't hash to the same slot해시 태그로 묶을 때는 핫스팟에 주의해야 합니다. {pending}:1, {pending}:2처럼 태그가 같으면 모든 키가 한 노드에 몰립니다. {user_id}:pending:1처럼 카디널리티가 높은 태그를 쓰면 여러 노드로 분산됩니다.
클러스터는 설정에서 모드를 켜고, 생성 명령으로 슬롯을 자동 배분합니다. 아래는 노트의 redis.conf와 생성 예시입니다.
port 6379
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
cluster-require-full-coverage yesredis-cli --cluster create \
10.0.0.1:6379 10.0.0.1:6380 \
10.0.0.2:6379 10.0.0.2:6380 \
10.0.0.3:6379 10.0.0.3:6380 \
--cluster-replicas 1--cluster-replicas 1은 마스터마다 레플리카를 하나씩 붙이라는 뜻입니다. cluster-node-timeout은 노드를 죽었다고 보기까지의 시간으로, 노트 예시는 15,000밀리초입니다.
MOVED와 ASK 리디렉션
클라이언트가 키를 엉뚱한 노드에 보내면 Cluster는 에러 대신 리디렉션 응답을 돌려줍니다. 종류는 두 가지이고, 가리키는 상황이 다릅니다.
MOVED는 슬롯의 주인이 바뀐 영구 이동입니다. 클라이언트가 캐시한 슬롯-노드 매핑이 낡았을 때 나오며, 응답에 담긴 올바른 노드로 다시 요청하고 자기 매핑도 갱신합니다.
ASK는 리샤딩 중에만 나오는 일시 리디렉션입니다. 슬롯을 옮기는 도중 특정 키가 이미 목표 노드로 갔을 때, 그 키 하나만 목표 노드로 보내라는 신호입니다. 슬롯 매핑 자체는 갱신하지 않습니다.
리샤딩은 다운타임 없이 슬롯을 옮기며, 그 사이 요청을 ASK로 처리합니다.
Step 1: 소스 노드가 슬롯을 MIGRATING 상태로 표시
Step 2: 목표 노드가 IMPORTING 상태로 표시
Step 3: MIGRATE 명령으로 키를 하나씩 원자적으로 이동
Step 4: 이동 중 들어온 요청은 ASK 리디렉션으로 처리리샤딩이 끝나면 슬롯의 새 주인이 확정되고, 이후 요청은 MOVED로 정리됩니다. 클러스터 인식 클라이언트는 이 두 응답을 자동으로 처리하므로, 애플리케이션 코드는 슬롯 위치를 신경 쓰지 않습니다.
from redis.cluster import RedisCluster
rc = RedisCluster.from_url("redis://10.0.0.1:6379")
rc.set("key", "value") # 슬롯 → 노드 매핑은 라이브러리가 처리선택 기준과 일관성
두 모드의 트레이드오프를 표로 정리합니다. 아래는 배포 모드 선택 매트릭스입니다.
| 항목 | Standalone | Sentinel | Cluster |
|---|---|---|---|
| 고가용성 | 없음 | 자동 failover | 샤드별 failover |
| 수평 확장 | 불가 | 불가(수직만) | 최대 ~1,000 노드 |
| 데이터 용량 | 단일 노드 | 단일 마스터 | 마스터 합산 |
| 최소 노드 | 1 | 5+ | 6 |
| 멀티키 연산 | 전체 지원 | 전체 지원 | 같은 슬롯만 |
| 운영 복잡도 | 낮음 | 중간 | 높음 |
| 일관성 | 강함 | 비동기 | 비동기 |
표를 한 줄로 줄이면 기준은 데이터 크기와 멀티키 빈도입니다. 데이터가 100GB 미만이고 멀티키 연산이 많으면 Sentinel이 맞습니다. 데이터가 그보다 크고 쓰기 처리량을 늘려야 하면 Cluster를 고릅니다.
일관성은 두 모드 모두 약한 지점입니다. 복제가 비동기라, 마스터가 클라이언트에 응답한 뒤 레플리카로 전파되기 전 죽으면 그 쓰기는 사라질 수 있습니다. 유실 윈도우는 노트 기준 최대 몇 초입니다.
유실 가능성은 설정으로 줄입니다. WAIT 명령으로 일정 수의 레플리카가 받을 때까지 기다리거나, min-replicas 설정으로 레플리카가 부족하면 쓰기를 거부합니다. AOF(Append Only File)를 켜면 노드 단위 유실도 줄어듭니다.
# 레플리카 2개가 받을 때까지 최대 1초 대기
redis.execute_command("WAIT", 2, 1000)min-replicas-to-write 1
min-replicas-max-lag 10
appendonly yes
appendfsync everysec이 장치들은 지연과 가용성을 맞바꿉니다. WAIT나 min-replicas는 안전을 높이지만, 레플리카가 느리면 쓰기가 막힙니다. 캐시처럼 약간의 유실을 감수하는 데이터라면 굳이 켜지 않아도 됩니다.
정리
Sentinel과 Cluster는 같은 고가용성 도구로 묶이지만 푸는 문제가 다릅니다. Sentinel은 마스터-레플리카 복제에 모니터링과 자동 failover를 더해 가용성만 해결하고, 마스터는 하나로 남습니다. Cluster는 16,384개 슬롯으로 데이터를 샤딩해 가용성과 용량을 함께 늘리는 대신 멀티키 제약과 운영 복잡도를 안습니다.
고를 때는 데이터 크기와 멀티키 연산 빈도를 먼저 봅니다. 100GB 미만에 멀티키가 많으면 Sentinel, 그 이상에 쓰기 확장이 필요하면 Cluster가 무난합니다. 두 모드 모두 복제가 비동기라, 유실을 줄이려면 WAIT와 min-replicas, AOF를 데이터 성격에 맞게 켜야 합니다.