caching について
caching は、インメモリ、Redis、CDN、クライアント側のキャッシュ設計とコードレビューを支援する開発者向けスキルです。無効化、TTL、キー設計の指針に加え、アンチパターンと実装例を示します。
主な機能
- 01元データの変更に応じた無効化方針
- 02データの変動頻度に基づく TTL 設定
- 03結果に影響するパラメーターのキーへの反映
- 04キャッシュ層の選択と組み合わせ
- 05機密データやエラーのキャッシュリスクの把握
- 06一般的な構成のコード例
活用シーン
- 01API やサービスのキャッシュ層設計
- 02古いデータやキャッシュ集中の調査
- 03キャッシュ読み書きコードのレビュー
- 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 つのインスタンスだけが更新するようにしてください。
コメント
Markdown 対応