이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Redis 캐시를 실전 서비스에 넣기 전 어떤 설계 항목을 문서화해야 하는가?
  • key, TTL, value 크기, serializer, 장애 fallback, 운영 지표는 어떻게 연결되는가?
  • 개인 프로젝트와 기업 운영에서 Redis 캐시 설계 수준은 어떻게 달라지는가?

개요

Redis 캐시 실전 설계는 @Cacheable을 붙이는 작업이 아니다. 캐시할 데이터, key 형식, TTL, value 크기, invalidation, 장애 fallback, 운영 지표, 배포 후 검증을 정하는 일이다.

상품 상세 API의 p95를 낮추기 위해 Redis를 도입한다고 하자. 설계 문서에는 “Redis 사용”이 아니라 “상품 상세 중 어떤 필드를 캐시하고, 가격과 재고는 어떻게 다루며, Redis 장애 시 DB fallback을 허용하는가?”가 남아야 한다.

요구사항 정리

먼저 캐시 대상 API를 구체화한다.

API: GET /api/products/{productId}
현재 p95: 420ms
목표 p95: 120ms
peak read: 5,000 QPS
write: 상품 설명 변경 10 QPS 이하, 가격 변경 50 QPS 이하
stale 허용: 설명 5분, 가격 30초 이하, 재고 캐시 제외
장애 정책: Redis 장애 시 DB fallback, DB 장애 시 설명 stale fallback 가능

이 정도가 있어야 TTL과 key를 정할 수 있다. 성능 목표만 있고 stale 허용 범위가 없으면 캐시 정책은 반쪽이다.

Key 설계

key는 충돌 없이 읽히고 운영자가 추적할 수 있어야 한다.

product:detail:v1:{productId}
product:price:v1:{productId}
product:null:v1:{productId}

key에 version을 넣으면 value 구조가 바뀔 때 구버전 캐시와 충돌하지 않는다. prefix는 도메인과 데이터 종류를 드러내야 한다.

나쁜 key 예시는 다음이다.

detail:1001
cache:1001
product:1001

운영 중 key만 봐서는 어떤 API와 value 구조인지 알기 어렵다.

Value 설계

캐시 value는 너무 크면 Redis memory와 network를 압박한다.

{
  "productId": 1001,
  "name": "Keyboard",
  "description": "...",
  "sellerName": "acme",
  "updatedAt": "2026-06-30T10:00:00+09:00"
}

상품 상세에 리뷰 목록, 추천 상품, 판매자 전체 프로필까지 넣으면 value가 커지고 invalidation 범위도 넓어진다. 캐시 value는 API 응답 전체가 아니라 안정적으로 재사용할 수 있는 조각으로 나누는 편이 나을 수 있다.

serializer도 정해야 한다.

  • JSON: 사람이 읽기 쉽고 디버깅이 쉽다.
  • binary serializer: 크기와 속도에 유리할 수 있지만 호환성과 디버깅 비용이 있다.
  • Java native serialization: 장기 운영과 호환성 측면에서 피하는 편이 좋다.

TTL과 메모리 추정

메모리 추정은 간단히라도 해야 한다.

hot product count = 200,000
average value size = 4KB
raw data = 800MB
Redis overhead + fragmentation 여유 = 2배
필요 메모리 = 약 1.6GB
replica 포함 = 최소 3.2GB 이상

TTL은 메모리와 hit ratio를 함께 바꾼다. TTL이 길면 hit ratio는 올라가지만 stale과 memory가 늘어난다. TTL이 짧으면 stale은 줄지만 miss와 DB 부하가 늘어난다.

cache:
  product-detail:
    key-prefix: product:detail:v1
    ttl: 5m
    ttl-jitter: 60s
  product-price:
    key-prefix: product:price:v1
    ttl: 30s
    invalidation: ProductPriceChanged
  product-stock:
    cache: false

재고를 캐시하지 않는 것도 설계 결정이다. 모든 데이터를 Redis에 넣는 것이 좋은 설계는 아니다.

Spring 구현 경계

Spring Cache 추상화를 쓰더라도 정책을 코드 주변에 드러낸다.

@Cacheable(
    cacheNames = "productDetail",
    key = "'product:detail:v1:' + #productId",
    unless = "#result == null"
)
public ProductDetail getProductDetail(Long productId) {
    return productRepository.findDetail(productId)
        .orElseThrow(ProductNotFoundException::new);
}

쓰기 경로는 캐시 삭제를 포함해야 한다.

@Transactional
public void updateProductDescription(Long productId, String description) {
    productRepository.updateDescription(productId, description);
    productEventPublisher.publish(new ProductDescriptionChanged(productId));
}

이벤트 consumer에서 삭제한다.

public void handle(ProductDescriptionChanged event) {
    redisTemplate.delete("product:detail:v1:" + event.productId());
}

이 consumer는 중복 이벤트를 받아도 안전해야 한다. delete는 보통 멱등적이므로 이벤트 중복에는 강하지만, 이벤트 유실에는 취약하다. 중요한 데이터라면 재시도 가능한 메시지나 outbox를 검토한다.

Redis 장애 정책

Redis 장애 시 세 가지 선택이 있다.

선택장점위험
요청 실패DB 보호사용자 영향이 즉시 커진다.
DB fallback사용자 기능 유지DB가 갑자기 피크 read를 받는다.
stale local fallback핵심 read 유지오래된 값과 메모리 관리 문제가 있다.

상품 상세는 DB fallback이 가능할 수 있다. 하지만 Redis 장애가 피크 시간에 발생하면 DB가 평소보다 10배 이상의 read를 받을 수 있다.

peak read = 5,000 QPS
cache hit = 90%
평소 DB read = 500 QPS
Redis 장애 후 DB fallback = 5,000 QPS

DB가 5,000 QPS를 못 버티면 fallback은 장애를 DB로 이동시킨다. 이 경우 rate limit, stale fallback, 비핵심 필드 생략을 함께 둔다.

운영 지표

Redis 캐시 운영 지표는 hit ratio, get/set latency, Redis CPU, used memory, fragmentation, eviction count, expired keys, keyspace cardinality, hot key 후보, command error, DB fallback QPS, invalidation event lag를 함께 본다.

hit ratio가 높아도 Redis CPU가 90%이면 위험하다. hit ratio가 낮아도 DB가 충분히 버티고 stale 문제를 줄이는 선택이라면 허용할 수 있다.

개인 프로젝트 기준

개인 프로젝트에서는 캐시 대상 API, key 형식, TTL, 캐시 제외 데이터, DB 업데이트 시 삭제 방법, Redis 장애 시 동작, 간단한 hit/miss 로그를 문서화하면 충분히 좋다.

README에 “상품 상세 설명은 5분 TTL로 캐시하고, 가격 변경 시 key를 삭제하며, 재고는 캐시하지 않는다”라고 적을 수 있어야 한다.

기업 운영 기준

기업 운영에서는 Redis sizing 근거, eviction policy, cluster 또는 replica 구성, hot key 탐지, 장애 시 DB fallback 한계, invalidation 실패 알림, key version 전환 계획, 데이터 민감도별 stale 허용 정책이 필요하다.

Redis는 빠른 저장소지만 운영 책임이 없는 저장소가 아니다.

위험 신호!

  • key naming 규칙이 없어 운영자가 key를 보고 의미를 알 수 없다.
  • value 크기와 key 수를 계산하지 않고 Redis 용량을 잡는다.
  • Redis 장애 시 모든 요청을 DB fallback으로 보내도록 둔다.
  • 가격, 재고, 권한을 같은 TTL로 캐시한다.
  • invalidation 실패를 감지하거나 재처리할 방법이 없다.

확인 질문

확인 질문

  • 확인 질문: Redis 캐시 설계 문서에 반드시 들어가야 하는 것은 무엇인가?
    • 답변: 대상 API, key 형식, TTL, value 크기, invalidation 방식, 장애 fallback, 운영 지표다.
  • 확인 질문: Redis 장애 시 DB fallback이 항상 안전하지 않은 이유는 무엇인가?
    • 답변: 평소 캐시가 흡수하던 read traffic이 한 번에 DB로 들어가 DB 장애로 전파될 수 있기 때문이다.
  • 확인 질문: key version을 두는 이유는 무엇인가?
    • 답변: value 구조나 의미가 바뀔 때 기존 캐시와 충돌하지 않고 점진적으로 전환하기 위해서다.

참고 문서