Redis는 인메모리(In-Memory) 기반의 Key-Value 저장소이다.
데이터를 메모리에 저장하기 때문에 디스크 기반 저장소보다 매우 빠른 접근 속도를 제공한다.
key - value 형태로 저장한다.Redis를 하나의 서버로만 운영하면, 그 서버가 장애로 다운될 경우 전체 서비스가 영향을 받는다.
즉, 단일 서버 구조는 다음과 같은 문제가 있다.
이러한 문제를 줄이기 위해 복제 구조(Replication) 를 사용할 수 있다.
일반적으로는 다음과 같이 구성한다.
즉 Master에서 데이터 변경이 발생하면, 그 변경 내용이 Replica로 비동기 복제된다.
이 구조의 장점은 다음과 같다.
하지만 이 구조만으로는 한계가 있다.
여전히 실제 쓰기 처리는 하나의 Master가 담당하기 때문이다.
Replica만 두었다고 해서 장애 대응이 자동으로 되는 것은 아니다.
장애를 감지하고, 새로운 Master를 선출하고, 클라이언트가 그 변경을 알 수 있어야 한다.
이 역할을 하는 것이 Redis Sentinel이다.
Sentinel의 주요 역할은 다음과 같다.
Sentinel은 장애를 두 단계로 판단한다.
quorum은 Master가 장애라고 판단하기 위해 동의해야 하는 Sentinel 수를 의미한다.
다만 주의할 점은, quorum은 장애 감지 기준이고
실제 failover를 수행하려면 Sentinel 다수결(majority) 도 필요하다는 점이다.
즉:
Sentinel을 사용하면 다음과 같은 장점이 있다.
단, Sentinel은 Redis Cluster를 사용하지 않는 환경에서의 고가용성 구성이다.
Sentinel과 Replica를 도입해도 다음 문제는 여전히 남는다.
즉, 고가용성은 보완할 수 있지만
수평 확장(Scale-Out) 문제는 해결되지 않는다.
이 한계를 넘기기 위해 등장한 것이 Sharding 이다.
Sharding은 데이터를 여러 인스턴스에 분산 저장하는 방식이다.
Redis Cluster에서는 이 Sharding을 Hash Slot 기반으로 구현한다.
Redis Cluster는 전체 키 공간을 16384개의 hash slot으로 나눈다.
각 키는 다음 공식으로 slot 번호가 결정된다.
HASH_SLOT = CRC16(key) mod 16384
즉 각 키는 해시 계산을 통해 특정 slot에 매핑되고,
그 slot을 담당하는 Master 노드에 저장된다.
예를 들어 Master가 3개라면,
16384개의 slot을 세 Master가 나눠서 담당하는 식이다.
이 구조의 장점은 다음과 같다.
Redis Cluster는 Sentinel 없이도 고가용성과 분산 처리를 지원한다.
그 이유는 Cluster 내부 노드들이 서로를 알고 있으며,
주기적으로 상태와 구성 정보를 주고받기 때문이다.
즉 Cluster는 내부적으로 다음을 자체 처리한다.
따라서 Cluster 환경에서는 Sentinel을 따로 두지 않는다.
클라이언트가 어떤 키에 대해 요청했는데,
현재 연결된 노드가 그 키의 slot 담당 노드가 아닐 수 있다.
이 경우 Redis Cluster는 다음과 같이 동작한다.
해당 노드가 MOVED 또는 ASK 응답을 반환
클라이언트가 실제 slot 담당 노드로 다시 요청
즉, Cluster에서는 키가 속한 slot을 담당하는 노드로 요청이 라우팅된다.
이런 상황은 다음과 같은 경우에 발생할 수 있다.
Redis Cluster에서는 멀티키 연산에 제약이 있다.
예를 들어 MSET, MGET 같은 명령은 여러 키를 한 번에 처리하는 명령인데,
이 키들이 서로 다른 slot에 있으면 처리할 수 없다.
이 경우 다음과 같은 에러가 발생한다.
CROSSSLOT Keys in request don't hash to the same slot
즉, 멀티키 연산은 같은 slot에 속한 키들끼리만 가능하다.
Hash Tag로 해결하기
이 문제를 해결하기 위해 Redis Cluster는 Hash Tag 기능을 제공한다.
키 이름에 {} 를 사용하면,
전체 키가 아니라 중괄호 안의 문자열만 기준으로 slot을 계산한다.
예를 들어:
user:{100}:name
user:{100}:email
이 두 키는 모두 {100}을 기준으로 hash slot이 계산되므로
같은 slot에 저장된다.
따라서 다음과 같이 관련된 여러 키를 같은 slot에 강제로 배치할 수 있다.
user:{100}:name
user:{100}:email
user:{100}:profile
이렇게 설계하면 멀티키 연산에서도 CROSSSLOT 에러를 피할 수 있다.