【テクニカル・上級編】 Index ScanとBitmap Index Scanの違い – PostgreSQL

「Index Scanか、Bitmap Scanか」— 実行計画の裏側で何が起きているのか

PostgreSQLの `EXPLAIN` を眺めているとき、ふと自分の直感とオプティマイザの選択が食い違うことはありませんか?「これならIndex Scanで一発だろう」と思っていたクエリが、なぜか `Bitmap Index Scan` を選択し、その後に `Bitmap Heap Scan` が続く。

この挙動を「なんとなく」で済ませているなら、それは非常にもったいない。PostgreSQLがなぜその選択をしたのか、その裏にある「ランダムI/Oへの恐怖」と「メモリ戦略」を読み解くことは、大規模データを扱うエンジニアにとって避けては通れない教養です。

Index Scanの本質:一点突破のコスト

まず、純粋な `Index Scan` は、B-treeインデックスを辿り、見つけたTID(Tuple ID)を元に、直接ヒープ(テーブル本体)のページへアクセスしに行きます。

これは「特定の数行だけをピンポイントで引き抜く」際には最強の手段です。しかし、ここに大きな落とし穴があります。もしインデックスでヒットする行が大量にあったらどうなるか。

1. インデックスのリーフノードを順に辿る。
2. そのたびにヒープの別々のページへランダムアクセスが発生する。
3. OSのファイルシステムキャッシュが追い出され、ディスクI/Oが激増する。

つまり、Index Scanは「結果セットが少ないとき」には神速ですが、ある閾値を超えると、バラバラに散らばったページへ何度も何度もアクセスする「ランダムI/Oの嵐」に飲み込まれるのです。

Bitmap Scan:順序立てて「まとめ読み」する知恵

ここで登場するのが `Bitmap Index Scan` です。これは、一度インデックスをスキャンして、条件に合致するページの「地図(ビットマップ)」をメモリ上に作成します。

その後の流れはこうです。

1. Bitmap Index Scan: インデックスをスキャンし、ヒットしたページ番号のビットを立てる。
2. Bitmap Heap Scan: 作成されたビットマップに基づき、物理的なページ番号順にヒープへアクセスする。

この「ページ順にアクセスする」という点がミソです。バラバラだったアクセスの順序を、物理的な配置順に整列させることで、ランダムアクセスをシーケンシャルアクセスに近い形へ変換する。これこそが、PostgreSQLがBitmap Scanを選ぶ最大の理由です。

どのタイミングで切り替わるのか?

オプティマイザがどちらを選ぶかは、主に `random_page_cost` と `seq_page_cost`、そしてテーブル全体のサイズと抽出対象のカーディナリティのバランスで決まります。

もし皆さんの環境で「インデックスが効くはずなのに遅い」と感じたら、以下の点を確認してみてください。

  • 統計情報の鮮度: `ANALYZE` を怠ると、オプティマイザは「ヒットする行数」を誤認します。Bitmap Scanの方が速いケースでIndex Scanを強行し、結果としてI/O負荷で自滅しているケースが多々あります。
  • メモリ設定(work_mem): Bitmapの構築にはメモリが必要です。`work_mem` が不足すると、ビットマップの一部がディスクに溢れ(lossy bitmapといいます)、再スキャンが発生します。これはパフォーマンスにとって致命的な「隠れたコスト」です。

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

現場でチューニングを行う際、私はよく `EXPLAIN (ANALYZE, BUFFERS)` を使います。ここで注目すべきは `Shared Hit/Read` の数値です。

もし `Bitmap Heap Scan` で膨大な `Read` が発生しているなら、それはビットマップが巨大すぎて、インデックスから得た情報を保持しきれず、ディスクI/Oに逃げている証拠かもしれません。あるいは、純粋に「インデックスを貼るべきカラム」を見直すべきタイミングです。

最後に:道具としての実行計画

PostgreSQLのオプティマイザは非常に賢いですが、決して完璧ではありません。特に、データ分布が偏っている場合や、複雑なJOINが絡む場合には、人間が意図した「物理的な最適解」と乖離することがあります。

しかし、なぜBitmap Scanが選ばれたのかという「理由」を理解していれば、`enable_indexscan` をオフにして無理やり矯正するような荒療治ではなく、インデックスの設計そのものや、クエリの書き方を根本から見直すという、より建設的なアプローチが可能になります。

データベースの内部構造を知ることは、単なる知識の蓄積ではありません。それは、マシンと対話するための「共通言語」を磨く行為です。皆さんのシステムが、今日も健やかに軽快に動くことを願っています。

コメント

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