fos-blog/study
01 / 홈02 / 카테고리
01 / 홈02 / 카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 커머스/F&B 도메인 설계 면접 대비 — 슬롯 경험을 주문·결제·쿠폰·매장 상태 설계로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • investing 페이지로 이동
    • stock-notes 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] CJ푸드빌 디지털 채널 Back-end 개발자 직무 분석
    • [초안] CJ푸드빌 디지털 채널 Back-end 면접 답변집 — 슬롯 도메인 경험을 커머스/F&B 설계로 번역하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/database/[초안] Redis Cluster 운영 실전…
db

[초안] Redis Cluster 운영 실전: 16384 슬롯, 노드 토폴로지 변경, 페일오버

대규모 커머스 환경에서 Redis는 단일 인스턴스로 버틸 수 있는 트래픽을 한참 넘어선다. 올리브영처럼 프로모션·광고 푸시·라이브 방송이 동시에 터지는 도메인에서는 평시 QPS 대비 10배 이상의 스파이크가 일상이고, 캐시 미스 한 번이 곧바로 RDBMS로 전이되어 장애를 만든다. 이때 단일 마스터-슬레이브 구성은 두 가지 한계에 부딪힌다. 첫째, 메모리...

2026.04.27·12 min read·18 views

왜 이 주제가 중요한가

대규모 커머스 환경에서 Redis는 단일 인스턴스로 버틸 수 있는 트래픽을 한참 넘어선다. 올리브영처럼 프로모션·광고 푸시·라이브 방송이 동시에 터지는 도메인에서는 평시 QPS 대비 10배 이상의 스파이크가 일상이고, 캐시 미스 한 번이 곧바로 RDBMS로 전이되어 장애를 만든다. 이때 단일 마스터-슬레이브 구성은 두 가지 한계에 부딪힌다. 첫째, 메모리 상한이 단일 노드의 RAM에 묶인다. 둘째, write QPS가 단일 마스터의 CPU/네트워크 한계에 막힌다.

Redis Cluster는 이 두 가지를 동시에 풀기 위해 설계된 공식 분산 모드다. 그런데 운영자가 가장 자주 질문받고 가장 자주 사고를 내는 영역도 바로 여기다. 슬롯이 무엇인지, MOVED와 ASK는 언제 나오는지, 노드 추가 중에 트래픽이 깨지지 않게 하는 절차가 어떻게 되는지, 네트워크 파티션이 일어났을 때 split-brain을 어떻게 막는지. 이 글은 그 운영 실전을 면접에서 설명할 수 있을 정도까지 정리한다.

캐시 키 설계나 TTL 전략 같은 일반 Redis 사용 패턴은 Cache-Aside 패턴 또는 같은 디렉터리의 다른 문서를 참조한다. 이 문서는 의도적으로 클러스터 토폴로지 운영에 범위를 좁힌다.

핵심 개념: 16384 슬롯 기반 샤딩

왜 16384인가

Redis Cluster는 키 공간을 0번부터 16383번까지 총 16384개의 해시 슬롯으로 분할한다. 슬롯 매핑 공식은 단순하다.

plaintext
slot = CRC16(key) mod 16384

16384(=2^14)라는 숫자가 어색해 보이지만 이유가 있다. 클러스터 노드 간에는 서로의 슬롯 보유 정보를 비트맵으로 주고받는다. 16384비트는 2KB에 불과하다. 만약 65536으로 잡았다면 8KB가 되고, 노드가 1000개로 늘어났을 때 가십(gossip) 메시지가 너무 커진다. 반대로 1024는 너무 작아서 노드 수가 늘었을 때 슬롯 단위가 너무 굵어 균등 분산이 어렵다. Redis 저자 antirez가 공식적으로 밝힌 트레이드오프다.

면접에서 “왜 16384개인가”는 단순 암기 질문 같지만, 사실은 “클러스터 메시지 비용을 이해하고 있는가”를 보는 질문이다.

해시 태그(hash tag)

같은 슬롯에 키를 강제로 묶고 싶을 때 {} 안의 부분만 해시에 사용한다.

plaintext
user:{1234}:profile   → CRC16("1234") mod 16384
user:{1234}:cart      → 같은 슬롯
user:{1234}:orders    → 같은 슬롯

이렇게 묶어 두지 않으면 MGET user:1234:profile user:1234:cart 같은 멀티키 명령이 CROSSSLOT Keys in request don't hash to the same slot 오류로 거절된다. 같은 사용자에 대한 여러 키를 트랜잭션(MULTI/EXEC), 파이프라인, Lua 스크립트로 묶으려면 해시 태그는 사실상 필수다.

다만 해시 태그를 남발하면 특정 슬롯이 비대해지는 hot slot 문제가 생긴다. 커머스에서는 “인기 상품 ID” 단위로 묶고 싶어지는데, 인기 상품일수록 트래픽이 몰려 hot slot이 만들어진다. 해시 태그는 “함께 읽어야 하는 키들”에만 한정하고, 트래픽 분산이 목적이라면 절대 쓰지 않는다.

MOVED와 ASK 리다이렉션

클라이언트가 잘못된 노드에 명령을 보냈을 때 Redis는 두 가지 응답으로 안내한다.

  • MOVED 3999 10.0.0.7:6379: 슬롯 3999는 영구적으로 10.0.0.7로 이동했다. 클라이언트는 자기 슬롯 맵을 갱신해야 한다.
  • ASK 3999 10.0.0.7:6379: 슬롯 3999는 마이그레이션 중이다. 이번 요청만 ASKING 명령과 함께 새 노드로 다시 보내라. 슬롯 맵은 갱신하지 마라.

Lettuce, Jedis, redis-py 같은 메인 클라이언트는 이 두 응답을 자동 처리한다. 하지만 “자동 처리한다”의 디테일은 클라이언트마다 다르다. 예를 들어 Lettuce는 기본적으로 topology refresh가 주기적/이벤트 기반으로 일어나며, MOVED를 받으면 즉시 재조회한다. 이 주기가 너무 길게 잡혀 있으면 마이그레이션 중에 매 요청마다 한 번씩 redirect를 먹는다. 면접에서 “왜 마이그레이션 중에 latency가 두 배가 됐나”라는 질문이 나오면 이 redirect 비용을 답할 수 있어야 한다.

노드 추가/제거 실전 절차

새 마스터 노드 추가하기

상황: 기존에 6노드(마스터 3 + 슬레이브 3) 클러스터가 있고, 트래픽 증가로 4번째 마스터를 추가한다.

plaintext
# 1) 새 인스턴스 두 대를 띄운다 (M4, S4)
redis-server /etc/redis/7004.conf --daemonize yes
redis-server /etc/redis/7005.conf --daemonize yes
 
# 2) 마스터로 클러스터에 합류
redis-cli --cluster add-node 10.0.0.10:7004 10.0.0.1:7000
 
# 3) 슬레이브로 합류시키되, 새 마스터(M4)를 따라가게 함
redis-cli --cluster add-node 10.0.0.11:7005 10.0.0.1:7000 \
  --cluster-slave --cluster-master-id <M4-NODE-ID>
 
# 4) 슬롯 재분배: 기존 3개 마스터에서 M4로 일부 슬롯 이전
redis-cli --cluster reshard 10.0.0.1:7000 \
  --cluster-from <M1-ID>,<M2-ID>,<M3-ID> \
  --cluster-to <M4-ID> \
  --cluster-slots 4096 \
  --cluster-yes

add-node만으로 트래픽이 분산되지 않는다는 점이 자주 잊힌다. 새 마스터는 슬롯이 0개인 상태로 합류하기 때문에, 명시적으로 reshard를 돌려야 비로소 키가 옮겨간다. 4096개 슬롯이 옮겨오면 평균적으로 25%의 키가 새 노드로 이동한다.

슬롯 마이그레이션이 내부적으로 하는 일

reshard는 결국 CLUSTER SETSLOT 명령들의 조합이다. 한 슬롯을 옮기는 절차를 풀어 보면 이렇다.

plaintext
1. 대상 노드에서:  CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
2. 출발 노드에서:  CLUSTER SETSLOT <slot> MIGRATING <target-node-id>
3. 출발 노드에서:  CLUSTER GETKEYSINSLOT <slot> <count>  # 키 목록 추출
4. 출발 노드에서:  MIGRATE <target-host> <target-port> "" 0 <timeout> KEYS k1 k2 ...
5. 모든 마스터에게: CLUSTER SETSLOT <slot> NODE <target-node-id>

이 동안 슬롯 N의 상태는 “마이그레이션 중”이다. 클라이언트가 슬롯 N의 키를 출발 노드에 요청하면:

  • 키가 아직 출발 노드에 있으면 정상 응답.
  • 키가 이미 이동했으면 ASK 리다이렉트.

마이그레이션이 끝난 뒤 CLUSTER SETSLOT NODE가 모든 마스터에 전파되는 시점에야 비로소 MOVED로 바뀐다. 즉 마이그레이션 중에는 ASK, 끝난 뒤에는 MOVED다. 이 차이가 트래픽에 미치는 영향을 잘못 이해하면 “왜 갑자기 latency 스파이크가 떴다 사라지냐”를 못 잡는다.

노드 제거

plaintext
# 1) 제거할 마스터의 슬롯을 다른 마스터로 옮긴다
redis-cli --cluster reshard 10.0.0.1:7000 \
  --cluster-from <M-OLD-ID> \
  --cluster-to <M-OTHER-ID> \
  --cluster-slots 4096 \
  --cluster-yes
 
# 2) 슬롯이 0개가 된 것을 확인
redis-cli --cluster check 10.0.0.1:7000
 
# 3) 제거
redis-cli --cluster del-node 10.0.0.1:7000 <M-OLD-ID>

“슬롯이 한 개라도 남아 있으면 del-node가 거부된다”는 점을 잊으면 운영 중에 당황한다. 슬레이브를 먼저 제거하고, 마스터의 슬롯을 0으로 만든 뒤, 마스터를 제거하는 순서를 굳혀 둔다.

페일오버: 자동과 수동

자동 페일오버 흐름

마스터 M1이 죽으면 클러스터는 다음 단계를 거친다.

  1. PFAIL 감지: 다른 노드들이 가십으로 M1에게 PING을 보낸다. cluster-node-timeout(기본 15s) 안에 응답이 없으면 해당 노드는 M1을 PFAIL(probable failure)로 표시한다.
  2. FAIL 합의: 클러스터의 마스터 과반수가 PFAIL을 보고하면 FAIL로 승격된다. 이 정보가 가십으로 전체에 퍼진다.
  3. 슬레이브 선출: M1을 따르던 슬레이브들이 자기 replication offset을 비교해 가장 최신을 가진 슬레이브가 선거를 시작한다. 마스터 과반수의 투표를 받으면 새 마스터로 승격된다.
  4. 슬롯 인계: 새 마스터가 자기 ID로 슬롯 소유권을 가져온다. 클라이언트들은 MOVED를 통해 갱신된다.

여기서 주의할 두 가지 파라미터가 있다.

  • cluster-node-timeout: 너무 짧게 잡으면 일시적인 GC 정지나 네트워크 흔들림에도 페일오버가 발생한다. 너무 길면 실제 장애 시 복구가 늦어진다. 커머스 환경에서는 보통 5~15초 사이를 쓰며, 짧게 잡고 싶다면 모니터링 인프라가 그만큼 안정적이어야 한다.
  • cluster-replica-validity-factor: 슬레이브가 마스터와 너무 오랫동안 끊겨 있었다면 후보 자격을 잃는다. (node-timeout * factor) + repl-ping-replica-period 시간 이상 동기화가 끊겼던 슬레이브는 자동 승격 대상에서 제외된다. 0으로 두면 “얼마나 오래 끊겼든 무조건 후보”가 되는데, 이는 stale한 슬레이브가 마스터로 올라가 데이터 손실을 키울 수 있어 권장되지 않는다.

수동 페일오버

배포·유지보수 목적으로 마스터를 의도적으로 내릴 때는 슬레이브에서 다음을 실행한다.

plaintext
# 슬레이브 노드에 접속해서:
CLUSTER FAILOVER          # 일반 모드: 마스터 사전 동의 필요
CLUSTER FAILOVER FORCE    # 강제: 마스터 동의 없이 진행 (마스터가 비정상일 때)
CLUSTER FAILOVER TAKEOVER # 클러스터 합의 없이 진행 (네트워크 격리 상황)

일반적인 운영(예: 마스터 노드의 OS 패치)에서는 CLUSTER FAILOVER 일반 모드를 쓴다. 이는 다음을 보장한다.

  1. 슬레이브가 마스터에게 “나에게 페일오버를 넘겨라”라고 요청한다.
  2. 마스터가 클라이언트 트래픽을 잠깐 막고, 자기 replication 버퍼를 슬레이브에 모두 보낸다.
  3. 슬레이브가 자기 offset이 마스터와 동일해진 것을 확인한 뒤 승격된다.

이 절차 덕분에 데이터 손실 없이 마스터 교체가 가능하다. 면접에서 “무중단으로 마스터 교체하려면?”이라는 질문에는 이 흐름을 설명하면 된다. FORCE나 TAKEOVER는 “정상 상황에서 쓰면 안 되는” 옵션이라는 점을 함께 짚으면 더 좋다.

운영 시 주의사항

네트워크 파티션과 split-brain 방지

3마스터 클러스터를 가정하자. AZ-A에 M1, M2가, AZ-B에 M3와 M3의 슬레이브 S3가 있다고 하자. AZ 사이의 네트워크가 끊기면 어떤 일이 벌어지나.

  • AZ-A 쪽에는 마스터 2명이 있어 “마스터 과반수”다. M3을 FAIL 처리하고, M3의 슬레이브가 AZ-A에 있다면 그 슬레이브를 새 마스터로 올린다.
  • AZ-B 쪽에는 마스터 1명(M3)뿐이다. 과반수가 아니므로 M3은 자기가 살아 있어도 새 슬레이브를 만들 수 없고, 클라이언트의 write를 받지도 못해야 한다.

이 “마이너 파티션은 write를 거부한다”를 보장하는 옵션이 cluster-require-full-coverage와 cluster-allow-reads-when-down이다.

  • cluster-require-full-coverage yes(기본): 클러스터 전체 슬롯이 다 커버되지 않으면 어떤 키도 받지 않는다. 가용성보다 일관성을 더 중시한다.
  • cluster-require-full-coverage no: 일부 슬롯만 살아 있어도 그 슬롯에 대한 요청은 받는다. 캐시 용도라면 이 쪽이 합리적일 수 있다.

커머스에서 캐시로만 쓰고 있다면 no로 두고 일부 슬롯이 죽어도 나머지를 살리는 게 낫다. 반대로 세션 스토어, 결제 토큰처럼 일관성이 중요하면 yes로 두고 전체 거부 → 빠른 복구로 가는 편이 안전하다. 단순한 “기본값을 그대로 둔다”는 답은 면접에서 높은 점수를 받기 어렵다.

또한 슬레이브 배치는 AZ에 분산해야 한다. M1의 슬레이브 S1을 같은 AZ에 두면 AZ 단위 장애가 났을 때 M1과 S1이 함께 죽는다. 슬레이브는 항상 마스터와 다른 장애 도메인에 둔다.

슬롯 마이그레이션 중 트래픽 처리

마이그레이션은 “키 단위”로 진행된다. MIGRATE 명령은 해당 키들에 대해 출발 노드에서 잠깐 잠금을 건다. 큰 키 하나(수 MB짜리 hash, 수만 개 원소짜리 list)가 들어 있으면 그 키를 옮기는 동안 출발 노드의 다른 명령들이 줄을 선다. 즉 “큰 키”는 마이그레이션의 적이다.

운영 룰로 굳혀야 할 것들:

  • 단일 키 크기 상한을 정해 둔다. 보통 1MB 이상은 경고, 10MB 이상은 거의 항상 안티패턴.
  • redis-cli --bigkeys, MEMORY USAGE로 평소에 모니터링한다.
  • 마이그레이션은 트래픽이 적은 시간대에, 작은 슬롯 단위로 쪼개서 한다. redis-cli --cluster reshard --cluster-pipeline으로 한 번에 옮길 키 개수를 조절할 수 있다.

또한 마이그레이션 중에는 클라이언트 connection pool 사이즈를 살핀다. ASK redirect가 늘어나면 사실상 hop이 두 배가 되므로 pool이 빠르게 마른다. AWS ElastiCache나 GCP Memorystore 같은 매니지드 서비스는 reshard를 점진적으로 진행하는 옵션을 제공하는데, 자체 구축이라면 직접 페이스를 조절해야 한다.

replica-read 트래픽 분산의 함정

READONLY 명령 후 슬레이브에서 읽기를 받으면 read 트래픽을 분산할 수 있다. 하지만 슬레이브는 비동기 복제 지연(replication lag)이 있다. 결제 직후 “내 주문 내역” 조회처럼 read-after-write 일관성이 필요한 경로에서는 슬레이브 읽기를 쓰면 안 된다. 캐시 미스 후 DB에서 읽어 캐시에 채운 직후, 같은 요청이 슬레이브를 향했다가 빈 응답을 받는 사례가 가장 흔한 함정이다.

잘못된 예 vs 개선된 예

안티패턴 1: 멀티키 트랜잭션을 해시 태그 없이

java
// BAD
RTransaction tx = redisson.createTransaction(...);
RBucket<String> a = tx.getBucket("user:1001:cart");
RBucket<String> b = tx.getBucket("user:1001:wishlist");
// → CROSSSLOT 오류 위험
java
// GOOD: 해시 태그로 같은 슬롯 보장
RBucket<String> a = tx.getBucket("user:{1001}:cart");
RBucket<String> b = tx.getBucket("user:{1001}:wishlist");

안티패턴 2: 빅 키 + 클러스터 reshard 동시 진행

평일 점심에 광고 캠페인 캐시(수십 MB hash 1개)가 살아있는 노드를 reshard 대상으로 잡았다가, 해당 키 마이그레이션 중 출발 노드의 p99 latency가 수 초로 튄 사례가 흔하다. 개선:

  • 마이그레이션 전에 MEMORY USAGE로 빅키 점검.
  • 빅키는 더 작은 단위(예: hash → 여러 개의 hash로 분할)로 리팩터링 후 마이그레이션.
  • 부득이한 경우 트래픽 적은 시간대 + 슬롯 수를 잘게 쪼개기.

안티패턴 3: cluster-node-timeout을 1초로 둔 채 GC 튜닝 안 함

JVM GC pause가 2초 발생 → PFAIL → 페일오버 → 트래픽 일시적 중단. 개선:

  • timeout은 평균 GC pause + 안전 여유를 둔다.
  • JVM 쪽에서는 G1/ZGC pause 모니터링을 분리해서 측정.

로컬 실습 환경

Docker만 있으면 6노드 클러스터를 5분 안에 띄울 수 있다. 다음을 docker-compose.yml로 둔다.

yaml
version: "3.8"
services:
  redis-1:
    image: redis:7.2
    command: redis-server --port 7001 --cluster-enabled yes --cluster-config-file nodes-7001.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
  redis-2:
    image: redis:7.2
    command: redis-server --port 7002 --cluster-enabled yes --cluster-config-file nodes-7002.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
  redis-3:
    image: redis:7.2
    command: redis-server --port 7003 --cluster-enabled yes --cluster-config-file nodes-7003.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
  redis-4:
    image: redis:7.2
    command: redis-server --port 7004 --cluster-enabled yes --cluster-config-file nodes-7004.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
  redis-5:
    image: redis:7.2
    command: redis-server --port 7005 --cluster-enabled yes --cluster-config-file nodes-7005.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
  redis-6:
    image: redis:7.2
    command: redis-server --port 7006 --cluster-enabled yes --cluster-config-file nodes-7006.conf --cluster-node-timeout 5000 --appendonly yes
    network_mode: host
bash
docker compose up -d
 
# 클러스터 초기화 (마스터 3 + 슬레이브 3)
redis-cli --cluster create \
  127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
  127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
  --cluster-replicas 1 --cluster-yes

실행 가능한 시나리오

시나리오 1: 슬롯 매핑 확인과 해시 태그

bash
# 키 → 슬롯 확인
redis-cli -p 7001 -c CLUSTER KEYSLOT user:1001:cart
# → (integer) 1234
 
redis-cli -p 7001 -c CLUSTER KEYSLOT 'user:{1001}:cart'
redis-cli -p 7001 -c CLUSTER KEYSLOT 'user:{1001}:wishlist'
# → 같은 슬롯이 나오는지 확인

시나리오 2: 자동 페일오버 관찰

bash
# 어떤 마스터가 어떤 슬레이브를 가지는지 확인
redis-cli -p 7001 -c CLUSTER NODES
 
# 마스터를 강제 종료 (예: 7001이 마스터)
docker stop <redis-1 컨테이너>
 
# 5~10초 뒤 슬레이브가 승격되는지 확인
redis-cli -p 7002 -c CLUSTER NODES
 
# 다시 살리면 슬레이브로 합류
docker start <redis-1 컨테이너>

시나리오 3: reshard 중 ASK redirect 직접 보기

bash
# 한 슬롯만 옮겨 보기
redis-cli --cluster reshard 127.0.0.1:7001 \
  --cluster-from <M1-ID> \
  --cluster-to <M2-ID> \
  --cluster-slots 1 \
  --cluster-yes
 
# 옮기는 동안 그 슬롯에 속하는 키를 -c 없이(=리다이렉트 비활성) 호출
redis-cli -p 7001 GET <key-in-migrating-slot>
# → "ASK <slot> 127.0.0.1:7002" 응답 직접 확인

이 실습은 “MOVED와 ASK가 실제 어떤 형태로 오는가”를 머릿속에 박아 두는 데 의미가 있다.

인터뷰 답변 프레임

면접에서 “Redis Cluster를 운영해 본 경험이 있는가”가 들어오면 다음 4단 구조로 답한다.

  1. 구조 이해: 16384 슬롯 기반 샤딩, 슬롯 비트맵을 가십으로 교환하기 때문에 슬롯 수가 16384로 잡혔다는 점.
  2. 노드 변경 경험: add-node 후 reshard가 별개 단계라는 점, MIGRATE 동안 ASK가 발생한다는 점, 빅키가 마이그레이션의 적이라는 점.
  3. 페일오버 운영: cluster-node-timeout과 replica-validity-factor의 의미, 무중단 교체 시 CLUSTER FAILOVER 일반 모드를 쓰는 이유.
  4. 장애 대응 원칙: 네트워크 파티션 시 마이너 파티션의 write 거부 동작, AZ 분산 슬레이브 배치, 캐시 vs 일관성 스토어에 따른 cluster-require-full-coverage 선택 차이.

특히 “장애 났을 때 어떻게 대응했나”는 시니어 백엔드에게 거의 반드시 들어오는 질문이다. 시간순(감지 → 트리아지 → 가설 → 검증 → 조치 → 사후) 구조로 한 번 정리해 두면 어떤 사고든 같은 틀로 설명할 수 있다.

다음과 같은 꼬리 질문에 미리 답을 준비해 둔다.

  • “왜 16384인가?” → 가십 메시지 비트맵 비용과 노드 수 균등 분산의 균형.
  • “MOVED와 ASK 차이?” → 영구 이동 vs 마이그레이션 중 일시 redirect.
  • “마스터 무중단 교체?” → 슬레이브에서 CLUSTER FAILOVER, replication 동기화 후 승격.
  • “네트워크 파티션 시 split-brain은 어떻게 막나?” → 마스터 과반수 투표, 마이너 파티션의 write 거부, cluster-require-full-coverage 정책 선택.
  • “캐시인데 왜 cluster를 쓰나? 그냥 sentinel + 단일 마스터로 충분하지 않나?” → 메모리 상한, write QPS 상한이 단일 노드를 넘는 경우의 답.

체크리스트

  • CRC16 mod 16384 슬롯 매핑 공식을 외우고 있다.
  • 16384가 선택된 이유를 가십 메시지 크기 관점에서 설명할 수 있다.
  • 해시 태그 {} 사용 사례와 안티패턴(hot slot)을 구분한다.
  • MOVED와 ASK의 의미·발생 시점·클라이언트 동작 차이를 설명할 수 있다.
  • add-node와 reshard가 별개 단계라는 사실을 안다.
  • del-node 전 슬롯을 0으로 비워야 한다는 점을 기억한다.
  • CLUSTER FAILOVER의 일반/FORCE/TAKEOVER 차이를 구분한다.
  • cluster-node-timeout 튜닝과 GC pause의 상호작용을 이해한다.
  • cluster-replica-validity-factor의 의미를 알고 0으로 두지 않는다.
  • cluster-require-full-coverage를 캐시 vs 일관성 스토어에 따라 선택한다.
  • AZ 단위 장애를 가정해 슬레이브 배치 도메인을 분산한다.
  • 빅키 점검 루틴(--bigkeys, MEMORY USAGE)을 평소에 돌린다.
  • 슬레이브 읽기와 read-after-write 일관성 깨짐 사례를 알고 있다.
  • 로컬에서 6노드 docker compose로 add-node, reshard, 강제 페일오버를 직접 재현해 본 적이 있다.
on this page
  • 01왜 이 주제가 중요한가
  • 02핵심 개념: 16384 슬롯 기반 샤딩
  • 왜 16384인가
  • 해시 태그(hash tag)
  • MOVED와 ASK 리다이렉션
  • 03노드 추가/제거 실전 절차
  • 새 마스터 노드 추가하기
  • 슬롯 마이그레이션이 내부적으로 하는 일
  • 노드 제거
  • 04페일오버: 자동과 수동
  • 자동 페일오버 흐름
  • 수동 페일오버
  • 05운영 시 주의사항
  • 네트워크 파티션과 split-brain 방지
  • 슬롯 마이그레이션 중 트래픽 처리
  • `replica-read` 트래픽 분산의 함정
  • 06잘못된 예 vs 개선된 예
  • 안티패턴 1: 멀티키 트랜잭션을 해시 태그 없이
  • 안티패턴 2: 빅 키 + 클러스터 reshard 동시 진행
  • 안티패턴 3: `cluster-node-timeout`을 1초로 둔 채 GC 튜닝 안 함
  • 07로컬 실습 환경
  • 08실행 가능한 시나리오
  • 시나리오 1: 슬롯 매핑 확인과 해시 태그
  • 시나리오 2: 자동 페일오버 관찰
  • 시나리오 3: reshard 중 ASK redirect 직접 보기
  • 09인터뷰 답변 프레임
  • 10체크리스트

댓글 (0)