FILLFACTOR:PostgreSQLの「余白」がもたらす、静かなるパフォーマンス革命
データベースのチューニングにおいて、私たちはつい「いかに効率よく詰め込むか」に意識を向けがちです。しかし、PostgreSQLという深淵なシステムと長く付き合っていると、ある真理に気づかされます。それは、「詰め込みすぎないことこそが、最強の最適化である」という逆説です。
今日は、PostgreSQLにおける`FILLFACTOR`という、一見地味ながらも極めて強力なパラメータについて、少し深い話をしようと思います。
—
なぜ、8KBのページに「隙間」が必要なのか
PostgreSQLのデータページ(通常8KB)は、単なる情報のコンテナではありません。MVCC(多版型同時実行制御)というアーキテクチャの上で、行の更新は「既存の行の削除」と「新しい行の挿入」として処理されます。
ここで重要になるのが「HOT (Heap Only Tuple) 更新」の存在です。
もし更新された行が、現在のページ内に収まる十分な空きスペースを持っていれば、PostgreSQLはインデックスを更新することなく、ページ内のポインタ(Line Pointer)を書き換えるだけで更新を完了させます。これが「HOT更新」です。逆に、ページがいっぱいであればどうなるか。新しい行は別のページに書き出され、インデックスの再構築(インデックス・エントリの追加)という重いコストが発生します。
`FILLFACTOR`は、この「ページ内の余裕」をあらかじめ確保するためのパラメータです。デフォルト値の100(または90)をそのままにしていると、更新頻度の高いテーブルでは、すぐにページが飽和し、HOT更新の恩恵を自ら放棄することになります。
「断片化」という見えない敵との戦い
経験豊富なエンジニアなら、`VACUUM`が追いつかないほど更新が激しいテーブルで、テーブルサイズが異常に肥大化する現象に遭遇したことがあるはずです。
FILLFACTORを適切に下げる(例えば80や70に設定する)と、以下のような好循環が生まれます。
- HOT更新の最適化: ページ内に常に「書き込みの余白」があるため、UPDATE処理が高速化する。
- 物理的な断片化の抑制: 更新によるページ分裂(Page Split)や、それに伴う不要な行の増殖を物理的なレベルで緩和できる。
- IO負荷の軽減: インデックスの更新頻度が下がるため、バックグラウンドでの書き込み処理が平滑化される。
もちろん、これは「トレードオフ」です。FILLFACTORを下げることは、実質的に「ストレージ効率の低下」を意味します。スキャンすべきページ数が増え、メモリ(Buffer Cache)上に載るデータ密度も下がります。読み取り専用のマスターデータでこれをやれば、ただの無駄遣いになってしまいます。
パフォーマンストラブルシューティングの勘所
現場で「更新が遅い」「VACUUMがCPUを食いつぶしている」という相談を受けたとき、私はまず`pg_stat_user_tables`を覗きます。
SELECT relname, n_tup_upd, n_tup_hot_upd
FROM pg_stat_user_tables
WHERE n_tup_upd > 0;
もし`n_tup_hot_upd`(HOT更新数)が`n_tup_upd`(総更新数)に対して極端に低いなら、それはFILLFACTORを検討すべき強力なシグナルです。
ただし、注意点があります。「安易な一括設定」は禁物です。テーブルのアクセスパターン、行サイズ、更新頻度の統計を丹念に観察し、検証環境で`pgbench`を回して、「スループットと物理サイズのバランスがどこで最大化するか」を突き止める。これこそが、エンジニアの腕の見せ所です。
最後に:理想的なチューニングとは
FILLFACTORの設定は、いわば「データベースの呼吸」を整える作業です。あまりにタイトに詰め込まれたページは、更新という名の圧力に耐えかねて、インデックスの再配置という名の「悲鳴」を上げます。
少しの「余白」を与えることで、PostgreSQLは驚くほど軽やかに、そして長く安定して走り続けてくれます。
カタログスペックの数値に満足せず、アプリケーションが刻む「更新の鼓動」を聴きながら、パラメータという楽器を調律する。そんな玄人好みのチューニングを、ぜひ次のプロジェクトで試してみてください。
—
執筆後記:皆さんの環境では、FILLFACTORの「スイートスポット」はどのあたりでしたか? 80で落ち着くケースが多いですが、非常に巨大な行を扱うテーブルだと、さらに大胆な設定が効くこともあります。ぜひ、皆さんの知見をコメントで聞かせてください。
コメント