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

複合インデックスの「順序」という名の魔物:PostgreSQLエンジニアが知るべき深淵

PostgreSQLのインデックス設計をしていると、ふと立ち止まる瞬間がある。「この列の組み合わせ、本当にこれで最適か?」と。

単一列のインデックスは単純だ。だが、複合インデックス(Multi-column Indexes)の世界に足を踏み入れた途端、それは途端に「魔法」から「呪い」へと姿を変えることがある。今回は、教科書的な「左端一致」という言葉の裏側にある、PostgreSQLの内部挙動と、現場で僕たちが直面するトラブルシューティングの勘所について話をしよう。

「左端一致」はなぜ絶対的なのか

まず、基本を再確認しよう。「複合インデックスは、先頭列から順に使われる」。これはB-treeインデックスの構造そのものだ。

B-treeのノードは、インデックスキーの順序に基づいてソートされている。例えば `(a, b)` という複合インデックスがある場合、データはまず `a` でソートされ、`a` が同じであれば次に `b` でソートされる。

ここで重要なのは、`WHERE b = ?` というクエリを投げたとき、PostgreSQLのオプティマイザがなぜそのインデックスをスルーするのか、だ。答えは単純で、`b` だけのインデックスが作られていないから。`a` の値が確定しない限り、`b` の値がインデックスのどこに散らばっているか、B-treeを辿る術がないからだ。

これを理解していないと、インデックスを増やせば増やすほど「インデックスの肥大化」と「書き込み負荷の増大」という負の遺産を抱えることになる。

高度な設計:列の順序を決定する「カーディナリティ」と「選択性」

よくある誤解が、「カーディナリティ(値の種類の多さ)が高い列を先頭に置くべきだ」というもの。半分正解で、半分間違いだ。

真に重要なのは「クエリの選択性(Selectivity)」である。
例えば、`status`(値が3つしかない)と `user_id`(数百万ある)があるとする。教科書的には `user_id` を先頭にしたくなるが、もしあなたのサービスが常に「特定のステータスのユーザー」を検索するクエリばかりを投げているなら、`status` を先頭に置く方が、インデックスの絞り込み効率(スキャン対象の範囲)が劇的に改善されることもある。

  • 等価条件(=): どの列を先頭にしてもいい。
  • 範囲条件(<, >, BETWEEN): これが含まれる列を複合インデックスの「末尾」に配置するのが鉄則。

なぜか?範囲条件を途中で使ってしまうと、その後の列はインデックスとして機能しなくなるからだ。例えば `(a, b, c)` で `b` に範囲条件を使うと、`c` はインデックスの絞り込みに使われず、フィルタリング(Heap Fetch)の段階に回されてしまう。

パフォーマンストラブルシューティング:Explainの「嘘」を見抜く

現場で「インデックスを貼ったのに遅い」という相談を受けるとき、僕は決まって `EXPLAIN (ANALYZE, BUFFERS)` を見る。

特に注意すべきは `Index Cond` と `Filter` の境界線だ。

— 実行計画の確認
EXPLAIN ANALYZE SELECT FROM logs WHERE type = ‘error’ AND created_at > ‘2023-01-01’;

もし `Index Cond` に `type` しか含まれていなければ、`created_at` はテーブル全体をスキャンした後の「後付けのフィルタ」として処理されている。これは数百万行のテーブルでは致命的だ。

僕がよく行うチューニングのヒント:
1. `pg_stat_user_indexes` を監視する: そのインデックス、本当に使われているか? `idx_scan` が0に近いインデックスは、メモリとディスクを食いつぶすだけの「お荷物」だ。
2. Include句の検討: PostgreSQL 11から導入された `INCLUDE` 句は、インデックスのリーフノードに付加的な情報を保持できる。ソートには使えないが、Index Only Scanを誘発させるための「隠し味」としては最強だ。

最後に:エンジニアの美学

結局のところ、インデックス設計に「銀の弾丸」はない。
アプリケーションのクエリパターンは生き物だ。昨日まで最適だったインデックスが、データ分布の変化やクエリの複雑化によって、明日にはボトルネックになる。

だからこそ、僕たちは常に「なぜこの順序なのか」「本当にこの列が必要か」という問いを繰り返す必要がある。インデックスを削ぎ落とし、最小限の構造で最大のパフォーマンスを引き出すこと。それこそが、PostgreSQLの深淵を覗き込んだエンジニアが到達すべき「美学」なのだと僕は思う。

さあ、あなたのデータベースの実行計画を見てみよう。そこに隠された無駄を見つけることが、パフォーマンス改善の第一歩だ。

コメント

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