Read-Through 캐시 패턴은 캐시 미스 시 캐시 자체가 DB에서 데이터를 로드해 클라이언트에 반환하는 패턴이다. Cache-Aside와 달리 캐시가 DB 접근을 추상화한다.
Cache-Aside vs Read-Through
Cache-Aside (애플리케이션이 관리):
1. 캐시 조회
2. 미스 → 앱이 DB 조회
3. 앱이 캐시에 저장
4. 데이터 반환
Read-Through (캐시가 관리):
1. 캐시 조회
2. 미스 → 캐시가 DB 조회
3. 캐시가 저장
4. 데이터 반환
※ 앱은 캐시만 바라봄
구현 예시
typescript
// Read-Through 캐시 추상화
class ReadThroughCache<T> {
constructor(
private redis: Redis,
private loader: (key: string) => Promise<T>, // DB 로더
private ttl: number = 3600
) {}
async get(key: string): Promise<T | null> {
// 1. 캐시 조회
const cached = await this.redis.get(key);
if (cached) return JSON.parse(cached);
// 2. 캐시 미스: 캐시가 직접 DB 조회 (Lock으로 중복 방지)
const lockKey = `lock:${key}`;
const acquired = await this.redis.set(
lockKey, '1', 'NX', 'EX', 5
);
if (!acquired) {
// 다른 요청이 로딩 중 → 잠시 대기 후 재시도
await new Promise(r => setTimeout(r, 100));
return this.get(key);
}
try {
const data = await this.loader(key); // DB 조회
if (data) {
await this.redis.setex(key, this.ttl, JSON.stringify(data));
}
return data;
} finally {
await this.redis.del(lockKey);
}
}
}
// 사용
const userCache = new ReadThroughCache(
redis,
(userId) => db.users.findById(userId),
3600
);
const user = await userCache.get('user:123');
Cache Stampede 방지
문제: 캐시 만료 시 동시에 DB 요청 폭주
해결책:
1. 뮤텍스 락: 첫 번째 요청만 DB 조회, 나머지 대기
2. 확률적 조기 갱신:
만료 전 확률적으로 갱신
exp_time - ttl * β * log(random()) < current_time
3. Refresh-Ahead: 미리 갱신
관련 개념