Skip to main content
Summary

한 대의 Redis를 넘어서는 순간

앞 장에서는 Primary-Replica 복제와 Sentinel을 통해 Redis의 고가용성을 살펴봤다. 이 구조는 장애가 난 Primary를 대체할 수 있게 해 주지만, 데이터는 여전히 하나의 Primary가 담당한다. 즉, 쓰기 트래픽과 전체 데이터 크기는 한 노드의 CPU, 메모리, 네트워크 한계에 묶인다.

서비스가 커지면 다음과 같은 문제가 나타난다.

  • Redis 데이터가 한 서버 메모리에 담기 어려워진다.

  • 특정 인기 키나 랭킹 갱신 때문에 한 Primary의 CPU가 포화된다.

  • 네트워크 처리량이 한 노드의 한계를 넘는다.

  • Replica를 늘려 읽기는 분산했지만 쓰기 병목은 해결되지 않는다.

이때 필요한 것이 Redis Cluster다. Cluster는 데이터를 여러 Primary 노드에 나누어 저장한다. 이를 샤딩(sharding)이라고 한다. 샤딩의 핵심은 단순하다. 모든 키를 한 서버에 넣지 않고, 키마다 담당 노드를 정해 분산하는 것이다.

다만 Cluster는 서버를 여러 대 붙인다고 모든 문제가 자동으로 해결되는 기능이 아니다. 데이터가 나뉘기 때문에 멀티 키 명령, 트랜잭션, 장애 복구, 리샤딩 방식이 단일 Redis와 달라진다. Cluster를 도입할 때는 "더 많은 용량"과 "더 복잡한 운영"을 함께 받아들이는 셈이다.

Cluster와 수평 확장AI 생성 이미지

Hash Slot으로 데이터를 나누는 방식

Redis Cluster는 전체 키 공간을 16,384개의 해시 슬롯(hash slot)으로 나눈다. 각 Primary 노드는 이 슬롯 중 일부를 맡는다. 예를 들어 Primary가 3대라면 다음처럼 슬롯을 나눌 수 있다.

node-a: slot 0 ~ 5460
node-b: slot 5461 ~ 10922
node-c: slot 10923 ~ 16383

클라이언트가 user:1001:profile 키를 저장하면 Redis Cluster는 먼저 이 키가 몇 번 슬롯에 속하는지 계산한다. 기본 계산 방식은 다음과 같다.

HASH_SLOT = CRC16(key) mod 16384

즉, 키 이름을 해시 함수에 넣고 그 결과를 16,384로 나눈 나머지를 슬롯 번호로 사용한다. Redis Cluster의 Primary 노드는 각자 특정 슬롯 범위를 소유하며, 안정 상태에서는 하나의 슬롯을 하나의 노드가 담당한다.1

여기서 중요한 점은 Cluster가 "키 단위"로 노드를 고르는 것이 아니라 "슬롯 단위"로 노드를 고른다는 것이다. 많은 키가 같은 슬롯에 들어갈 수 있고, 슬롯은 특정 Primary에 배정된다.

key -> hash slot -> primary node

이 구조 덕분에 노드를 추가하거나 제거할 때 모든 키를 다시 해싱하지 않아도 된다. 특정 슬롯만 다른 노드로 옮기면 된다. 이것이 Redis Cluster가 확장과 재배치를 처리하는 기본 단위다.

MOVED: 클라이언트가 잘못된 노드에 요청했을 때

Cluster 환경에서는 클라이언트가 아무 노드에나 명령을 보낼 수 있다. 하지만 그 노드가 해당 키의 슬롯을 담당하지 않을 수 있다. 이때 Redis는 직접 다른 노드로 명령을 전달해 주지 않는다. 대신 클라이언트에게 "그 키는 저 노드에 있다"고 알려 준다.

예를 들어 클라이언트가 node-a에 다음 명령을 보냈다고 하자.

GET user:1001:profile

그런데 이 키의 슬롯을 node-b가 담당한다면 node-a는 대략 다음과 같은 응답을 돌려준다.

-MOVED 9189 10.0.0.2:6379

MOVED는 요청한 슬롯의 정상 소유자가 다른 노드라는 뜻이다. 클라이언트는 응답에 포함된 주소로 다시 요청해야 한다.

좋은 Redis Cluster 클라이언트 라이브러리는 이 과정을 자동으로 처리한다. 처음에는 한 노드에 요청했다가 MOVED를 받으면, 클러스터의 슬롯-노드 매핑 정보를 갱신하고 다음 요청부터는 올바른 노드로 바로 보낸다. 그래서 애플리케이션 코드에서는 보통 MOVED를 직접 다루지 않는다.

하지만 운영자는 이 에러를 이해해야 한다. redis-cli로 직접 명령을 보내거나, Cluster를 지원하지 않는 오래된 클라이언트를 사용하면 MOVED 응답이 그대로 보일 수 있다. Cluster에서는 반드시 Cluster-aware client, 즉 슬롯 매핑과 리다이렉션을 이해하는 클라이언트를 사용해야 한다.

ASK: 슬롯 이동 중에 발생하는 임시 리다이렉션

MOVED가 비교적 영구적인 위치 안내에 가깝다면, ASK는 슬롯 이동 중에 발생하는 임시 안내다.

노드를 추가해 슬롯을 옮기는 과정을 생각해 보자. node-a가 담당하던 슬롯 5000을 node-d로 옮기는 중이다. 슬롯 안에는 많은 키가 있고, Redis는 이 키들을 한 번에 모두 옮기는 것이 아니라 점진적으로 이동한다.

이때 어떤 키는 아직 node-a에 있고, 어떤 키는 이미 node-d로 옮겨졌을 수 있다. 클라이언트가 node-a에 요청했는데 해당 키가 이미 이동된 상태라면 Redis는 ASK 리다이렉션을 반환할 수 있다.

-ASK 5000 10.0.0.4:6379

클라이언트는 대상 노드에 ASKING 명령을 먼저 보낸 뒤 원래 명령을 다시 보낸다.

ASKING
GET some:key

ASK는 "앞으로도 계속 저 노드로 보내라"는 뜻이 아니다. "지금 이 요청만 저 노드에 물어보라"는 의미다. 슬롯 이동이 끝나고 클러스터 전체의 슬롯 소유 정보가 갱신되면 그때부터는 MOVED 또는 정상 슬롯 매핑에 따라 요청이 처리된다.

리샤딩 중에는 이 추가 왕복 때문에 일부 요청의 지연 시간이 늘어날 수 있다. 특히 큰 키가 많거나 쓰기 부하가 높은 상태에서 슬롯을 옮기면 지연 시간이 튈 수 있다. 그래서 리샤딩은 가능하면 트래픽이 낮은 시간대에 수행하고, 애플리케이션 타임아웃과 재시도 정책을 미리 확인해야 한다.

멀티 키 명령이 제한되는 이유

단일 Redis에서는 다음 명령이 자연스럽다.

MGET user:1001:profile user:1001:settings

하지만 Cluster에서는 두 키가 서로 다른 슬롯에 들어갈 수 있다. 슬롯이 다르면 담당 Primary도 다를 수 있다. Redis Cluster는 한 명령을 여러 노드에 자동으로 나누어 실행하고 결과를 조합해 주지 않는다.

그래서 Cluster에서 멀티 키 명령은 일반적으로 모든 키가 같은 슬롯에 있을 때만 안전하게 사용할 수 있다. MGET, MSET, SUNION, SINTER, 여러 키를 다루는 Lua 스크립트나 트랜잭션도 같은 제약을 받는다.

문제는 우리가 보기에는 관련 있어 보이는 키도 해시 결과는 다를 수 있다는 점이다.

user:1001:profile   -> slot 3201
user:1001:settings  -> slot 11290

이렇게 슬롯이 다르면 한 번의 MGET으로 묶기 어렵다. 이 제약을 해결하기 위해 Redis Cluster는 hash tag를 제공한다.

Hash Tag로 관련 키를 같은 슬롯에 배치하기

Hash tag는 키 이름 중 {} 안의 문자열만 해시 대상으로 사용하는 규칙이다. 예를 들어 다음 두 키는 전체 문자열은 다르지만 {1001} 부분이 같다.

user:{1001}:profile
user:{1001}:settings

또는 다음처럼 태그 안에 의미를 더 명확하게 담을 수도 있다.

{user:1001}:profile
{user:1001}:settings

Redis Cluster는 {와 } 사이에 비어 있지 않은 값이 있으면 그 부분만 해싱한다. 따라서 위처럼 같은 hash tag를 가진 키들은 같은 슬롯에 배치된다.1

이제 다음 명령을 Cluster에서도 사용할 수 있다.

MGET {user:1001}:profile {user:1001}:settings

Hash tag는 강력하지만 남용하면 안 된다. 모든 키를 {global} 같은 하나의 태그로 묶으면 결국 한 슬롯에 데이터와 트래픽이 몰린다. Cluster를 쓰면서도 실제로는 한 노드만 바쁜 구조가 된다.

좋은 hash tag는 "함께 읽고 써야 하는 작은 범위"를 묶는 데 사용한다.

  • 한 사용자의 프로필, 설정, 세션 일부

  • 한 주문과 관련된 임시 처리 상태

  • 한 채팅방 안에서 함께 처리해야 하는 메타데이터

반대로 전체 사용자, 전체 상품, 전체 랭킹을 하나의 태그로 묶는 것은 피해야 한다.

노드 추가와 리샤딩 흐름

Redis Cluster에서 수평 확장은 보통 다음 순서로 진행된다.

  1. 새 Redis 노드를 준비한다.

  2. 기존 Cluster에 새 노드를 추가한다.

  3. 새 노드에 슬롯을 할당한다.

  4. 기존 노드의 일부 슬롯을 새 노드로 이동한다.

  5. 클라이언트와 모니터링 지표를 확인한다.

새 Primary 노드를 추가했다고 해서 즉시 데이터가 분산되는 것은 아니다. 새 노드는 처음에는 슬롯을 갖고 있지 않을 수 있다. 슬롯이 없으면 실제 요청을 처리하지 않는다. 따라서 노드 추가 후에는 반드시 rebalance 또는 reshard 작업으로 슬롯을 나누어야 한다.

예를 들어 redis-cli의 Cluster 도구를 사용하면 다음과 같은 흐름으로 노드를 추가하고 슬롯을 재분배할 수 있다.2

redis-cli --cluster add-node 10.0.0.4:6379 10.0.0.1:6379
redis-cli --cluster rebalance 10.0.0.1:6379 --cluster-use-empty-masters

특정 노드에서 다른 노드로 일부 슬롯을 옮길 때는 reshard를 사용한다.

redis-cli --cluster reshard 10.0.0.1:6379

리샤딩 중에는 대상 노드가 해당 슬롯을 가져올 준비를 하고, 원본 노드는 슬롯을 내보내는 상태가 된다. 키는 내부적으로 점진적으로 이동한다. 이 과정에서 기존 키는 원본 노드가 처리하고, 이미 옮겨진 키는 대상 노드로 ASK 리다이렉션될 수 있다.3

운영 관점에서는 "온라인 리샤딩이 가능하다"와 "영향이 없다"를 구분해야 한다. Cluster는 전체 서비스를 내리지 않고 슬롯을 옮길 수 있지만, 이동 중인 슬롯의 요청은 지연 시간이 늘어날 수 있다. 큰 키를 옮길 때는 더 오래 막힐 수 있고, 쓰기 부하가 높은 시간에는 재시도와 리다이렉션이 겹쳐 체감 장애처럼 보일 수 있다.

장애 복구와 Cluster의 가용성

Redis Cluster도 Replica를 사용한다. 각 Primary에 Replica를 붙여 두면 Primary 장애 시 Replica가 승격되어 슬롯을 이어받을 수 있다. 이 점은 Sentinel 기반 장애 조치와 비슷해 보인다.

하지만 Cluster에서는 슬롯 커버리지(slot coverage)가 중요하다. 16,384개 슬롯 중 일부를 담당할 Primary가 사라지고 대체 Replica도 없다면, 그 슬롯의 키는 처리할 수 없다. 설정과 상태에 따라 클러스터 전체가 실패 상태로 전환될 수 있다.

따라서 Cluster 운영에서는 다음을 확인해야 한다.

  • 모든 Primary에 적절한 Replica가 있는가?

  • 같은 물리 서버나 같은 장애 도메인에 Primary와 Replica가 함께 있지 않은가?

  • 특정 노드에 슬롯이나 큰 키가 과도하게 몰려 있지 않은가?

  • 리샤딩 중 cluster_slots_ok 같은 상태 지표가 정상인가?

  • 애플리케이션 클라이언트가 MOVED, ASK, failover 중 연결 실패를 처리하는가?

특히 노드를 제거할 때는 먼저 그 노드가 가진 슬롯을 다른 노드로 모두 옮겨야 한다. 슬롯을 가진 노드를 무작정 제거하면 슬롯이 비어 서비스 장애로 이어질 수 있다.

Cluster 도입 전 점검할 질문

Redis Cluster는 데이터가 커질 때 유용한 확장 방식이다. 하지만 모든 Redis 사용 사례에 필요한 것은 아니다. 단순 캐시이고 데이터가 한 노드에 충분히 들어가며 장애 조치는 Sentinel로 해결된다면 Cluster는 과한 선택일 수 있다.

도입 전에는 다음 질문에 답해야 한다.

  • 현재 병목은 메모리인가, CPU인가, 네트워크인가, 아니면 DB인가?

  • 키가 여러 노드에 나뉘어도 애플리케이션 로직이 단순하게 유지되는가?

  • 멀티 키 명령을 많이 사용하고 있지는 않은가?

  • 함께 처리해야 하는 키에 hash tag 설계를 적용할 수 있는가?

  • 사용하는 Redis 클라이언트가 Cluster를 안정적으로 지원하는가?

  • 리샤딩, 장애 조치, 백업, 모니터링 절차가 준비되어 있는가?

여기에 한 가지를 더 기억해야 한다. Redis Cluster는 독립 실행형 Redis처럼 여러 데이터베이스를 선택해 쓰는 방식을 지원하지 않고, 기본적으로 DB 0만 사용한다. SELECT로 DB를 나누던 애플리케이션이라면 Cluster 전환 전에 키 네임스페이스 설계를 다시 정리해야 한다.1

Cluster의 장점은 명확하다. 데이터를 여러 Primary에 나누어 더 큰 메모리와 처리량을 사용할 수 있다. 단점도 명확하다. 키 배치와 명령 제약을 이해해야 하고, 리샤딩과 장애 복구 중 일시적인 지연과 실패를 감수해야 한다.

Redis Cluster를 안전하게 쓰는 핵심은 슬롯을 이해하는 것이다. 키는 슬롯에 속하고, 슬롯은 노드에 속한다. 확장은 슬롯을 옮기는 일이며, 멀티 키 제약은 슬롯이 갈라지기 때문에 생긴다. 이 원리를 이해하면 Cluster의 에러 메시지와 운영 절차가 훨씬 덜 낯설어진다.

참고

1Redis cluster specification

2Scale with Redis Cluster

책 목록

Redis 입문과 실전 운영

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기