【実務・中級編】 GINインデックスのチューニング – PostgreSQL

やあ。今日もデータベースと格闘してるかい?

PostgreSQLを使っていて、JSONBや全文検索(tsvector)にお世話になることは多いよね。そんな時に欠かせないのが「GINインデックス」だ。でも、こいつ、ときどき「書き込みが遅い」「インデックスの肥大化が止まらない」なんていう爆弾を抱えていることがある。

今日は、GINインデックスの「裏側」にある`fastupdate`という機能について、現場でハマりがちなポイントを交えて深掘りしていこう。教科書には載っていない、リアルな話をね。

—

GINインデックスはなぜ「更新」が重いのか

まず大前提として、GINは「転置インデックス」だ。1つの行に複数の値が含まれる場合、それを分解してインデックスに登録する。だから、1行更新するだけで、実際にはインデックス側で複数のエントリを書き換える必要があるんだ。

これがボトルネックになる。そこでPostgreSQLは、更新のたびにインデックスをガリガリ書き換えるのではなく、「とりあえず一旦溜めておこう」という仕組みを用意した。それが`fastupdate`だ。

—

`fastupdate` とは?その仕組みと罠

`fastupdate`が有効(デフォルトでON)だと、更新データはメインのインデックスに即座に反映されるのではなく、「pending list」という一時的な領域に書き込まれる。

  • メリット: 更新操作が爆速になる。インデックスの構造を毎回いじらなくて済むからだ。
  • デメリット: 検索時にその「未反映のリスト」もスキャンしなきゃいけない。リストが肥大化すると、検索速度がガタ落ちする。

この「pending list」は、`VACUUM`が走った時や、リストが一定サイズ(`gin_pending_list_limit`)を超えた時に、まとめてメインのインデックスにマージされる。

ここが現場の落とし穴

「検索が最近遅いな……」と思って実行計画(EXPLAIN)を見ると、`gin_pending_list`のサイズが膨れ上がっていることがよくある。データ量が多い環境でバッチ処理を大量に流すと、このリストが数GBに達することさえあるんだ。

—

チューニングの実践的なステップ

じゃあ、どう調整すればいいのか。現場でやるべきことは大きく分けて3つだ。

1. そもそも更新頻度が低いならOFFにする

もし、データが一度入ったらほとんど更新されないテーブルなら、`fastupdate`は不要だ。ONにするメリットがないどころか、無駄なオーバーヘッドを生むだけだよ。

— インデックス作成時に無効化する
CREATE INDEX idx_my_data_gin ON my_table USING GIN (my_jsonb_col) WITH (fastupdate = off);

これで、更新のたびに即座にインデックスが整理されるようになる。書き込みは重くなるけど、検索の安定性は抜群だ。

2. `gin_pending_list_limit` を適切に設定する

`fastupdate`をONにするなら、このパラメータが重要だ。デフォルトは4MBだけど、現代のメモリ環境ならもっと大きく取ってもいい。

— セッションごとに設定もできるし、設定ファイルでもOK
SET gin_pending_list_limit = ‘256MB’;

大きくすれば、マージ(同期)の頻度が減って書き込み性能は稼げる。でも、マージ処理が走った瞬間に重いクエリが発行されるリスクがあるから、運用中の負荷状況を見ながら調整してほしい。

3. 明示的にマージしてあげる

「夜間バッチの後に、きれいにインデックスを整えておきたい」という時は、SQLから強制的にマージできる。

— これを実行すると、pending listをメインのインデックスに強制マージする
SELECT gin_clean_pending_list(‘my_table_idx’);

これ、現場で非常に重宝するコマンドだ。夜間バッチの最後にこれを仕込んでおけば、翌朝の業務開始時のパフォーマンスを安定させられる。

—

まとめ:結局どう使い分けるべき?

僕が設計する時は、こんな基準で決めているよ。

  • 更新が頻繁で、書き込み性能が最優先: `fastupdate = on` にしつつ、`gin_pending_list_limit`を余裕のあるサイズ(64MB〜256MB程度)に設定。
  • 読み取りがメインで、検索速度を安定させたい: `fastupdate = off` にして、更新のオーバーヘッドを許容する。
  • 巨大なバッチ処理がある: `fastupdate = on` で運用し、バッチ完了後に `gin_clean_pending_list` を手動で叩く。

GINは強力な武器だけど、扱い方を間違えると牙を剥く。インデックスは「作って終わり」じゃない。データがどう増えて、どう更新されるのか。その「呼吸」に合わせてパラメータを調整してあげるのが、一流のエンジニアの仕事だよ。

さて、そろそろログを確認して、PENDINGリストが暴れていないかチェックしてくるとしようかな。君も今のうちに、`pg_stat_gin_pending_stats`ビューを覗いてみるといい。意外な発見があるはずさ。

また、現場の深い話でお会いしよう。

コメント

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