Redis & 클러스터
Redis의 클러스터링, 샤딩, 복제에 대한 심층적인 이해를 다룹니다.
Redis 클러스터와 싱글스레드
클러스터링 되어있어도 싱글스레드가 맞는가?
Yes, 각 노드는 여전히 싱글스레드입니다.
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Node 1 │ │ Node 2 │ │ Node 3 │
│ (싱글스레드) │ │ (싱글스레드) │ │ (싱글스레드) │
│ Slot 0-5460 │ │ Slot 5461- │ │ Slot 10923- │
│ │ │ 10922 │ │ 16383 │
└─────────────┘ └─────────────┘ └─────────────┘
- 노드 단위: 각 Redis 노드는 싱글스레드로 명령어 처리
- 클러스터 단위: 여러 노드가 데이터를 분산 처리하여 전체 처리량 증가
- Redis 6.0+: I/O 멀티스레딩 도입 (명령어 실행은 여전히 싱글스레드)
복제 직접 구현
복제를 직접 구현한다면?
1. Command 로깅 방식
class ReplicationLog:
def __init__(self):
self.log = []
self.offset = 0
def append(self, command):
self.log.append({
'offset': self.offset,
'command': command,
'timestamp': time.time()
})
self.offset += 1
def get_commands_since(self, offset):
return [entry for entry in self.log if entry['offset'] > offset]2. 스냅샷 + 증분 복제
- 초기 동기화: RDB 스냅샷 전송
- 이후: AOF 명령어 스트리밍
3. 고려사항
- 네트워크 파티션 시 처리
- 복제 지연 모니터링
- 복제본 승격 로직
클러스터 연결 방식
클러스터에 어떻게 연결하고 싶은지?
Smart Client 방식
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("redis1", 6379));
nodes.add(new HostAndPort("redis2", 6379));
nodes.add(new HostAndPort("redis3", 6379));
JedisCluster jedisCluster = new JedisCluster(nodes);Proxy 방식
Client → Proxy (Twemproxy/Codis) → Redis Nodes
| 방식 | 장점 | 단점 |
|---|---|---|
| Smart Client | 낮은 지연시간, 추가 인프라 불필요 | 클라이언트 복잡도 증가 |
| Proxy | 클라이언트 단순화 | 추가 홉, SPOF 가능성 |
인스턴스 추가/삭제 시 작업
노드 추가 절차
# 1. 새 노드 클러스터에 추가
redis-cli --cluster add-node new_host:6379 existing_host:6379
# 2. 슬롯 리샤딩
redis-cli --cluster reshard existing_host:6379
# 3. 리밸런싱
redis-cli --cluster rebalance existing_host:6379노드 삭제 절차
# 1. 슬롯을 다른 노드로 이동
redis-cli --cluster reshard existing_host:6379 \
--cluster-from <node-id> \
--cluster-to <target-node-id> \
--cluster-slots <num>
# 2. 노드 삭제
redis-cli --cluster del-node existing_host:6379 <node-id>데이터 라우팅
클러스터링하면 데이터를 어떻게 찾아서 인스턴스를 선택하는가?
해시 슬롯 방식
def get_slot(key):
# CRC16 해시 후 16384로 모듈러
return crc16(key) % 16384
def get_node(key, cluster_slots):
slot = get_slot(key)
for node, (start, end) in cluster_slots.items():
if start <= slot <= end:
return node
raise Exception("Slot not found")해시 태그로 같은 슬롯 강제
user:{123}:profile → slot = hash("{123}")
user:{123}:session → slot = hash("{123}")
# 같은 슬롯에 저장되어 MULTI 명령 가능
MOVED vs ASK 리다이렉션
# MOVED: 슬롯이 완전히 이동됨 (캐시 갱신 필요)
-MOVED 3999 127.0.0.1:6381
# ASK: 마이그레이션 중 (일시적, 캐시 갱신 불필요)
-ASK 3999 127.0.0.1:6381
샤딩
샤딩 전략 비교
1. Range Sharding
User ID 1-1000000 → Shard 1
User ID 1000001-2000000 → Shard 2
- 장점: 범위 쿼리 효율적
- 단점: 핫스팟 발생 가능
2. Hash Sharding
Shard = hash(user_id) % num_shards
- 장점: 균등 분산
- 단점: 범위 쿼리 비효율적, 리샤딩 어려움
3. Consistent Hashing
┌───────────────────┐
│ Ring │
│ Node A ● │
│ ○ Key1 │
│ Node B ● │
│ ○ Key2 │
│ ● Node C │
└───────────────────┘
- 장점: 노드 추가/삭제 시 최소 데이터 이동
- 단점: 구현 복잡도
Redis Cluster vs Application Sharding
| 측면 | Redis Cluster | Application Sharding |
|---|---|---|
| 운영 | 자동 관리 | 수동 관리 필요 |
| 유연성 | 16384 슬롯 제한 | 자유로운 설계 |
| 트랜잭션 | 같은 슬롯 내에서만 | 제약 없음 (2PC 필요) |
| 복잡도 | 낮음 | 높음 |