mcpField
C
caching

caching

github.com/zebbern開発4.7k40 回インストール2 日前に更新
リポジトリ

caching について

caching は、インメモリ、Redis、CDN、クライアント側のキャッシュ設計とコードレビューを支援する開発者向けスキルです。無効化、TTL、キー設計の指針に加え、アンチパターンと実装例を示します。

主な機能

  1. 01元データの変更に応じた無効化方針
  2. 02データの変動頻度に基づく TTL 設定
  3. 03結果に影響するパラメーターのキーへの反映
  4. 04キャッシュ層の選択と組み合わせ
  5. 05機密データやエラーのキャッシュリスクの把握
  6. 06一般的な構成のコード例

活用シーン

  1. 01API やサービスのキャッシュ層設計
  2. 02古いデータやキャッシュ集中の調査
  3. 03キャッシュ読み書きコードのレビュー
  4. 04TanStack Query のキャッシュ時間設定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/zebbern/claude-code-guide --skill caching

インストール後、Claude Code で/cachingを入力して読み込まれたか確認します。

詳細

使用するタイミング

  • API やサービスにキャッシュレイヤー(インメモリ、Redis、CDN)を実装するとき。
  • キャッシュデータの TTL 値や無効化戦略を決めるとき。
  • 衝突や古いデータに関するバグを防ぐため、キャッシュキーのスキーマを設計するとき。
  • キャッシュの読み取りまたは書き込みを行うコードをレビューするとき。
  • 古いデータ、キャッシュスタンピード、または一貫性のないレスポンスをデバッグするとき。
  • クライアント側キャッシュの TanStack Query staleTime/gcTime を設定するとき。

無効化

  • [P0-MUST] すべてのキャッシュに無効化戦略を定義してください。古いデータはキャッシュがないよりも悪影響があります。
  • [P0-MUST] 元データが変更されたらキャッシュを無効化してください。TTL の期限切れだけに頼らないでください。
  • [P1-SHOULD] 時間ベースの期限切れだけに頼るのではなく、イベント駆動型の無効化(書き込み/更新/削除時)を優先してください。
  • [P1-SHOULD] データスキーマが変更されたときは、キャッシュのバージョン管理(バージョンキーを含める)を使用してください。

TTL のガイドライン

  • [P1-SHOULD] データの変化の速さに基づいて TTL を設定してください。静的な設定は数時間から数日、ユーザープロフィールは数分、リアルタイムデータは数秒またはキャッシュなしにします。
  • [P1-SHOULD] stale-while-revalidate を使用してください。バックグラウンドで更新しながら、古いデータをすぐに返します。
  • [P2-MAY] 開発環境では TTL を短くし、本番環境では長くしてください。

キャッシュキー

  • [P0-MUST] 結果に影響するすべてのクエリパラメーターをキャッシュキーに含めてください。
  • [P1-SHOULD] 一貫したキー形式を使用してください:<entity>:<id>:<variant>(例:user:123:profile、products:list:page=2)。
  • [P1-SHOULD] 衝突を防ぐため、サービスまたはモジュールごとにキーを名前空間化してください。
  • [P2-MAY] ストレージを効率的に保つため、長いキーや複雑なキーをハッシュ化してください。

キャッシュレイヤー

  • [P1-SHOULD] 用途に適したキャッシュレイヤーを使用してください。
レイヤー最適な用途TTL の範囲
インメモリ(Map、LRU)頻繁に使うデータ、単一インスタンスのアプリ数秒から数分
Redis/Memcachedインスタンス間で共有するキャッシュ、セッション数分から数時間
CDN/エッジ静的アセット、公開 API レスポンス数時間から数日
HTTP キャッシュヘッダーブラウザーキャッシュ、API レスポンスリソースによって異なる
  • [P1-SHOULD] キャッシュを階層化してください。メモリ → Redis → オリジンの順に確認します。キャッシュミス時はライトスルーします。

キャッシュしない場合

  • [P0-MUST] ユーザー固有の機密データ(認証トークン、決済情報)を共有キャッシュに保存しないでください。
  • [P1-SHOULD] 古くなることで不正な動作につながる、頻繁に変化するデータ(在庫数、リアルタイム価格)はキャッシュしないでください。
  • [P1-SHOULD] エラーレスポンスはキャッシュしないでください。短い TTL を使うか、失敗時はキャッシュをスキップしてください。
  • [P2-MAY] 計算コストが低く、データセットが小さい場合はキャッシュを避けてください。

コード例

TTL 付きインメモリ LRU キャッシュ
const cache = new Map<string, { value: unknown; expires: number }>();
const MAX_SIZE = 500;

export function getOrSet<T>(key: string, ttlMs: number, compute: () => T): T {
  const entry = cache.get(key);
  if (entry && entry.expires > Date.now()) return entry.value as T;

  const value = compute();
  if (cache.size >= MAX_SIZE) {
    // Evict oldest entry (first inserted)
    const oldest = cache.keys().next().value!;
    cache.delete(oldest);
  }
  cache.set(key, { value, expires: Date.now() + ttlMs });
  return value;
}
ioredis を使った Redis の stale-while-revalidate
import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);

export async function swr<T>(
  key: string,
  freshSec: number,
  staleSec: number,
  fetcher: () => Promise<T>,
): Promise<T> {
  const raw = await redis.get(key);
  if (raw) {
    const { value, createdAt } = JSON.parse(raw) as { value: T; createdAt: number };
    const ageMs = Date.now() - createdAt;
    if (ageMs < freshSec * 1000) return value; // Fresh — return immediately
    if (ageMs < staleSec * 1000) {
      // Stale — return cached, refresh in background
      fetcher().then((v) =>
        redis.set(key, JSON.stringify({ value: v, createdAt: Date.now() }), "EX", staleSec),
      );
      return value;
    }
  }
  const value = await fetcher();
  await redis.set(key, JSON.stringify({ value, createdAt: Date.now() }), "EX", staleSec);
  return value;
}
Express/Hono の HTTP キャッシュヘッダー
// Immutable assets (hashed filenames)
app.use("/assets", (_, res, next) => {
  res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
  next();
});

// API responses — short cache with revalidation
app.get("/api/products", (_, res) => {
  res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
  res.json(products);
});
TanStack Query のキャッシュ設定
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // Data fresh for 5 minutes
      gcTime: 30 * 60 * 1000,   // Garbage-collect after 30 minutes
      retry: 2,
      refetchOnWindowFocus: false,
    },
  },
});

// Usage in a component
const { data } = useQuery({
  queryKey: ["products", { page, category }], // Cache key includes params
  queryFn: () => fetchProducts({ page, category }),
});

アンチパターン

  • キャッシュして放置 — 無効化戦略なしでデータをキャッシュすることです。データが恒久的に古くなります。

    • 代わりに:すべてのキャッシュキーについて、明示的な無効化(書き込み時のイベント駆動型、または上限を設けた TTL)を定義してください。
  • 一律の TTL — データの変化の速さに関係なく、すべてのデータに同じ TTL(例:1 時間)を使用することです。

    • 代わりに:データの変更頻度に TTL を合わせてください。価格は数秒、プロフィールは数分、設定は数時間です。
  • キーのパラメーター不足 — キャッシュキーにユーザー ID、ロケール、またはクエリパラメーターが含まれず、誤ったデータを返すことです。

    • 代わりに:結果に影響するすべてのパラメーターを含めてください:products:list:page=2:locale=en。
  • エラーのキャッシュ — エラーレスポンス(500 エラー、タイムアウト)を長い TTL で保存することです。

    • 代わりに:失敗時はキャッシュをスキップするか、素早く再試行できるように非常に短い TTL(5~10 秒)を使用してください。
  • キャッシュスタンピード — 人気のキーの期限が切れたとき、すべてのインスタンスが同時にオリジンへアクセスすることです。

    • 代わりに:stale-while-revalidate、ジッターを加えた TTL、またはミューテックスロックを使用し、1 つのインスタンスだけが更新するようにしてください。

コメント