왜 리샤딩이 문제가 되는가
Redis 클러스터는 16384개의 해시 슬롯을 노드에 나눠 갖는다. 트래픽이 특정 노드로 쏠리거나 새 노드를 추가할 때, 슬롯 소유권을 옮겨야 한다. 문제는 이동 중인 슬롯에 속한 키다. 소스와 타깃 노드가 동시에 같은 슬롯의 키를 바라보는 짧은 구간이 존재하고, 이때 잘못 설계하면 MOVED 폭주, 일부 키 조회 실패, 클라이언트 커넥션 스톰이 발생한다.
키 개수가 수백만 개인 슬롯 하나를 옮기는 데 수십 초가 걸릴 수 있으며, 그동안 애플리케이션은 정상 동작해야 한다. 즉 리샤딩의 핵심은 "옮기는 속도"가 아니라 "옮기는 동안의 일관성"이다.
슬롯 이동의 내부 동작
슬롯 하나를 A(소스)에서 B(타깃)로 옮기는 절차는 세 단계다. 먼저 타깃을 IMPORTING, 소스를 MIGRATING 상태로 표시한다. 이 상태에서 클라이언트가 소스에 키를 요청하면, 그 키가 아직 있으면 정상 응답하고, 이미 넘어갔으면 ASK 리다이렉트를 반환한다. MOVED와 달리 ASK는 "이번 한 번만" 타깃으로 가라는 임시 지시라 클라이언트가 슬롯 맵을 갱신하지 않는다.
이후 MIGRATE 명령으로 키를 실제로 옮기고, 마지막에 SETSLOT ... NODE로 소유권을 확정한다. 이 순서 덕분에 이동 중에도 읽기/쓰기가 끊기지 않는다.
실제 리샤딩 명령
운영에서는 보통 redis-cli --cluster reshard로 배치 처리한다. 하지만 세밀한 제어가 필요할 때는 슬롯 단위로 직접 호출한다.
#!/bin/bash
# 소스 7000, 타깃 7001, 슬롯 1000번 이동
SRC="127.0.0.1:7000"
DST="127.0.0.1:7001"
SRC_ID=$(redis-cli -p 7000 cluster myid)
DST_ID=$(redis-cli -p 7001 cluster myid)
SLOT=1000
# 1) 상태 마킹
redis-cli -p 7001 cluster setslot $SLOT importing $SRC_ID
redis-cli -p 7000 cluster setslot $SLOT migrating $DST_ID
# 2) 키를 배치로 이동 (한 번에 100개, 타임아웃 5s)
while keys=$(redis-cli -p 7000 cluster getkeysinslot $SLOT 100); do
[ -z "$keys" ] && break
# shellcheck disable=SC2086
redis-cli -p 7000 migrate 127.0.0.1 7001 "" 0 5000 keys $keys
done
# 3) 양쪽에 소유권 확정
redis-cli -p 7000 cluster setslot $SLOT node $DST_ID
redis-cli -p 7001 cluster setslot $SLOT node $DST_ID
비어 있는 키 문자열과 KEYS 옵션을 쓰면 여러 키를 한 번의 MIGRATE로 원자적으로 옮길 수 있어 왕복 횟수가 크게 준다.
클라이언트가 ASK를 처리하게 하라
서버가 아무리 정확히 리다이렉트를 줘도, 클라이언트가 ASK를 무시하면 이동 중인 키 조회가 실패한다. 대부분의 클러스터 지원 라이브러리는 자동 처리하지만, 재시도 정책과 타임아웃은 직접 조정해야 한다.
from redis.cluster import RedisCluster
from redis.backoff import ExponentialBackoff
from redis.retry import Retry
rc = RedisCluster(
host="127.0.0.1", port=7000,
# ASK/MOVED 리다이렉트를 몇 번까지 따라갈지
cluster_error_retry_attempts=5,
retry=Retry(ExponentialBackoff(cap=0.5, base=0.05), retries=5),
socket_timeout=1.0,
socket_connect_timeout=1.0,
)
rc.get("user:1000") # 이동 중이면 라이브러리가 ASK를 따라감
재시도 횟수가 너무 작으면 리샤딩 피크에서 에러가, 너무 크면 장애 시 지연이 쌓인다. 슬롯 이동 시간과 backoff 상한을 함께 계산해 정한다.
MOVED와 ASK의 차이
| 구분 | MOVED | ASK |
|---|---|---|
| 의미 | 슬롯이 영구히 다른 노드로 이동함 | 이번 요청만 타깃으로 시도하라 |
| 발생 시점 | 소유권 확정 후 | 슬롯 이동 진행 중 |
| 슬롯 맵 갱신 | 클라이언트가 즉시 갱신 | 갱신하지 않음 |
| 타깃 접근 전제 | 없음 | 먼저 ASKING 전송 필요 |
이 표에서 가장 실수하기 쉬운 곳은 마지막 행이다. ASK를 받은 클라이언트는 타깃 노드에 ASKING을 먼저 보낸 뒤 명령을 실행해야 한다. 안 그러면 타깃이 아직 IMPORTING 상태라 MOVED로 되돌려보낸다.
운영 시 주의점
- 대용량 키 주의: 하나의 키가 수백 MB인 경우
MIGRATE가 소스를 블로킹한다.migrate타임아웃을 넉넉히 주되, 큰 컬렉션은 애플리케이션 레벨에서 분할하는 편이 안전하다. - 동시성 제한: 한 번에 하나의 슬롯만
MIGRATING/IMPORTING으로 두는 것을 권장한다. 여러 슬롯을 병렬 이동하면 실패 시 롤백이 복잡해진다. - 중단 복구: 스크립트가 중간에 죽으면 슬롯이
migrating/importing에 걸린 채 남는다.cluster setslot <slot> stable로 상태를 초기화한 뒤 재개한다. - 모니터링: 리샤딩 중에는
cluster nodes의 슬롯 표기와redis-cli --cluster check로 불일치를 지속 확인한다. 클라이언트 에러율,ASK리다이렉트 카운트를 함께 보면 이상 구간을 빠르게 잡는다.
리샤딩은 명령 몇 줄로 끝나 보이지만, 무중단의 성패는 상태 마킹 순서와 클라이언트의 리다이렉트 처리에 달려 있다. 이 두 축을 먼저 검증한 뒤 배치 이동을 돌리면 트래픽이 흐르는 상태에서도 안전하게 슬롯을 옮길 수 있다.