Chuyển đến nội dung chính

レッスン 7: キャッシュ戦略 - キャッシュを使用してパフォーマンスを最適化する

キャッシュ層: クライアント、CDN、Web サーバー、アプリケーション、データベース。キャッシュ パターン: キャッシュ アサイド、ライトスルー、ライトビハインド、リフレッシュ アヘッド。キャッシュエビクションポリシー (LRU、LFU、TTL)。 Redis 対 Memcached。キャッシュスタンピード、サンダーリングハードとその対処方法。分散キャッシュ。

🏗️ アーキテクチャ — レッスン 7 レッスン 7: キャッシュ戦略 - パフォーマンスの最適化 キャッシュを備えた機能

システムアーキテクチャ: ゼロからヒーローへ

パート 2: インフラストラクチャ コンポーネント

xdev.asia

はじめに

「コンピュータ サイエンスで難しいことは 2 つだけです。キャッシュの無効化と名前付けです。」 — フィル・カールトン

キャッシュは、待ち時間を短縮し、スループットを向上させるための最も重要な技術です。キャッシュ ヒットにより、応答時間が 100 ミリ秒から 1 ミリ秒に短縮され、100 倍 の改善が得られます。


1. レイヤーのキャッシュ

┌──────────────────────────────────────────────┐
│                  User Request                 │
└───────────────────┬──────────────────────────┘
                    ▼
        ┌───────────────────┐
        │  Browser Cache    │  ← HTML, CSS, JS, Images
        └─────────┬─────────┘
                  ▼
        ┌───────────────────┐
        │    CDN Cache       │  ← Static files, API responses
        └─────────┬─────────┘
                  ▼
        ┌───────────────────┐
        │  Load Balancer     │  ← SSL session cache
        └─────────┬─────────┘
                  ▼
        ┌───────────────────┐
        │  Application Cache │  ← Redis/Memcached
        └─────────┬─────────┘
                  ▼
        ┌───────────────────┐
        │  Database Cache    │  ← Query cache, Buffer pool
        └───────────────────┘

2. キャッシュ パターン

2.1 キャッシュアサイド (遅延読み込み)

Read:
  App ──► Cache: "Có user:123 không?"
  Cache:  "Không" (MISS)
  App ──► Database: SELECT * FROM users WHERE id=123
  DB ──►  App: {name: "John", ...}
  App ──► Cache: SET user:123 = {name: "John", ...}  ← Cache lại
  App ──► Client: {name: "John", ...}

Lần sau:
  App ──► Cache: "Có user:123 không?"
  Cache:  "Có!" (HIT) → Return ngay, không cần DB
def get_user(user_id):
    # 1. Check cache
    cached = redis.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)

    # 2. Cache miss -> query DB
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)

    # 3. Cache the result
    if user:
        redis.setex(f"user:{user_id}", 3600, json.dumps(user))  # TTL 1h

    return user

利点: 要求されたデータのみをキャッシュする、シンプル 欠点: キャッシュミス = 3 トリップ (キャッシュのチェック + DB のクエリ + キャッシュの設定)

2.2 ライトスルー

Write:
  App ──► Cache: SET user:123 = {name: "Jane"} ← Update cache
  Cache ──► Database: UPDATE users SET name='Jane' WHERE id=123
  Cache ──► App: Success

Read:
  App ──► Cache: GET user:123 → Always HIT (data luôn trong cache)

利点: キャッシュは常に DB と同期しており、読み取りは常に高速です 欠点: 書き込みが遅い (キャッシュ + DB を経由する必要がある)、キャッシュ データが読み取られない可能性がある

2.3 ライトビハインド (ライトバック)

Write:
  App ──► Cache: SET user:123 = {name: "Jane"} ← Update cache ngay
  Cache: "OK, sẽ ghi vào DB sau"
  App ──► Client: Success ngay lập tức!

  Background (async):
  Cache ──► Database: UPDATE users SET name='Jane'

利点: 非常に高速な書き込み、バッチ書き込み 欠点: DB に書き込む前にキャッシュがクラッシュした場合、データが失われるリスクがあります。

2.4 先行リフレッシュ

Cache có TTL = 60s

T=0:   Cache data (TTL=60s)
T=50s: TTL sắp hết → Background refresh từ DB
T=55s: Cache refreshed (TTL reset = 60s)
T=60s: Cache vẫn có data → Không có cache miss

→ Giảm cache miss gần như về 0

利点: キャッシュミスがほとんどない 欠点: 間違った予測 → 無駄で複雑な更新


3. キャッシュエビクションポリシー

キャッシュがいっぱいになった場合は、どのエントリを削除するかを決定する必要があります。

ポリシー説明こんな方に最適
LRU (最近使用されていないもの)最も最近アクセスされていないエントリを削除する汎用
LFU (最も頻繁に使用されない)最もアクセスの少ないエントリを削除ホット データ パターン
FIFO (先入れ先出し)最も古いエントリを削除簡単な使用例
TTL (生存時間)一定時間経過後に削除データには自然有効期限があります
ランダムランダム削除分布が均等な場合

LRU の視覚化

Cache size = 3

Access A: [A]
Access B: [B, A]
Access C: [C, B, A]       ← Cache đầy
Access D: [D, C, B]       ← A bị evict (ít truy cập gần đây nhất)
Access B: [B, D, C]       ← B move lên đầu
Access E: [E, B, D]       ← C bị evict

4. Redis と Memcached の比較

特長レディスメムキャッシュ
データ構造文字列、リスト、セット、ソートされたセット、ハッシュ、ストリーム文字列のみ
永続性RDB+AOFいいえ
レプリケーションマスタースレーブいいえ
クラスターRedis クラスタークライアント側のシャーディング
パブ/サブ✓いいえ
Lua スクリプト✓いいえ
メモリ効率良い単純な文字列の方が良い
マルチスレッドシングルスレッド (6.0 以降の io スレッド)マルチスレッド

推奨事項: ほとんどのユースケースでは Redis を使用してください。 Memcached は、単純な文字列キャッシュ、マルチスレッドのパフォーマンスが必要な場合にのみ使用してください。


5. キャッシュの問題と解決策

5.1 キャッシュスタンピード (雷鳴の群れ)

Vấn đề:
  Popular key expires
  1000 requests đồng thời → tất cả MISS
  → 1000 queries tới DB cùng lúc → DB crash!

Giải pháp 1 - Locking:
  Request 1: Cache MISS → Lock key → Query DB → Set cache → Release lock
  Request 2-1000: Cache MISS → Thấy lock → Đợi → Get from cache

Giải pháp 2 - Stale-While-Revalidate:
  Trả cached data (dù expired) → Background refresh

5.2 キャッシュの侵入

Vấn đề:
  Query cho data KHÔNG TỒN TẠI
  Cache always MISS → DB query returns empty → Không cache
  → Attacker spam requests cho non-existent IDs

Giải pháp 1 - Cache empty result:
  redis.setex("user:99999", 300, "NULL")  # Cache "không có" 5 phút

Giải pháp 2 - Bloom Filter:
  Trước khi query, check Bloom Filter
  Nếu key chắc chắn không tồn tại → Return empty ngay

5.3 キャッシュ雪崩

Vấn đề:
  Nhiều keys expire cùng lúc → Massive cache misses → DB overload

Giải pháp:
  Thêm random jitter vào TTL
  TTL = base_ttl + random(0, base_ttl * 0.1)

  Ví dụ: base_ttl = 3600s
  Key 1: TTL = 3600 + random(0, 360) = 3847s
  Key 2: TTL = 3600 + random(0, 360) = 3612s
  Key 3: TTL = 3600 + random(0, 360) = 3955s

6. 分散キャッシュ

6.1 一貫性のあるハッシュ

Khi thêm/xóa cache node, consistent hashing đảm bảo
chỉ 1/N keys cần migrate (thay vì tất cả)

Hash Ring:
          Node A
         ╱      ╲
   Node D        Node B
         ╲      ╱
          Node C

Key "user:123" → hash → vị trí trên ring → Node B
Thêm Node E → chỉ một phần keys từ Node B chuyển sang E

概要

パターン書き込み速度読み取り速度一貫性使用例
キャッシュアサイド通常高速 (1 回目以降)最終的な汎用
ライトスルー遅い常に速い強い大量の読み取り + 一貫性
後書き速い常に速い最終的な書き込みが多い
先を更新通常常に速いほぼリアルタイム予測可能なアクセス

演習

  1. キャッシュ戦略: ニュース アプリケーションのキャッシュを設計します: (a) 人気の記事 (100 万回のビュー)、(b) 古い記事 (数回のビュー)、(c) リアルタイムのコメント。タイプごとにキャッシュパターンとTTLを選択します。

  2. キャッシュの問題: フラッシュ セール システムでは、10 万人のユーザーが 1 つの製品に同時にアクセスします。キャッシュキー product:123 販売開始と同時に有効期限が切れます。スタンピード キャッシュを回避するソリューションを設計します。

  3. Redis 設計: リーダーボード (スコアに基づく上位 1​​00 ユーザー) の Redis データ構造を設計します。サポートが必要です: スコアの追加/更新、上位 N の取得、1 ユーザーのランクの取得。