백엔드 데이터 인프라 55편 — Redis TTL + Eviction Policy 8가지

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 55편. Redis 키 만료 — EXPIRE·PEXPIRE·TTL·PERSIST 명령어와, 메모리 가득 찼을 때 어떤 키부터 쫓아낼지 결정하는 maxmemory-policy 8가지(noeviction·allkeys-lru·allkeys-lfu·volatile-lru 등) 정확히 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 55편 — Redis TTL + Eviction Policy 8가지

이 글은 백엔드 데이터 인프라 시리즈 130편 중 55편이에요. 54편 으로 자주 쓰는 6종 데이터 타입(49~54편) 을 끝냈다면, 이번 55편부터는 운영 영역키 만료(TTL, Time To Live — 키 자동 삭제 시한)메모리 가득 찼을 때 어떻게 동작하나(eviction policy — 메모리 부족 시 키 추방 정책) 를 풀어 가요. 캐시 운영의 핵심.

TTL·Eviction이 어렵게 느껴지는 이유

캐시 운영에서 가장 자주 깨지는 자리. 두 가지가 처음 잡히지 않아요.

첫째, TTL 명령어가 8개나 됩니다. EXPIRE·PEXPIRE·EXPIREAT·PEXPIREAT·TTL·PTTL·EXPIRETIME·PEXPIRETIME·PERSIST. 초 vs 밀리초 · 상대 시간 vs 절대 시간 · 조회 형식 차이 — 차이가 미세해서 어느 걸 써야 할지 헷갈려요.

둘째, eviction policy 가 8가지. noeviction · allkeys-lru · allkeys-lfu · allkeys-random · volatile-lru · volatile-lfu · volatile-random · volatile-ttl. 이름이 비슷비슷한데 "실수로 잘못 박으면 운영 사고" 가 자주 일어나는 영역이에요. 특히 "왜 갑자기 캐시가 다 날아갔지?" 의 절반은 eviction 정책 오설정.

이 글에서 TTL 명령어 8개를 3개 그룹 으로 묶어 정리하고, eviction policy 8가지를 선택 결정 트리 로 정리해요. 한 번 잡으면 운영 사고가 안 나는 영역.

TTL 명령어 — 3그룹으로 묶어 외우기

그룹 1: 만료 설정 (EXPIRE 계열, 4개)

> SET session:abc "user42"
> EXPIRE session:abc 3600          # 3600초 (1시간) 후 만료
(integer) 1
> PEXPIRE session:abc 3600000       # 3,600,000 밀리초 = 1시간
(integer) 1
> EXPIREAT session:abc 1747500000   # 절대 시각 (Unix timestamp, 초)
(integer) 1
> PEXPIREAT session:abc 1747500000000  # 절대 시각 밀리초
(integer) 1
명령어 단위 기준
EXPIRE 상대 (지금부터 N초 후)
PEXPIRE 밀리초 상대
EXPIREAT 절대 (Unix timestamp)
PEXPIREAT 밀리초 절대

옵션 (Redis 7+): NX·XX·GT·LT

> EXPIRE session:abc 7200 GT   # 기존 TTL 보다 클 때만 갱신

그룹 2: TTL 조회 (4개)

> TTL session:abc            # 남은 시간 (초)
(integer) 3598
> PTTL session:abc           # 남은 시간 (밀리초)
(integer) 3598123
> EXPIRETIME session:abc     # 절대 만료 시각 (초)
(integer) 1747500000
> PEXPIRETIME session:abc    # 절대 만료 시각 (밀리초)
(integer) 1747500000000

여기서 시험 함정이 하나 있어요 — TTL 반환값의 3가지 경우:

  • 양수 — 남은 초
  • -1 — 키는 있는데 TTL 없음 (영구)
  • -2 — 키 없음

-1-2 차이를 모르고 모두 없음으로 처리 하면 영구 키불필요하게 만료 되는 버그가 자주 나요.

그룹 3: 만료 제거

> PERSIST session:abc        # TTL 제거 (영구 키로)
(integer) 1
> TTL session:abc
(integer) -1                  # 이제 영구

SET ... KEEPTTL 옵션도 값 갱신 시 만료 유지. (49편 String 참고)

SET 과 함께 한 줄로 — SET ... EX N

대부분의 실무에서 SET + EXPIRE 를 따로 부르지 않고 한 줄로 끝내요:

> SET session:abc "user42" EX 3600
OK
> SET rate:user42 1 PX 1000              # 1초 (밀리초)
> SET cache:foo "value" EXAT 1747500000  # 절대 시각

장점은 세 가지로 압축돼요. 먼저 atomic (원자적 — 중간에 끊기지 않음) 하니까 SETEXPIRE 사이에 서버가 다운돼도 만료 없는 키 가 남는 버그가 차단되고, 다음으로 명령이 한 번이라 네트워크 왕복도 한 번이며, 마지막으로 코드도 깔끔해져요.

SET 옵션 = EX (초) · PX (밀리초) · EXAT · PXAT · KEEPTTL. 거의 모든 만료 있는 SET 은 이 형태로 쓰는 게 표준.

한 줄 정리 — TTL은 SET ... EX N 으로 한 번에 박는 게 표준. 따로 EXPIRE 부르는 건 이미 있는 키 의 TTL 만 갱신할 때.

Lazy + Active — Redis 만료 처리 메커니즘

여기서 가끔 묻는 질문 — "EXPIRE 박은 키, 정확히 그 시점에 메모리에서 사라지나요?". 답은 "정확히 그 시점은 아니고, 두 가지 방식으로 처리".

Lazy Expiration

키에 접근할 때 만료됐는지 체크 → 만료됐으면 즉시 삭제 + nil 반환.

> SET temp "x" EX 1
> # 2초 후
> GET temp
(nil)                          # 이 시점에 만료 + 삭제됨

접근 시에만 처리하니 비용은 0인데, 대신 접근 안 되는 만료 키 가 메모리에 계속 남는 게 약점이에요.

Active Expiration

Redis 가 백그라운드 스레드에서 주기적으로 임의의 키 20개 샘플링 → 만료된 것 제거. 만료 비율이 25% 넘으면 다시 샘플. 초당 100ms 정도 CPU 사용.

장점은 접근 안 되는 만료 키도 결국 제거 된다는 점이고, 단점은 정확히 만료 시점에 제거되지는 않는다는 점.

결과 — 만료된 키가 일시적으로 메모리에 남아 있을 수 있음. 그래도 접근하면 보이지 않고(nil), 결국 active 가 청소 함. 정확한 바이트 단위 메모리 관리 가 필요한 환경에서는 이 메커니즘을 이해해야.

maxmemory — 메모리 한계 설정

maxmemory 4gb              # 최대 4GB까지 사용

redis.conf 또는 CONFIG SET. 도달하면 eviction policy 가 발동.

Eviction Policy 8가지 — 정확한 선택

가장 중요한 자리. 이름 패턴 두 축:

  • allkeys- vs volatile-모든 키 대상 vs TTL 박힌 키만 대상
  • lru vs lfu vs random vs ttl — 어떤 기준으로 쫓아낼지

8가지 풀 리스트

Policy 의미
noeviction 쫓아내지 않음 — 메모리 가득 차면 쓰기 명령 에러 (DB 모드)
allkeys-lru 모든 키 중 최근에 적게 쓴 것(LRU) 부터 제거
allkeys-lfu 모든 키 중 자주 안 쓰는 것(LFU) 부터 제거
allkeys-random 모든 키 중 무작위 제거
volatile-lru TTL 있는 키 중 LRU
volatile-lfu TTL 있는 키 중 LFU
volatile-random TTL 있는 키 중 무작위
volatile-ttl TTL 있는 키 중 만료 시각이 가장 가까운 것부터

LRU vs LFU 차이

  • LRU (Least Recently Used — 가장 오래 안 본 것)마지막 접근 시각 기준. 최근에 접근 안 한 것 부터 제거. 시간적 지역성 가정.
  • LFU (Least Frequently Used — 가장 적게 본 것)접근 빈도 기준. 덜 자주 접근한 것 부터 제거. 접근 빈도가 의미 있는 환경 (예: 주말마다 보는 콘텐츠).

LFU 가 이론적으로 더 정교 하지만, LRU 도 충분히 좋고 오버헤드 적음. 처음 도입은 LRU 가 무난.

한 줄 암기 — LRU = 언제 봤나, LFU = 얼마나 자주 봤나.

Volatile vs Allkeys

  • volatile-TTL 안 박은 키는 절대 안 사라짐. 영구 보관 키와 캐시* 키를 같은 인스턴스에 섞을 때 안전.
  • allkeys-TTL 무관, 모든 키가 eviction 대상. 순수 캐시* 인스턴스에 적합.

여기서 정말 중요한 시험 함정 — volatile-* 정책인데 TTL 없는 키만 가득 차면 → eviction 못 함 → noeviction 처럼 동작. "왜 갑자기 쓰기 실패?" 의 자주 보이는 원인.

선택 결정 트리

이 Redis 인스턴스의 용도는?
├─ 순수 캐시 (날아가도 OK)
│   ├─ 접근 빈도 의미 있음 → allkeys-lfu
│   └─ 시간적 지역성 → allkeys-lru (무난한 기본)
├─ 캐시 + 영구 데이터 혼합
│   ├─ TTL 있는 캐시만 쫓아냄 → volatile-lru / volatile-lfu
│   └─ 만료 임박 우선 쫓음 → volatile-ttl
└─ DB 모드 (날아가면 안 됨)
    └─ noeviction (쓰기 실패가 데이터 손실보다 나음)

자주 보이는 조합은 셋. 캐시 전용 인스턴스allkeys-lruallkeys-lfu 로 가고, 세션 저장소 + 캐시 처럼 섞여 있으면 세션에 TTL 이 박혀 있으니 volatile-lru 가 안전해요. Redis 를 DB 처럼 쓰는 자리면 noeviction 으로 두고 메모리 부족 시 쓰기 에러를 알람 트리거로 받는 게 정석.

기본값 = noeviction. 명시적으로 안 바꾸면 메모리 가득 시 쓰기 에러 가 발생해 조용히 캐시가 날아가는 사고가 안 난다는 안전 디폴트.

설정 방법

# 런타임 변경 (재시작 X)
> CONFIG SET maxmemory 4gb
OK
> CONFIG SET maxmemory-policy allkeys-lru
OK

# 현재 설정 조회
> CONFIG GET maxmemory
1) "maxmemory"
2) "4294967296"
> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"

영구 적용은 redis.conf 수정 + 재시작 또는 CONFIG REWRITE:

# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru

메모리 모니터링 — INFO memory

> INFO memory
# Memory
used_memory:1048576              # 사용 중 (바이트)
used_memory_human:1.00M
used_memory_rss:1572864          # OS 가 본 실제 사용 (fragmentation 포함)
used_memory_peak:2097152
maxmemory:4294967296             # 한계
maxmemory_human:4.00G
mem_fragmentation_ratio:1.5       # 1.5 = 50% fragmentation

운영 모니터링에서 매번 보는 메트릭. used_memory_rss / used_memory 가 1.5 넘어가면 fragmentation (메모리 단편화 — 빈틈이 많아 실사용보다 큰 상태) 큼 → Redis 4.0+ active defragmentation 활성화 검토.

시험 직전 한 번 더 — TTL · Eviction 함정 압축 노트

  • TTL 명령 4가지 setter = EXPIRE(초)·PEXPIRE(ms)·EXPIREAT(절대 초)·PEXPIREAT(절대 ms)
  • TTL 조회 4가지 = TTL(초)·PTTL(ms)·EXPIRETIME·PEXPIRETIME
  • TTL 반환 = 양수(남은 초) / -1(영구) / -2(키 없음)
  • -1 vs -2 혼동이 영구 키 실수 만료 버그의 원인
  • PERSIST = TTL 제거 → 영구 키로
  • SET ... EX N = SET + EXPIRE 한 줄, atomic — 실무 표준
  • SET ... KEEPTTL = 값 갱신 시 만료 유지
  • 만료 처리 = Lazy (접근 시) + Active (백그라운드 샘플링)
  • 만료 키 = 일시적으로 메모리 남을 수 있음, 결국 정리
  • maxmemory = Redis 메모리 한계 설정
  • 도달 시 maxmemory-policy 발동
  • 8가지 policy = noeviction / allkeys-{lru,lfu,random} / volatile-{lru,lfu,random,ttl}
  • LRU = 마지막 접근 시각 / LFU = 접근 빈도
  • LFU 가 정교, LRU 가 무난한 기본
  • volatile- = TTL 있는 키만 대상 — TTL 없는 키는 안 사라짐*
  • allkeys- = 모든 키 대상 — 순수 캐시* 에 적합
  • 함정 = volatile-* + TTL 없는 키만 가득 = eviction 못 함 = 쓰기 실패
  • 기본값 = noeviction (메모리 부족 시 쓰기 에러 → 알람 트리거)
  • 선택 결정 — 캐시 전용=allkeys-lru/lfu / 캐시+영구=volatile-lru / DB모드=noeviction
  • 런타임 변경 = CONFIG SET maxmemory-policy ...
  • 영구 적용 = redis.conf 수정 또는 CONFIG REWRITE
  • 모니터링 = INFO memory (used_memory·peak·fragmentation_ratio)
  • fragmentation_ratio > 1.5 = active defragmentation 검토

공식 문서: Redis Eviction 에서 maxmemory-policy 8가지 사양을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!