LESSON 30分

ストーリー

高橋アーキテクト
アプリケーションにRedisキャッシュを入れました。でもまだ遅いです

高橋アーキテクトが図を描いた。

高橋アーキテクト
キャッシュは1層だけじゃない。ブラウザ、CDN、アプリケーション、データベース。各層にキャッシュを配置して、リクエストがなるべく遠くまで到達しないようにするんだ

4層のキャッシュ

全体像

graph TD
    User["ユーザー"]
    L1["Layer 1: ブラウザキャッシュ<br/>Cache-Control, ETag"]
    L2["Layer 2: CDNキャッシュ<br/>CloudFront, Cloudflare"]
    L3["Layer 3: アプリケーションキャッシュ<br/>Redis, Memcached"]
    L4["Layer 4: データベースキャッシュ<br/>クエリキャッシュ、バッファプール"]
    DB[("データベース")]

    User -->|"最速"| L1
    L1 --> L2
    L2 --> L3
    L3 --> L4
    L4 -->|"最も遅い"| DB

    classDef fast fill:#cfc,stroke:#393
    classDef slow fill:#fcc,stroke:#933
    class L1 fast
    class DB slow

Layer 1: ブラウザキャッシュ

ユーザーのブラウザにデータを保存します。ネットワークリクエストが発生しないため最速です。

// HTTPヘッダーでブラウザキャッシュを制御
class CacheHeaderMiddleware {
  // 静的アセット: 長期キャッシュ + キャッシュバスティング
  staticAssets(res: Response): void {
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    // 1年間キャッシュ。ファイル名にハッシュを含めて更新を管理
    // 例: style.abc123.css
  }

  // API レスポンス: 短期キャッシュ + 再検証
  apiResponse(res: Response): void {
    res.setHeader('Cache-Control', 'private, max-age=60, must-revalidate');
    res.setHeader('ETag', '"abc123"');
    // 60秒キャッシュ。期限後はETagで変更確認
  }

  // 動的コンテンツ: キャッシュしない
  dynamicContent(res: Response): void {
    res.setHeader('Cache-Control', 'no-store');
    // 決済ページ、個人情報ページなど
  }
}
ヘッダー用途
Cache-Control: publicCDNでもキャッシュ可能
Cache-Control: privateブラウザのみキャッシュ可能
max-age=NN秒間キャッシュを有効にする
ETagコンテンツのバージョン識別子
no-storeキャッシュしない

Layer 2: CDNキャッシュ

CDN(Content Delivery Network)のエッジサーバーにコンテンツをキャッシュします。

// CDN設定の例
const cdnConfig = {
  // 静的アセット: 長期キャッシュ
  staticAssets: {
    pathPattern: '/assets/*',
    ttl: 86400 * 365,  // 1年
    behaviors: {
      compress: true,
      minTTL: 86400,
    },
  },

  // API レスポンス: 短期キャッシュ
  apiResponses: {
    pathPattern: '/api/products/*',
    ttl: 60,           // 1分
    behaviors: {
      queryStringCaching: 'all', // クエリ文字列もキーに含める
      headerWhitelist: ['Accept-Language'],
    },
  },

  // 動的ページ: キャッシュしない
  dynamicPages: {
    pathPattern: '/api/cart/*',
    ttl: 0,
    behaviors: {
      forwardCookies: 'all',
    },
  },
};

Layer 3: アプリケーションキャッシュ

RedisやMemcachedを使ったサーバーサイドのキャッシュです。

// Redis を使ったアプリケーションキャッシュ
class ApplicationCache {
  constructor(private redis: RedisClient) {}

  // クエリ結果のキャッシュ
  async getCachedQuery<T>(
    queryKey: string,
    fetchFn: () => Promise<T>,
    ttlSeconds: number = 300
  ): Promise<T> {
    const cached = await this.redis.get(queryKey);
    if (cached) {
      return JSON.parse(cached);
    }

    const result = await fetchFn();
    await this.redis.setex(queryKey, ttlSeconds, JSON.stringify(result));
    return result;
  }

  // 計算結果のキャッシュ
  async getCachedComputation<T>(
    key: string,
    computeFn: () => Promise<T>,
    ttlSeconds: number = 600
  ): Promise<T> {
    return this.getCachedQuery(key, computeFn, ttlSeconds);
  }
}

// 使用例
const products = await cache.getCachedQuery(
  'popular-products:page1',
  () => db.getPopularProducts({ page: 1, limit: 20 }),
  300 // 5分間キャッシュ
);

Layer 4: データベースキャッシュ

データベース自身が持つキャッシュ機構です。

// PostgreSQLの場合
// shared_buffers: メモリ上にデータページをキャッシュ
// effective_cache_size: OSのファイルシステムキャッシュも含めた推定値

// クエリプラン最適化のためのヒント
const dbConfig = {
  sharedBuffers: '4GB',        // DB専用キャッシュ
  effectiveCacheSize: '12GB',  // OS含む全キャッシュ
  workMem: '256MB',            // ソート・ハッシュ用
};

各層の特性比較

レイテンシ容量管理者適したデータ
ブラウザ0msフロントエンド静的アセット、UIデータ
CDN1-50msインフラ静的ファイル、公開APIレスポンス
アプリケーション1-10msバックエンドクエリ結果、セッション
データベース1-5msDBAクエリプラン、データページ

まとめ

ポイント内容
多層構造ブラウザ → CDN → アプリ → DB の4層
原則ユーザーに近い層でキャッシュするほど高速
ブラウザCache-Control, ETag でHTTPキャッシュ
CDNエッジサーバーで地理的に分散
アプリケーションRedis/Memcached でクエリ結果をキャッシュ
データベースバッファプール、クエリキャッシュ

チェックリスト

  • 4層のキャッシュアーキテクチャを説明できる
  • 各層の適したデータとレイテンシを理解した
  • Cache-Controlヘッダーの基本的な使い方を把握した
  • CDNキャッシュの役割を理解した

次のステップへ

次は「キャッシュの無効化」を学びます。“コンピュータサイエンスで最も難しい問題の1つ”と言われるキャッシュ無効化の戦略を理解しましょう。


推定読了時間: 30分