전체 목록
데이터베이스Medium#110

Redis를 캐시로 사용할 때 주요 캐싱 전략과 주의사항을 설명해주세요.

#Redis#캐시#DB#성능

답변 포인트

Cache Aside, Write Through, TTL, 캐시 무효화를 중심으로 생각해보세요.

정답 및 해설

빠른 요약

Redis 캐싱 전략에는 Cache Aside, Write Through, Write Behind 등이 있습니다. Cache Aside는 캐시를 먼저 조회하고 없으면 DB에서 읽어 캐시에 저장하는 방식으로 가장 흔합니다.

Redis는 메모리 기반 key-value 저장소로, 빠른 조회가 필요한 데이터를 캐싱할 때 자주 사용됩니다. 하지만 캐시는 원본 데이터베이스와의 일관성, 만료 정책, 장애 상황을 함께 고려해야 합니다.

Cache Aside

애플리케이션이 캐시를 먼저 조회하고, 없으면 DB에서 읽은 뒤 캐시에 저장하는 방식입니다.

TypeScript
async function getUser(id: string) {
  const cached = await redis.get(`user:${id}`);
  if (cached) return JSON.parse(cached);

  const user = await userRepository.findById(id);
  await redis.set(`user:${id}`, JSON.stringify(user), { EX: 300 });

  return user;
}

장점은 구현이 단순하고 읽기 중심 서비스에 적합하다는 점입니다. 단점은 캐시 미스 시 DB 부하가 발생하고, 데이터 변경 시 캐시 무효화를 직접 처리해야 한다는 점입니다.

Write Through

쓰기 요청이 들어오면 DB와 캐시에 동시에 반영합니다.

TypeScript
async function updateUser(id: string, input: UpdateUserInput) {
  const user = await userRepository.update(id, input);
  await redis.set(`user:${id}`, JSON.stringify(user), { EX: 300 });
  return user;
}

읽기 일관성은 좋아지지만, 쓰기 경로가 느려질 수 있습니다.

Write Behind

먼저 캐시에 쓰고, DB 반영은 비동기로 지연 처리합니다. 쓰기 성능은 좋지만 캐시 장애나 큐 유실 시 데이터 손실 위험이 있으므로 신중하게 사용해야 합니다.

캐시 무효화

데이터가 변경되면 관련 캐시를 삭제하거나 갱신해야 합니다.

TypeScript
async function changeNickname(id: string, nickname: string) {
  await userRepository.changeNickname(id, nickname);
  await redis.del(`user:${id}`);
}

무효화 대상이 여러 개인 경우 키 설계가 중요합니다.

Text
user:10
user:10:posts
post:list:popular

TTL 설정

TTL이 없으면 오래된 데이터가 계속 남거나 메모리가 가득 찰 수 있습니다.

TypeScript
await redis.set("ranking:daily", JSON.stringify(ranking), { EX: 60 });

데이터 성격에 따라 TTL을 다르게 둡니다.

  • 사용자 프로필: 수분
  • 인기 게시글: 수십 초
  • 설정값: 수십 분 이상
  • 인증/인가 데이터: 보안 요구에 맞춰 짧게

Cache Stampede

인기 키가 동시에 만료되면 많은 요청이 한꺼번에 DB로 몰릴 수 있습니다.

해결 방법:

  • TTL에 랜덤 지터 추가
  • 락을 사용해 한 요청만 DB를 조회
  • 만료 전 백그라운드 갱신
  • stale 데이터를 잠시 허용
TypeScript
const ttl = 300 + Math.floor(Math.random() * 60);
await redis.set(key, value, { EX: ttl });

주의사항

Redis는 빠르지만 영구 저장소의 대체재가 아닙니다. 캐시 데이터는 언제든 사라질 수 있다고 가정해야 하며, 원본 데이터는 DB에 있어야 합니다. 또한 사용자별 민감 정보를 캐싱할 때는 키 충돌, TTL, 접근 권한을 엄격히 관리해야 합니다.

정리

Redis 캐싱은 읽기 성능을 크게 개선하지만, 캐시 무효화와 만료 전략이 핵심입니다. Cache Aside를 기본으로 시작하고, 쓰기 일관성 요구와 트래픽 패턴에 따라 Write Through, pre-warming, stampede 방지 전략을 추가하는 것이 일반적입니다.

추가로 고려할 운영 포인트

Redis 캐시는 코드 한 줄로 성능을 올려주지만, 운영에서는 키 설계와 장애 대응이 더 중요합니다.

Text
  
user:{userId}:profile:v1
post:list:popular:{date}
product:{productId}:stock-cache

버전을 키에 포함하면 데이터 구조가 바뀌었을 때 전체 삭제 없이 자연스럽게 새 캐시로 전환할 수 있습니다.

장애 시나리오

  • Redis 장애: DB로 fallback할 것인지, 일부 기능을 제한할 것인지 결정합니다.
  • 캐시 대량 만료: TTL jitter와 background refresh로 DB 스파이크를 줄입니다.
  • 큰 value 저장: 네트워크 비용과 직렬화 비용 때문에 오히려 느려질 수 있습니다.
  • hot key: 특정 키에 트래픽이 몰리면 Redis 단일 shard가 병목이 될 수 있습니다.

실무 체크리스트

  • 모든 캐시에는 소유자와 만료 정책이 있는가?
  • 개인정보/권한 정보가 다른 사용자에게 섞일 가능성은 없는가?
  • 캐시 삭제 실패 시 데이터 불일치를 허용할 수 있는가?
  • hit ratio, latency, evicted keys를 모니터링하고 있는가?

관련 질문

같은 카테고리/태그 기준