Redis 복원력 패턴의 선택 기준
Redis는 단순한 빠른 저장소가 아니라 DB 과부하를 흡수하고 장애 전파를 끊는 복원력 인프라로 다뤄야 한다. 이 스크립트는 캐시 전략, Fallback, Circuit Breaker, Rate Limiting, Cache Stampede 방지, 운영 장애 포인트를 선택 기준 중심으로 정리한다.
Script Companion
오디오와 함께 스크립트 보기
- 01
Redis를 복원력 관점에서 볼 때 출발점은 캐시 전략 선택이다. Cache Aside, 또는 Lazy Loading은 가장 일반적인 방식이고 구현 복잡도가 낮지만, 첫 요청은 MISS 때문에 느릴 수 있고 TTL이 끝나기 전까지 stale 데이터가 남을 수 있다. Write Through는 쓰기 시 DB와 캐시를 함께 갱신해 일관성이 높지만 쓰기 성능은 느려진다. Write Back, 또는 Write Behind는 캐시에 먼저 쓰고 DB는 나중에 반영하므로 쓰기 성능은 매우 빠르지만, 장애 시 데이터 유실 위험이 있다.
- 02
선택 기준은 트래픽 성격과 손실 허용 여부로 나뉜다. 읽기 많은 일반 API라면 Cache Aside가 자연스럽고, 읽기와 쓰기가 균형을 이루며 일관성이 중요하면 Write Through가 맞다. 조회수처럼 초고빈도 쓰기이고 허용 가능한 손실이 있다면 Write Back이 후보가 된다. 여기서 Redis는 빠른 캐시라는 장점만 보는 것이 아니라, stale 데이터, 쓰기 지연, 장애 시 유실 같은 경계 조건까지 함께 보아야 한다.
- 03
Redis 장애 시 가장 먼저 떠올릴 수 있는 패턴은 Fallback이다. Redis 연결 실패를 감지하면 DB를 직접 조회하는 방식이지만, 핵심은 Redis 장애가 DB 장애로 연쇄되지 않게 격리하는 것이다. hit rate 95% 캐시가 죽으면 DB는 정상 트래픽의 20배 burst를 견뎌야 Fallback이 안전하다. DB headroom이 5배보다 작으면 Fallback을 금지하고 503 반환과 circuit 회복 대기가 더 안전하며, 5배에서 20배 사이는 Rate Limiter를 반드시 함께 두어야 한다.
- 04
Fallback에서 더 까다로운 실패는 Redis가 완전히 죽지 않고 느려지는 silent failure다. 예를 들어 평소 0.5ms 응답이 200ms로 늘어나도 명시적 connection refuse가 없으면 catch 블록이 발동하지 않을 수 있다. 이때 Node.js의 event loop만 막히고 전체 API p99가 커지는데, Circuit Breaker는 실패로 인식하지 못한다. commandTimeout이 없으면 Redis 지연이 에러가 되지 않아 차단이 어렵기 때문에, ioredis 설정 뒤 latency history와 Circuit Breaker의 open 이벤트 로그가 지연 burst와 맞물리는지 확인해야 한다.
- 05
Circuit Breaker와 Cache 조합은 Redis 호출 자체를 상태 기계로 다루는 접근이다. Redis 응답이 연속 N회 실패하면 일정 시간 Redis 호출을 차단하고, 이후 다시 시도하면서 Closed, Open, Half-Open 상태를 전환한다. 단순 Fallback은 구현이 쉽지만 Redis 상태를 직접 추적해야 한다. 반대로 Circuit Breaker는 상태 전환이 자동화되고 모니터링이 명확하므로 프로덕션에서 권장된다. 여기서 목표는 Redis 실패를 빨리 감지하는 것보다, 실패한 의존성으로 계속 요청을 보내 전체 요청 경로를 느리게 만들지 않는 것이다.
- 06
이 패턴은 Redis에만 갇히지 않는다. Circuit Breaker는 opossum으로 Redis 호출을 차단하는 것과 유사하게, HTTP 클라이언트에서는 axios retry와 연속 실패 시 호출 차단으로 나타나고, DB 커넥션 풀에서는 HikariCP connectionTimeout 초과 시 차단으로 나타난다. API Gateway와 NGINX에서는 proxy_next_upstream 실패 시 업스트림 차단이 같은 사고방식에 가깝다. Rate Limiting도 Redis의 INCR과 EXPIRE, HTTP의 token_bucket, DB의 max_pool_size, NGINX의 limit_req_zone, API Gateway Usage Plan으로 전이된다.
- 07
Cache Stampede 방지는 캐시 키가 만료되는 순간 요청이 한꺼번에 DB로 몰리는 문제를 다룬다. 가장 간단한 방법은 TTL Jitter로, 여러 키가 같은 시점에 만료되지 않도록 흔들어 주는 방식이다. 단일 키이고 DB 조회 비용이 크면 SETNX 기반 Mutex Lock이 더 강한 선택지가 된다. 트래픽이 지속적으로 높은 Hot Key라면 Probabilistic Early Expiry, 또는 X-Fetch가 후보가 된다. 같은 전이 맥락에서 HTTP 클라이언트에는 ETag와 If-None-Match, DB에는 SELECT FOR UPDATE, CDN에는 stale-while-revalidate가 대응된다.
- 08
Rate Limiting은 Redis를 시스템 보호 장치로 쓰는 대표 예다. 고정 윈도우 방식은 INCR과 EXPIRE의 원자적 조합으로 구현하며, 구현 난이도와 메모리 사용은 낮지만 정확도는 낮다. 슬라이딩 윈도우 Rate Limiter는 Sorted Set을 활용하고, 더 정확한 트래픽 제어가 필요할 때 적합하지만 구현 난이도와 메모리 사용이 높다. 분산 Lock은 멀티 인스턴스 환경에서 동일 작업이 중복 실행되지 않도록 막는 장치이며, SETNX를 통해 동시 실행을 격리한다.
- 09
자료구조 선택도 복원력과 연결된다. String은 SET, GET, SETEX, INCR을 통해 Rate Limiting이나 JWT 블랙리스트에 쓰이고, Hash는 유저 프로필과 권한 캐싱처럼 필드 단위 갱신에 맞다. List는 재시도가 필요 없는 간이 작업 큐에, Set은 로그아웃 토큰 블랙리스트와 태그 교집합에, Sorted Set은 실시간 랭킹과 슬라이딩 윈도우 Rate Limit에 연결된다. BackOps 환경에서는 API 응답 캐싱, JWT 블랙리스트, 분산 Lock, Cache Stampede 방지, Circuit Breaker, BullMQ 백엔드 같은 형태로 나타난다.
- 10
운영 장애는 증상과 원인을 분리해서 봐야 한다. Cache Stampede는 인기 캐시 키가 만료되는 순간 수백 개 요청이 DB로 몰리는 증상이고, 원인은 MISS 이후 모든 요청이 동시에 DB 조회를 시도하는 데 있다. 메모리 초과, 즉 OOM은 TTL 미설정, 대용량 데이터 저장, 메모리 Eviction 정책 미설정에서 자주 시작되며 DatabaseMemoryUsagePercentage 80% 초과 알람이 언급된다. Redis Cluster 모드의 CROSSSLOT 에러는 여러 키를 다루는 커맨드에서 키들이 같은 슬롯에 있지 않을 때 발생한다.
- 11
정리하면 Redis는 DB 앞의 빠른 캐시이면서, 장애 전파를 끊고 부하를 제한하는 복원력 레이어다. 캐시 전략은 읽기 중심이면 Cache Aside, 일관성이 중요하면 Write Through, 초고빈도 쓰기와 손실 허용이 있으면 Write Back으로 나뉜다. 운영에서는 모든 키에 TTL을 두고, maxmemory-policy allkeys-lru를 설정하며, KEYS 별표 대신 SCAN을 사용해 블로킹을 피한다. Redis 클라이언트는 싱글톤으로 관리해 연결 누수를 막고, 내부 구조와 RDB, AOF, 클러스터 운영은 L8 redis-internals.md 범위로 넘긴다.
같은 레이어