Redis "OOM command not allowed" 에러 5분 진단·복구 실전 가이드
지금 애플리케이션 로그에 SET, LPUSH가 줄줄이 실패하고 5xx가 튀고 있나요? 그리고 Redis에서 이런 에러를 보고 계신가요?
(error) OOM command not allowed when used memory > 'maxmemory'.먼저 한 문장으로 정리하겠습니다. 이건 리눅스 커널의 OOMKilled도, JVM의 OutOfMemoryError: Java heap space도 아닙니다. Redis 프로세스가 살아있는 상태에서, Redis 내부의 maxmemory 한계에 도달했고 maxmemory-policy=noeviction이라서 "쓰기 명령만" 스스로 거부하고 있는 것입니다. 즉, 프로세스가 죽은 게 아니라 정책에 의해 쓰기를 막는 정상 동작입니다. 그래서 복구가 빠릅니다. 바로 들어가죠.
에러 발생 메커니즘: 왜 쓰기만 거부되고 읽기는 될까
Redis는 used_memory가 maxmemory에 도달하면, 설정된 maxmemory-policy에 따라 행동을 결정합니다. 기본값이거나 명시적으로 noeviction인 경우, Redis는 기존 데이터를 절대 지우지 않고 새 데이터를 받는 쓰기 명령을 거부합니다.
used_memory ≥ maxmemory + policy = noeviction
│
├─ SET / LPUSH / HSET / SADD / INCR ... → ❌ OOM 에러 (거부)
└─ GET / LRANGE / HGETALL / EXISTS ... → ✅ 정상 (허용)읽기가 되는 이유는 간단합니다. 읽기는 메모리를 늘리지 않으니까요. noeviction은 "데이터를 한 톨도 잃을 수 없는" DB-of-record 용도에는 안전하지만, 순수 캐시 용도에서는 오히려 장애의 원인이 됩니다. 핵심은 메모리를 비우거나, 정책을 바꾸거나 둘 중 하나라는 것.
5분 즉시 진단: 복붙 명령어 4종
장애 중이니 설명은 뒤로 미루고 명령부터 칩시다. 첫 번째로 메모리 현황을 한 줄로 확인합니다.
redis-cli INFO memory | grep -E "used_memory:|used_memory_human|maxmemory:|maxmemory_human|maxmemory_policy|mem_fragmentation_ratio"출력 해석 포인트는 이렇습니다.
used_memory_human≈maxmemory_human→ 한계 도달 확정maxmemory_policy:noeviction→ 쓰기 거부의 직접 원인mem_fragmentation_ratio→ 1.5 이상이면 단편화 과다(실제 데이터보다 OS가 더 많이 잡고 있음)
이어서 상세 통계와 자동 진단을 봅니다.
redis-cli MEMORY STATS # peak.allocated, dataset.bytes 등 상세 분해
redis-cli MEMORY DOCTOR # Redis가 직접 진단 메시지를 줌
redis-cli CONFIG GET maxmemory* # maxmemory, maxmemory-policy 현재값 확인MEMORY DOCTOR가 "high fragmentation" 또는 "peak memory가 현재보다 1.5배 높다" 같은 메시지를 주면 그게 곧 다음 액션의 힌트입니다.
💡 컨테이너 환경 주의: 도커/쿠버네티스에서
maxmemory를 0(무제한)으로 두면, Redis는 cgroup 메모리 한계를 모릅니다. 그러면 이 OOM 에러가 아니라 커널이 컨테이너를 OOMKill 해버립니다. 둘은 완전히 다른 증상입니다. 컨테이너에서는 메모리 limit의 70~75% 정도로maxmemory를 반드시 명시하세요.
즉시 복구 + 근본 정리
1단계: 정책을 결정한다
지금 이 Redis가 캐시인지, 데이터의 원본 저장소인지에 따라 길이 갈립니다.
| 정책 | 동작 | 적용 상황 | 위험 / 주의 |
|---|---|---|---|
noeviction | 한계 도달 시 쓰기 거부, 데이터 보존 | 큐·세션 원본 등 유실 불가 데이터(DB-of-record) | 메모리 차면 즉시 장애. 용량 증설 필수 |
allkeys-lru | 전체 키 중 LRU로 제거 | 순수 캐시(원본이 DB에 또 있음) | 캐시 미스 증가. 원본 없는 키 있으면 데이터 유실 |
volatile-ttl | TTL 설정된 키 중 만료 임박 순 제거 | TTL 키와 영구 키가 섞인 환경 | TTL 없는 키는 안 지워져 다시 꽉 찰 수 있음 |
판단 기준: Redis 안의 데이터가 DB나 다른 곳에 또 있다면 거의 항상 allkeys-lru가 정답입니다. 세션 스토어나 큐처럼 원본이 여기뿐이면 noeviction을 유지하고 메모리를 늘리는 쪽으로 가야 합니다.
2단계: 무중단으로 적용한다
CONFIG SET은 재시작 없이 런타임에 즉시 반영됩니다.
# 런타임 즉시 반영 (서비스 중단 없음)
redis-cli CONFIG SET maxmemory-policy allkeys-lru
# 적용 확인
redis-cli CONFIG GET maxmemory-policy이 한 줄로 쓰기가 즉시 풀립니다. 하지만 이건 휘발성입니다. 재시작하면 원래대로 돌아갑니다. 영구 반영을 위해 redis.conf도 함께 고치세요.
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru# 현재 런타임 설정을 conf 파일에 기록 (주의: 주석/포맷이 재작성됨)
redis-cli CONFIG REWRITE⚠️
CONFIG REWRITE는 기존redis.conf를 Redis가 다시 써버립니다. 손으로 정리해둔 주석이 사라질 수 있으니, 형상관리(Ansible/Helm)로 conf를 관리한다면 REWRITE 대신 소스 conf를 직접 수정 후 롤링 재배포하는 편이 안전합니다.
3단계: 빅키와 TTL 미설정 키를 잡는다
정책을 바꿔 급한 불은 껐어도, 메모리를 먹는 범인을 찾아야 재발을 막습니다.
# 데이터 타입별 가장 큰 키 탐지
redis-cli --bigkeys
# 메모리를 가장 많이 쓰는 키 (Redis 6.2+)
redis-cli --memkeys
# 특정 키의 실제 메모리 사용량(byte)
redis-cli MEMORY USAGE mybigkey그리고 TTL이 안 걸린 키는 영원히 안 사라지므로 메모리 누적의 주범입니다. 운영 중인 Redis에서 절대 KEYS *를 쓰지 마세요. 단일 스레드를 블로킹해 또 다른 장애를 만듭니다. 반드시 SCAN으로 커서를 돌리며 점검합니다.
# 커서 기반 논블로킹 순회
redis-cli SCAN 0 COUNT 100
# 특정 키의 TTL 확인 → -1 이면 만료 없음(영구 키)
redis-cli TTL session:abc123
# (integer) -1 ← 이런 키들이 쌓이면 메모리가 계속 찬다TTL이 -1로 나오는 캐시성 키가 보이면, 애플리케이션에서 SET key value EX 3600처럼 만료시간을 함께 설정하도록 코드를 고치는 것이 근본 해결입니다.
maxmemory 적정값 산정과 재발 방지
산정 공식
maxmemory는 **물리 메모리(또는 컨테이너 limit)의 약 7075%**로 잡는 것이 정석입니다. 나머지 2530%는 RDB/AOF 저장 시 fork의 Copy-on-Write, 클라이언트 출력 버퍼, OS 페이지 캐시 여유분입니다. 이 여유를 안 두면 백그라운드 저장 중에 진짜 OOMKill이 납니다.
# 예: 물리 메모리 6GB 인스턴스 → 약 70%인 4GB
redis-cli CONFIG SET maxmemory 4gb모니터링과 알림
장애를 매번 겪지 않으려면 80% 임계값 알림이 필수입니다. 요즘 표준은 Prometheus + redis_exporter 조합입니다.
# used_memory가 maxmemory의 80%를 넘으면 경고
redis_memory_used_bytes / redis_memory_max_bytes > 0.8이 알림이 울리면 OOM이 터지기 전에 정책 점검·증설·빅키 정리를 할 시간을 벌 수 있습니다. 참고로 Redis 포크인 Valkey도 maxmemory/maxmemory-policy 설정과 위 명령들이 동일하게 호환되니, 본문 명령을 그대로 쓰면 됩니다.
실무 경험 한 줄
제가 겪은 사례의 90%는 "순수 캐시인데 기본 정책 noeviction을 그대로 둔 것"이 원인이었습니다. 캐시 용도라면 구축 시점에 allkeys-lru로 박아두고, TTL을 강제하는 코드 컨벤션을 두는 것만으로 이 장애의 대부분이 사라집니다.
복구 후 체크리스트
-
CONFIG GET maxmemory-policy로 의도한 정책 확인 -
redis.conf에maxmemory·maxmemory-policy영구 반영 -
--bigkeys로 비정상 빅키 정리 - TTL 미설정(
-1) 캐시 키에 만료시간 부여 - Prometheus 80% 알림 동작 확인
자주 묻는 질문 (FAQ)
Q. CONFIG SET maxmemory-policy allkeys-lru로 바꾸면 데이터가 즉시 지워지나요?
A. 한계에 도달한 상태라면 새 쓰기를 받기 위해 LRU 순으로 일부 키가 제거됩니다. 원본이 DB에 또 있는 캐시라면 안전하지만, 원본이 Redis뿐이라면 유실 위험이 있으니 noeviction을 유지하고 메모리를 증설하세요.
Q. 이 에러가 리눅스 OOMKilled와 같은 건가요?
A. 다릅니다. 이 에러는 Redis 프로세스가 살아있는 채로 내부 maxmemory 정책에 따라 쓰기를 거부하는 것이고, OOMKilled는 커널이 메모리 부족으로 프로세스 자체를 죽이는 것입니다. 컨테이너에서 maxmemory를 0으로 두면 후자가 발생합니다.
Q. 운영 중인데 키를 확인하려면 KEYS *를 써도 되나요?
A. 절대 안 됩니다. KEYS *는 단일 스레드를 블로킹해 전체 응답 지연·장애를 유발합니다. 반드시 SCAN으로 커서를 나눠 순회하세요.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...