ビットマップヒープスキャンの「裏側」を読み解く:PostgreSQLのクエリ最適化における妥協と知恵
PostgreSQLの実行計画を見ているとき、`Bitmap Heap Scan`の文字を目にしない日はありません。初心者向けの記事では「インデックスを使った効率的な検索」と一言で片付けられがちですが、現場の泥臭いチューニングを経験したエンジニアなら知っているはずです。これは、インデックスの「点」のアクセスと、ヒープの「線(シーケンシャル)」のアクセスの間にある、絶妙なトレードオフの産物であることを。
今日は、このビットマップヒープスキャンが内部で何をしているのか、そしてパフォーマンスが期待通りに出ないとき、どこを疑うべきかについて掘り下げてみましょう。
—
1. なぜ「ビットマップ」が必要なのか?
PostgreSQLが単純な `Index Scan` を選ばず、あえて `Bitmap Heap Scan` を選択する動機は明確です。それは「ランダムアクセスのコスト」を抑えるためです。
もし数万件のレコードをインデックス経由で取得しようとすると、通常の `Index Scan` では、インデックスのリーフページを辿った後、その都度ヒープ領域へランダムアクセスが発生します。SSDであっても、OSレベルのI/Oランダムアクセスはコストが高い。
そこでPostgreSQLはこう考えます。「一度インデックスを全部なめて、必要な行位置(TID)をメモリ上にビットマップで保持してから、ヒープを物理的な順序で読みに行けば、シーケンシャルアクセスに近く効率的じゃないか?」
これが `Bitmap Index Scan` と `Bitmap Heap Scan` の二段構えの正体です。
2. アーキテクチャの核心:lossy(損失あり)という戦略
ここからが少し深い話です。ビットマップを生成するとき、PostgreSQLは `work_mem` 内にビットマップを構築します。しかし、もし検索対象が膨大で、メモリ内に正確なTID(Tuple ID)をすべて保持できなくなったらどうなるでしょうか?
PostgreSQLはここで「妥協」をします。ある閾値を超えると、特定のページ内のすべての行が「当たり」であると見なす、いわゆる「Lossy Bitmap(損失ありビットマップ)」に切り替わります。
- Exact(正確な状態): 特定の行がヒットしていると断定できる。
- Lossy(損失あり): 「このページの中に当たりがあるはずだ」という情報しか持たない。
Lossyになった場合、`Bitmap Heap Scan` はヒープページを読み込んだ後、もう一度そのページ内のすべての行に対して条件式(WHERE句など)の再評価を行います。つまり、インデックスで絞り込んだはずなのに、ヒープ側でさらにCPUを消費するのです。
3. パフォーマンストラブルの兆候を見抜く
`EXPLAIN ANALYZE` を叩いたとき、以下の現象が見えたら要注意です。
- `Heap Blocks: lossy=XXXX` という表示
この値が大きければ大きいほど、メモリ不足により「大雑把な検索」が行われています。再評価コストがかさんでいる証拠です。
- 期待以上にスキャン時間が長い
ビットマップをソートしてヒープを読みに行くというオーバーヘッドが、インデックスの絞り込み効果を相殺しているケースです。
チューニングの着眼点
1. `work_mem` の見直し
もし頻繁にLossyが発生しているなら、`work_mem` を増やすことで、より多くのTIDを正確に保持できるようになります。ただし、接続数×`work_mem` でメモリが枯渇しないよう、控えめに調整するのがプロの流儀です。
2. インデックスの最適化
ビットマップスキャンが選ばれすぎるということは、クエリの選択率(Selectivity)が中途半端である可能性が高いです。インデックスにカラムを追加して(Covering Index)、ヒープへのアクセス自体を不要にできないか検討してください。
3. `enable_bitmapscan` の誘惑を断つ
「ビットマップスキャンが遅いからオフにする」というのは短絡的です。なぜオプティマイザがそれを選択したのか、統計情報が古くないか、あるいはデータの物理的な配置が断片化していないかを先に疑いましょう。
—
最後に:データベースは「確率」のゲーム
ビットマップヒープスキャンは、PostgreSQLが「どうすれば最も安く、かつ安全にデータを拾ってこれるか」を計算した結果の戦術です。
もしあなたがクエリの遅さに悩んでいるなら、統計情報が正確であると仮定した上で、オプティマイザがなぜその戦術をとったのか、その「論理」を追いかけてみてください。ボトルネックはクエリそのものではなく、クエリを解釈するエンジン側の限界にあることが往々にしてあります。
チューニングとは、単なる設定変更ではなく、データベースという「生き物」の呼吸を理解する作業です。皆さんの現場のクエリが、明日より少しだけスムーズに動くことを祈っています。
コメント