Cache Eviction — 정책 시각 비교

같은 접근 시퀀스를 4개 정책에 동시에 흘려보내며 어디서 갈리는지

① 6가지 정책 개요

🕓

LRU Least Recently Used

가장 오래 안 쓴 것을 버림
✓ 가장 흔함, 시간 지역성 활용
✗ 스캔(일회성 대량) 한 번에 인기 데이터 다 밀려남 (pollution)
📊

LFU Least Frequently Used

가장 적게 쓴 것을 버림
✓ 인기 데이터 보호에 강함
✗ 옛날에 인기였던 게 영원히 안 빠짐 (과거에 갇힘)
📥

FIFO

먼저 들어온 것을 버림 (입력 순)
✓ 단순, 구현 쉬움
✗ 지역성 무시 — 자주 쓰여도 오래 됐으면 내쫓음

TTL

만료 시간 지난 것을 버림
✓ 시간 기준 정합성에 자연스러움
✗ 인기·빈도 무관 → 보통 LRU/LFU와 조합해서 씀
🎲

Random

무작위 하나를 버림
✓ 매우 쌈, 의외로 나쁘지 않음
✗ 운에 맡김, 최적 보장 X

W-TinyLFU Caffeine 기본

LFU + LRU 혼합 (TinyLFU 필터 + Window)
✓ 현대적, 적중률 최고, pollution 저항
✗ 구현 복잡 (Caffeine 같은 라이브러리 사용)

② 인터랙티브 시뮬레이터 ⭐

캐시 용량 4슬롯. 시퀀스를 한 단계씩 진행하며 4개 정책이 어떻게 다르게 동작하는지 비교. 9단계가 끝나면 LRU/FIFO와 LFU/TinyLFU의 hit율 차이가 드러납니다.

Step 1/9 — 접근: A
🕓 LRU 대기
hit 0miss 0
📊 LFU 대기
hit 0miss 0
📥 FIFO 대기
hit 0miss 0
⭐ W-TinyLFU 대기
hit 0miss 0
시작하려면 다음 → 버튼을 누르세요.

③ Cache Pollution — LRU의 약점

"야간 배치"처럼 일회성 대량 스캔이 한 번 지나가면 LRU는 인기 데이터를 다 잃는다. LFU/W-TinyLFU는 빈도를 기억해서 살려둔다.

초기 상태 (4슬롯, 인기 데이터 가득)

A · freq=15 B · freq=12 C · freq=10 D · freq=8

→ 모두 "뜨거운" 데이터. 평소 hit율 90%+.

야간 배치 스캔: E,F,G,H,I,J,K,L 1회씩 접근

🕓 LRU 결과 (재앙):
IJKL
A,B,C,D 모두 축출됨. 다음날 hit율 폭락 💀
📊 LFU / ⭐ W-TinyLFU 결과:
ABCD
인기 데이터 그대로 유지. E~L은 freq=1이라 들어왔다가 바로 축출 ✅

💡 그래서 Caffeine은 W-TinyLFU를 기본으로 채택했다. TinyLFU(빈도 필터) + LRU 윈도우의 결합으로 pollution 저항 + 빠른 변화 적응을 동시에 달성.

④ 어느 정책을 선택할까 — 결정 트리

Q1. 워크로드에 일회성 스캔(배치·full table scan)이 자주 일어나나?
W-TinyLFU / LFU (pollution 방어) 아니오 → Q2로
Q2. 인기 데이터가 시간에 따라 빠르게 바뀌나? (트렌드 변화 큼)
LRU (최신성 우선) / W-TinyLFU (윈도우가 적응) 아니오LFU (꾸준한 인기 보호)
Q3. 시간 기준 만료가 정합성에 중요한가? (예: 1분 stale OK, 그 이상 안 됨)
TTL + LRU/LFU 조합 (Redis 기본 패턴)
Q4. 매우 단순한 캐시면 충분하고 구현 비용 최소화?
FIFO 또는 Random (의외로 큰 사이즈에선 LRU와 차이 작음)
Q5. 현대 JVM 앱에서 별다른 제약 없이 기본 선택?
W-TinyLFU (Caffeine) — 적중률·pollution 저항 다 좋음. 사실상 새 표준.

⑤ 실전 설정 — Redis & Caffeine

Redis (maxmemory-policy)

maxmemory 2gb
maxmemory-policy allkeys-lru     # 모든 키 대상 LRU (가장 흔함)
# 또는
maxmemory-policy allkeys-lfu     # LFU
maxmemory-policy volatile-ttl    # TTL 있는 키 중 만료 임박 우선
maxmemory-policy volatile-lru    # TTL 있는 키만 LRU 적용
maxmemory-policy noeviction      # 가득 차면 새 쓰기 거부 (안전 우선)

→ 일반 캐시: allkeys-lru. 인기 보존 중요: allkeys-lfu. 정합성 절대: noeviction + 알람.

Caffeine (Java/Kotlin, 기본 W-TinyLFU)

val cache: Cache<Long, Product> = Caffeine.newBuilder()
    .maximumSize(10_000)                      // W-TinyLFU 자동 적용
    .expireAfterWrite(Duration.ofMinutes(10)) // TTL 조합
    .recordStats()                            // hit율 측정 ⭐
    .build()

// 모니터링
cache.stats().hitRate()       // 0.0 ~ 1.0
cache.stats().evictionCount() // 누적 축출 수

recordStats()로 hit율·축출 수를 꾸준히 모니터링 권장.