「ねえ、PostgreSQLでクエリが遅いとき、とりあえずインデックスを貼って満足してない?」
ふと後輩のコードを見ていてそう思うことがよくあります。もちろんインデックスは大事。でも、そのインデックスが「本当に最大限の仕事」をしているかまで気にしているエンジニアは、意外と少ないんですよね。
今日は、PostgreSQLのパフォーマンスチューニングにおける「最後の切り札」の一つ、Index Only ScanとVisibility Mapの話をしようと思います。これを知っているだけで、クエリの実行計画が劇的に変わる瞬間があるんです。
—
インデックスを貼ったのに、なぜ「テーブル」を見に行くの?
まず、基本のおさらいから。`SELECT id FROM users WHERE email = ‘…’` というクエリがあって、`email`にインデックスを貼ったとします。
普通なら、PostgreSQLはインデックスで`email`を検索し、その場所(ポインタ)を頼りにテーブル本体(ヒープ)までデータを見に行きますよね。これを「Index Scan」と呼びます。
でも、考えてみてほしいんです。「インデックスの中だけで、欲しいデータが完結していたら、わざわざ重たいテーブルを見に行く必要はないんじゃないか?」と。
そう、これがIndex Only Scanです。でも、ここで一つ壁があるんです。
—
「そのデータ、最新ですか?」というPostgreSQLの悩み
PostgreSQLはMVCC(多版型同時実行制御)という仕組みで動いています。インデックスには「このデータがどのトランザクションから見て有効か」という細かいフラグまでは載っていないことが多いんです。
だから、たとえインデックスに欲しいカラムが揃っていても、PostgreSQLは「このデータが本当に最新で、今見える状態なのか?」を確認するために、わざわざテーブルのヒープ領域を見に行かなきゃいけなかったんです。
……そう、Visibility Mapが登場するまでは。
—
Visibility Map(可視性マップ)という「近道」
Visibility Mapは、各ページ(データのかたまり)に対して「このページ内のすべての行は、どのトランザクションから見ても有効(全員に見える状態)ですよ」という情報を管理しているビットマップです。
もしVisibility Mapを見て「このページは全員から見える」ことが保証されていれば、PostgreSQLはわざわざ重いヒープを見に行かなくていい。「よし、インデックスの情報だけで完結だ!」と判断して、高速なIndex Only Scanに切り替えてくれるわけです。
—
実践:Index Only Scanを狙い撃ちする
じゃあ、どうすればこの恩恵を受けられるのか。コードで見てみましょう。
1. カバリングインデックスを作る
まず、クエリで使うカラムをすべてインデックスに含めます。
— よくあるクエリ
SELECT email, name FROM users WHERE email = ‘hoge@example.com’;
— こういうインデックスを貼る
CREATE INDEX idx_users_email_name ON users (email, name);
これで、インデックスの中に`email`と`name`が並びました。
2. VACUUMを適切に回す
ここが一番のポイント。Visibility Mapは`VACUUM`(あるいは`AUTOVACUUM`)が更新します。テーブルの更新が激しすぎて`VACUUM`が追いついていないと、Visibility Mapが「最新だよ!」と判断できず、結局ヒープを見に行く羽目になります。
実務で「インデックスを貼ったのにIndex Only Scanにならない!」と悩んだら、まず`EXPLAIN ANALYZE`を叩いてみてください。
EXPLAIN (ANALYZE, BUFFERS)
SELECT email, name FROM users WHERE email = ‘hoge@example.com’;
実行計画の出力に `Heap Fetches` という項目が出ます。これが「0」なら完璧なIndex Only Scanです。逆にここが大きな数字になっているなら、まだまだ改善の余地があるということ。
—
先輩からのアドバイス:過信は禁物
最後に、ちょっとした注意点を。
Index Only Scanは魔法じゃないです。例えば、インデックスのサイズが巨大すぎてメモリに乗り切らなかったり、頻繁に`UPDATE`がかかるテーブルだと、Visibility Mapがすぐに「無効」になってしまい、恩恵を受けにくくなります。
- 読み取り専用に近いテーブルや、特定のカラムしか参照しないクエリには劇薬のように効く。
- 逆に頻繁に更新されるテーブルなら、インデックスの貼りすぎは更新コストを爆上げする。
「とりあえずインデックス」じゃなくて、「クエリがどこまでインデックスの中だけで完結できるか」を意識できるようになると、DBエンジニアとしての視座が一段階上がりますよ。
次はぜひ、自分の担当しているプロジェクトの`EXPLAIN`結果を眺めてみてください。そこに、まだ見ぬ最適化のヒントが隠れているはずです。
それでは、また現場で!
コメント