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 など任意の実装を組み合わせられる。

実務メモ

このスニペットは、TypeScript、design-pattern、performance、observability の周辺で同じ操作や判定を毎回書きたくない時に向く。小さな補助として切り出しておくと、呼び出し側では意図だけを追いやすい。

逆に、分岐や前提条件が増えて責務が膨らむなら、1本のスニペットに詰め込まない方がよい。手順と helper を分けるか、役割ごとに切り出す方が保守しやすい。