[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
---
|
||||
id: cs-cache-eviction
|
||||
title: Cache Eviction — LRU / LFU / ARC / TinyLFU
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [cs, cache, eviction, vibe-coding]
|
||||
tech_stack: { language: "TS", applicable_to: ["Backend"] }
|
||||
applied_in: []
|
||||
aliases: [LRU, LFU, ARC, TinyLFU, W-TinyLFU, cache eviction, hit rate]
|
||||
---
|
||||
|
||||
# Cache Eviction
|
||||
|
||||
> Cache 가득 = 무엇을 빼지? **LRU (가장 오래 안 본), LFU (가장 적게 본), ARC (Adaptive), W-TinyLFU (Caffeine)**. Workload 따라 hit rate 큰 차이.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- LRU: Least Recently Used.
|
||||
- LFU: Least Frequently Used.
|
||||
- ARC: Adaptive — 둘 다 + auto.
|
||||
- TinyLFU / W-TinyLFU: 빈도 + 최근성 + admission.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### LRU (가장 일반)
|
||||
```ts
|
||||
class LRUCache<K, V> {
|
||||
private cache = new Map<K, V>(); // Map 가 insertion order 유지
|
||||
|
||||
constructor(private max: number) {}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
if (!this.cache.has(key)) return undefined;
|
||||
const v = this.cache.get(key)!;
|
||||
this.cache.delete(key);
|
||||
this.cache.set(key, v); // 최근 접근으로
|
||||
return v;
|
||||
}
|
||||
|
||||
set(key: K, value: V) {
|
||||
if (this.cache.has(key)) this.cache.delete(key);
|
||||
this.cache.set(key, value);
|
||||
if (this.cache.size > this.max) {
|
||||
const first = this.cache.keys().next().value;
|
||||
this.cache.delete(first!); // 가장 오래된
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### lru-cache (Node)
|
||||
```ts
|
||||
import LRU from 'lru-cache';
|
||||
|
||||
const cache = new LRU<string, User>({
|
||||
max: 1000,
|
||||
ttl: 60_000, // 60초 TTL
|
||||
updateAgeOnGet: true, // get 시 age reset
|
||||
fetchMethod: async (key) => {
|
||||
return await db.users.find(key);
|
||||
},
|
||||
});
|
||||
|
||||
const user = await cache.fetch('u1');
|
||||
```
|
||||
|
||||
### LFU (빈도)
|
||||
```ts
|
||||
class LFUCache<K, V> {
|
||||
private cache = new Map<K, { value: V; freq: number }>();
|
||||
|
||||
constructor(private max: number) {}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) return undefined;
|
||||
entry.freq++;
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
set(key: K, value: V) {
|
||||
if (this.cache.size >= this.max) {
|
||||
// 가장 적게 본 것 evict
|
||||
let minFreq = Infinity;
|
||||
let minKey: K | undefined;
|
||||
for (const [k, e] of this.cache) {
|
||||
if (e.freq < minFreq) { minFreq = e.freq; minKey = k; }
|
||||
}
|
||||
this.cache.delete(minKey!);
|
||||
}
|
||||
this.cache.set(key, { value, freq: 0 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
⚠️ Pure LFU 의 문제: cache pollution (오래된 popular 가 영원 남음).
|
||||
|
||||
### ARC (Adaptive Replacement Cache)
|
||||
```
|
||||
4개 list 관리:
|
||||
- T1: recent (한 번 본)
|
||||
- T2: frequent (여러 번 본)
|
||||
- B1: T1 evict ghost
|
||||
- B2: T2 evict ghost
|
||||
|
||||
Hit on B1 → T1 가중치 ↑
|
||||
Hit on B2 → T2 가중치 ↑
|
||||
Adaptive
|
||||
```
|
||||
|
||||
→ IBM 특허 — 사용 제한 가능.
|
||||
|
||||
### W-TinyLFU (Caffeine, Java)
|
||||
```
|
||||
Window LRU (1%) + Main (LFU + LRU).
|
||||
|
||||
Admission policy:
|
||||
새 entry 가 window 통과 → 빈도 비교 → 통과 시 main 으로.
|
||||
|
||||
매우 좋은 hit rate.
|
||||
```
|
||||
|
||||
→ Java Caffeine 가 이 알고리즘.
|
||||
|
||||
### Node — better-cache
|
||||
```ts
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
const cache = new Cache({
|
||||
store: 'memory',
|
||||
max: 1000,
|
||||
ttl: 60_000,
|
||||
});
|
||||
|
||||
await cache.set('key', value);
|
||||
const v = await cache.get('key');
|
||||
```
|
||||
|
||||
### Two-tier cache
|
||||
```
|
||||
L1: in-process (LRU, 작은) — ns access
|
||||
L2: Redis (큰) — ms access
|
||||
|
||||
Get: L1 → miss → L2 → miss → DB
|
||||
```
|
||||
|
||||
```ts
|
||||
async function getUser(id: string): Promise<User> {
|
||||
const l1 = lru.get(id);
|
||||
if (l1) return l1;
|
||||
|
||||
const l2 = await redis.get(`user:${id}`);
|
||||
if (l2) {
|
||||
const user = JSON.parse(l2);
|
||||
lru.set(id, user);
|
||||
return user;
|
||||
}
|
||||
|
||||
const user = await db.users.find(id);
|
||||
lru.set(id, user);
|
||||
await redis.setex(`user:${id}`, 60, JSON.stringify(user));
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
### Cache stampede (위 redis 문서 참조)
|
||||
```
|
||||
1000 concurrent miss → 모두 DB 호출.
|
||||
→ Singleflight / lock.
|
||||
```
|
||||
|
||||
```ts
|
||||
const inflight = new Map<string, Promise<User>>();
|
||||
|
||||
async function getUser(id: string): Promise<User> {
|
||||
const cached = lru.get(id);
|
||||
if (cached) return cached;
|
||||
|
||||
if (inflight.has(id)) return inflight.get(id)!;
|
||||
|
||||
const promise = db.users.find(id).then(user => {
|
||||
lru.set(id, user);
|
||||
inflight.delete(id);
|
||||
return user;
|
||||
});
|
||||
inflight.set(id, promise);
|
||||
return promise;
|
||||
}
|
||||
```
|
||||
|
||||
→ 같은 key 동시 fetch = 1번만.
|
||||
|
||||
### TTL + jitter
|
||||
```ts
|
||||
const ttl = 60_000 + Math.random() * 10_000; // 60-70s
|
||||
cache.set(key, value, { ttl });
|
||||
```
|
||||
|
||||
→ 동시 expire 방지 (thundering herd).
|
||||
|
||||
### Cache key design
|
||||
```ts
|
||||
// ❌ 너무 specific
|
||||
`user:${id}:${page}:${filter}:${sort}` // 매번 다른 key
|
||||
|
||||
// ✅ 분리
|
||||
`user:${id}` + 그 user 의 page list 별도
|
||||
```
|
||||
|
||||
### Negative cache (없는 것도 cache)
|
||||
```ts
|
||||
async function getUser(id: string): Promise<User | null> {
|
||||
const cached = cache.get(id);
|
||||
if (cached === '__NULL__') return null;
|
||||
if (cached) return cached as User;
|
||||
|
||||
const user = await db.users.find(id);
|
||||
if (!user) {
|
||||
cache.set(id, '__NULL__', { ttl: 30_000 }); // 짧게
|
||||
return null;
|
||||
}
|
||||
cache.set(id, user);
|
||||
return user;
|
||||
}
|
||||
```
|
||||
|
||||
→ "없는 user" 반복 query 방지.
|
||||
|
||||
### Hit rate 측정
|
||||
```ts
|
||||
class InstrumentedCache<K, V> {
|
||||
private hits = 0;
|
||||
private misses = 0;
|
||||
|
||||
get(key: K) {
|
||||
const v = this.cache.get(key);
|
||||
if (v) { this.hits++; return v; }
|
||||
this.misses++;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
hitRate() { return this.hits / (this.hits + this.misses); }
|
||||
}
|
||||
```
|
||||
|
||||
→ 80%+ 가 일반 목표.
|
||||
|
||||
### Sized cache (memory budget)
|
||||
```ts
|
||||
const cache = new LRU<string, Buffer>({
|
||||
maxSize: 100 * 1024 * 1024, // 100 MB
|
||||
sizeCalculation: (value) => value.length,
|
||||
});
|
||||
```
|
||||
|
||||
### Cache 종류 비교
|
||||
```
|
||||
Code-level: Map / LRU
|
||||
In-memory shared: Memcached / Redis
|
||||
CDN: Cloudflare / CloudFront
|
||||
Browser: HTTP cache + Service Worker
|
||||
DB: shared_buffers / buffer pool
|
||||
```
|
||||
|
||||
### 패턴
|
||||
```
|
||||
Cache-aside (look-aside): App 가 read miss 시 cache.set
|
||||
Read-through: Cache 가 자동 fetch (lru-cache fetchMethod)
|
||||
Write-through: Write 가 DB + cache 같이
|
||||
Write-behind: Write cache → 비동기 DB
|
||||
Refresh-ahead: TTL 임박 시 미리 refresh
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 워크로드 | 추천 |
|
||||
|---|---|
|
||||
| 일반 web | LRU |
|
||||
| Skewed (popular minority) | LFU / W-TinyLFU |
|
||||
| Adaptive | ARC (legal 시) / W-TinyLFU |
|
||||
| 큰 throughput | Caffeine (Java) / 자체 |
|
||||
| 분산 | Redis + 클라 LRU L1 |
|
||||
| Workload 다양 | W-TinyLFU 가 안정 |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **TTL 없음**: stale.
|
||||
- **무한 size**: OOM.
|
||||
- **Hit rate 모니터링 X**: cache 효과 모름.
|
||||
- **Cache stampede 무시**: 동시 1000 miss = DB 다운.
|
||||
- **Negative cache 없음**: "없는 거" 반복 query.
|
||||
- **Key namespace 충돌**: prefix 명시.
|
||||
- **Cache pollution (모든 거 cache)**: 빈도 낮은 거 evict 자주.
|
||||
- **Pure LFU 사용 + 패턴 변화**: 옛 popular 가 영원.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- LRU + TTL + jitter 가 안전 default.
|
||||
- Hot/cold 가 strict 면 W-TinyLFU.
|
||||
- Stampede = singleflight 또는 lock.
|
||||
- Hit rate 모니터링 + alarm.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[DB_Redis_Patterns]]
|
||||
- [[Web_HTTP_Cache_Headers]]
|
||||
- [[Backend_Rate_Limiting]]
|
||||
Reference in New Issue
Block a user