コンテンツにスキップ

インデックスがクエリを速くするのに、なぜキャッシュが必要なのか?

原文

バックエンド面接:インデックスがクエリを速くするのに、なぜキャッシュが必要なのか?

要約

データベースのインデックスとキャッシュはどちらもパフォーマンス最適化だが、解決する問題が異なる。その違いと併用する理由を問う面接質問。

回答

インデックスは 「ディスク上のデータを効率的に見つける」 ための仕組み。キャッシュは 「そもそもディスクにアクセスしない」 ための仕組み。異なるレイヤーの最適化であり、併用することで最大のパフォーマンスが得られる。

解説

インデックスとは何か

インデックスは、本の索引のようなもの。データベースが目的のデータを素早く見つけるためのデータ構造。

インデックスなしの検索(フルテーブルスキャン):
  テーブル全行を1行ずつ確認 → O(n)
  50M行なら50M回の比較が必要

インデックスありの検索(B-Treeインデックス):
  ツリー構造を辿って検索 → O(log n)
  50M行でも約25回の比較で到達

具体例:

-- インデックスなし: 全行スキャン → 数秒〜数十秒
SELECT * FROM users WHERE email = 'user@example.com';

-- email にインデックスを作成
CREATE INDEX idx_users_email ON users(email);

-- インデックスあり: B-Tree探索 → 数ミリ秒
SELECT * FROM users WHERE email = 'user@example.com';

キャッシュとは何か

キャッシュは、頻繁にアクセスされるデータをメモリ上に保持し、データベースへのアクセスそのものを省略する仕組み。

キャッシュなし:
  アプリ → DB(ディスクI/O発生) → 結果返却
  所要時間: 5〜50ms

キャッシュあり(Redis等):
  アプリ → Redis(メモリアクセスのみ) → 結果返却
  所要時間: 0.1〜1ms

なぜ両方必要なのか

観点 インデックス キャッシュ
何を最適化 データの探索効率 データへのアクセス経路
保存場所 ディスク(DBの一部) メモリ(Redis等)
速度 数ms(ディスクI/O) 0.1ms以下(メモリ)
常に最新 はい(DBと連動) いいえ(TTLで管理)
対象 全データ ホットデータのみ
コスト ディスク容量(安い) メモリ容量(高い)

具体的にキャッシュが必要な場面

1. 同じクエリが大量に繰り返される場合

例: 人気商品のページ
  - 1秒間に1000回同じSELECTが実行される
  - インデックスがあっても、毎回DBにクエリ → DBの負荷が問題

  キャッシュで解決:
  - 1回目: DB → Redis に結果を保存(TTL: 60秒)
  - 2〜1000回目: Redis から返却 → DBへのアクセスゼロ

2. 複雑な集計クエリの結果

-- ダッシュボードの集計(JOINや集計が多く、インデックスがあっても重い)
SELECT category, COUNT(*), AVG(price), SUM(quantity)
FROM orders
JOIN products ON orders.product_id = products.id
WHERE orders.created_at > '2026-01-01'
GROUP BY category;

-- インデックスあり: 500ms(テーブル結合・集計処理が重い)
-- Redis キャッシュ: 0.5ms

インデックスはデータの探索は速くするが、集計・結合の計算コストは削減できない。

3. DBサーバーの負荷分散

インデックスだけの場合:
  全リクエスト → DB → DBのCPU/メモリが逼迫

キャッシュ併用:
  90%のリクエスト → Redis(メモリ)
  10%のリクエスト → DB
  → DBの負荷が1/10に

4. ネットワークレイテンシの削減

DBが別リージョンにある場合:
  アプリ(東京) → DB(大阪): ネットワーク往復 10ms + クエリ 5ms = 15ms

ローカルキャッシュ:
  アプリ(東京) → ローカルRedis(東京): 0.5ms

キャッシュの注意点(トレードオフ)

  1. データの鮮度: キャッシュは古いデータを返す可能性がある
  2. TTL(Time To Live)で制御
  3. 書き込み時にキャッシュを無効化(Cache Invalidation)

  4. キャッシュの一貫性: DB更新とキャッシュ更新のタイミング問題

  5. Write-Through: DB書き込みと同時にキャッシュ更新
  6. Write-Behind: キャッシュに書き込み、後でDBに反映
  7. Cache-Aside: アプリがキャッシュとDBを個別に管理(最も一般的)

  8. キャッシュスタンピード: TTL切れ時に大量リクエストがDBに殺到

  9. ロック機構で1リクエストだけDBに問い合わせ
  10. TTLにランダムな揺らぎを加える

結論:レイヤーの違い

リクエスト
[L1] ブラウザキャッシュ     ← 最速(ネットワークなし)
[L2] CDNキャッシュ          ← 静的コンテンツ
[L3] アプリケーションキャッシュ(Redis) ← 動的データ
[L4] DBクエリ + インデックス  ← 最も正確だが最も遅い

インデックスはL4を速くする。キャッシュはL4にそもそも到達させない。異なるレイヤーの最適化だから両方必要。

面接で差がつくポイント

  1. 「インデックスが解決する問題」と「キャッシュが解決する問題」の違いを明確に説明
  2. キャッシュのトレードオフ(鮮度、一貫性)にも言及
  3. レイヤーの概念で整理して説明できる
  4. 具体的な数値感(ms単位)を持っている

リンク