インデックスオンリースキャン(IOS)の深淵:なぜ「ヒープ」に触れてはいけないのか
PostgreSQLのパフォーマンスチューニングにおいて、避けては通れない壁、そして強力な武器が「インデックスオンリースキャン(Index Only Scan: IOS)」です。
インベストメント(投資)という言葉がありますが、DBエンジニアにとってのIOSは、まさに「I/Oという名のコスト」を極限まで圧縮するための投資です。多くのエンジニアが「インデックスを貼れば速くなる」と信じていますが、なぜIOSが効くのか、そしてなぜ時として「効かない」のか。その深淵を覗いてみましょう。
1. なぜ「ヒープへのアクセス」は悪なのか
まず、アーキテクチャの基本を再確認します。PostgreSQLのインデックス(B-tree)は、データ本体である「ヒープ(テーブル)」へのポインタ(TID: Tuple Identifier)を持っています。
通常のインデックススキャンは、こう動きます。
1. インデックスを辿り、TIDを取得する。
2. そのTIDを元に、ヒープ上のページをフェッチする。
3. 可視性(Visibility)を確認する。
ここでボトルネックになるのが、2番目の「ヒープへのランダムアクセス」です。特にデータサイズがメモリ(Shared Buffers)に乗り切らない巨大なテーブルでは、このヒープアクセスが発生した瞬間にディスクI/Oが走り、クエリのレイテンシは劇的に悪化します。
IOSの真髄は、この2番目のステップを「完全に排除する」ことにあります。 インデックスの中に必要なカラム値がすべて含まれていれば、ヒープを見に行く必要はありません。これが実現できた瞬間、クエリはメモリ上の構造体だけで完結します。
2. 「Visibility Map」という名の守護神
しかし、ここからがPostgreSQLの面白いところです。実は、インデックスの中に値が揃っていても、それだけではIOSは発動しません。
PostgreSQLはMVCC(多版同時実行制御)を採用しているため、ある行が「現在トランザクションから見て有効か」を判断するには、ヒープ上の行ヘッダにある`xmin`と`xmax`を確認しなければなりません。
ここで登場するのが Visibility Map (VM) です。
VMは、各ページ内のすべてのタプルが「どのトランザクションからも可視である(=古いバージョンが存在しない)」ことを示すビットマップです。
- IOSの条件:
- インデックスに全カラムが含まれている。
- 参照先のヒープページが、Visibility Mapで「全可視(All-Visible)」とマークされている。
つまり、IOSが効かないときは、単に「インデックスが足りない」のか、「まだヒープが真空掃除(VACUUM)されておらず、VMが更新されていない」のかを見極める必要があります。ここが、経験の差が出るポイントです。
3. パフォーマンストラブルシューティング:なぜIOSが効かないのか?
もし「EXPLAIN ANALYZE」の結果が `Index Scan` になり、`Index Only Scan` になっていないなら、以下のチェックリストを疑ってください。
- Visibility Mapの鮮度:
`VACUUM` が滞っていませんか?特に更新頻度が高いテーブルでは、VMが最新の状態に追いつかず、PostgreSQLは安全のためにヒープを見に行きます。`autovacuum_vacuum_scale_factor` のチューニングがここでも効いてきます。
- INCLUDE句の活用:
B-treeインデックスのキーに含めるほどではないが、頻繁に参照するカラムがある場合、`CREATE INDEX … INCLUDE (col_name)` を活用しましょう。インデックスのリーフノードに値を「おまけ」として格納することで、IOSへの道を切り開けます。
- 型の不一致:
地味ですが、検索条件とカラムの型が微妙に異なると、暗黙的な型変換が発生してインデックスが使われないことがあります。これもIOS以前の基本ですが、案外見落とされがちです。
4. エンジニアとしての矜持
IOSを追い求めすぎるあまり、テーブルごとに無数のインデックスを貼るのは悪手です。インデックスの肥大化は、書き込み性能(UPDATE/INSERT)の低下を招きます。
「読み取りの速さ」と「書き込みの重さ」。このトレードオフをどうハンドリングするか。
私は、IOSを「銀の弾丸」とは思っていません。しかし、クエリの実行計画を見たときに、`Index Only Scan` という文字が並んでいるのを見ると、やはり心地よいものです。それは、データベースの内部構造を理解し、その上で動くSQLを最適化したという証なのですから。
皆さんのDBで、今日もインデックスは無駄にヒープを叩いていませんか?
一度 `EXPLAIN` を叩いて、Visibility Mapに思いを馳せてみてください。PostgreSQLの本当の速さは、その先に見えてくるはずです。
コメント