- Published on
Redis 클러스터 고가용성 운영 가이드: Sentinel, Cluster 모드, 장애 복구 전략
- Authors
- Name
- 서론: 왜 Redis 고가용성이 지금 중요한가
- 공식 문서 및 1차 출처
- Sentinel vs Cluster 모드 비교
- Sentinel 구성과 운영
- Cluster 모드 구성과 운영
- 장애 시뮬레이션 및 복구 스크립트
- 운영 주의사항과 실패 사례
- 성능 튜닝 체크리스트
- 프로덕션 운영 체크리스트
- 모니터링 및 알림 구성
- 복제(Replication) 심화: PSYNC와 부분 동기화
- 영속화 전략: RDB와 AOF 비교
- 마무리
- 참고 자료
서론: 왜 Redis 고가용성이 지금 중요한가
Redis는 인메모리 데이터 스토어로서 캐시, 세션 관리, 실시간 리더보드, 메시지 큐 등 현대 애플리케이션의 핵심 인프라로 자리 잡았다. 2026년 현재, 마이크로서비스 아키텍처가 보편화되면서 Redis에 대한 의존도는 더욱 높아졌고, 단일 Redis 인스턴스의 장애가 전체 시스템 다운타임으로 직결되는 사례가 빈번하다.
특히 다음과 같은 상황에서 Redis 고가용성 구성은 필수다:
- 세션 스토어: Redis 장애 시 모든 사용자가 동시에 로그아웃
- 분산 락: Redis 다운 시 동시성 제어 실패로 데이터 정합성 훼손
- 실시간 캐시: 캐시 미스 폭증으로 데이터베이스 과부하(Cache Stampede)
- 이벤트 스트리밍: Redis Streams 기반 파이프라인 중단
이 글에서는 Redis의 두 가지 고가용성 전략인 Sentinel과 Cluster 모드를 깊이 비교하고, 실제 프로덕션 환경에서의 구성 방법, 장애 감지 및 자동 페일오버 메커니즘, 그리고 실전 트러블슈팅 사례까지 다룬다.
공식 문서 및 1차 출처
이 글에서 참고하는 Redis 공식 문서와 1차 출처는 다음과 같다:
- Redis Sentinel Documentation - https://redis.io/docs/management/sentinel/
- Redis Cluster Specification - https://redis.io/docs/reference/cluster-spec/
- Redis Cluster Tutorial - https://redis.io/docs/management/scaling/
- Redis Replication - https://redis.io/docs/management/replication/
- Redis Persistence (RDB/AOF) - https://redis.io/docs/management/persistence/
- redis-py Cluster Client - https://redis-py.readthedocs.io/en/stable/clustering.html
Sentinel vs Cluster 모드 비교
아키텍처 비교표
| 항목 | Sentinel | Cluster |
|---|---|---|
| 목적 | 고가용성(HA) | 고가용성 + 수평 확장 |
| 최소 노드 | Sentinel 3 + Master 1 + Replica 1 | Master 3 + Replica 3 (총 6) |
| 데이터 분산 | 불가 (단일 마스터) | 해시 슬롯 기반 자동 분산 (16384 슬롯) |
| 쓰기 확장 | 불가 | 멀티 마스터 지원 |
| 읽기 확장 | 레플리카 READONLY | 레플리카 READONLY |
| 장애 감지 | Sentinel 쿼럼 투표 | 클러스터 버스 Gossip 프로토콜 |
| 페일오버 | Sentinel 리더가 실행 | 레플리카 자체 승격 (과반수 투표) |
| 클라이언트 복잡도 | Sentinel-aware 필요 | MOVED/ASK 리다이렉션 처리 필요 |
| 멀티키 명령 | 무제한 | 같은 해시 슬롯 내에서만 가능 |
| 최대 데이터 크기 | 단일 노드 메모리 한계 | 노드 수에 비례하여 확장 |
| 운영 복잡도 | 중간 | 높음 |
| 적합 시나리오 | 단일 데이터셋 HA | 대규모 데이터 + 높은 처리량 |
언제 무엇을 선택할 것인가
# 의사결정 흐름도
#
# Q1: 전체 데이터가 단일 노드 메모리(예: 64GB)에 수용 가능한가?
# YES -> Q2로 진행
# NO -> Cluster 모드 필수
#
# Q2: 쓰기 처리량이 단일 마스터로 충분한가?
# YES -> Sentinel 모드 권장
# NO -> Cluster 모드 필요
#
# Q3: 멀티키 트랜잭션(MULTI/EXEC)이 빈번한가?
# YES -> Sentinel 우선 (Cluster에서는 해시 태그 설계 필요)
# NO -> 어느 모드든 가능
#
# Q4: 운영팀 규모와 인프라 경험은?
# 소규모 -> Sentinel (운영 단순)
# 전담팀 -> Cluster (복잡하지만 강력)
Sentinel 구성과 운영
Sentinel 설정 파일
Sentinel은 최소 3대의 독립적인 프로세스로 배포해야 한다. 각 Sentinel은 마스터를 모니터링하고, 장애 발생 시 쿼럼(quorum) 기반 합의를 통해 페일오버를 수행한다.
# sentinel.conf - 기본 구성
port 26379
daemonize yes
logfile "/var/log/redis/sentinel.log"
dir "/var/lib/redis/sentinel"
# 마스터 모니터링 설정
# sentinel monitor <master-name> <ip> <port> <quorum>
sentinel monitor mymaster 10.0.1.10 6379 2
# 마스터 응답 없음 판정 시간 (밀리초)
sentinel down-after-milliseconds mymaster 5000
# 페일오버 타임아웃 (밀리초)
# 이 시간 내에 페일오버가 완료되지 않으면 재시도
sentinel failover-timeout mymaster 60000
# 새 마스터로 동시에 동기화하는 레플리카 수
# 낮을수록 안전 (동기화 중 레플리카 읽기 불가)
sentinel parallel-syncs mymaster 1
# 인증이 필요한 경우
sentinel auth-pass mymaster StrongP@ssw0rd!
# Sentinel 자체 인증 (Redis 6.2+)
requirepass SentinelP@ss!
# 알림 스크립트 (장애 발생 시 실행)
sentinel notification-script mymaster /opt/redis/notify.sh
# 페일오버 완료 후 실행 스크립트
sentinel client-reconfig-script mymaster /opt/redis/reconfig.sh
마스터 Redis 설정
# redis.conf - Master 설정
bind 0.0.0.0
port 6379
requirepass StrongP@ssw0rd!
masterauth StrongP@ssw0rd!
# 복제 관련 설정
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes
repl-diskless-sync-delay 5
# 영속화 설정
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# 메모리 설정
maxmemory 4gb
maxmemory-policy allkeys-lru
# 네트워크 튜닝
tcp-backlog 511
tcp-keepalive 300
timeout 0
페일오버 동작 원리
Sentinel의 장애 감지와 페일오버는 다음 단계로 진행된다:
# === 단계 1: SDOWN (Subjective Down) ===
# 개별 Sentinel이 down-after-milliseconds 동안 마스터 응답 없음을 감지
# 이 시점에서는 해당 Sentinel만의 주관적 판단
# Sentinel 로그 확인
# +sdown master mymaster 10.0.1.10 6379
# === 단계 2: ODOWN (Objective Down) ===
# quorum 수 이상의 Sentinel이 SDOWN에 동의하면 ODOWN으로 전환
# 이것이 객관적 장애 판정
# +odown master mymaster 10.0.1.10 6379 #quorum 2/2
# === 단계 3: Sentinel Leader 선출 ===
# Raft 기반 알고리즘으로 페일오버를 수행할 리더 선출
# majority(과반수)의 투표 필요
# 3대 Sentinel -> 최소 2표 필요
# === 단계 4: 레플리카 선택 ===
# 리더 Sentinel이 최적의 레플리카를 선택
# 선택 기준 (우선순위 순):
# 1. replica-priority 값이 가장 낮은 노드 (0은 제외)
# 2. 복제 오프셋이 가장 큰 노드 (데이터가 가장 최신)
# 3. run ID가 사전순으로 가장 작은 노드
# === 단계 5: 페일오버 실행 ===
# 선택된 레플리카에 REPLICAOF NO ONE 명령 전송
# 나머지 레플리카를 새 마스터에 연결
# Sentinel 설정 파일 자동 갱신
# 페일오버 상태 모니터링
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster
redis-cli -p 26379 SENTINEL replicas mymaster
redis-cli -p 26379 SENTINEL sentinels mymaster
Python에서 Sentinel 연결
import redis
from redis.sentinel import Sentinel
# Sentinel 인스턴스 목록으로 연결
sentinel = Sentinel(
[
("10.0.1.20", 26379),
("10.0.1.21", 26379),
("10.0.1.22", 26379),
],
socket_timeout=0.5,
sentinel_kwargs={"password": "SentinelP@ss!"},
)
# 마스터 주소 확인
master_host, master_port = sentinel.discover_master("mymaster")
print(f"Current master: {master_host}:{master_port}")
# 마스터 연결 (쓰기용)
master = sentinel.master_for(
"mymaster",
socket_timeout=0.5,
password="StrongP@ssw0rd!",
db=0,
)
# 레플리카 연결 (읽기용)
replica = sentinel.slave_for(
"mymaster",
socket_timeout=0.5,
password="StrongP@ssw0rd!",
db=0,
)
# 쓰기는 마스터로
master.set("session:user:1001", "active")
# 읽기는 레플리카로 (읽기 부하 분산)
session = replica.get("session:user:1001")
print(f"Session status: {session}")
# 페일오버 발생 시 자동으로 새 마스터에 재연결
# redis-py의 SentinelConnectionPool이 자동 처리
try:
master.set("key", "value")
except redis.exceptions.ConnectionError:
# Sentinel이 새 마스터를 알려줌 -> 자동 재연결
print("Failover detected, reconnecting...")
master.set("key", "value") # 재시도 시 새 마스터에 연결
Cluster 모드 구성과 운영
Cluster 노드 설정
# redis-cluster.conf - 각 노드 공통 설정
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
# 클러스터 버스 포트 (기본: port + 10000)
# cluster-port 17000
# 인증
requirepass ClusterP@ss!
masterauth ClusterP@ss!
# 복제 설정
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync yes
# 영속화
appendonly yes
appendfsync everysec
save 900 1
save 300 10
# 메모리
maxmemory 8gb
maxmemory-policy allkeys-lru
# 네트워크
bind 0.0.0.0
tcp-backlog 511
tcp-keepalive 300
# 데이터 일부라도 사용 불가 시 클러스터 전체를 다운시킬지 여부
# yes: 일부 슬롯이라도 커버 안 되면 전체 거부
# no: 커버되는 슬롯의 키만 서비스 (권장)
cluster-require-full-coverage no
# 레플리카가 없는 마스터에 다른 마스터의 여유 레플리카를 자동 이전
cluster-allow-replica-migration yes
Cluster 생성과 관리 명령어
# 6개 노드로 클러스터 생성 (3 Master + 3 Replica)
redis-cli -a ClusterP@ss! --cluster create \
10.0.1.10:7000 10.0.1.11:7001 10.0.1.12:7002 \
10.0.1.10:7003 10.0.1.11:7004 10.0.1.12:7005 \
--cluster-replicas 1
# 클러스터 정보 확인
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER INFO
# cluster_state:ok
# cluster_slots_assigned:16384
# cluster_slots_ok:16384
# cluster_size:3
# cluster_known_nodes:6
# 노드 목록 확인
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER NODES
# <node-id> 10.0.1.10:7000@17000 myself,master - 0 0 1 connected 0-5460
# <node-id> 10.0.1.11:7001@17001 master - 0 1234567890 2 connected 5461-10922
# <node-id> 10.0.1.12:7002@17002 master - 0 1234567890 3 connected 10923-16383
# ...
# 슬롯 분배 확인
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER SLOTS
# 클러스터 상태 점검
redis-cli -a ClusterP@ss! --cluster check 10.0.1.10:7000
# === 노드 추가 ===
# 새 마스터 노드 추가
redis-cli -a ClusterP@ss! --cluster add-node \
10.0.1.13:7006 10.0.1.10:7000
# 새 레플리카 노드 추가 (특정 마스터의 레플리카로)
redis-cli -a ClusterP@ss! --cluster add-node \
10.0.1.13:7007 10.0.1.10:7000 \
--cluster-slave --cluster-master-id <master-node-id>
# === 리샤딩 ===
# 기존 마스터에서 새 마스터로 슬롯 이동
redis-cli -a ClusterP@ss! --cluster reshard 10.0.1.10:7000 \
--cluster-from <source-node-id> \
--cluster-to <target-node-id> \
--cluster-slots 4096 \
--cluster-yes
# 자동 리밸런싱 (슬롯 균등 분배)
redis-cli -a ClusterP@ss! --cluster rebalance 10.0.1.10:7000 \
--cluster-threshold 2 \
--cluster-use-empty-masters
# === 노드 제거 ===
# 먼저 슬롯을 다른 노드로 이동한 후 제거
redis-cli -a ClusterP@ss! --cluster del-node \
10.0.1.10:7000 <node-id-to-remove>
해시 슬롯과 해시 태그
Redis Cluster는 CRC16 해시 함수를 사용하여 키를 16384개의 슬롯 중 하나에 매핑한다. 멀티키 명령(MGET, MSET, 파이프라인)을 사용하려면 관련 키들이 같은 슬롯에 있어야 한다.
import redis
from redis.cluster import RedisCluster
# Cluster 클라이언트 연결
rc = RedisCluster(
startup_nodes=[
{"host": "10.0.1.10", "port": 7000},
{"host": "10.0.1.11", "port": 7001},
{"host": "10.0.1.12", "port": 7002},
],
password="ClusterP@ss!",
decode_responses=True,
# MOVED/ASK 리다이렉션 자동 처리
skip_full_coverage_check=True,
)
# 기본 사용
rc.set("user:1001:name", "Kim")
rc.set("user:1001:email", "kim@example.com")
# 해시 태그를 사용하여 같은 슬롯에 배치
# 중괄호 안의 문자열로 슬롯이 결정됨
rc.set("{user:1001}:name", "Kim")
rc.set("{user:1001}:email", "kim@example.com")
rc.set("{user:1001}:session", "abc123")
# 이제 MGET으로 한 번에 조회 가능 (같은 슬롯)
values = rc.mget(
"{user:1001}:name",
"{user:1001}:email",
"{user:1001}:session",
)
print(values) # ['Kim', 'kim@example.com', 'abc123']
# 파이프라인도 같은 슬롯 내에서 사용 가능
pipe = rc.pipeline()
pipe.hset("{order:5001}:info", "status", "pending")
pipe.hset("{order:5001}:info", "amount", "15000")
pipe.expire("{order:5001}:info", 3600)
pipe.execute()
# 슬롯 확인
slot = rc.cluster_keyslot("{user:1001}:name")
print(f"Slot: {slot}")
# 클러스터 정보 조회
info = rc.cluster_info()
print(f"Cluster state: {info['cluster_state']}")
print(f"Known nodes: {info['cluster_known_nodes']}")
Cluster 페일오버 메커니즘
Redis Cluster의 페일오버는 Sentinel 없이 클러스터 노드 자체적으로 수행된다.
# === Cluster 장애 감지 흐름 ===
# 1. Gossip 프로토콜
# 모든 노드는 클러스터 버스(port+10000)를 통해
# 매초 무작위 노드에 PING 전송, PONG 응답 확인
# 2. PFAIL (Probable Fail)
# cluster-node-timeout 동안 PONG 미수신 시
# 해당 노드를 PFAIL로 표시 (주관적 판단)
# 3. FAIL (확정 장애)
# 과반수의 마스터가 PFAIL에 동의하면 FAIL로 전환
# 클러스터 전체에 FAIL 메시지 브로드캐스트
# 4. 레플리카 승격
# 장애 마스터의 레플리카가 선거 시작
# 다른 마스터들이 투표 (과반수 필요)
# 당선된 레플리카가 새 마스터로 승격
# === 수동 페일오버 ===
# 레플리카 노드에서 실행 (계획된 유지보수 시)
redis-cli -a ClusterP@ss! -h 10.0.1.10 -p 7003 CLUSTER FAILOVER
# TAKEOVER: 다른 마스터 동의 없이 강제 승격 (비상 시에만)
redis-cli -a ClusterP@ss! -h 10.0.1.10 -p 7003 CLUSTER FAILOVER TAKEOVER
# 페일오버 후 클러스터 상태 확인
redis-cli -a ClusterP@ss! --cluster check 10.0.1.10:7000
장애 시뮬레이션 및 복구 스크립트
프로덕션 배포 전에 반드시 장애 시나리오를 시뮬레이션하여 복구 절차를 검증해야 한다.
#!/bin/bash
# failover-simulation.sh - Redis 장애 시뮬레이션 및 검증 스크립트
REDIS_CLI="redis-cli -a ClusterP@ss!"
MASTER_HOST="10.0.1.10"
MASTER_PORT=7000
echo "=== Step 1: 현재 클러스터 상태 확인 ==="
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT CLUSTER INFO | head -5
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT CLUSTER NODES
echo ""
echo "=== Step 2: 테스트 데이터 삽입 ==="
for i in $(seq 1 100); do
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT \
SET "test:failover:$i" "value_$i" EX 300 > /dev/null 2>&1
done
echo "100개의 테스트 키 삽입 완료"
echo ""
echo "=== Step 3: 마스터 프로세스 강제 종료 (장애 시뮬레이션) ==="
# 주의: 프로덕션에서는 절대 실행하지 말 것
$REDIS_CLI -c -h $MASTER_HOST -p $MASTER_PORT DEBUG SLEEP 30 &
# 또는 실제 프로세스 종료
# ssh $MASTER_HOST "redis-cli -p $MASTER_PORT -a ClusterP@ss! SHUTDOWN NOSAVE"
echo "마스터 노드 장애 시뮬레이션 시작"
echo "cluster-node-timeout(5초) 대기 중..."
sleep 10
echo ""
echo "=== Step 4: 페일오버 후 클러스터 상태 확인 ==="
# 다른 노드를 통해 클러스터 상태 확인
$REDIS_CLI -c -h 10.0.1.11 -p 7001 CLUSTER INFO | head -5
$REDIS_CLI -c -h 10.0.1.11 -p 7001 CLUSTER NODES
echo ""
echo "=== Step 5: 데이터 무결성 검증 ==="
FOUND=0
MISSING=0
for i in $(seq 1 100); do
RESULT=$($REDIS_CLI -c -h 10.0.1.11 -p 7001 \
GET "test:failover:$i" 2>/dev/null)
if [ -n "$RESULT" ]; then
FOUND=$((FOUND + 1))
else
MISSING=$((MISSING + 1))
fi
done
echo "검증 결과: 찾은 키=$FOUND, 유실된 키=$MISSING"
echo ""
echo "=== Step 6: 장애 노드 복구 ==="
# 장애 노드를 레플리카로 재합류시키기
echo "장애 노드를 재시작하면 자동으로 레플리카로 합류합니다."
echo "수동 확인: redis-cli --cluster check 10.0.1.11:7001"
운영 주의사항과 실패 사례
사례 1: Split Brain (스플릿 브레인)
스플릿 브레인은 네트워크 분할로 인해 둘 이상의 노드가 동시에 마스터 역할을 수행하는 상황이다. 이는 데이터 불일치와 유실을 초래한다.
발생 시나리오: 3대의 Sentinel 중 2대가 마스터와 네트워크가 끊어져 새 마스터를 선출했지만, 기존 마스터는 여전히 클라이언트 쓰기를 받고 있는 상황
방지 설정:
# redis.conf - Split Brain 방지
# 최소 N개의 레플리카가 연결되어 있어야 쓰기 허용
min-replicas-to-write 1
# 레플리카의 최대 지연 시간 (초)
# 이 시간 이상 ACK가 없으면 연결이 끊어진 것으로 간주
min-replicas-max-lag 10
# 위 설정의 의미:
# 마스터가 네트워크에서 분리되면,
# 연결된 레플리카가 0개이므로 쓰기를 거부
# -> 기존 마스터에 쓰인 데이터가 새 마스터와 충돌하는 것을 방지
실제 장애 사례: A사의 프로덕션 환경에서 네트워크 장비 교체 중 일시적 분할이 발생했다. min-replicas-to-write 미설정 상태였기 때문에 두 마스터 모두 쓰기를 수행했고, 페일오버 완료 후 기존 마스터의 데이터(약 30초 분량)가 유실되었다. 이후 위 설정을 적용하여 재발을 방지했다.
사례 2: 메모리 초과 (OOM)
# 문제 상황: maxmemory 미설정으로 Redis가 시스템 메모리를 모두 소진
# Linux OOM Killer가 Redis 프로세스를 강제 종료
# 확인 명령어
redis-cli INFO memory
# used_memory_human:12.45G
# used_memory_peak_human:14.23G
# maxmemory_human:0B <- 제한 없음! 위험!
# mem_fragmentation_ratio:1.35
# 방지 설정
# redis.conf
maxmemory 8gb
maxmemory-policy allkeys-lru
# 메모리 사용량 모니터링 스크립트
# crontab -e
# */5 * * * * /opt/redis/check_memory.sh
# check_memory.sh 예시
# USED=$(redis-cli INFO memory | grep used_memory_bytes | cut -d: -f2 | tr -d '\r')
# MAX=8589934592 # 8GB
# RATIO=$(echo "scale=2; $USED * 100 / $MAX" | bc)
# if (( $(echo "$RATIO > 80" | bc -l) )); then
# echo "WARNING: Redis memory usage at ${RATIO}%" | \
# mail -s "Redis Memory Alert" ops@company.com
# fi
사례 3: Cluster 리샤딩 중 타임아웃
리샤딩 중에 대량의 키 이동이 발생하면 클라이언트 요청이 ASK 리다이렉션을 통해 처리되는데, 이때 일시적인 지연이 발생할 수 있다.
# 리샤딩 중 클러스터 상태 확인
redis-cli -a ClusterP@ss! -c -h 10.0.1.10 -p 7000 CLUSTER NODES
# IMPORTING/MIGRATING 상태의 슬롯이 보이면 리샤딩 진행 중
# 리샤딩 속도 조절 (대규모 환경에서 권장)
redis-cli -a ClusterP@ss! --cluster reshard 10.0.1.10:7000 \
--cluster-from <source-id> \
--cluster-to <target-id> \
--cluster-slots 500 \
--cluster-timeout 10000 \
--cluster-pipeline 100 \
--cluster-yes
# 리샤딩이 비정상 중단된 경우 복구
redis-cli -a ClusterP@ss! --cluster fix 10.0.1.10:7000
사례 4: Sentinel 설정 파일 권한 문제
Sentinel은 페일오버 시 설정 파일을 자동으로 갱신한다. 설정 파일의 쓰기 권한이 없으면 페일오버 자체는 성공하지만, Sentinel 재시작 시 이전 마스터 정보를 참조하여 잘못된 노드에 연결하는 문제가 발생한다.
# sentinel.conf 권한 확인
ls -la /etc/redis/sentinel.conf
# -rw-r--r-- 1 redis redis 1234 Mar 14 10:00 sentinel.conf
# Redis 사용자에게 쓰기 권한 부여
chown redis:redis /etc/redis/sentinel.conf
chmod 640 /etc/redis/sentinel.conf
# Sentinel이 설정 파일을 수정하는 것을 확인
# 페일오버 후 sentinel.conf의 마스터 IP가 변경되었는지 체크
grep "sentinel monitor" /etc/redis/sentinel.conf
성능 튜닝 체크리스트
네트워크 및 OS 레벨
# /etc/sysctl.conf - 커널 파라미터 튜닝
# TCP 백로그 크기 증가
net.core.somaxconn = 65535
# Overcommit memory 허용 (Redis fork 시 필요)
vm.overcommit_memory = 1
# THP(Transparent Huge Pages) 비활성화
# Redis는 THP로 인해 메모리 사용량이 급증하고 latency가 증가할 수 있음
# echo never > /sys/kernel/mm/transparent_hugepage/enabled
# 파일 디스크립터 제한 증가
# /etc/security/limits.conf
# redis soft nofile 65536
# redis hard nofile 65536
Redis 레벨
# 1. 슬로우 로그 설정 (10ms 이상 걸리는 명령 기록)
slowlog-log-slower-than 10000
slowlog-max-len 128
# 슬로우 로그 확인
redis-cli SLOWLOG GET 10
redis-cli SLOWLOG LEN
redis-cli SLOWLOG RESET
# 2. latency 모니터링 활성화
latency-monitor-threshold 100
# latency 이벤트 확인
redis-cli LATENCY LATEST
redis-cli LATENCY HISTORY event-name
# 3. 클라이언트 출력 버퍼 제한
# 레플리카가 느리면 마스터 메모리 폭증 방지
client-output-buffer-limit replica 256mb 64mb 60
# 4. 커넥션 풀 설정 (클라이언트 측)
# maxclients 기본값은 10000
maxclients 10000
프로덕션 운영 체크리스트
배포 전 반드시 아래 항목을 점검해야 한다:
구성 단계
- Sentinel 또는 Cluster 모드 중 적절한 방식을 선택했는가
- 최소 노드 수를 충족하는가 (Sentinel 3+, Cluster 6+)
- 모든 노드에 인증(requirepass, masterauth)이 설정되어 있는가
maxmemory와 적절한 축출 정책이 설정되어 있는가- 영속화 전략(RDB/AOF)이 구성되어 있는가
min-replicas-to-write로 Split Brain을 방지하고 있는가
모니터링
- 각 노드의 메모리 사용량을 모니터링하고 있는가
- 복제 지연(replication lag)을 추적하고 있는가
- 슬로우 로그를 주기적으로 확인하고 있는가
- Sentinel/Cluster 이벤트에 대한 알림이 설정되어 있는가
- 클러스터 상태(cluster_state)를 헬스체크에 포함했는가
장애 대비
- 페일오버 시뮬레이션을 수행하고 복구 시간을 측정했는가
- 백업 및 복원 절차가 문서화되어 있는가
- 네트워크 분할 시나리오를 테스트했는가
- 모든 노드의 설정 파일 쓰기 권한이 올바른가
- 클라이언트 라이브러리가 페일오버 시 자동 재연결을 지원하는가
OS 레벨
vm.overcommit_memory = 1이 설정되어 있는가- Transparent Huge Pages가 비활성화되어 있는가
- 파일 디스크립터 제한이 충분한가
net.core.somaxconn이 Redistcp-backlog보다 큰가- Swap 사용을 최소화하도록
vm.swappiness를 조정했는가
모니터링 및 알림 구성
#!/usr/bin/env python3
"""Redis 클러스터 헬스체크 및 알림 스크립트"""
import redis
from redis.cluster import RedisCluster
import smtplib
from email.mime.text import MIMEText
import json
import time
def check_cluster_health(hosts, password):
"""클러스터 전체 상태를 점검한다."""
alerts = []
try:
rc = RedisCluster(
startup_nodes=[{"host": h, "port": p} for h, p in hosts],
password=password,
decode_responses=True,
socket_timeout=3,
)
# 1. 클러스터 상태 확인
info = rc.cluster_info()
if info.get("cluster_state") != "ok":
alerts.append(
f"CRITICAL: cluster_state = {info.get('cluster_state')}"
)
# 2. 슬롯 커버리지 확인
slots_ok = int(info.get("cluster_slots_ok", 0))
if slots_ok < 16384:
alerts.append(
f"CRITICAL: 슬롯 커버리지 부족 - {slots_ok}/16384"
)
# 3. 각 노드 메모리 사용량 확인
for node in rc.cluster_nodes():
node_info = rc.info(target_nodes=node)
used = node_info.get("used_memory", 0)
maxmem = node_info.get("maxmemory", 0)
if maxmem > 0:
usage_pct = (used / maxmem) * 100
if usage_pct > 85:
alerts.append(
f"WARNING: 노드 메모리 {usage_pct:.1f}% 사용 중"
)
# 4. 복제 지연 확인
for node in rc.cluster_nodes():
repl_info = rc.info(section="replication", target_nodes=node)
lag = repl_info.get("master_repl_offset", 0)
if lag > 1000000: # 1MB 이상 지연
alerts.append(
f"WARNING: 복제 지연 감지 - offset lag: {lag}"
)
except redis.exceptions.ConnectionError as e:
alerts.append(f"CRITICAL: 클러스터 연결 실패 - {str(e)}")
except Exception as e:
alerts.append(f"ERROR: 헬스체크 실패 - {str(e)}")
return alerts
def send_alert(alerts, smtp_config):
"""알림 이메일 전송"""
if not alerts:
return
body = "Redis 클러스터 알림:\n\n"
body += "\n".join(f" - {a}" for a in alerts)
body += f"\n\n점검 시각: {time.strftime('%Y-%m-%d %H:%M:%S')}"
msg = MIMEText(body)
msg["Subject"] = f"[Redis Alert] {len(alerts)}건의 이슈 감지"
msg["From"] = smtp_config["from"]
msg["To"] = smtp_config["to"]
with smtplib.SMTP(smtp_config["host"], smtp_config["port"]) as server:
server.send_message(msg)
if __name__ == "__main__":
CLUSTER_HOSTS = [
("10.0.1.10", 7000),
("10.0.1.11", 7001),
("10.0.1.12", 7002),
]
alerts = check_cluster_health(CLUSTER_HOSTS, "ClusterP@ss!")
if alerts:
print("Issues detected:")
for a in alerts:
print(f" {a}")
# send_alert(alerts, SMTP_CONFIG)
else:
print("Cluster health: OK")
복제(Replication) 심화: PSYNC와 부분 동기화
Redis의 복제는 풀 동기화(Full Sync)와 부분 동기화(Partial Sync, PSYNC)로 나뉜다. 레플리카가 잠시 연결이 끊겼다가 재연결될 때, PSYNC를 통해 변경분만 전송하면 전체 데이터를 다시 보내는 비용을 줄일 수 있다.
# 복제 백로그 설정 (마스터)
# 부분 동기화를 위한 메모리 버퍼
repl-backlog-size 128mb
# 백로그 유지 시간 (모든 레플리카 연결 해제 후)
repl-backlog-ttl 3600
# 디스크리스 복제 (네트워크가 디스크보다 빠를 때 유리)
repl-diskless-sync yes
repl-diskless-sync-delay 5
repl-diskless-sync-period 0
# 복제 상태 확인
redis-cli INFO replication
# role:master
# connected_slaves:2
# slave0:ip=10.0.1.11,port=6379,state=online,offset=1234567,lag=0
# slave1:ip=10.0.1.12,port=6379,state=online,offset=1234567,lag=1
# master_replid:abc123...
# master_repl_offset:1234567
# repl_backlog_active:1
# repl_backlog_size:134217728
PSYNC가 실패하여 풀 동기화가 발생하면 마스터에서 RDB 스냅샷 생성이 시작되고, 이 과정에서 CPU와 메모리를 크게 소비한다. repl-backlog-size를 충분히 크게 설정하는 것이 핵심이다. 초당 쓰기량(MB/s)과 예상 연결 해제 시간(초)을 곱한 값 이상으로 설정하는 것을 권장한다.
영속화 전략: RDB와 AOF 비교
| 항목 | RDB (스냅샷) | AOF (Append Only File) |
|---|---|---|
| 방식 | 주기적 전체 스냅샷 | 모든 쓰기 명령 기록 |
| 데이터 안전성 | 마지막 스냅샷 이후 유실 가능 | fsync 정책에 따라 최소 유실 |
| 파일 크기 | 작음 (압축) | 큼 (명령어 로그) |
| 복구 속도 | 빠름 | 느림 (명령 재실행) |
| 성능 영향 | fork 시 일시적 지연 | fsync마다 I/O 발생 |
| 권장 조합 | AOF와 함께 사용 | RDB와 함께 사용 |
# 권장: RDB + AOF 동시 사용
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec
# AOF rewrite 트리거 조건
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 혼합 모드 (Redis 4.0+, 권장)
# AOF rewrite 시 RDB 포맷 + 이후 AOF 로그 혼합
aof-use-rdb-preamble yes
마무리
Redis의 고가용성 구성은 단순히 Sentinel이나 Cluster를 켜는 것 이상의 깊은 이해를 요구한다. 쿼럼과 과반수의 차이, 해시 슬롯의 분배 원리, Split Brain 방지 전략, 복제 백로그 크기 산정, 영속화 전략 선택까지 모든 요소가 유기적으로 연결되어 있다.
프로덕션 배포 전에 반드시 장애 시뮬레이션을 수행하고, 모니터링 체계를 구축하며, 위에서 제시한 체크리스트를 모두 점검해야 한다. 특히 min-replicas-to-write와 maxmemory 설정은 가장 빈번한 장애 원인을 사전에 차단하는 핵심 설정이므로 반드시 적용하기 바란다.
참고 자료
- Redis Sentinel Documentation - Sentinel 아키텍처와 구성 가이드
- Redis Cluster Specification - Cluster 프로토콜 명세
- Redis Cluster Tutorial - Cluster 구성 및 운영 튜토리얼
- Redis Replication - 복제 메커니즘과 PSYNC
- Redis Persistence - RDB와 AOF 영속화 전략
- redis-py Documentation - Python Redis 클라이언트 공식 문서
- Redis Administration - 운영 관리 가이드
- Redis Latency Problems Troubleshooting - 지연 문제 진단 가이드