インデックスがクエリを速くするのに、なぜキャッシュが必要なのか?¶
原文¶
バックエンド面接:インデックスがクエリを速くするのに、なぜキャッシュが必要なのか?
要約¶
データベースのインデックスとキャッシュはどちらもパフォーマンス最適化だが、解決する問題が異なる。その違いと併用する理由を問う面接質問。
回答¶
インデックスは 「ディスク上のデータを効率的に見つける」 ための仕組み。キャッシュは 「そもそもディスクにアクセスしない」 ための仕組み。異なるレイヤーの最適化であり、併用することで最大のパフォーマンスが得られる。
解説¶
インデックスとは何か¶
インデックスは、本の索引のようなもの。データベースが目的のデータを素早く見つけるためのデータ構造。
インデックスなしの検索(フルテーブルスキャン):
テーブル全行を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
キャッシュの注意点(トレードオフ)¶
- データの鮮度: キャッシュは古いデータを返す可能性がある
- TTL(Time To Live)で制御
-
書き込み時にキャッシュを無効化(Cache Invalidation)
-
キャッシュの一貫性: DB更新とキャッシュ更新のタイミング問題
- Write-Through: DB書き込みと同時にキャッシュ更新
- Write-Behind: キャッシュに書き込み、後でDBに反映
-
Cache-Aside: アプリがキャッシュとDBを個別に管理(最も一般的)
-
キャッシュスタンピード: TTL切れ時に大量リクエストがDBに殺到
- ロック機構で1リクエストだけDBに問い合わせ
- TTLにランダムな揺らぎを加える
結論:レイヤーの違い¶
リクエスト
↓
[L1] ブラウザキャッシュ ← 最速(ネットワークなし)
↓
[L2] CDNキャッシュ ← 静的コンテンツ
↓
[L3] アプリケーションキャッシュ(Redis) ← 動的データ
↓
[L4] DBクエリ + インデックス ← 最も正確だが最も遅い
インデックスはL4を速くする。キャッシュはL4にそもそも到達させない。異なるレイヤーの最適化だから両方必要。
面接で差がつくポイント¶
- 「インデックスが解決する問題」と「キャッシュが解決する問題」の違いを明確に説明
- キャッシュのトレードオフ(鮮度、一貫性)にも言及
- レイヤーの概念で整理して説明できる
- 具体的な数値感(ms単位)を持っている