본문으로 건너뛰기
거대한 눈사태가 마을을 덮치는 일러스트

# Redis Cache Avalanche: 눈사태처럼 쏟아지는 트래픽 막아내기

Table of Contents

금요일 오후 6시, 서버실의 악몽

대규모 트래픽을 처리하는 서비스에서 가장 무서운 시나리오는 바로 **“캐시가 꺼지는 순간”**입니다. 평소에 데이터베이스(DB) 앞단에서 수십만 TPS를 막아주던 Redis가 죽거나, 혹은 대량의 데이터가 동시에 만료된다면 어떻게 될까요? 그 막대한 트래픽은 고스란히 데이터베이스로 쏟아집니다. 마치 산꼭대기에서 굴러떨어진 작은 눈덩이 하나가 거대한 눈사태가 되어 마을을 덮치는 것처럼 말이죠.

이것이 바로 Cache Avalanche(캐시 눈사태) 현상입니다.

Cache Avalanche vs Cache Stampede

두 현상은 비슷해 보이지만 발생 원인과 양상이 다릅니다. 정확한 처방을 위해서는 이 둘을 명확히 구분해야 합니다.

1. Cache Avalanche (눈사태)

  • 원인: 수많은 캐시 키(Key)들이 동시에 만료되거나, Redis 서버 자체가 장애로 인해 죽었을 때 발생합니다.
  • 현상: 전체적인 트래픽이 일시에 DB로 쏠리며 DB 서버의 CPU와 메모리가 급증합니다.
  • 예시: 서버 재시작 시점에 TTL = 1시간으로 모든 데이터를 캐싱했습니다. 정확히 1시간 뒤, 모든 데이터가 동시에 만료되면서 순간적으로 DB에 수천 배의 부하가 가해집니다.

2. Cache Stampede (우르르 몰려감)

  • 원인: **“매우 인기 있는 키 하나”**가 만료되었을 때 발생합니다.
  • 현상: 그 키를 조회하려는 수천 개의 요청이 동시에 “어? 캐시가 없네? 내가 DB에서 가져와야지”라고 판단하고 DB를 타격합니다. (Thundering Herd 문제와 유사합니다)
  • 예시: 인기 가수의 콘서트 티켓 예매 페이지에서 좌석 정보(seat_map) 캐시가 만료된 찰나의 순간.

어떻게 막을 수 있을까?

Avalanche 방지: “랜덤(Random)은 나의 친구”

모든 캐시의 만료 시간을 똑같이(예: 60분) 설정하는 것은 시한폭탄을 설치하는 것과 같습니다. 여기에 **Jitter(랜덤 값)**를 섞어주세요.

  • 기본 TTL: 60분
  • 실제 설정 TTL: 60분 + Random(0 ~ 10분)

이렇게 설정하면 캐시 만료 시점이 10분에 걸쳐 고르게 분산되므로, DB가 숨 쉴 틈이 생깁니다. 물론, Redis 서버 자체를 고가용성(Cluster, Sentinel)으로 구성하여 서버 장애에 대비하는 것은 기본 중의 기본입니다.

Stampede 방지: “줄을 서시오”

인기 있는 키가 만료되었을 때, 너도나도 DB로 달려가게 두면 안 됩니다. **Mutex Lock(상호 배제 락)**을 사용하여 “가장 먼저 도착한 1명”만 DB에 보내고, 나머지는 기다리게 해야 합니다.

# 의사 코드 (Pseudo-code)
data = redis.get(key)
if not data:
    # 락 획득 시도 (setnx: set if not exists)
    if redis.setnx(lock_key, "locking"): 
        # 1등(락 획득 성공)만 여기를 실행
        data = db.query(key)
        redis.set(key, data)
        redis.del(lock_key) # 락 해제
    else:
        # 나머지는 락이 풀릴 때까지 잠깐 대기 후 다시 캐시 조회
        sleep(0.1)
        data = redis.get(key)

또 다른 방법으로는 PER(Probabilistic Early Recomputation) 알고리즘이 있습니다. 만료 시간이 완전히 끝나기 전에, 트래픽이 많아지면 확률적으로 미리미리 캐시를 갱신해두는 스마트한 기법입니다.

마치며

캐시는 양날의 검과 같습니다. 잘 쓰면 성능이 폭발적으로 좋아지지만, 캐시에만 의존하다가 장애가 발생하면 시스템 전체가 마비되는 캐시 의존성(Cache Dependency) 문제가 생길 수 있습니다. 개발할 때 “캐시가 있으니까 DB는 괜찮겠지”라고 안일하게 생각하면 안 됩니다. **“캐시가 모두 날아갔을 때, 우리 DB가 이 트래픽을 견딜 수 있는가?”**를 항상 고민하고 테스트해야 합니다.

만약 DB가 견딜 수 없다면? Circuit Breaker를 도입하여 과도한 요청을 차단하거나, 랜덤 TTL과 락 전략으로 무장하여 최악의 상황을 대비해야 합니다. 준비된 시스템만이 눈사태 속에서도 살아남을 수 있습니다.

이 글 공유하기:
My avatar

글을 마치며

이 글이 도움이 되었기를 바랍니다. 궁금한 점이나 의견이 있다면 댓글로 남겨주세요.

더 많은 기술 인사이트와 개발 경험을 공유하고 있으니, 다른 포스트도 확인해보세요.

유럽살며 여행하며 코딩하는 노마드의 여정을 함께 나누며, 함께 성장하는 개발자 커뮤니티를 만들어가요! 🚀


관련 포스트

# 프로덕션의 악몽, Database Connection Pool 고갈: 디버깅부터 예방까지

게시:

Database Connection Pool Exhaustion 문제는 겉으로 드러난 증상일 뿐, 진짜 원인은 따로 있습니다. 커넥션 누수, 슬로우 쿼리, 타임아웃 설정 부재 등 풀 고갈을 유발하는 근본적인 원인을 파악하고, Circuit Breaker와 동적 풀 사이징 같은 실전 대응 전략을 알아봅니다.

읽기