콘텐츠로 이동

Redis 내부 원리 & ElastiCache 운영

분류: Layer 8 - DB 심화 | 작성일: 2026-04-10

📌 Redis를 복원력 패턴(Cache Stampede, Circuit Breaker 등)으로 활용하는 방법은 L6/redis-cache-basics.md에서 다룹니다. 이 문서는 Redis의 내부 동작 원리ElastiCache 운영 을 다룹니다.


Redis는 단일 스레드 이벤트 루프 위에서 다양한 자료구조를 메모리에 저장하고, RDB/AOF 영속화와 클러스터 샤딩을 통해 내구성과 수평 확장을 제공하는 In-Memory 데이터 스토어다.


2. 왜 L8 DB 심화 토픽인가 — L6 한계와 운영 SLA

섹션 제목: “2. 왜 L8 DB 심화 토픽인가 — L6 한계와 운영 SLA”

L6는 Redis를 cache·queue·rate-limiter로 쓰는 법(SDK API·복원력 패턴)을 다룬다. 그러나 production 운영에서 마주치는 3가지 의사결정은 L6 지식만으로 답이 안 나온다.

  1. 메모리 운영 헤드룸 부재 — Redis 공식 FAQ는 “쓰기 부하 + RDB save·AOF rewrite 동시 발생 시 최대 2배 메모리 사용까지 튀어오를 수 있으므로, 가용 메모리가 10GB면 maxmemory를 8~9GB로 설정하라”고 명시한다 (redis.io/topics/faq). L6에는 이 헤드룸 권고가 없어, maxmemory를 시스템 메모리에 가깝게 설정해두면 BGREWRITEAOF 트리거 시점에 OOM 또는 swap 진입 → p99 latency가 수십 ms로 튀는 사고가 반복된다.

  2. 단일 노드 SLA 한계의 정량값 부재 — Redis 공식 벤치마크는 단일 인스턴스 SET p50 0.143ms, 약 180k ops/sec(파이프라이닝 없음), 1M SET 중 99.76%가 1ms 이하임을 보고한다 (redis.io 벤치마크 문서). 동시 연결도 같은 문서에서 “30,000 연결 인스턴스는 100 연결 인스턴스 처리량의 절반”이라는 정량 한계를 명시한다. 이 SLA가 어디서 깨지는지(블로킹 커맨드·fork·복제 lag)는 §3-2에서 다룬다.

  3. 분산 결정의 사전성 — ElastiCache Cluster 모드에서 CROSSSLOT 에러는 런타임 버그가 아니라 키 설계 단계에서 결정되는 트레이드오프다. Hash Tag 원리를 모르면 코드가 잘 돌다가 cluster 모드 전환 첫날에 한 번에 깨진다(§3-4).

2-2. 이 토픽이 해결하는 메커니즘과 도입 결정 사례

섹션 제목: “2-2. 이 토픽이 해결하는 메커니즘과 도입 결정 사례”

§3은 위 3가지 한계를 (a) 자료구조 인코딩 임계값, (b) fork+COW 영속화 + I/O multiplexing, (c) CRC16 % 16384 슬롯 매핑이라는 내부 메커니즘으로 노출시켜, “어떤 워크로드에서 어떤 설정값이 어떤 비용을 만드는지” 정량 결정이 가능한 상태로 바꾼다. 이 토픽이 사라지면: OOM·CROSSSLOT·AOF tail truncation 같은 silent failure를 “재시작하면 사라지는 일시적 버그”로 오인하게 된다.

실제 도입 결정 사례: 50GB 세션 저장 워크로드를 Sentinel(단일 마스터) vs Cluster 중 어디로 갈지 결정한다고 하자. 메모리 카탈로그만 보면 r6g.2xlarge(약 52GB)로 Sentinel이 충분해 보인다. 그러나 위 (1)의 2배 헤드룸 권고를 적용하면 안전 가용은 ≈ 26GB로 떨어져 Sentinel은 부족 → Cluster 3 샤드(r6g.large × 3 또는 r6g.xlarge × 3)로 결정. L6 수준 지식으로는 이 헤드룸을 보지 못해 운영 첫 분기에 OOM/swap을 겪는 패턴이 반복된다.

2-3. 단순 사용 vs 내부 원리 이해 — 시나리오 대비

섹션 제목: “2-3. 단순 사용 vs 내부 원리 이해 — 시나리오 대비”
상황내부 원리 이해 없이내부 원리 이해 후
Sorted Set 100만 건 ZRANGE 요청느린 이유를 모름Skip List O(log N) 특성 이해 → 적절한 범위 쿼리
Redis 재시작 후 데이터 손실원인 파악 불가RDB vs AOF 영속화 설정 문제로 즉시 진단
ElastiCache 클러스터 모드 CROSSSLOT 에러개별 GET으로 바꾸면 해결 정도만 앎CRC16 Hash Slot 원리 이해 → Hash Tag로 최적화
메모리 사용량 급증모니터링만 확인ziplist→hashtable 변환 임계값 튜닝으로 예방

Redis는 자료구조별로 데이터 크기에 따라 **압축 인코딩(compact encoding)**을 자동 선택한다. 소규모 데이터에서는 메모리를 아끼는 압축 형식을 쓰고, 임계값을 넘으면 연산 효율 우선 형식으로 변환한다.

Redis의 기본 문자열 타입. C의 char*를 쓰지 않고 자체 SDS 구조체를 사용한다.

/* SDS 구조체 (단순화) */
struct sdshdr {
int len; // 현재 문자열 길이
int free; // 남은 여유 공간
char buf[]; // 실제 데이터
};

SDS 장점:

  • O(1) 길이 조회 (len 필드 저장)
  • 이진 안전(binary-safe) — null 문자 포함 가능
  • 사전 할당(pre-allocation)으로 append 성능 향상

인코딩 선택:

조건인코딩설명
정수 범위 내 값 (0~9999999999)int포인터 자체에 정수 저장 (8 bytes)
44 bytes 이하 문자열embstr헤더+데이터를 단일 메모리 블록
44 bytes 초과raw일반 SDS
Terminal window
# 인코딩 확인
SET num 42
OBJECT ENCODING num # → int
SET short "hello"
OBJECT ENCODING short # → embstr
SET long "this is a very long string that exceeds 44 bytes limit"
OBJECT ENCODING long # → raw

ziplist (압축 리스트): 항목 수가 적고(hash-max-ziplist-entries: 128) 값이 작을 때(hash-max-ziplist-value: 64 bytes) 사용. 연속된 메모리 블록에 저장하여 캐시 효율 극대화.

[zlbytes][zltail][zllen][entry1][entry2]...[entryN][zlend]

hashtable: 임계값 초과 시 자동 전환. 키-값 쌍을 해시 테이블로 저장.

Terminal window
# 임계값 확인/변경
CONFIG GET hash-max-ziplist-entries # → 128
CONFIG SET hash-max-ziplist-entries 64
# 인코딩 확인
HSET user:1 name "Alice" age 30
OBJECT ENCODING user:1 # → ziplist (항목 수 적음)

실무 튜닝 포인트: 작은 해시 수백만 개를 저장할 때 ziplist 임계값을 늘리면 메모리를 30~50% 절약할 수 있다. 단, 조회 시간은 O(N)으로 증가하므로 항목 수와 트레이드오프를 고려한다.


listpack (Redis 7.0+, 구버전은 ziplist): 소규모 리스트에서 사용하는 압축 인코딩.

quicklist: 임계값(list-max-ziplist-size: 128) 초과 시 전환. 여러 개의 listpack을 이중 연결 리스트로 연결한 구조.

quicklist:
node1(listpack) ↔ node2(listpack) ↔ node3(listpack)
  • LPUSH/RPUSH: O(1)
  • LINDEX: O(N) — 중간 접근 비용 주의
  • LRANGE 0 -1: O(N) — 전체 조회 비용 주의

intset: 모든 요소가 정수이고 64개 이하일 때 사용. 정렬된 정수 배열로 이진 탐색.

listpack: 소규모 문자열 셋에서 사용 (Redis 7.2+).

hashtable: 요소가 많거나 문자열이 길면 전환.

Terminal window
SADD intset-example 1 2 3 4 5
OBJECT ENCODING intset-example # → intset
SADD str-example "apple" "banana"
OBJECT ENCODING str-example # → listpack 또는 hashtable

Sorted Set — listpack → skiplist + hashtable

섹션 제목: “Sorted Set — listpack → skiplist + hashtable”

가장 복잡한 자료구조. listpack 임계값(zset-max-listpack-entries: 128, zset-max-listpack-value: 64) 초과 시 Skip List + Hash Table 조합으로 전환.

Skip List 구조:

Level 4: [head] ────────────────────────── [tail]
Level 3: [head] ──────── [30] ───────────── [tail]
Level 2: [head] ── [10] ─ [30] ── [50] ──── [tail]
Level 1: [head] ─ [10] ─ [20] ─ [30] ─ [40] ─ [50] ─ [tail]
  • 삽입/삭제: O(log N) 평균
  • 범위 조회(ZRANGE, ZRANGEBYSCORE): O(log N + M)
  • 특정 Score 조회: O(log N)

Hash Table (병용): Score로 정렬된 Skip List와 별도로, 멤버 → Score 매핑을 위한 해시 테이블도 유지. ZSCORE 커맨드가 O(1)인 이유다.

Terminal window
# ZADD: Skip List에 삽입 (O(log N))
ZADD leaderboard 1500 "alice" 2000 "bob" 1200 "charlie"
# ZRANGE: 순위 범위 조회 (O(log N + M))
ZRANGE leaderboard 0 -1 WITHSCORES
# ZSCORE: Hash Table 조회 (O(1))
ZSCORE leaderboard "alice"
# ZRANK: Skip List 순회 (O(log N))
ZRANK leaderboard "alice"

3-2. 단일 스레드 + I/O Multiplexing 원리

섹션 제목: “3-2. 단일 스레드 + I/O Multiplexing 원리”

Redis는 단일 스레드로 모든 커맨드를 처리한다. 이것이 어떻게 초당 수십만 건의 처리를 가능하게 하는가?

클라이언트 연결들
[epoll/kqueue] ← I/O Multiplexing (OS 레벨)
이벤트 루프 (단일 스레드)
커맨드 큐 → 순차 실행 → 응답 반환

핵심 원리:

  1. 비블로킹 I/O: epoll(Linux) / kqueue(macOS)로 수천 개의 소켓을 동시에 감시. 데이터가 준비된 소켓만 처리.
  2. 메모리 연산: 디스크 I/O 없이 RAM에서 직접 연산 → 마이크로초 단위 응답.
  3. 단일 스레드의 장점: 락(Lock) 없음 → 컨텍스트 스위칭 오버헤드 없음 → 모든 커맨드가 원자적(Atomic).
싱글 스레드라 Lock 없음
→ INCR이 항상 원자적 (Read-Modify-Write가 끊기지 않음)
→ MULTI/EXEC 트랜잭션이 단순하게 동작
약점대응 방법
CPU 코어 하나만 사용여러 Redis 인스턴스 실행 (포트별)
블로킹 커맨드(KEYS *, SORT 대용량)SCAN으로 대체, 대용량 연산 분리
대용량 데이터 직렬화 비용Pipeline으로 네트워크 왕복 감소

단일 스레드 블로킹이 실제로 발생하는 조건:

커맨드 / 작업규모블로킹 시간영향
KEYS * 전체 스캔키 100만 개약 100~300ms단일 스레드 전체 정지 → 모든 클라이언트 요청 대기
SMEMBERS 전체 조회Set 요소 100만 개약 100~300ms동일 (응답 크기 + 직렬화 비용)
BGSAVE fork()메모리 8GB약 200~500msfork() 완료 전까지 짧은 블로킹 (COW 페이지 테이블 복사)
BGREWRITEAOF fork()메모리 8GB약 200~500msBGSAVE와 동일 메커니즘

→ 이 기간 동안 모든 클라이언트 요청이 대기하므로, 프로덕션에서는 KEYS * / 대용량 SMEMBERS 대신 SCAN을 사용하고, BGSAVE 타이밍을 트래픽이 적은 시간대로 조정해야 한다.

Redis 6.0+ I/O 스레드: 네트워크 읽기/쓰기는 멀티스레드로 처리하되, 커맨드 실행 자체는 여전히 단일 스레드. 처리량이 20~30% 향상.

Terminal window
# redis.conf
io-threads 4 # I/O 스레드 수 (CPU 코어 수보다 작게)
io-threads-do-reads yes

Redis는 기본적으로 메모리 저장소다. 재시작 시 데이터를 복구하려면 영속화(Persistence)가 필요하다.

특정 시점의 전체 데이터를 바이너리 파일(.rdb)로 덤프.

Terminal window
# redis.conf
save 900 1 # 900초(15분) 내에 1번 이상 변경 시 저장
save 300 10 # 300초(5분) 내에 10번 이상 변경 시 저장
save 60 10000 # 60초 내에 10,000번 이상 변경 시 저장
dbfilename dump.rdb
dir /var/lib/redis

RDB 저장 메커니즘 — fork + Copy-On-Write:

메인 프로세스 (서비스 계속)
↓ fork()
자식 프로세스 → RDB 파일 작성 (부모 메모리를 복사하지 않고 참조)
↓ Copy-On-Write
부모가 데이터 수정 시에만 해당 페이지 복사
  • fork() 시점에 메모리 전체를 복사하지 않음 → 메모리 효율적
  • 자식이 쓰는 동안 부모는 계속 요청 처리
  • fork() 자체는 짧은 블로킹 발생 (데이터 크기에 비례)

RDB 장점/단점:

항목내용
장점파일 크기 작음, 복구 빠름, 성능 영향 적음
단점마지막 스냅샷 이후 데이터 손실 가능 (최대 수분)
적합주기적 백업, 데이터 손실 허용 가능한 캐시 용도

모든 쓰기 커맨드를 순서대로 파일에 기록. 재시작 시 파일을 replay하여 복구.

Terminal window
# redis.conf
appendonly yes
appendfilename "appendonly.aof"
# fsync 정책 (핵심 선택)
appendfsync always # 매 쓰기마다 fsync → 가장 안전, 가장 느림
appendfsync everysec # 1초마다 fsync → 최대 1초치 손실, 권장값
appendfsync no # OS에 위임 → 빠르지만 위험

AOF Rewrite (압축):

AOF 파일이 커지면 재작성:
SET a 1 → SET a 2 → SET a 3 → ... (수천 줄)
↓ BGREWRITEAOF
SET a 3 (현재 상태만 기록, 중간 과정 제거)
Terminal window
# 자동 Rewrite 설정
auto-aof-rewrite-percentage 100 # 파일 크기가 2배 될 때
auto-aof-rewrite-min-size 64mb # 최소 64MB 이상일 때

Silent Failure — AOF tail truncation 묵음 손실

기본 설정 aof-load-truncated yes에서 AOF 파일 tail이 잘리면(서버 비정상 종료 직후가 대표) Redis는 에러를 띄우지 않고 마지막 미완성 커맨드 이후를 잘라낸 채 정상 시작한다. 클라이언트 응답·PING 헬스체크·CloudWatch 지표는 모두 정상이지만, 비정상 종료 직전 최대 1초 분량(appendfsync everysec 기준)의 쓰기가 조용히 사라진다 — “에러는 없는데 결과가 잘못된” 전형적 silent failure다.

Terminal window
# 감지: 시작 로그에서 truncation 흔적 grep
journalctl -u redis | grep -E "Truncating the AOF at offset|short read while loading"
# 출력이 있으면 silent 손실 발생, 없으면 손실 없음

결제 큐·재고 차감 등 정합성이 손실보다 우선인 워크로드는 aof-load-truncated no로 두어 부팅 자체를 실패시키고 운영자 개입을 강제한다. 가용성이 우선인 캐시는 기본값(yes)으로 두고 위 grep을 모니터링에 포함한다. — redis.io persistence 문서


RDB + AOF 혼합 모드 (Redis 4.0+, 권장)

섹션 제목: “RDB + AOF 혼합 모드 (Redis 4.0+, 권장)”
Terminal window
# redis.conf
aof-use-rdb-preamble yes # AOF 파일의 앞부분은 RDB 형식으로 저장
  • 재시작 시 RDB로 빠르게 로드 → 이후 AOF 로그만 replay → 빠른 복구 + 낮은 데이터 손실
  • BullMQ 등 작업 큐를 Redis에 저장할 때 반드시 AOF 활성화 필요
상황권장 설정
순수 캐시 (손실 허용)RDB만 (AOF 비활성화)
세션 저장 (최대 1초 손실 허용)AOF everysec
BullMQ 큐 (손실 불허)AOF always 또는 혼합
실시간 랭킹 (재연산 가능)RDB만

단일 Redis 인스턴스의 메모리 한계를 넘어 수평 확장하는 방법.

총 16,384개의 슬롯 → N개의 마스터 노드에 균등 분배
키 → CRC16(key) % 16384 = 슬롯 번호 → 해당 슬롯을 가진 노드로 라우팅
예시 (3 마스터):
노드 A: 슬롯 0 ~ 5460
노드 B: 슬롯 5461 ~ 10922
노드 C: 슬롯 10923 ~ 16383
Terminal window
# 슬롯 확인
redis-cli --cluster check [primary-endpoint]:6379
# 특정 키가 몇 번 슬롯인지 확인
redis-cli CLUSTER KEYSLOT "product:123" # → e.g., 7638 (노드 B 담당)
일반 키: CRC16("user:123") % 16384 → 임의 슬롯
Hash Tag: CRC16("{user}") % 16384 → {user}로 묶인 키는 모두 같은 슬롯
{user}:123, {user}:456, {user}:789 → 모두 동일 슬롯 → MGET/Pipeline 가능
// Hash Tag 활용
const keys = ["{user}:123", "{user}:456", "{user}:789"];
const values = await redis.mget(...keys); // CROSSSLOT 에러 없음
클라이언트 → 노드 A에 요청 (키가 노드 B에 있음)
노드 A → MOVED 7638 [노드B IP]:6379
클라이언트 → 노드 B로 재요청
클라이언트 → 슬롯 맵 캐시 업데이트 (다음 요청부터 직접 접근)

ioredis는 자동으로 클러스터 슬롯 맵을 캐시하고 MOVED/ASK를 처리한다.

import { Cluster } from "ioredis";
const cluster = new Cluster(
[
{ host: "node-a", port: 6379 },
{ host: "node-b", port: 6379 },
{ host: "node-c", port: 6379 },
],
{
redisOptions: {
password: process.env.REDIS_PASSWORD,
},
// 슬롯 맵 갱신 간격
slotsRefreshTimeout: 2000,
slotsRefreshInterval: 10_000,
},
);
마스터 A (슬롯 0~5460) ← 복제 → 슬레이브 A'
마스터 B (슬롯 5461~10922) ← 복제 → 슬레이브 B'
마스터 C (슬롯 10923~16383) ← 복제 → 슬레이브 C'
  • 마스터 장애 시 슬레이브가 자동으로 마스터로 승격 (Failover)
  • 과반수 마스터가 살아있어야 클러스터 정상 동작 (최소 3 마스터 권장)

클러스터 모드 없이 단일 Primary + Replica 구성에서 자동 Failover를 제공한다.

Sentinel 1 Sentinel 2 Sentinel 3
↓ ↓ ↓
Primary Redis
Replica Redis

Sentinel 역할:

  1. Monitoring: Primary/Replica 상태를 주기적으로 확인
  2. Notification: 장애 감지 시 알림
  3. Automatic Failover: Primary 장애 시 Replica를 자동 승격
  4. Configuration Provider: 클라이언트가 현재 Primary 주소를 Sentinel에 질의
Terminal window
# sentinel.conf
sentinel monitor mymaster [primary-ip] 6379 2 # 과반수(2개) 동의 필요
sentinel down-after-milliseconds mymaster 5000 # 5초 무응답 → 장애 판정
sentinel failover-timeout mymaster 60000 # 60초 Failover 타임아웃
// ioredis Sentinel 연결
const redis = new Redis({
sentinels: [
{ host: "sentinel-1", port: 26379 },
{ host: "sentinel-2", port: 26379 },
{ host: "sentinel-3", port: 26379 },
],
name: "mymaster", // Sentinel이 모니터링하는 이름
password: process.env.REDIS_PASSWORD,
});
항목SentinelCluster
사용 목적HA (고가용성)HA + 수평 확장
데이터 분산없음 (모든 데이터가 Primary에)있음 (16,384 슬롯으로 분산)
메모리 한계단일 노드 메모리노드 수 × 단일 노드 메모리
구성 복잡도낮음높음
다중 키 연산제한 없음동일 슬롯 키만 (CROSSSLOT 주의)
적합한 경우수십~수백 GB 이하, 단순 구성수 TB, 초고처리량

결정 임계값 (정량 변환)

위 표의 정성 비교를 운영 의사결정 가능한 수치로 환산하면:

  • 메모리: 단일 인스턴스 maxmemory는 시스템 메모리의 80~90% 권고 (redis.io FAQ). RDB/AOF rewrite 동시 발생 시 최대 2배 사용 가능 → 안전한 실제 데이터 크기 ≈ 인스턴스 메모리 × 0.45.
  • 처리량: 단일 인스턴스 100180k ops/sec 부근에서 CPU 1코어 포화(단일 스레드) (공식 벤치마크). io-threads 4로 2030% 추가 가능, 그 이상은 Cluster 수평 분산이 답.
  • 동시 연결: 약 30,000 연결에서 처리량 ~50% 하락(공식 벤치마크). 그 이상은 connection pool 재설계 또는 read replica 분리.

이 기준으로 내린 결정 예: 평균 70k ops/sec, 데이터 60GB, 동시 연결 5천 워크로드. 메모리 기준 r6g.4xlarge(104GB × 0.45 ≈ 47GB)로도 부족하다. 처리량·연결은 단일도 감당 가능하지만 메모리에서 먼저 깨졌으므로 Cluster 3샤드(r6g.2xlarge × 3, 샤드당 약 23GB)로 결정. 반대로 데이터가 20GB였다면 동일 처리량에서도 Sentinel + r6g.2xlarge가 정답이다 — 즉, “처리량·연결 한계보다 메모리 한계가 먼저 깨지는지”가 Sentinel/Cluster 분기점이다.


Redis 내부에서 배운 원리는 다른 시스템을 이해할 때 그대로 재사용된다.

Redis 원리Redis 적용다른 도메인의 동일 원리
Skip List (O(log N) 탐색)Sorted Set 범위 쿼리LevelDB/RocksDB의 MemTable 인덱스; Cassandra의 파티션 내 정렬; PostgreSQL brin index의 범위 탐색 보완
Copy-On-Write fork() (읽기 시 복사 지연)RDB 스냅샷 저장PostgreSQL MVCC(변경 시점에만 새 버전 생성); Docker 이미지 레이어(변경된 레이어만 새 복사본); Linux fork() 기반 모든 프로세스 복제
I/O Multiplexing (epoll) (단일 스레드로 다수 소켓)단일 스레드 이벤트 루프Node.js 이벤트 루프(libuv+epoll); Nginx Worker 프로세스; Go의 netpoller(I/O 이벤트 다중화)
Hash Slot (CRC16 % 16384) (키→노드 결정론적 매핑)Cluster 데이터 분산Cassandra Consistent Hashing; DynamoDB 파티션 키 해싱; Kafka 파티션 키 해싱(murmur2 % numPartitions)
단일 스레드 원자성 (락 없이 원자적 연산)INCR, MULTI/EXECNode.js의 싱글 스레드 보장; Erlang의 Actor 모델(메시지 직렬화)

새 기술을 만났을 때 던지는 4가지 질문

위 표를 단일 질문(“어떤 Redis 원리와 같은가?”)이 아니라 다음 4축으로 쪼개면, 새 시스템의 내부 구조를 첫날부터 비교 분석할 수 있다.

  1. 인덱스 자료구조 — 정렬·범위 쿼리를 어떤 구조로 처리하는가? (Skip List / B+Tree / LSM-Tree)
  2. 분산 키→노드 매핑 — 키를 노드에 어떻게 결정론적으로 매핑하는가? (Consistent Hashing / Modulo / Range partition)
  3. 읽기-수정-쓰기 원자성 — 락 없이 어떻게 원자성을 보장하는가? (단일 스레드 / 메시지 직렬화 / Optimistic concurrency)
  4. 메모리·디스크 경계 — 변경이 즉시 영속되는가, 지연되는가? (WAL / Copy-On-Write / Write-back cache)

적용 시나리오 1 — Kafka를 4질문으로 분석하기

Kafka를 처음 배울 때 위 질문을 그대로 적용하면:

  • (1) 인덱스: 파티션 내 offset은 정렬된 append-only 로그 → Redis의 quicklist(listpack 연결)와 동일한 sequential 접근.
  • (2) 분산 매핑: murmur2(key) % numPartitions → Redis Cluster의 CRC16(key) % 16384와 정확히 같은 구조. 따라서 파티션 키 설계는 Redis의 Hash Tag 설계와 동일한 트레이드오프(분산 효율 ↔ 동일 파티션 강제)에 빠진다.
  • (3) 원자성: 컨슈머 그룹의 파티션 1개에 멤버 1명 → Redis 단일 스레드 이벤트 루프의 파티션 버전.
  • (4) 영속: producer acks=all은 AOF appendfsync always와 같은 강한 보장.

→ Kafka 입문 첫날부터 “리밸런싱은 Redis의 슬롯 마이그레이션과 같은 비용”, “키 편중은 Hash Tag 남용과 같은 핫스팟”임을 예측할 수 있다.

적용 시나리오 2 — Cassandra를 4질문으로 분석하기

Cassandra의 SSTable + MemTable 구조를 같은 4질문에 넣으면, (1) MemTable은 Skip List(Redis Sorted Set과 동일 O(log N))이고, (2) 키 분산은 Consistent Hashing(Redis Cluster의 Hash Slot보다 일반화된 형태로, 노드 추가 시 재분배 키 수가 더 적음), (4) MemTable flush는 Redis BGSAVE의 fork+COW 대신 LSM-Tree의 immutable file 전환을 사용한다. 이 (4)의 차이가 “Cassandra는 쓰기 우위·단편화 없음, Redis는 읽기 우위·fork 비용 있음”의 근원이다.


AWS ElastiCache는 Redis를 관리형으로 제공한다. 핵심 튜닝 포인트를 정리한다.

Terminal window
# ElastiCache 파라미터 그룹에서 변경 가능한 핵심 값들
# 메모리 관리
maxmemory-policy = allkeys-lru # 캐시 용도의 경우 권장
reserved-memory-percent = 25 # 복제/AOF용 메모리 예약 (%)
# 영속화
appendonly = yes # AOF 활성화 (BullMQ 필수)
appendfsync = everysec # 1초마다 flush
# 연결 관리
maxclients = 65000 # 최대 동시 연결 수
tcp-keepalive = 300 # 유휴 연결 유지 간격 (초)
timeout = 0 # 클라이언트 타임아웃 (0=비활성)
# 자료구조 압축 임계값 (메모리 최적화)
hash-max-listpack-entries = 128
hash-max-listpack-value = 64
zset-max-listpack-entries = 128
zset-max-listpack-value = 64
워크로드권장 노드 타입이유
개발/소규모cache.t3.micro최소 비용
일반 캐시 (수십 GB)cache.r6g.large메모리 최적화, Graviton2
고처리량 (초당 수십만 요청)cache.r6g.xlarge 이상네트워크 대역폭 충분
BullMQ/세션 (영속화 필수)cache.r6g.* + AOF메모리 여유 필요 (AOF 오버헤드)
지표정상 범위경보 기준
EngineCPUUtilization< 50%> 80% 지속 시 알람
DatabaseMemoryUsagePercentage< 75%> 80% 시 즉시 대응
CacheHitRate> 90%< 80% 지속 시 캐시 전략 점검
CurrConnections안정적급증 시 연결 누수 점검
Evictions0 (캐시 외 용도)증가 시 메모리 부족 신호
ReplicationLag< 1초지속 증가 시 복제 지연 점검
Terminal window
# CloudWatch Alarm 생성 예시 (AWS CLI)
aws cloudwatch put-metric-alarm \
--alarm-name "redis-memory-high" \
--metric-name DatabaseMemoryUsagePercentage \
--namespace AWS/ElastiCache \
--statistic Average \
--period 300 \
--threshold 80 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 2 \
--alarm-actions [SNS-ARN]

ElastiCache 클러스터 모드 vs 비클러스터 모드

섹션 제목: “ElastiCache 클러스터 모드 vs 비클러스터 모드”
비클러스터 모드 (Cluster Mode Disabled):
Primary + Replica(s)
→ Sentinel과 유사, 단일 엔드포인트 제공
→ 읽기: Reader Endpoint, 쓰기: Primary Endpoint
클러스터 모드 (Cluster Mode Enabled):
여러 샤드 × (Primary + Replica)
→ Configuration Endpoint 하나로 접속
→ ioredis의 Cluster 클라이언트 필요
// 비클러스터 모드 (ioredis)
const redis = new Redis({
host: process.env.REDIS_PRIMARY_HOST, // Primary Endpoint
port: 6379,
tls: {},
password: process.env.REDIS_AUTH_TOKEN,
});
// 읽기 분산 (Reader Endpoint)
const redisReader = new Redis({
host: process.env.REDIS_READER_HOST, // Reader Endpoint
port: 6379,
tls: {},
password: process.env.REDIS_AUTH_TOKEN,
});
// 클러스터 모드 (ioredis)
const cluster = new Cluster(
[{ host: process.env.ELASTICACHE_CONFIG_ENDPOINT, port: 6379 }],
{
redisOptions: {
tls: {},
password: process.env.REDIS_AUTH_TOKEN,
},
clusterRetryStrategy: (times) => Math.min(times * 200, 2000),
enableOfflineQueue: false, // 연결 끊겼을 때 커맨드 큐잉 비활성화 (Failfast)
},
);

BackOps 환경에서 내부 원리가 필요한 순간

섹션 제목: “BackOps 환경에서 내부 원리가 필요한 순간”
상황관련 원리
BullMQ 큐 데이터가 재시작 후 사라짐AOF 영속화 미설정 → 3-3 참고
ElastiCache CROSSSLOT 에러Hash Slot 원리 → 3-4 참고
Sorted Set 조회가 느려짐Skip List 특성 → 3-1 참고
메모리 사용량이 예상보다 높음ziplist→hashtable 변환 임계값 → 3-1 참고
ElastiCache 노드 장애 시 Failover 속도Sentinel vs Cluster 구조 → 3-5 참고
Redis 재시작 후 복구 시간이 너무 긴 경우RDB + AOF 혼합 모드 → 3-3 참고

항목RedisMemcached
자료구조String, Hash, List, Set, ZSet 등단순 String만
영속화RDB + AOF 지원없음 (재시작 시 전체 손실)
클러스터링내장 Cluster 모드클라이언트 사이드 샤딩만
멀티스레드싱글 스레드 (I/O는 멀티)완전 멀티스레드
Pub/Sub지원미지원
Lua 스크립트지원미지원
메모리 효율자료구조별 오버헤드 있음순수 Key-Value로 효율적
적합한 경우캐시 + 큐 + 세션 + 랭킹초대규모 단순 캐시 (멀티코어 활용)

5.5 장애 시나리오별 트러블슈팅

섹션 제목: “5.5 장애 시나리오별 트러블슈팅”

상세 복구 절차(에러 → 진단 → 복구 runbook)는 §9.5 장애 복구 Runbook에 수록되어 있다. 아래는 장애 유형별 빠른 진단 경로 요약이다.

장애 유형첫 번째 진단 커맨드핵심 원인빠른 대응
AOF 재시작 후 데이터 불일치redis-check-aof appendonly.aofAOF 파일 손상 (비정상 종료 등)redis-check-aof --fix → 재시작
CROSSSLOT 에러CLUSTER KEYSLOT key1멀티 키가 서로 다른 슬롯에 배치Hash Tag {prefix}:key 패턴으로 리팩토링
메모리 급증 + EvictionINFO memory + redis-cli --bigkeysTTL 없는 키 누적 또는 단편화TTL 설정, active-defrag 활성화, maxmemory 증가
특정 요청 레이턴시 급증SLOWLOG GET 10블로킹 커맨드 실행KEYS *SCAN, SMEMBERS 대용량 → SSCAN
복제 지연 (ReplicationLag 증가)INFO replication마스터 처리 과부하 또는 네트워크 병목repl-backlog-size 증가, 읽기를 Replica로 분산

  • 각 자료구조의 인코딩 전환 임계값을 알고 있는가? (OBJECT ENCODING 확인)
  • 단일 스레드 특성상 블로킹 커맨드(KEYS *, 대용량 SMEMBERS)를 피하는가?
  • SCAN을 사용하여 대용량 키 순회를 비블로킹으로 처리하는가?
  • BullMQ 사용 시 AOF가 활성화되어 있는가?
  • appendfsync everysec 이상으로 설정되어 있는가?
  • 혼합 모드(aof-use-rdb-preamble yes)를 사용하여 복구 속도를 최적화했는가?
  • 멀티 키 커맨드(MGET, Pipeline)에서 CROSSSLOT 에러를 처리했는가?
  • Hash Tag를 사용하여 관련 키를 동일 슬롯에 배치했는가?
  • Sentinel 또는 Cluster 구성에서 Failover 시나리오를 테스트했는가?
  • maxmemory-policy를 용도에 맞게 설정했는가?
  • reserved-memory-percent를 AOF + 복제 오버헤드에 맞게 설정했는가?
  • CloudWatch 주요 지표 알람을 설정했는가?
  • 읽기 부하 분산을 위해 Reader Endpoint를 활용하는가?

키워드설명
SDSSimple Dynamic String — Redis의 문자열 내부 구현체
ziplist / listpack소규모 자료구조를 위한 압축 인코딩 형식
Skip ListSorted Set의 범위 쿼리를 O(log N)으로 처리하는 자료구조
I/O Multiplexingepoll/kqueue로 다수 소켓을 단일 스레드에서 처리하는 기법
RDBRedis Database — 특정 시점의 스냅샷 파일
AOFAppend Only File — 모든 쓰기 커맨드를 순서대로 기록하는 영속화
BGSAVE백그라운드 RDB 저장 (fork + Copy-On-Write)
BGREWRITEAOF백그라운드 AOF 재작성 (파일 크기 압축)
Hash SlotRedis Cluster의 데이터 분산 단위 (총 16,384개)
CRC16키를 슬롯 번호로 매핑하는 해시 함수
Hash Tag{tag}:key 형식으로 동일 슬롯 강제 지정
MOVED클라이언트가 다른 노드로 재요청해야 할 때 반환되는 에러
ASK슬롯 이동 중(Resharding) 임시 리다이렉션 에러
SentinelRedis Primary 장애 시 자동 Failover를 수행하는 고가용성 컴포넌트
Configuration EndpointElastiCache 클러스터 모드의 단일 진입점
Reader EndpointElastiCache 비클러스터 모드의 읽기 전용 엔드포인트
reserved-memory-percentAOF/복제를 위해 예약하는 메모리 비율
EngineCPUUtilizationRedis 프로세스의 CPU 사용률 (CloudWatch 지표)
CacheHitRate캐시 적중률 (CloudWatch 지표)


Terminal window
docker run -d --name redis-intern -p 6379:6379 redis:7-alpine
docker exec -it redis-intern redis-cli
# String 인코딩
SET num 42
OBJECT ENCODING num # → int
SET short "hello"
OBJECT ENCODING short # → embstr
SET long "$(python3 -c "print('a'*50)")"
OBJECT ENCODING long # → raw
# Hash 인코딩 (ziplist → hashtable 전환 관찰)
HSET small-hash a 1 b 2
OBJECT ENCODING small-hash # → listpack (또는 ziplist)
# 128개 이상 필드 추가
for i in $(seq 1 130); do redis-cli HSET big-hash field$i value$i; done
OBJECT ENCODING big-hash # → hashtable
# Sorted Set 인코딩
ZADD small-zset 1 "a" 2 "b"
OBJECT ENCODING small-zset # → listpack

스스로 답해보기

위 실험 결과로 다음에 답할 수 있어야 한다.

  1. big-hash가 hashtable로 전환된 임계값은 정확히 몇인가? (CONFIG GET hash-max-listpack-entries와 비교 — 128번째 필드까지 listpack, 129번째에서 전환)
  2. hash-max-listpack-entries를 256으로 올린 뒤 동일 실험을 반복하면 인코딩이 어떻게 달라지며, 그 trade-off는? (listpack 유지 → 메모리 절약, 조회는 O(N)로 악화)
  3. OBJECT ENCODING shortembstr인 키에 1바이트 APPEND를 호출하면 어떤 인코딩으로 바뀌는가? (Redis 3.2+는 embstr이 read-only로 취급되어 즉시 raw로 전환)

메모리 절약 효과 정량 측정 — 다음 단계 실험

§3-1에서 언급한 “메모리 30~50% 절약” 주장을 직접 측정해본다.

Terminal window
# 1. 항상 hashtable로 강제 (임계값 0) → baseline
redis-cli FLUSHALL
redis-cli CONFIG SET hash-max-listpack-entries 0
for i in $(seq 1 100000); do redis-cli HSET h:$i a 1 b 2 c 3 > /dev/null; done
redis-cli INFO memory | grep used_memory_human # baseline 메모리
# 2. 기본 임계값(128)로 복원 → 작은 해시는 listpack 유지
redis-cli FLUSHALL
redis-cli CONFIG SET hash-max-listpack-entries 128
for i in $(seq 1 100000); do redis-cli HSET h:$i a 1 b 2 c 3 > /dev/null; done
redis-cli INFO memory | grep used_memory_human # baseline 대비 감소율 = 실제 절약폭
# 3. 두 used_memory_human 차이가 30~50% 범위에 들어오는지 확인

→ 만약 절약폭이 20% 미만이면 해시당 필드 수가 너무 적거나 값이 너무 크다는 신호이고, 50% 이상이면 ziplist→hashtable 변환 회피의 효과가 그 해시 모양에 특히 큰 것이다. 같은 패턴을 Sorted Set(zset-max-listpack-entries)에도 적용해 비교하면 자료구조별 오버헤드 차이를 직접 본다.

Terminal window
# AOF 활성화 후 커맨드 기록 확인
docker run -d --name redis-aof -p 6380:6379 redis:7-alpine \
redis-server --appendonly yes
docker exec -it redis-aof redis-cli -p 6379
SET test-key "hello"
SET another-key "world"
EXIT
# AOF 파일 내용 확인
docker exec redis-aof cat /data/appendonly.aof
# → *2, $6, SELECT, $1, 0 등 RESP 형식으로 기록된 커맨드 확인
# 수동 RDB 저장
docker exec -it redis-aof redis-cli BGSAVE
docker exec redis-aof ls -la /data/ # dump.rdb 파일 생성 확인
Terminal window
# 100만 건 Sorted Set 생성 (성능 테스트)
redis-cli -n 0 EVAL "
for i=1,1000000 do
redis.call('zadd', 'bigzset', i, 'member:'..i)
end
" 0
# 범위 쿼리 성능 측정
redis-cli --latency-history -i 1 -p 6379
# 순위 범위 조회 (O(log N + M))
time redis-cli ZRANGE bigzset 0 99 WITHSCORES
# 점수 범위 조회
time redis-cli ZRANGEBYSCORE bigzset 500000 500100 WITHSCORES
Terminal window
# 로컬 클러스터 시뮬레이션
docker network create redis-cluster-net
# 6개 노드 실행 (3 master + 3 replica)
for port in 7001 7002 7003 7004 7005 7006; do
docker run -d --name redis-$port --net redis-cluster-net \
-p $port:6379 redis:7-alpine \
redis-server --cluster-enabled yes --cluster-config-file nodes.conf \
--cluster-node-timeout 5000 --appendonly yes
done
# 클러스터 생성
docker exec redis-7001 redis-cli --cluster create \
redis-7001:6379 redis-7002:6379 redis-7003:6379 \
redis-7004:6379 redis-7005:6379 redis-7006:6379 \
--cluster-replicas 1 --cluster-yes
# 슬롯 확인
docker exec -it redis-7001 redis-cli CLUSTER INFO
docker exec -it redis-7001 redis-cli CLUSTER NODES
# Hash Tag 실습
docker exec -it redis-7001 redis-cli -c
CLUSTER KEYSLOT "{user}:123"
CLUSTER KEYSLOT "{user}:456"
# 같은 슬롯 번호인지 확인
MSET "{user}:123" "alice" "{user}:456" "bob" # CROSSSLOT 없음

실제 운영 환경에서 자주 마주치는 3가지 장애 유형별로 에러 감지 → 진단 → 복구 절차를 정리한다.


장애 1: AOF 손상 — 재시작 후 데이터 불일치

섹션 제목: “장애 1: AOF 손상 — 재시작 후 데이터 불일치”

증상

  • Redis 재시작 후 일부 키가 사라지거나 값이 이전 상태로 롤백됨
  • 로그에 Bad file format reading the append only file 또는 Unexpected end of file 출력

진단

Terminal window
# 1. AOF 파일 무결성 검사
redis-check-aof appendonly.aof
# 출력 예시:
# AOF analyzed: size=1234567, ok_up_to=1230000, diff=4567
# This will shrink the AOF from 1234567 bytes, with 4567 bytes, to 1230000 bytes
# 2. 영속화 상태 확인
redis-cli INFO persistence
# aof_enabled: 1
# aof_last_rewrite_status: ok ← err이면 마지막 재작성 실패
# aof_last_bgrewrite_status: ok

복구

Terminal window
# 1. Redis 서비스 중지
systemctl stop redis
# 2. AOF 파일 백업 (복구 전 반드시)
cp /var/lib/redis/appendonly.aof /var/lib/redis/appendonly.aof.bak
# 3. 손상된 부분 제거 후 수정 (자동으로 손상 지점 이후를 잘라냄)
redis-check-aof --fix /var/lib/redis/appendonly.aof
# 출력 예시:
# Successfully truncated AOF
# 4. Redis 재시작 및 데이터 확인
systemctl start redis
redis-cli INFO keyspace # DB별 키 수 확인

참고: --fix 옵션은 손상 지점 이후 데이터를 버린다. 손실 범위를 먼저 확인하고 비즈니스 팀과 협의 후 실행한다. — redis.io/docs/latest/operate/oss_and_stack/management/persistence/


장애 2: CROSSSLOT 에러 — 클러스터 모드 다중 키 연산 실패

섹션 제목: “장애 2: CROSSSLOT 에러 — 클러스터 모드 다중 키 연산 실패”

증상

(error) CROSSSLOT Keys in request don't hash to the same slot

Pipeline, MGET, MSET, Lua 스크립트에서 여러 키가 서로 다른 슬롯에 속할 때 발생.

진단

Terminal window
# 각 키의 슬롯 번호 확인
redis-cli CLUSTER KEYSLOT "user:123" # → e.g., 5474
redis-cli CLUSTER KEYSLOT "order:456" # → e.g., 3278 ← 다른 슬롯
# 슬롯 분포 확인
redis-cli --cluster check [primary-endpoint]:6379

복구 — Hash Tag 패턴으로 키 리팩토링

Terminal window
# 기존: 슬롯이 서로 다른 독립 키
SET user:123 "alice"
SET order:456 "item-A"
MGET user:123 order:456 # → CROSSSLOT 에러
# 변경: Hash Tag로 동일 슬롯 강제 지정
# CRC16("{user_123}") % 16384 로 슬롯이 결정 → 두 키 모두 동일 슬롯
SET "{user_123}:profile" "alice"
SET "{user_123}:order" "item-A"
MGET "{user_123}:profile" "{user_123}:order" # → 정상 동작
// ioredis에서 Hash Tag 활용
const userId = "123";
const keys = [`{user_${userId}}:profile`, `{user_${userId}}:order`];
const [profile, order] = await redis.mget(...keys); // CROSSSLOT 없음

장애 3: 메모리 급증 후 Eviction 발생

섹션 제목: “장애 3: 메모리 급증 후 Eviction 발생”

증상

  • CloudWatch Evictions 지표 급증
  • CloudWatch CacheHitRate 하락 (Eviction된 키 재조회 시 Cache Miss)
  • 애플리케이션 레이턴시 증가 (DB fallback 빈도 증가)

Silent Failure 핵심 — eviction은 클라이언트에 에러를 반환하지 않는다

allkeys-lru·volatile-lrunoeviction 외 정책에서는 maxmemory 초과 시 키가 에러 없이 조용히 사라진다. 애플리케이션 입장에서 GET cache:user:123이 nil을 반환하면 보통 ‘TTL 만료’로 오인하지만, 실제로는 LRU에 의해 evict된 케이스가 섞여 있다 — 두 케이스를 코드만 봐서는 구분할 수 없다. 구분은 INFO stats의 누적 카운터 비교로:

Terminal window
redis-cli INFO stats | grep -E "evicted_keys|expired_keys"
# evicted_keys:1234 ← 증가분이 실제 silent eviction 발생 건수
# expired_keys:98765 ← TTL 만료 정상 건수

세션·BullMQ 큐처럼 “조용히 사라지면 안 되는” 데이터는 maxmemory-policy noeviction으로 두어 OOM 에러로 명시적으로 노출시킨다 (write 실패가 silent eviction보다 디버깅이 쉽다). — redis.io eviction 문서

진단

Terminal window
# 1. 메모리 사용 현황 확인
redis-cli INFO memory
# used_memory_human: 7.50G ← 실제 사용량
# mem_fragmentation_ratio: 1.8 ← 1.5 이상이면 단편화 심각, 2.0 이상이면 즉시 대응 필요
# maxmemory_human: 8.00G ← 설정된 최대값
# 2. 메모리 상태 진단 (Redis 4.0+)
redis-cli MEMORY DOCTOR
# 출력 예시:
# "Hi Sam, I have detected a few issues in this Redis instance memory implants."
# "High allocator frag ratio: This instance has a memory fragmentation > 1.4. ..."
# 3. 대용량 키 탐색
redis-cli --bigkeys
# 출력 예시:
# Biggest string found 'session:abc123' has 512000 bytes
# Biggest hash found 'user-cache:all' has 150000 fields
# 4. 핫키 및 유휴 키 확인
redis-cli OBJECT FREQ key-name # LFU 정책일 때 접근 빈도
redis-cli OBJECT IDLETIME key-name # 마지막 접근 후 경과 시간 (초)

복구

Terminal window
# 옵션 A: TTL 없는 키에 만료 설정 (무기한 키 정리)
redis-cli TTL "some-key" # → -1이면 TTL 없음
redis-cli EXPIRE "some-key" 86400 # 24시간 TTL 설정
# 옵션 B: 압축 인코딩 임계값 조정 (메모리 절약)
# hash-max-listpack-entries를 늘리면 listpack 유지 범위 확대 → 메모리 절약
redis-cli CONFIG SET hash-max-listpack-entries 256
redis-cli CONFIG SET zset-max-listpack-entries 256
# 옵션 C: maxmemory 증가 (임시 조치)
redis-cli CONFIG SET maxmemory 12gb
# 옵션 D: 단편화 해소 (Redis 4.0+ Active Defrag)
redis-cli CONFIG SET activedefrag yes
redis-cli CONFIG SET active-defrag-ignore-bytes 100mb
redis-cli CONFIG SET active-defrag-threshold-lower 10

단편화 비율 판단 기준:

mem_fragmentation_ratio상태대응
1.0 ~ 1.5정상모니터링 유지
1.5 ~ 2.0단편화 심각Active Defrag 활성화 검토
2.0 이상즉시 대응 필요Active Defrag 활성화 + maxmemory 증가 + 노드 스케일업 검토
1.0 미만Swap 사용 중즉시 노드 스케일업 (성능 급락 위험)

Terminal window
# 블로킹 커맨드 탐지 (단일 스레드 블로킹 원인 식별)
# 기본 임계값은 10ms (slowlog-log-slower-than)
redis-cli SLOWLOG GET 10
# 출력 예시:
# 1) 1) (integer) 42 ← 로그 ID
# 2) (integer) 1713500000 ← 타임스탬프
# 3) (integer) 150234 ← 실행 시간 (마이크로초) → 150ms
# 4) 1) "KEYS" ← 실행된 커맨드
# 2) "*"
# Slowlog 임계값 설정 (10ms = 10000 마이크로초)
redis-cli CONFIG SET slowlog-log-slower-than 10000
redis-cli CONFIG SET slowlog-max-len 128
# 지연 측정 (이벤트별 최대 지연)
redis-cli LATENCY LATEST
# 출력 예시:
# 1) 1) "command" ← 이벤트 이름
# 2) (integer) 1713500000 ← 마지막 발생 시각
# 3) (integer) 150 ← 최대 지연 (ms)
# 4) (integer) 12 ← 평균 지연 (ms)
# 이벤트별 지연 이력 확인
redis-cli LATENCY HISTORY command

참고: SLOWLOGLATENCY 커맨드는 Redis 2.2.12+ / 2.8.13+ 이상에서 사용 가능. — redis.io/docs/latest/commands/slowlog/


Redis 내부 원리를 이해하면 세 가지 영역에서 실질적인 차이를 만들 수 있다.

자료구조 최적화:

  • 각 자료구조의 압축 인코딩 임계값을 조정하면 메모리를 30~50% 절약 가능
  • Sorted Set의 Skip List 특성을 이해하면 올바른 범위 쿼리를 설계할 수 있음

영속화 전략:

  • BullMQ 등 손실이 허용되지 않는 데이터: AOF always 또는 혼합 모드
  • 순수 캐시: RDB만으로 충분
  • aof-use-rdb-preamble yes로 빠른 복구 + 낮은 손실을 동시에 달성

클러스터 운영:

  • Hash Slot 원리를 이해하면 CROSSSLOT 에러를 Hash Tag로 예방
  • Sentinel vs Cluster 구분 기준: 수평 확장 필요 여부
  • ElastiCache 파라미터 그룹 튜닝으로 메모리 효율과 성능을 최적화

복원력 패턴(Cache Stampede, Circuit Breaker, Rate Limiting)은 L6/redis-cache-basics.md를 참고한다.