ビットマップヒープスキャンという「妥協点」:PostgreSQLのメモリとI/Oの狭間で
PostgreSQLでクエリの実行計画を眺めていると、`Bitmap Heap Scan`という言葉に出くわすことがよくあります。多くのエンジニアにとって、これは「インデックスを複数使ったんだな」くらいの認識かもしれません。しかし、大規模なデータセットや高負荷な環境でパフォーマンスをチューニングしようとすると、このステップが「性能のボトルネック」あるいは「劇的な改善の鍵」へと表情を変えることに気づくはずです。
今日は、このビットマップヒープスキャンが内部で何をしているのか、そして`work_mem`というパラメータがなぜこの処理の生命線なのかを深掘りしてみましょう。
インデックススキャンとビットマップの「中間」
通常、`Index Scan`は、インデックスで見つけた各エントリに対して、即座にヒープ(テーブル本体)へランダムアクセスを行います。これは行数が少ないときは非常に高速ですが、対象となる行が数万件、数十万件と増えていくと、ランダムI/Oの嵐となってディスクのヘッド(あるいはSSDのコントローラ)を叩きのめします。
ここで登場するのが`Bitmap Index Scan`と`Bitmap Heap Scan`の組み合わせです。
1. Bitmap Index Scan: まず、インデックスをスキャンして、条件に合致する「行の位置(TID: Tuple Identifier)」をメモリ上にビットマップとして展開します。
2. Bitmap Heap Scan: すべてのインデックススキャンが終わった後、このビットマップを使ってヒープをスキャンします。
この手法の賢いところは、「ヒープをスキャンする順序を物理的な配置順に並べ替える」という点です。これにより、バラバラに散らばっていたランダムアクセスを、可能な限りシーケンシャルなアクセスへと昇華させます。いわば、エンジニアが手作業で行うような「効率的な移動ルートの最適化」を、PostgreSQLが自動で行ってくれているわけです。
work_memが運命を分ける瞬間
ここで、皆さんが日頃悩まされる`work_mem`の話をしましょう。
ビットマップを保持するメモリ領域には制限があります。このメモリが足りなくなると、PostgreSQLは苦渋の決断を迫られます。
- Lossy Bitmap(損失ありビットマップ)への格下げ
メモリが不足すると、PostgreSQLは個々のTIDを管理するのを諦め、ページ単位で「このページには該当する行が少なくとも1つある」というマークを付ける「Lossy」な方式に切り替えます。
- これが何を意味するか?
Lossyになると、ヒープスキャン時には「そのページ内のどの行が条件に合致するのか」が分からなくなるため、改めてページ内の全行をチェック(Recheck)する必要があります。せっかくビットマップを作ったのに、CPU負荷とヒープアクセスの効率が著しく低下する――これが、`work_mem`不足による「隠れたパフォーマンス劣化」の正体です。
パフォーマンストラブルシューティングの勘所
現場で「なぜかクエリが遅い」という時に、`EXPLAIN ANALYZE`を見てみてください。もし`Bitmap Heap Scan`の行に`Lossy=true`や、それに伴う`Recheck Cond`のコストが見えたら、それは`work_mem`を増やすサインかもしれません。
ただし、闇雲に`work_mem`を上げるのは禁物です。`work_mem`はコネクションごとに割り当てられるため、安易に巨大な値を設定すれば、並列クエリが走った瞬間にメモリ枯渇(OOM Killer)を招くリスクがあります。
私たちがやるべきは、以下のステップです。
1. 統計情報の確認: `pg_stat_user_indexes`を確認し、ビットマップスキャンが頻発している箇所を特定する。
2. 実行計画の精査: `EXPLAIN (ANALYZE, BUFFERS)`を叩き、`Heap Fetches`が`Rows`に対して異常に多くなっていないか確認する。
3. セッション単位でのチューニング: グローバルな`work_mem`を弄るのではなく、特定の重い処理を行うトランザクション内だけで`SET LOCAL work_mem = ’64MB’;`のように局所的にメモリを確保する戦術をとる。
最後に
ビットマップヒープスキャンは、PostgreSQLが「インデックスの速さ」と「シーケンシャルアクセスの効率」という、相反する要件をどうにか両立させようともがいた結果生まれた、非常に人間臭い機能です。
内部で何が起きているかを理解すれば、PostgreSQLは単なるデータベースではなく、共に最適解を探るパートナーになります。チューニングに魔法の杖はありませんが、こうしたアーキテクチャの細部を理解しておくことで、誰よりも速く、的確にボトルネックを見抜くことができるはずです。
皆さんのクエリが、次回の実行計画でより最適化されることを願っています。
コメント