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

データベースの「死角」を突く:PostgreSQLのINCLUDE句とインデックスオンリースキャンの深淵

データベースエンジニアとして長く現場に立っていると、ふとした瞬間に「なぜ、これほどまでにインデックスを貼っているのに、このクエリは遅いのか?」という壁にぶつかることがあります。

特に、PostgreSQLを使っていると、インデックスを貼れば貼るほど更新負荷(Write Amplification)が跳ね上がり、かといってインデックスを削れば読み取りが遅くなるという、終わりのないトレードオフに頭を悩ませるものです。

今回は、その悩みを解決する強力な武器の一つである「INCLUDE句」と、それが実現する「インデックスオンリースキャン(Index Only Scan)」について、一歩踏み込んだ話をしましょう。

—

「インデックスオンリースキャン」を阻む見えない壁

PostgreSQLのインデックスオンリースキャンは、文字通り「テーブル本体(ヒープ)に一切触れずにクエリを完結させる」手法です。これが決まると、I/Oコストは劇的に下がります。

しかし、ここで多くのエンジニアが躓くポイントがあります。
例えば、`SELECT email FROM users WHERE name = ‘Taro’;` というクエリがあったとします。`name`列にインデックスを貼っていても、PostgreSQLはヒープへ問い合わせに行こうとします。なぜなら、インデックスには`email`の情報が含まれていないからです。

ここで「じゃあ`name`と`email`の両方をインデックスに入れればいいじゃないか」と考えるでしょう。しかし、B-treeインデックスのキーとしてこれらを追加すると、ソート順の考慮やインデックスサイズの肥大化、さらには更新時のオーバーヘッドが無視できないレベルになります。

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

INCLUDE句が提供する「実質的な解決」

PostgreSQL 11から導入されたこの機能は、インデックスの構造を根本から変えるものではありません。あくまで、B-treeインデックスの「非キー列(Non-key attributes)」としてデータを持たせる手法です。

CREATE INDEX idx_users_name_email ON users (name) INCLUDE (email);

このインデックスを作成すると、B-treeのキーとなるのは`name`だけです。`email`はリーフページに格納されますが、ソートや検索の対象にはなりません。

内部アーキテクチャの視点

何が嬉しいのか。それはインデックスのメンテナンスコストを最小限に抑えつつ、ヒープ参照を回避できる点にあります。

  • ソートの負荷がない: 非キー列はB-treeの論理的な整列順序に影響を与えません。これにより、キー列のみで構成されるインデックスと同等の更新コストで運用可能です。
  • カバリングの完成: 検索条件には使わないが、結果として取得したい列(ペイロード)をインデックス側に「おまけ」として持たせることができます。これにより、オプティマイザは自信を持ってインデックスオンリースキャンを選択できるようになります。

—

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

現場でこの機能を使いこなすために、いくつか注意すべき「罠」があります。

1. Visibility Map の罠

インデックスオンリースキャンが効かない原因の多くは、実はVisibility Map(VM)にあります。
PostgreSQLは、リーフページにあるデータが「現在、すべてのトランザクションから可視か」を確認するためにVMを参照します。もしデータが更新されたばかりで、VACUUMが走っていない場合、結局ヒープを見に行かざるを得ないことがあります。
「インデックスを貼ったのに遅い」ときは、単なるインデックスの構成不足ではなく、`VACUUM`の頻度や統計情報の鮮度を疑うのが、熟練者の作法です。

2. INCLUDE句の「付けすぎ」は禁物

非キー列だからといって、全ての列を詰め込めばいいというものではありません。インデックスサイズが肥大化すれば、メモリ(shared_buffers)に乗るインデックスの数が減り、キャッシュヒット率が低下します。
あくまで、「頻繁に叩かれるクエリ」のために、「数列だけ」をピンポイントで追加するのが、パフォーマンス維持の黄金律です。

—

最後に:エンジニアとしての嗅覚を研ぎ澄ます

データベースチューニングにおいて、「正解」は常にクエリのパターンとデータの分布の中にあります。

`INCLUDE`句は、魔法ではありません。しかし、無駄なヒープアクセスを排除し、I/Oというデータベース最大のボトルネックを確実に削り取るための、極めて論理的な選択肢です。

皆さんの現場で、「このクエリ、本当にヒープまで見に行く必要があるのか?」と感じた時、ぜひこの構成を試してみてください。インデックスのリーフページに隠されたデータが、劇的なパフォーマンス改善の鍵を握っているはずです。

データベースの内部構造を理解し、その挙動を意図的に制御する。それこそが、エンジニアとしての醍醐味ではないでしょうか。

コメント

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