【テクニカル・上級編】 複合インデックス – PostgreSQL

複合インデックスの「順序」という名の落とし穴

PostgreSQLを長年触っていると、たまに「インデックスを貼ったのに遅い」という相談を受けることがあります。Explain Analyzeの結果を見ると、せっかく作成した複合インデックスが無視されていたり、Index ScanではなくBitmap Index Scanが走っていたり……。

多くの現場で、インデックスを「魔法の杖」のように捉えて列を適当に並べているケースを見かけます。しかし、PostgreSQLのB-treeインデックスにおいて、列の順序は単なる「設定」ではなく、データベースが物理的にデータをどう探索するかの「地図」そのものです。

今日は、複合インデックスの最左接頭辞(Leftmost Prefix)ルールを、現場のエンジニアが陥りやすい罠とともに深掘りしてみましょう。

—

1. なぜ「順序」がすべてなのか

PostgreSQLのB-treeインデックスは、指定された列の順序でソートされたツリー構造を保持しています。ここで重要なのは、「インデックスの左端から順に比較が行われる」という制約です。

例えば、`(tenant_id, user_id, created_at)` という複合インデックスを貼ったとしましょう。このインデックスは、物理的に「まず `tenant_id` で並び、その中で `user_id` が並び、最後に `created_at` で並ぶ」という階層構造になっています。

ここで、以下のクエリはどうなるでしょうか。

SELECT FROM events WHERE user_id = 123;

結論から言うと、このインデックスは「使われない(あるいはフルスキャンに近い効率になる)」可能性が高いです。なぜなら、PostgreSQLは `tenant_id` が指定されていない状態では、インデックスのツリーをどの枝から降りればいいのか判断できないからです。これが「最左接頭辞ルール」の本質です。

2. 現場で遭遇する「インデックスの死」

よくあるのが、範囲検索を途中に挟んでしまうケースです。

— インデックス: (a, b, c)
WHERE a = 1 AND b > 10 AND c = 5;

この場合、PostgreSQLは `a` と `b` まではインデックスを使って絞り込めます。しかし、`b` は範囲検索(Range Query)であるため、その後の `c` をインデックスで効率的に探すことができません。インデックスの構造上、`b` の範囲内では `c` はソートされていない状態と同じだからです。

パフォーマンストラブルシューティングの勘所

もし「インデックスを貼っているのに、実行計画の `Index Cond` にすべての条件が含まれていない」と感じたら、以下の順序で疑ってください。

1. 等値検索(=)を左側に寄せているか?
統計情報とオプティマイザの挙動を考慮すると、カーディナリティ(値の多様性)が高い列を左に置くのが定石ですが、それ以上に「等値条件で絞れる列を左に置く」ことが優先です。
2. 範囲検索(>, <, BETWEEN)が右端にあるか?
範囲検索はインデックスの「打ち止め」です。それより右側に条件を置いても、インデックスはフィルタリング(Filter)にしか貢献できません。
3. ソート(ORDER BY)との整合性
`ORDER BY` を伴うクエリでは、インデックスの順序がソートコストをゼロにできる鍵になります。`WHERE` 句だけでなく、アプリケーションで頻出するソート順を考慮してインデックスを設計できているか、再確認が必要です。

3. 「とりあえず複合インデックス」が招くコスト

複合インデックスは万能ではありません。列数を増やせば増やすほど、以下のコストが跳ね上がります。

  • 書き込み負荷(Write Amplification):

当然ですが、インデックス列が増えるたびに、INSERTやUPDATE時のインデックス更新コストが増大します。高頻度で更新されるテーブルに「念のため」と巨大な複合インデックスを貼ることは、システムの息の根を止める行為になりかねません。

  • ディスクI/Oとメモリ効率:

PostgreSQLはインデックスをメモリ(shared_buffers)にキャッシュしようとします。不要に長いインデックスはキャッシュ効率を下げ、物理的な読み込みを増やします。

最後に:エンジニアとしての矜持

私は、インデックス設計とは「クエリとの対話」だと思っています。

Explain Analyzeの出力結果、`pg_stats` から読み取れるデータの偏り、そしてアプリケーションがどのようなアクセスパターンを要求しているか。これらをパズルのように組み合わせ、最小のインデックスで最大のパフォーマンスを引き出す。これこそが、データベースエンジニアの醍醐味ではないでしょうか。

「とりあえずインデックスを貼る」段階から一歩進んで、インデックスの内部構造を頭の中に描きながらクエリを書く。そんな視点を持つだけで、あなたのパフォーマンスチューニングの精度は劇的に向上するはずです。

さて、皆さんの本番環境のインデックス、本当にその順序で最適でしょうか? 明日の朝、実行計画を眺めながら少しだけ確認してみることをお勧めします。

コメント

タイトルとURLをコピーしました