logo hsb.horse
← 스니펫 목록으로 돌아가기

Snippets

캐시 우선, 라이브 페치 오케스트레이션 패턴

캐시의 빠른 경로와 원격의 느린 경로를 조합한 오케스트레이션. 캐시 히트/미스, 레이턴시, 최종 결과를 메트릭으로 계측하고, 부작용은 외부에 위임한다.

게시일: 수정일:

캐시와 원격 페치를 조합할 때 로직이 뒤섞이기 쉽다. 오케스트레이션 함수로 묶으면 캐시 히트·미스 두 경로를 정리하고, 메트릭 계측과 부작용 위임도 한 곳에서 관리할 수 있다.

타입 정의

interface CacheProvider<T> {
get(key: string): Promise<T | undefined>;
set(key: string, value: T): Promise<void>;
}
interface RemoteProvider<T> {
fetch(key: string): Promise<T>;
}
interface MetricsReporter {
recordCacheHit(key: string, latencyMs: number): void;
recordCacheMiss(key: string): void;
recordFetchLatency(key: string, latencyMs: number): void;
recordOutcome(key: string, outcome: 'success' | 'error', latencyMs: number): void;
}

오케스트레이션 함수

async function orchestrate<T>(
key: string,
cache: CacheProvider<T>,
remote: RemoteProvider<T>,
metrics: MetricsReporter,
): Promise<T> {
const start = performance.now();
// 빠른 경로: 캐시 히트
const cached = await cache.get(key);
if (cached !== undefined) {
metrics.recordCacheHit(key, performance.now() - start);
return cached;
}
// 느린 경로: 라이브 페치
metrics.recordCacheMiss(key);
const fetchStart = performance.now();
try {
const data = await remote.fetch(key);
metrics.recordFetchLatency(key, performance.now() - fetchStart);
// 부작용은 외부에 위임 (fire and forget)
void cache.set(key, data);
metrics.recordOutcome(key, 'success', performance.now() - start);
return data;
} catch (error) {
metrics.recordOutcome(key, 'error', performance.now() - start);
throw error;
}
}

사용 예

const result = await orchestrate(
'user:42',
redisCache,
userApiClient,
datadogMetrics,
);

해설

  • 빠른 경로: 캐시에 히트하면 즉시 반환. 원격은 호출하지 않는다.
  • 느린 경로: 캐시 미스 시에만 원격으로 폴백. 데이터를 가져온 후 캐시를 업데이트한다 (fire and forget).
  • 메트릭: 캐시 히트·미스·페치 레이턴시·최종 결과를 모두 계측. MetricsReporter 인터페이스에 위임하므로 Datadog / Prometheus / 로그 등 구현에 의존하지 않는다.
  • 부작용 위임: 캐시 쓰기와 메트릭 전송은 오케스트레이션 함수 외부로 분리. 테스트가 용이하고 구현을 교체하기 쉽다.

응용

이 패턴은 다음과 같은 처리에 적합하다.

  • 임포터: 외부 데이터 소스에서 가져올 때 이미 처리된 데이터를 빠르게 건너뜀
  • 데이터 보강 작업: 이미 완성된 엔티티를 재처리하지 않음
  • 동기화 핸들러: 중복 페치를 방지하면서 메트릭으로 차이를 추적
  • 비용이 높은 UI 작업: 최초 로드만 원격을 호출하고 이후에는 캐시로 응답

CacheProvider / RemoteProvider / MetricsReporter는 각각 인터페이스로 분리되어 있어, Redis/in-memory, REST/gRPC, Datadog/StatsD 등 임의의 구현을 조합할 수 있다.