Redis 캐시 서버 레이어 설계 및 튜닝 전략

서비스 사용자나 트래픽이 눈덩이처럼 늘어날 때 백엔드 개발자와 인프라 담당자가 가장 먼저 마주하는 벽은 바로 데이터베이스(DB) 병목 현상입니다.

사용자가 들어올 때마다 매번 RDBMS(MySQL, PostgreSQL 등)로 쿼리를 날려 데이터를 가져오다 보면, 아무리 DB 성능을 올려도 순식간에 과부하가 걸리고 서비스가 멈춰 서기 십상인데요.

이럴 때 DB 앞단에 고성능 인메모리(In-Memory) 저장소인 Redis(레디스)를 캐시 레이어로 얹어주면 DB 부담을 획기적으로 줄이고 응답 속도를 수십 배 이상 끌어올릴 수 있습니다.

대용량 트래픽을 거뜬히 견뎌내는 Redis 캐시 전략과 아키텍처 패턴, 그리고 메모리 터짐(OOM) 방지를 위한 실무 튜닝 노하우를 알기 쉽게 풀어 정리했습니다.

1. 서비스 성격에 맞는 캐시 전략 패턴 (Look-Aside vs Cache-Through)

Redis를 캐시로 도입할 때 가장 먼저 고민해야 하는 것은 “애플리케이션이 캐시와 DB에 데이터를 어떻게 읽고 쓸 것인가?”에 대한 구조적 디자인입니다.

① 캐시 룩어사이드 (Look-Aside / Lazy Loading) 패턴

실무에서 가장 널리 쓰이는 표준적인 읽기 전략입니다.

  • 동작 방식:
  1. 클라이언트 요청이 들어오면 애플리케이션은 먼저 Redis(캐시)에 찾으려는 데이터가 있는지 확인합니다 (Cache Hit).
  2. 캐시에 데이터가 존재하면 DB를 거치지 않고 Redis에서 즉시 데이터를 꺼내 반환합니다.
  3. 캐시에 데이터가 없으면 (Cache Miss) 그제서야 DB로 찾아가 데이터를 조회해 오고, 이 데이터를 다음을 위해 Redis에도 저장한 뒤 클라이언트에 전달합니다.
  • 장점: Redis에 장애가 생기거나 서버가 죽어도 DB에서 직접 데이터를 가져올 수 있어 서비스 전체가 멈추지 않는 단단한 구조를 제공합니다.
  • 단점: 캐시 미스가 발생할 때마다 DB 조회와 캐시 저장을 거쳐야 하므로 첫 번째 요청의 응답 시간이 살짝 느릴 수 있습니다. (이 경우 미리 캐시를 채워두는 Cache Warming 작업이 유용합니다.)

② 캐시 쓰루 (Read-Through / Write-Through) 패턴

애플리케이션이 DB를 직접 바라보지 않고, 오직 캐시 서버만 바라보는 일체형 방식입니다.

  • 동작 방식:
  • Read-Through: 애플리케이션이 캐시에 데이터를 요청하면, 캐시 레이어가 알아서 DB에서 데이터를 가져와 스스로를 업데이트하고 반환합니다.
  • Write-Through: 데이터 생성이나 수정이 일어날 때, 애플리케이션은 캐시에만 데이터를 기록합니다. 그러면 캐시 레이어가 이를 감지해 DB에 동기식으로 최신 데이터를 함께 반영합니다.
  • 장점: 데이터 조회의 일관성이 매우 높고 애플리케이션 코드가 단순해집니다.
  • 단점: 데이터를 쓸 때마다 캐시와 DB 두 곳 모두에 저장이 완료될 때까지 기다려야 하므로 쓰기 응답 시간이 늘어날 수 있습니다. 또한 안 쓰는 데이터까지 전부 캐시에 들어가 메모리가 낭비될 수 있습니다.

2. Redis 캐시 서버 메모리 고갈(OOM) 방지를 위한 Maxmemory 및 삭제 정책(Eviction Policy) 설정

Redis는 데이터를 disk가 아닌 RAM(메모리)에 저장합니다.

메모리는 물리적으로 용량이 한정되어 있기 때문에, 관리를 안 해두면 순식간에 공간이 꽉 차서 OOM(Out Of Memory) 오류가 터지며 서버가 다운되는 대참사가 일어날 수 있습니다.

이를 방지하기 위해 redis.conf 설정 파일에서 Maxmemory(최대 메모리 한도)와 삭제 정책(Eviction Policy)을 명확하게 잡아두어야 합니다.

① maxmemory 옵션 설정

물리 메모리의 100%를 Redis에 할당하면 절대 안 됩니다. Redis 자체 연산이나 RDB/AOF 같은 데이터 백업 작업 시 부모-자식 프로세스가 메모리를 복사(Copy-on-Write)하면서 메모리가 순간적으로 2배 이상 튈 수 있기 때문입니다.

  • 권장 설정: 전체 물리 RAM 용량의 60% ~ 70% 수준으로 maxmemory를 제한해 두는 것이 가장 안전합니다. (예: 16GB RAM 서버라면 maxmemory 10gb 설정)

② maxmemory-policy (메모리 초과 시 삭제 정책) 추천

설정한 maxmemory 한도에 도달했을 때 Redis가 기존 데이터를 어떻게 지워 비워낼지 정하는 정책입니다.

  • volatile-lru / allkeys-lru (가장 추천):
    LRU(Least Recently Used)는 “가장 오래전에 사용된 데이터부터 지우는 방식”입니다. 실무에서 가장 인기 높은 정책입니다.
  • volatile-lru: 만료 시간(TTL)이 설정된 키 중에서 가장 오래된 녀석부터 삭제합니다.
  • allkeys-lru: TTL 설정 여부와 상관없이 Redis 전체 키 중에서 가장 오랫동안 조회되지 않은 키를 삭제해 공간을 확보합니다.
  • volatile-ttl: 만료 시간이 얼마 남지 않은(TTL이 가장 짧은) 데이터부터 먼저 지웁니다.
  • noeviction (기본값):
    메모리가 차더라도 데이터를 전혀 지우지 않습니다. 대신 새로운 데이터 쓰기 요청에 대해 OOM command not allowed 에러를 뱉어버리므로, 캐시 전용 서버로 사용할 때는 절대 그대로 두면 안 되는 위험한 기본값입니다.

3. 실무 대용량 환경에서 반드시 챙겨야 할 3가지 튜닝 팁

  1. 모든 캐시 데이터에는 반드시 만료 시간(TTL)을 설정하기
    TTL(Time To Live)이 없는 키는 영원히 메모리를 차지합니다. EXPIRE 명령어를 통해 몇 분, 혹은 몇 시간 단위의 만료 시간을 부여하여 유효기간이 지난 쓸모없는 데이터가 자동 삭제되도록 유도해야 합니다.
  2. KEYS * 같은 위험한 명령어 금지
    Redis는 싱글 스레드(Single Thread)로 동작합니다. 데이터가 수백만 건 쌓였을 때 모든 키를 검색하는 KEYS * 명령어를 치는 순간, Redis가 그 작업을 처리하느라 다른 모든 서비스 요청을 멈춰 세우게 됩니다 (서비스 먹통 원인 1위). 실무에선 반드시 SCAN 명령어를 나누어 사용해야 합니다.
  3. 캐시 스탬피드(Cache Stampede) 현상 대비하기
    인기 있는 메인 페이지 데이터의 캐시 만료 시간(TTL)이 끝나는 순간, 수천 수만 명의 사용자가 동시에 DB로 몰려들어 DB가 터지는 현상을 말합니다. 이를 막으려면 캐시 만료 시간에 미세하게 랜덤한 오차(Jitter)를 주거나, 캐시 갱신 작업을 백그라운드 스케줄러가 미리 처리하도록 구성하는 것이 바람직합니다.

대용량 트래픽 환경에서 Redis는 서비스의 생사고락을 함께하는 필수적인 핵심 무기입니다.

서비스 요구사항에 맞는 올바른 캐시 읽기/쓰기 패턴을 정립하고, 메모리 제한 및 LRU 삭제 정책을 타이트하게 잡아둔다면 어지간한 트래픽 폭주에도 끄떡없는 탄탄하고 빠른 백엔드 시스템을 유지하실 수 있습니다.