【テクニカル・上級編】 カバリングインデックス (INCLUDE句) – PostgreSQL

インデックス設計の「深淵」:PostgreSQLのINCLUDE句でIndex-Only Scanを極める

データベースのパフォーマンスチューニングにおいて、インデックスは諸刃の剣だ。適切なインデックスはクエリを劇的に加速させるが、過剰なインデックスは書き込み負荷を増大させ、バキュームのオーバーヘッドを招く。

多くのエンジニアが「インデックスは検索条件(WHERE句)のためにある」と考えがちだが、PostgreSQLのインデックス設計における真の「熟練の技」は、Index-Only Scan(インデックスのみスキャン)をいかに意図的に作り出すかに集約される。

今日は、そのための強力な武器である `INCLUDE` 句について、少し深く掘り下げてみたいと思う。

INCLUDE句が解決する「不都合な真実」

通常、B-Treeインデックスには「検索キー」となるカラムが含まれる。しかし、クエリが `SELECT` リストで他のカラムも要求した場合、PostgreSQLはどうするか。インデックスからキーを見つけた後、そのポインタを頼りにヒープ(テーブル本体のデータブロック)まで物理的に読みに行く必要がある。これが「Heap Fetch」だ。

もし、読み取りたい列がすべてインデックス内に存在していれば、このヒープへのアクセスを完全に回避できる。これがIndex-Only Scanの恩恵だ。

これまでは、すべての列を複合インデックスのキーに含めるしかなかった。だが、それは大きな欠点がある。

1. インデックスのサイズ肥大化: キーとしてインデックスに含めると、B-Treeのソート順序維持のために比較コストや領域消費が発生する。
2. ユニーク制約の制約: 全カラムをキーに含めると、一意性制約のスコープが意図せず広がってしまう。

ここで登場するのが `INCLUDE` 句だ。

CREATE INDEX idx_user_email_include_name
ON users (email)
INCLUDE (name, status);

この構文の美しさは、`email` は「検索キー」としてB-Treeの構造を構成するが、`name` と `status` は「付加データ」としてリーフノードに格納されるだけという点にある。ソート順序にも関与せず、一意性制約のチェック対象にもならない。まさに「美味しいところだけ」をインデックスに持たせることができるのだ。

なぜこれが「パフォーマンストラブルの特効薬」になるのか

実務でよくあるのが、特定のクエリだけが異常に遅く、かつCPU負荷が高いケースだ。原因を探ると、案の定 `Bitmap Heap Scan` や `Seq Scan` が走り、大量のページをランダムアクセスしていることが多い。

ここで `INCLUDE` を使う設計に切り替えると、実行計画は鮮やかに `Index Only Scan` に変わる。

ただし、注意が必要なのは Visibility Map(VM) の存在だ。PostgreSQLのIndex-Only Scanは、インデックス内のエントリが「現在のトランザクションから見て可視であるか」をヒープのVisibility Mapで確認する。つまり、テーブル全体が更新された直後などは、VMが更新されていないためにヒープを見に行かざるを得ず、Index-Only Scanが効かないことがある。

「インデックスを貼ったのに、なぜ効かないんだ?」と頭を抱える若手エンジニアをよく見かけるが、原因の多くはここにある。この時は `VACUUM` を適切に走らせるか、そもそも「本当にそのクエリが頻発するのか」を統計情報から再考する必要がある。

熟練エンジニアへのアドバイス:設計の引き出し

私がこの `INCLUDE` 句を設計に組み込む際は、以下の基準を設けている。

  • SELECT句の固定化: 頻繁に実行され、かつ特定の列しか取得しないクエリが対象。
  • 書き込みコストとのトレードオフ: `INCLUDE` で列を増やせば、当然ながらそのテーブルに対する `INSERT/UPDATE` のコストは増える。インデックスのメンテナンスコストと、そのクエリの高速化によるメリットを天秤にかける必要がある。
  • 不要なインデックスの削除: `INCLUDE` を使った複合インデックスを作ることで、これまであった「似たような別インデックス」を統合できることが多い。インデックスを減らすことは、データベース全体の健康状態を保つ上で最も重要なタスクの一つだ。

最後に

技術というものは、教科書的な知識を知っているだけでは不十分だ。その背後にあるアーキテクチャ、つまりデータがどう物理的に配置され、エンジンがどうそれらを読み込んでいるかという「動き」を想像できるかどうかが、エンジニアとしての格を分ける。

`INCLUDE` 句は、単なる構文ではない。データベースのパフォーマンスという極めて現実的な課題に対する、PostgreSQLからの「スマートな回答」なのだ。

ぜひ皆さんの本番環境でも、実行計画(`EXPLAIN (ANALYZE, BUFFERS)`)を眺めながら、ヒープへのアクセスを最小化する美しいインデックス設計を試みてほしい。きっと、クエリのレスポンスタイムだけでなく、システムの背骨が一段と強固になったことを実感できるはずだ。

コメント

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