본문 바로가기
🛠️ IT 실전 가이드 (SW 도구)

Redis OOM 에러 피하려면 필수! 캐시 메모리 최적화와 maxmemory-policy 완벽 가이드

by 데브프리 2026. 7. 30.

안녕하세요, 여러분의 든든한 IT 길잡이 데브프리입니다.

 

최근 2025~2026년을 기점으로 Redis 진영에 큰 변화가 있었죠. 7.4 버전에서 라이선스 이슈로 논란이 있었지만, 현재 최신 안정화 버전인 Redis 8.8 (2026년 기준)부터는 다시 AGPLv3를 포함한 삼중 라이선스(Tri-license)로 오픈 소스 생태계로 돌아오며 실무에서 여전히 압도적인 점유율을 자랑하고 있습니다.

 

스프링 부트(Spring Boot)나 Node.js로 백엔드 서버를 최적화할 때, 데이터베이스(DB) 부하를 줄이기 위해 가장 먼저 도입하는 도구가 바로 Redis 캐시(Cache)입니다. 하지만 "인메모리 데이터베이스니까 무조건 빠르겠지!" 하고 무작정 데이터를 넣다 보면, 서비스 론칭 직후 끔찍한 에러를 마주하게 됩니다.

 

오늘은 실제 현업 백엔드 실무에서 Redis를 캐시로 사용할 때 가장 흔하게 터지는 OOM(Out of Memory) 에러의 원인을 객관적으로 분석하고, 이를 방지하기 위한 maxmemory 및 maxmemory-policy(교체 정책) 설정 베스트 프랙티스를 논리적으로 정리해 드립니다.

 

1. 실전 트러블슈팅: Redis OOM (Out of Memory)의 공포

운영 중인 서버에서 트래픽이 몰리기 시작할 때, 갑자기 백엔드 애플리케이션 로그에 아래와 같은 에러가 폭포수처럼 쏟아지며 API 응답이 모두 실패하는 상황이 발생하곤 합니다.

Java

io.lettuce.core.RedisCommandExecutionException: OOM command not allowed when used memory > 'maxmemory'.
    at io.lettuce.core.ExceptionFactory.createExecutionException(ExceptionFactory.java:147)
    at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120)

왜 터졌을까? (에러 원인 분석)

이 에러 메시지를 직역하면 "사용 중인 메모리가 최대치(maxmemory)를 초과하여, 메모리를 소모하는 명령어(OOM command)를 실행할 수 없다"는 뜻입니다.

 

Redis는 하드디스크가 아닌 RAM(메모리)에 데이터를 저장합니다. 당연하게도 RAM의 용량은 물리적으로 유한하죠. 그런데 캐싱 로직을 짤 때 데이터를 넣기만 하고 언제 삭제할지(TTL, Time To Live)를 설정하지 않았거나, 메모리가 꽉 찼을 때 오래된 데이터를 밀어내는 교체 정책(Eviction Policy)을 명시하지 않았다면 어떻게 될까요?

 

Redis 메모리가 한계치에 도달하는 순간, 새로운 쓰기(SET) 요청을 전부 거부하며 시스템 병목 현상을 유발하고 결국 API 장애로 이어집니다.

Redis OOM(Out of Memory) 용량 초과 에러 비유
메모리(RAM) 용량은 물리적 한계가 존재하기 때문에, 오래된 데이터를 비워내는 정책이 없다면 결국 새로운 데이터를 받아들이지 못하고 시스템이 마비됩니다.

 

2. OOM 방지를 위한 실무 베스트 프랙티스

이 치명적인 에러를 막기 위해서는 redis.conf 설정 파일(또는 AWS ElastiCache 같은 클라우드 환경 설정)에서 다음 두 가지 핵심 옵션을 반드시 조율해야 합니다.

첫 번째: maxmemory 설정 (안전한 한계치 지정)

Redis가 사용할 수 있는 최대 메모리 용량을 명시적으로 지정해 줍니다.

  • 베스트 프랙티스: 서버에 장착된 전체 물리적 RAM 용량의 60% ~ 70% 수준으로 설정하는 것이 가장 안전합니다. 만약 100%에 가깝게 설정하면, 운영체제(OS)가 구동되거나 Redis 백그라운드 프로세스가 실행될 때 필요한 여유 메모리가 부족해져, 리눅스의 'OOM Killer'가 Redis 프로세스를 강제로 죽여버릴(Kill) 수 있습니다.
코드 스니펫

# 예시: 가용 메모리를 최대 2GB까지만 사용하도록 설정
maxmemory 2gb

두 번째: maxmemory-policy 설정 (어떤 데이터를 버릴 것인가?)

메모리가 지정한 maxmemory까지 가득 찼을 때, 어떻게 방을 뺄지 규칙을 정해주는 옵션입니다.

가장 주의해야 할 점은 Redis의 기본값이 noeviction이라는 것입니다. 이는 메모리가 꽉 차면 더 이상 데이터를 삭제하지 않고 그냥 에러를 뱉고 멈추겠다는 뜻이므로, 캐시 용도로 쓸 때는 반드시 변경해야 합니다.

 

현업에서 가장 추천하는 실무 픽(Pick) 두 가지를 소개합니다.

  • allkeys-lru (캐시용으로 가장 강력히 추천):
  • 가장 오랫동안 사용되지 않은(Least Recently Used) 데이터부터 삭제합니다. 일반적인 API 결과 캐싱, 인기 게시물 목록, 세션 관리 등 대부분의 유스케이스에 최적화되어 있으며, 자주 찾는 데이터는 남겨두기 때문에 캐시 적중률(Hit Rate)을 높게 유지할 수 있는 가장 범용적인 정책입니다.
  • volatile-ttl:
  • 만료 시간(TTL)이 설정된 데이터 중, 만료일이 가장 가까운 데이터부터 우선적으로 삭제합니다. 개발자가 저장하는 모든 데이터에 TTL을 아주 철저하고 정교하게 관리하고 있는 시스템이라면 매우 효율적입니다.

Redis LRU(Least Recently Used) 캐시 교체 알고리즘 동작 원리
allkeys-lru 정책은 컨베이어 벨트처럼, 가장 오랫동안 찾지 않은 낡은 데이터를 영리하게 밀어내어 새로운 데이터가 들어올 빈 공간을 스스로 확보합니다.

 

💡 [데브프리의 심층 면접 꿀팁: LRU의 치명적 맹점, 그리고 'allkeys-lfu'의 등장]

면접관이 "allkeys-lru의 단점이 무엇이며, 이를 어떻게 극복하나요?"라고 묻는다면 시니어급 답변을 꺼낼 차례입니다!

LRU(가장 오래전에 참조된 것 삭제)는 치명적인 맹점이 하나 있습니다. 만약 평소에 전혀 안 쓰던 과거의 데이터 수십만 건을 배치(Batch) 작업으로 단 한 번 훑고 지나가면 어떻게 될까요? 방금 한 번 조회되었다는 이유만으로 이 쓸모없는 데이터들이 '최신'으로 분류되고, 정작 평소에 꾸준히 조회되던 진짜 핵심 캐시들이 메모리 밖으로 억울하게 밀려나는 대참사가 발생합니다.

이러한 LRU의 약점을 보완하기 위해 도입된 정책이 바로 allkeys-lfu (Least Frequently Used)입니다! 단순히 '최근'이 아니라 '얼마나 자주(조회 빈도)' 쓰였는지를 계산하여 생존자를 결정하므로, 실무에서 캐시 적중률(Hit Rate)을 극한으로 방어해야 한다면 LFU 정책 도입을 적극 어필해 보세요!

 

3. 안정적인 캐시 운영을 위한 실무 3원칙

Redis를 단순한 툴이 아닌 든든한 무기로 활용하려면 실무에서 다음 세 가지를 꼭 습관화하시기 바랍니다.

  1. 무조건 TTL(만료 시간)을 설정하세요.
  2. 영구적으로 저장되어야 하는 데이터라면 애초에 Redis가 아니라 MySQL, PostgreSQL 같은 영속성(RDBMS) 데이터베이스에 저장해야 합니다.
  3. maxmemory-policy를 꼼꼼히 리뷰하세요.
  4. 팀 내 인프라 설정에 noeviction이 그대로 남아있는지 확인하고, 캐시용 인스턴스라면 allkeys-lru로 즉시 변경해야 대형 장애를 막을 수 있습니다.
  5. Key 네이밍 컨벤션을 정하세요.
  6. user:105:profile 처럼 콜론(:)으로 계층과 역할을 명확히 분리해 두면, 추후 모니터링 시 어떤 서비스에서 메모리를 과도하게 점유하고 있는지 쉽게 추적할 수 있습니다.

 

4. 전문가의 확고한 결론 (Verdict)

Redis (인메모리 캐시), 실무에 언제 도입하고 언제 피해야 할까요?

  • 👍 확실한 도입 타이밍:
  • 데이터베이스 조회 쿼리 비용(복잡한 Join 연산이나 통계 조회)이 높고 변경은 적은 데이터를 빈번하게 사용자에게 보여줘야 할 때 압도적인 성능을 발휘합니다. 또한 백엔드 서버가 2대 이상으로 스케일 아웃(Scale-out)된 마이크로서비스(MSA) 환경에서, 여러 서버가 동일한 로그인 세션이나 글로벌 캐시를 공유해야 한다면 Redis는 선택이 아닌 필수입니다.
  • 🛑 도입을 피해야 할 때:
  • 반대로 서버가 단 1대뿐인 모놀리식(Monolithic) 환경이면서 단순한 텍스트 캐싱이 필요하다면, 굳이 네트워크 통신 비용과 관리 포인트를 늘리는 외부 Redis를 띄울 필요가 없습니다. 이때는 스프링 부트 메모리 내부에 띄우는 로컬 캐시(Caffeine Cache 등)가 접근 속도 면에서 훨씬 효율적입니다. 또한, '시스템 다운 시 단 하나라도 유실되면 절대 안 되는 핵심 원장 데이터'를 오직 Redis에만 의존해 저장하는 설계는 매우 위험합니다.

캐시는 백엔드 API 응답 속도를 비약적으로 끌어올리는 강력한 툴이지만, 한정된 자원(메모리)을 어떻게 통제할지 미리 설계하지 않으면 OOM이라는 부메랑이 되어 돌아옵니다. 오늘 살펴본 메모리 정책을 여러분의 서버 환경에 꼭 적용해 보시기 바랍니다.

 

항상 팩트에 기반한 객관적인 실무 가이드로 여러분의 삽질 시간을 줄여드리겠습니다. 다음 포스팅에서도 개발자들에게 유용한 도구 최적화 팁으로 찾아뵙겠습니다!

 

👉 실서버 뻗기 전에 꼭 확인하세요! 내 서버 스펙에 맞는 최적의 DB 커넥션 풀 개수 계산기 [링크]