サイトのAPI図鑑B版
掲載情報が正確でない可能性があります。
開発者向けAPIツール

APIキャッシュ戦略の完全ガイド【Redis・CDN・HTTP Cache-Control】

REST API・GraphQL APIのパフォーマンスを向上させるキャッシュ戦略(HTTP Cache-Control・Redis・CDNキャッシュ・Edge Caching)の実装方法とキャッシュ無効化の設計を解説します。

#キャッシュ#Redis#CDN#パフォーマンス

APIキャッシュの重要性

APIのパフォーマンスとコストを最適化する最も効果的な手法の一つがキャッシュです。適切なキャッシュ戦略でレスポンスタイムを短縮し、データベース負荷を削減し、コストを削減できます。

HTTP Cache-Control ヘッダー

// パブリックAPIレスポンスのキャッシュ設定
app.get('/api/products', async (req, res) => {
  const products = await getProducts();
  
  res.set({
    'Cache-Control': 'public, max-age=300, stale-while-revalidate=60',
    // public: CDNとブラウザでキャッシュ可
    // max-age=300: 5分間キャッシュ有効
    // stale-while-revalidate=60: 有効期限後60秒間は古いキャッシュを返しながらバックグラウンド更新
    'ETag': generateETag(products), // コンテンツハッシュ
    'Last-Modified': new Date().toUTCString()
  });
  
  res.json(products);
});

// 認証必要なデータはprivate
res.set('Cache-Control', 'private, max-age=60');

Redisキャッシュの実装

import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

// キャッシュラッパー関数
const withCache = async (key, ttl, fetchFn) => {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);
  
  const data = await fetchFn();
  await redis.setEx(key, ttl, JSON.stringify(data));
  return data;
};

// 使用例
app.get('/api/rankings', async (req, res) => {
  const rankings = await withCache(
    'api:rankings:top50',
    60 * 10, // 10分TTL
    () => db.items.findMany({ orderBy: { viewCount: 'desc' }, take: 50 })
  );
  res.json(rankings);
});

// キャッシュの無効化
const invalidateRankingCache = async () => {
  await redis.del('api:rankings:top50');
};
// データ更新後に呼び出す

CDNキャッシュの設定(Cloudflare)

// Cloudflare Workersでのキャッシュコントロール
export default {
  async fetch(request, env) {
    const cacheUrl = new URL(request.url);
    const cacheKey = new Request(cacheUrl.toString(), request);
    const cache = caches.default;
    
    // キャッシュを確認
    let response = await cache.match(cacheKey);
    if (response) {
      return new Response(response.body, {
        headers: { ...response.headers, 'X-Cache': 'HIT' }
      });
    }
    
    // オリジンから取得
    response = await fetch(request);
    
    if (response.ok) {
      // Edge Cacheに保存
      const responseToCache = response.clone();
      event.waitUntil(cache.put(cacheKey, responseToCache));
    }
    
    return new Response(response.body, {
      headers: { ...response.headers, 'X-Cache': 'MISS' }
    });
  }
}

GraphQLのキャッシュ

GraphQLはPOSTリクエストが基本のため、HTTPキャッシュが使えません。Apollo Server・Apollo Clientのキャッシュ機能、またはPersistedQuerysを使ってGETリクエストとして送信してCDNキャッシュを活用します。

キャッシュ設計のベストプラクティス

  • 頻繁に変化するデータ:TTLを短く(30秒〜1分)かキャッシュしない
  • マスタデータ:TTLを長く(1時間〜1日)、更新時に明示的に無効化
  • ユーザー固有データ:Cache-Control: privateまたはユーザーIDをキーに含める
  • キャッシュスタンピード対策:大量のリクエストがキャッシュ切れと同時に来る問題を防ぐため、TTLに若干のジッターを加える

まとめ

APIキャッシュはパフォーマンス改善の中で最も費用対効果が高い施策の一つです。HTTP Cache-ControlヘッダーはCDNとブラウザでのキャッシュを制御し、RedisはDBクエリ結果をメモリにキャッシュしてレスポンスを高速化します。データの更新頻度・ユーザー固有性・整合性要件に応じて適切なキャッシュ戦略を選択してください。

よくある質問

Q.APIキャッシュとデータベースキャッシュの違いは何ですか?

APIキャッシュはHTTPレスポンス全体をキャッシュし、同一リクエストにはバックエンドを経由せずにキャッシュから直接返します。データベースキャッシュ(Redis等)はDBクエリ結果をキャッシュし、DB負荷を削減しますが、APIサーバーへのリクエストは発生します。

Q.キャッシュの無効化(Cache Invalidation)はどのように実装しますか?

データが更新されたタイミングでRedisの該当キーを削除(DEL)またはキャッシュを上書きします。CDNキャッシュの無効化はCloudflare・CloudFront・Fastlyのpurge APIを呼び出します。タグベースキャッシュ(キャッシュグループに名前付け)で関連するキャッシュを一括削除する方法が効率的です。

Q.ユーザー固有のデータはキャッシュできますか?

ユーザー固有のデータ(認証情報・個人プロフィール等)は共有キャッシュ(CDN)に保存すべきではありません。Cache-Control: privateを指定してブラウザのみでキャッシュするか、ユーザーIDをキャッシュキーに含めたRedisキャッシュを使います。

関連記事