「インデックスを貼ったのに、なぜかクエリが重い……」。
PostgreSQLを触っていると、一度はそんな壁にぶつかりますよね。特に空間データ(PostGIS)や全文検索(tsvector)で活躍する「GiSTインデックス」は、B-treeとは挙動が全くの別物。
今回は、このGiSTの「クセ」と、現場でどう付き合っていくべきかについて、少し深掘りしてみようと思います。
—
1. GiSTは「ざっくり」を許容する柔軟なインデックス
まず、GiST(Generalized Search Tree)を一言で言うと、「なんでも屋さん」です。B-treeが「大小関係」を並べるのに対し、GiSTは「重なり」や「包含関係」を扱うのが得意。
内部構造は「R-tree」に近いイメージを持つと分かりやすいかもしれません。データを矩形(バウンディングボックス)で囲み、その箱を階層的に管理します。
でも、ここで注意が必要なのが、GiSTは「完全な答え」を返すとは限らないという点です。GiSTはインデックス検索の結果として「候補(Candidate)」を返します。そのため、PostgreSQLはインデックスで絞り込んだ後、再度テーブルから実際のデータを見てフィルタリングする「Recheck」というステップを踏みます。
つまり、GiSTの性能が悪い=Recheckの負荷が高い、ということ。ここをどう減らすかが、チューニングの鍵になります。
—
2. 「バランス」が崩れるとGiSTは劣化する
B-treeはデータが挿入されるたびにきれいに再配置されますが、GiSTは違います。新しいデータが入ってきて、既存の「箱」からはみ出しそうになると、箱を大きくしたり、分割したりします。
このとき、「箱同士が重なりすぎている」状態になると最悪です。検索時に「どっちの箱を辿ればいいの?」と迷うノードが増え、インデックスの木が肥大化し、読み込み回数が激増します。これが、空間クエリが急に遅くなる原因の多くです。
—
3. 実践:インデックスのメンテナンスとチューニング
では、現場でどう対応すべきか。いくつか「これだけはやっておけ」というTIPSを紹介します。
① FILLFACTORを調整する
デフォルトのFILLFACTORは100ですが、更新頻度が高いテーブルであれば、これを少し下げて「余白」を作るのが定石です。
— インデックス作成時に余白を持たせる
CREATE INDEX idx_spatial_data ON my_table USING GIST (geom) WITH (FILLFACTOR = 80);
こうすることで、データの更新時にノードの分割が起こりにくくなり、インデックスのツリー構造が安定します。
② 定期的なREINDEX
GiSTは使い込むほどに「断片化」します。特に空間データは、広範囲にデータが追加されると木が歪みやすい。週次や月次のメンテナンスで `REINDEX` をかけるのは、実はかなり有効な手段です。
— インデックスを再構築して構造を最適化する
REINDEX INDEX CONCURRENTLY idx_spatial_data;
※ `CONCURRENTLY` をつけるのを忘れずに。これを忘れるとテーブルがロックされて、サービスが止まって悲鳴を上げることになります(笑)。
③ 適切なデータ型を選ぶ
例えば全文検索なら、無闇に全カラムを突っ込むのではなく、必要な情報だけを抽出した計算済みカラム(Generated Column)を作ってからGiSTを貼るのが鉄則です。
—
4. 最後に:インデックスは「育てていくもの」
GiSTは非常に強力ですが、B-treeのように「貼れば解決!」という万能薬ではありません。
- `EXPLAIN ANALYZE` を見て、`Rows Removed by Filter` が多すぎないか確認する。
- データの分布が変わってきたら、統計情報を更新する(`ANALYZE`)。
- それでも遅いなら、インデックスの設計自体を見直す。
データベースをチューニングするって、結局は「いかに無駄なデータ読み込みを省くか」というパズルなんですよね。
「なんか遅いな」と思ったら、まずはそのインデックスが綺麗に整理整頓されているか、箱同士が仲良く重なりすぎていないか、想像力を働かせてみてください。きっと、PostgreSQLがヒントをくれるはずです。
それでは、また現場でお会いしましょう!
コメント