티스토리 뷰

안녕하세요. 이 글에서는 Redis를 캐시로 활용하여 읽기 성능 병목을 완화하는 방법을 설명합니다. Redis 공식 Documentation과 Spring Data Redis 공식 문서를 기준으로, 캐싱의 목적·설계 판단 기준·실무 적용 시 주의점을 중심으로 정리합니다.

 

본 글의 목적은 “기술적으로 가능한 모든 패턴”을 나열하는 것이 아니라 과하지 않으면서도 안정적인 캐시 설계 기준을 제시하는 것입니다.

 


 

캐시를 도입하는 이유와 전제 조건

대규모 트래픽 환경에서 RDBMS는 다음과 같은 이유로 읽기 병목이 발생할 수 있습니다.

  • 디스크 기반 I/O는 메모리 접근보다 느림
  • 복잡한 JOIN, 집계 쿼리는 CPU와 I/O 소모가 큼
  • 동시 접근 증가 시 락 경합 및 대기 시간 증가

Redis 공식 문서에 따르면, Redis는 인메모리 기반 데이터 구조 저장소로 디스크 기반 데이터베이스 대비 매우 빠른 접근 속도를 제공합니다. 다만, 캐시는 성능과 데이터 정합성 사이의 트레이드오프입니다. 

  • 캐시를 사용하면 읽기 성능은 향상됨
  • 대신 데이터 변경 시 캐시 무효화 전략이 필요
  • 모든 데이터를 캐시하는 것은 바람직하지 않음

따라서 캐시는 선택적으로 도입해야 합니다.

 


 

Redis가 캐시로 사용되는 이유 

Redis가 캐시로 널리 사용되는 이유는 다음과 같습니다.

  • 데이터가 메모리에 저장되어 빠른 접근 가능
  • 단순한 Key-Value 모델
  • TTL(Time To Live)을 통한 자동 만료 지원
  • 단일 스레드 이벤트 루프 기반으로 예측 가능한 성능 특성

 

주의할 점

  • Redis의 “대부분 O(1)”이라는 표현은 자료구조와 연산에 따라 다름
  • 단일 스레드는 락이 없다는 장점이 있지만, CPU를 오래 점유하는 연산이 있으면 전체 처리량이 저하될 수 있음

Redis는 “무조건 빠른 만능 저장소”가 아니라 짧은 연산 + 빠른 응답에 적합한 캐시입니다.

 


 

캐싱 대상 선정의 핵심 기준

캐시 설계에서 가장 중요한 것은 무엇을 캐시할 것인가입니다.

 

3.1 캐싱에 적합한 데이터

다음 조건을 만족할수록 캐싱 효과가 큽니다.

  1. 변경 빈도가 낮음
  2. 생성 비용이 높음 (복잡한 JOIN, 집계 결과 등)
  3. 여러 요청에서 재사용 가능
  4. 일정 수준의 지연 허용 가능

예시:

  • 상품 정보
  • 공통 코드 / 카테고리 목록
  • 조회 전용 통계 데이터

 

캐싱에 부적합한 데이터

Redis 공식 문서에서도 강조하듯, 다음 데이터는 캐싱을 피하는 것이 안전합니다.

  • 결제 금액, 계좌 잔액 등 정확성이 절대적인 데이터
  • 실시간성이 중요한 데이터
  • 보안 토큰, 인증 정보

 “캐시할 수 있다” ≠ “캐시해야 한다”

 


 

 

캐싱 패턴 개요와 Cache Aside의 위치

Redis 공식 Documentation은 캐시 시스템의 내부 동작과 명령어(EXPIRE, TTL, eviction 정책 등)를 정의하지만,Cache Aside, Read Through, Write Through, Write Back과 같은 캐싱 패턴 자체를 Redis의 기능으로 규정하지는 않습니다.

 

이러한 패턴들은 Redis와 원본 데이터 저장소(DB)를 애플리케이션 계층에서 어떻게 조합하여 사용할 것인가에 대한 설계 패턴입니다. Redis 공식 문서에서는 Write Through caching을 하나의 예시 아키텍처로 설명하며, 나머지 패턴들은 AWS ElastiCache 등 신뢰 가능한 공식 문서에서 일반적인 캐싱 아키텍처 패턴으로 정의됩니다.

 

Redis 공식 문서에 따르면, Redis는 캐시 용도로 사용되는 인메모리 데이터 저장소이며 캐싱 전략 자체는 애플리케이션 계층에서 결정됩니다.

 

즉, 캐싱 패턴(Cache Aside, Write Through 등) 은 Redis의 기능이라기보다 애플리케이션과 Redis, RDBMS 간의 데이터 흐름을 정의하는 설계 패턴입니다.

 

실무에서 가장 많이 사용되는 패턴은 다음과 같습니다.

 

기본 캐싱 패턴: Cache Aside

Cache Aside 패턴은 애플리케이션이 캐시와 데이터베이스를 직접 제어하는 방식입니다. Spring Data Redis 공식 문서에서도 가장 일반적인 사용 방식으로 소개되는 패턴입니다.

  • 읽기: 캐시 조회 → 캐시 미스 시 DB 조회 후 캐시에 저장
  • 쓰기: DB에 먼저 저장 → 관련 캐시 삭제

Redis는 Cache Aside를 위한 전용 기능을 제공하지 않으며, 애플리케이션 코드에서 RedisTemplate 또는 Cache abstraction을 통해 구현합니다. 이 패턴은 구현이 단순하고 Redis 장애 시에도 DB를 통해 서비스가 가능하다는 장점이 있습니다. 

동작 방식

  1. 캐시 조회
  2. 캐시 미스 시 DB 조회
  3. 결과를 캐시에 저장
  4. 응답 반환, 쓰기 시 DB 갱신 후 캐시 삭제

이 패턴을 기본으로 추천하는 이유

  • 구현이 단순함
  • DB와 캐시의 결합도가 낮음
  • 장애 시 캐시를 제거해도 시스템이 동작함

캐시 미스 시 DB 부하 집중 가능하다는 단점이 있으나, 대부분의 서비스는 이 패턴만으로도 충분한 성능 개선 효과를 얻습니다. 읽기가 많고, 쓰기는 적은 데이터를 선택해야 하고 캐시 장애시 DB fallback이 가능한 구조여야 합니다. 

 

Write Through 패턴

Write Through는 데이터 쓰기 시점에 캐시와 데이터베이스를 동시에 갱신하는 방식입니다. Redis 공식 Documentation은 Write Through caching을 하나의 캐싱 아키텍처 예시로 설명합니다.

 

이 방식에서는 캐시가 항상 최신 데이터를 유지하므로, 읽기 시 캐시 적중률이 높습니다. 반면 모든 쓰기 요청이 캐시를 거치므로 쓰기 지연 시간이 증가할 수 있습니다. 

 

Redis 자체가 Write Through를 자동으로 처리하지는 않으며, 애플리케이션 또는 미들웨어 계층에서 명시적으로 구현해야 합니다.

 

Read Through

Read Through 패턴은 애플리케이션이 직접 DB를 조회하지 않고, 캐시 계층이 캐시 미스 시 DB에서 데이터를 읽어 캐시에 적재하는 구조를 의미합니다.

 

Redis 단독으로는 Read Through를 제공하지 않으며, AWS ElastiCache, 일부 Cache Provider 또는 프레임워크 추상화 계층에서 지원됩니다.

 

Spring Data Redis를 사용할 경우, Read Through는 Spring Cache Abstraction + Redis 을 통해 간접적으로 구현 가능합니다.

 

Write Back (Write Behind)

Write Back 패턴은 쓰기 요청을 캐시에만 반영하고, 이후 비동기적으로 DB에 반영하는 방식입니다.

 

Redis 공식 문서는 Write Back을 기본 캐싱 전략으로 권장하지 않습니다. 캐시 장애 발생 시 데이터 유실 가능성이 존재하기 때문입니다.

 

따라서 Redis를 캐시 용도로 사용하는 일반적인 웹 서비스에서는 Write Back 패턴보다는 Cache Aside 또는 Write Through가 더 안전합니다.

 


 

TTL 설계 시 현실적인 기준

TTL의 역할

Redis 공식 문서에 따르면 TTL은

“키가 자동으로 삭제되는 시간 제한”

 

TTL은 key의 유효 기간을 설정하는 메커니즘이며, 만료된 key는 접근 시 제거되거나 Redis의 active expiration 주기에 의해 삭제됩니다. 실무에서 TTL은 다음 기준을 함께 고려하여 결정해야 합니다.

  • 데이터 변경 주기 (변경 빈도보다 TTL이 길면 정합성 문제 발생)
  • 비즈니스 허용 오차 (몇 분의 stale data를 허용할 수 있는가)
  • DB 조회 비용 (TTL이 짧을수록 DB 부하 증가)

TTL은 정합성 도구가 아니라 메모리 관리 및 부하 완화 수단입니다.

  • TTL 만료 ≠ 데이터 변경
  • TTL은 “언젠가 버려도 되는 데이터”에 적합

TTL은 다음 요소를 기준으로 결정합니다.

  1. 데이터 변경 빈도
    • 변경 적음 → TTL 길게
    • 변경 잦음 → TTL 짧게 또는 명시적 무효화
  2. 데이터 생성 비용
    • JOIN / 집계 비용 큼 → TTL 길게
  3. 비즈니스 허용 오차
    • 몇 분 지연 허용 가능? → TTL 가능
    • 불가 → 캐시 미사용 또는 즉시 무효화

고정 TTL의 문제점 

  • 다수 키가 같은 TTL을 가지면 특정 시점에 캐시 재생성이 몰릴 수 있음

하지만 모든 서비스에서 Avalanche 대응이 필수는 아닙니다. 다음 조건일 때만 고려하세요.

  • 동일 TTL 키가 대량 존재
  • 캐시 미스 시 DB 부하가 큼

TTL Jitter 

TTL Jitter는 Redis 공식 기능이 아닙니다. Redis 공식 Documentation은 TTL 및 key expiration의 동작 방식만을 정의합니다. TTL Jitter라는 용어는 Redis 명령어나 기능이 아니라, 대량 캐시 만료로 인한 Cache Avalanche를 완화하기 위한 실무 설계 기법입니다.

 

TTL Jitter의 핵심 아이디어는 단순합니다. 동일한 TTL을 가진 다수의 key가 동시에 만료되지 않도록 TTL에 작은 랜덤 값을 더해 만료 시점을 분산시키는 것입니다.

 

이는 Redis의 동작 원리를 변경하는 것이 아니라, Redis가 제공하는 TTL 기능을 어떻게 사용할 것인가에 대한 설계 전략입니다.


다시 정리하면, Redis는 TTL 만료 시 즉시 키 삭제를 수행합니다. 동일 TTL을 가진 키들이 동시에 만료되면 → Cache Avalanche
TTL Jitter란, TTL에 랜덤 오프셋을 추가하여 만료 시점을 분산하는 기법입니다. 

Duration base = Duration.ofMinutes(30);
long jitter = ThreadLocalRandom.current().nextLong(-180, 180);
Duration ttl = base.plusSeconds(jitter);
  • 동시에 만료되지 않게 하는 것이 목적
  • ±5~10% 정도면 충분
  • 모든 캐시에 일괄 적용할 필요는 없음
  • 정합성과 직접적 관련 없음

Avalanche 방지 TTL 설계 원칙

  • 동일한 TTL 금지
  • TTL Jitter 적용
  • 핵심 데이터는 Refresh Ahead 고려

 


 

캐시 장애 패턴 

Cache Penetration (실무에서 자주 발생)

존재하지 않는 데이터 요청이 반복되어 DB 부하가 발생하는 경우

 

실용적인 대응

  • NULL 마커 저장
  • 짧은 TTL (수 분 이내)

 

Cache Stampede (Thundering Herd)

Cache Stampede는 특정 key가 만료되는 순간 다수의 요청이 동시에 DB로 몰리는 현상입니다. Redis 공식 문서에서는 이 용어를 직접 정의하지 않지만, 캐시 설계 관점에서 널리 사용되는 장애 패턴입니다.

 

Redis는 Stampede를 자동으로 방지하는 기능을 제공하지 않으며, 다음과 같은 애플리케이션 레벨 대응이 필요합니다.

 

대응

  • mutex key를 이용한 단일 재생성
  • 분산 락(Redis SET NX + TTL)
  • TTL Jitter 적용

 

Cache Avalanche

대량 키 동시 만료

 

대응

  • TTL Jitter
  • TTL 분산
  • Warm-up 전략

Hot Key 문제 

특정 키에 트래픽 집중, Redis CPU 사용률 상승


대응

  • 로컬 캐시(Caffeine)

모든 서비스에 로컬 캐시를 추가할 필요는 없습니다.

 

다음 조건일 때만 고려:

  • 명확한 Hot Key 존재
  • Redis 부하가 병목으로 확인됨

 


 

오버엔지니어링을 피하기 위한 체크리스트

다음 중 하나라도 “아니오”라면 단순한 설계를 유지하세요.

  • 현재 DB가 실제 병목인가?
  • 캐시 미스로 DB가 감당 불가한가?
  • 캐시 장애 대응 코드가 유지보수 부담이 되지는 않는가?
  • 운영 중 모니터링 지표로 필요성이 확인되었는가?

문제가 관측된 뒤에 복잡도를 추가해도 늦지 않습니다.

 


 

마무리 요약

    • Cache Aside는 기본
    • TTL은 정합성 도구가 아님
    • TTL Jitter는 Avalanche 방지용
    • Redis는 AOF, RDB를 통해 데이터 영속성을 제공하지만, DB의 대체가 아니라 성능 최적화를 위한 보조 계층으로 사용된다
    • 캐시 데이터는 언제든지 유실될 수 있다는 전제로 설계해야 한다
    • 장애 패턴을 모르면 캐시는 독이 된다
    • Redis는 캐싱을 위한 도구이지, 모든 문제의 해결책은 아니다
    • Cache Aside + 명확한 TTL 설계가 대부분의 경우 충분
    • TTL Jitter, 로컬 캐시, 고급 패턴은 필요할 때만
    • 공식 문서가 보장하지 않는 특성에 의존하지 말 것

Redis 공식 Documentation은 캐시 상태를 진단하기 위한 여러 명령어를 제공합니다. 

  • INFO stats: keyspace hit/miss 비율 확인
  • INFO memory: 메모리 사용량 및 eviction 여부 확인
  • SLOWLOG GET: 느린 명령 분석
  • TTL / PTTL: 개별 key의 만료 상태 확인

 

캐시 전략은 설계로 끝나지 않으며, 이러한 공식 도구를 통해 지속적으로 검증하고 조정해야 합니다.

 

공식 문서 기반 이해와 현실적인 설계 기준이 Redis를 성능 개선 도구로 만드는 핵심입니다.

좋은 캐시 설계란 “가장 단순한 구조로, 충분한 성능을 얻는 것” 입니다.