ぱと隊長日誌

ブログ運用もエンジニアとしての生き方も模索中

PostgreSQL の track_io_timing とパフォーマンス影響

概要

PostgreSQL の track_io_timing はデータベースによる I/O 待機の記録を有効にし、各種統計(pg_stat_* 系)や EXPLAIN などで参照できるようにする設定です。ですが、プラットフォームによっては深刻な負荷の原因になるとし、デフォルトでは無効となっています。

pgsql-hackers でもデフォルトで有効にすることが議論されましたが、プラットフォーム依存のオーバーヘッド懸念が指摘され、少なくとも当該スレッドではデフォルト変更に至っていません。
PostgreSQL: track_io_timing default setting

そこで、track_io_timing がパフォーマンスにどれほど影響を与えるのか、Hyper-V 上の Rocky Linux + PostgreSQL 18.2 で測定した一例を示します。

検証環境

Windows 11 マシンの Hyper-V 環境で検証しました。

ホスト

プロセッサ Intel Core i5-14500
メモリー 32 GB
OS Windows 11 Pro 25H2

Hyper-V

プロセッサ 10個の仮想プロセッサ
メモリー 8 GB
OS Rocky Linux release 9.7
DB PostgreSQL 18.2

検証

検証方針

track_io_timing パラメータの説明を抜粋します。

データベースによるI/O待機の記録を有効にします。 このパラメータはデフォルトで無効になっています。その理由は、現時点の時刻をオペレーティングシステムに繰り返し問い合わせるので、プラットフォームによっては深刻な負荷の原因になるからです。使用しているシステムにおける記録の負荷を計測するためpg_test_timingツールが使用できます。

19.9. 実行時統計情報

また、pg_test_timing の説明では、時間計測のオーバーヘッドやクロックソースによる影響が示されています。
pg_test_timing

これらを踏まえ、以下のように検証を進めることにします。

まずは利用可能なクロックソース毎に pg_test_timing で時間計測のオーバーヘッドを測定します。そして、最良と思われるクロックソースを選択します。

次に track_io_timing パラメータのパフォーマンス影響を計測します。ベンチマークとして pgbench の組み込みスクリプトである tpcb-like, select-only を用います。

クロックソース評価

# cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hyperv_clocksource_tsc_page hyperv_clocksource_msr acpi_pm

-- クロックソースを tsc に変更する場合
# echo tsc > /sys/devices/system/clocksource/clocksource0/current_clocksource

# taskset -c 0 /usr/pgsql-18/bin/pg_test_timing -d 10

「オーバーヘッド込みのループ時間毎」の結果をクロックソース毎に示します。なお、ヒストグラムは割愛していますが、妥当なバラツキと判断しました。

クロックソース ループ時間
tsc 15.29 ns
hyperv_clocksource_tsc_page 16.91 ns
hyperv_clocksource_msr 519.59 ns
acpi_pm 505.39 ns

この結果を受けて、今回は "tsc" を採用することにしました。OSインストール直後のデフォルト設定であること、かつ測定結果が良好であったことを重視しました。

track_io_timing とベンチマーク

$ createdb bench

$ pgbench -i -s 1000 bench

-- track_io_timing 変更後はデータベースを再起動した
# systemctl restart postgresql-18

-- select-only
$ pgbench -S -c 8 -j 8 -T 300 bench

-- tpcb-like
$ pgbench -c 8 -j 8 -T 300 bench

各条件で 3 回計測し、"latency average" の中央値を採用します。結果を以下に示します。

track_io_timing select-only tpcb-like
off 0.385 ms 3.958 ms
on 0.402 ms 3.913 ms

select-only では track_io_timing の有効化で latency が 4.4 % 悪化しました。

一方、tpcb-like は 1.1 % 改善しましたが、差は小さいため、今回の条件では「有意差」と断定せず参考値として扱います。

まとめ

今回の検証シナリオだけ見る限り、良い性能のクロックソースであれば、track_io_timing 有効化の影響は限定的といえそうです。有効化によるパフォーマンス影響を否定できず、通常時の適用は難しくとも、検証のために限定的に利用することは検討の余地があります。

ですが、track_io_timing を有効化したい場面は I/O 負荷を疑っている状況と推測され、調査のための有効化は状況をさらに悪化させる可能性があります。有効化の前に最低限でも pg_test_timing でクロックソースを評価すること。そして、可能であれば事前検証とリカバリー計画を用意した上で適用するのが望ましいと考えます。

PostgreSQLのfillfactorとパフォーマンス検証

概要

PostgreSQL には HOT (Heap-Only Tuples) という、更新のオーバーヘッドを減らす仕組みがあります。詳細は下記の記事を参照ください。
HOTの活用 | Let's POSTGRES

HOT 更新には条件があり、その一つが「HOT更新のための十分なページ領域」です。テーブルの fillfactor(以下、FF)を下げると、ページ内に空き領域を残しやすくなり、HOT 更新が発生しやすくなります。

テーブルのfillfactorを減らすことで、HOT更新のための十分なページ領域の可能性を高めることができます。 そうしない場合でも、HOT更新は発生します。 なぜなら、新しい行は新しいページや、新しい行バージョンのために十分な空き領域を持つ既存のページに自然に移動するからです。

66.7. ヒープ専用タプル(HOT)

PostgreSQL 18 時点で、テーブルの fillfactor のデフォルトは100(可能な限りページを詰める)です。

また、次のような記載もあります。

一般的には、FILLFACTOR=90でHOTが十分に機能するでしょう。

なお、FILLFACTORを設定すると空き領域をテーブルデータ内に作ることになるため、テーブルデータの密度が下がります。密度が下がると、読み込むデータ量が増えます(キャッシュヒット率の低下)。そのため、INSERT、SELECT処理がメインとなるテーブルについては、キャッシュヒット率を重視する意味で、FILLFACTORは指定せず、デフォルトである 100% の設定を使うほうが良いでしょう。

第三回 HOTの上手な使い方 | Let's POSTGRES

つまり、FFの調整には「HOT更新しやすくなる」メリットと、「テーブルが肥大化しやすくなり読み取りが不利になる」デメリットがあり、トレードオフになります。本記事では、このトレードオフを実験で確認します。

検証環境

Windows 11 マシンの Hyper-V 環境で検証しました。

ホスト

プロセッサ Intel Core i5-14500
メモリー 32 GB
OS Windows 11 Pro 25H2

Hyper-V

プロセッサ 10個の仮想プロセッサ
メモリー 8 GB
OS Rocky Linux release 9.7
DB PostgreSQL 18.2

検証コード/設定

検証コード及び設定は GitHub に格納しました。

GitHub - pato-taityo/fillfactor_effect

検証コードは ChatGPT で生成し、活用した部分については妥当性を確認しました。ただし、未活用の部分については十分な精査をしていません。

検証に用いた postgresql.conf も格納しています。

検証環境(10 vCPU VM)を踏まえ、並列度は次の値に変更しています。

export CLIENTS=20
export JOBS=10

検証

TPC-B(pgbenchの組み込みシナリオ)

pgbench の TPC-B に基づいたシナリオを使い、FF を 100 から 70 まで変化させて TPS を測定しました。

FF と TPS を比較した結果を示します。

TPC-BのシナリオでFFとTPSを比較した結果です

TPS に注目すると、FF=100 より FF=95 が良い結果となりました。ですが、FF<=90 では FF=100 よりも悪い結果となっています。

この結果を pgbench_accounts テーブルに着目して分析します。

pg_stat_all_tables の統計情報から、更新全体 (n_tup_upd) のうち HOT 更新 (n_tup_hot_upd) が占める割合を、HOT更新率の近似値として扱います(※統計値は累積なので、測定開始・終了時点の差分で見ています)。その結果を FF と比較したものが次です。

TPC-BのシナリオでFFとHOT更新率を比較した結果です

FF=95 の時点でほぼすべてが HOT 更新となっており、FF をさらに下げても「HOT更新率の改善」という観点では伸びしろが小さい状況でした。

一方で、pgbench_accounts テーブルのサイズは FF を下げるほど増大します。

FFとテーブルサイズを比較した結果です

テーブルサイズが増えると、同じ件数を扱っていても必要なページ数が増え、キャッシュヒット率の低下などを通じて読み取りが不利になります。

以上より、今回の検証環境下では、

  • FF=95 は HOT 更新の改善効果が有利に働き TPS が向上した
  • しかし FF を下げすぎると、テーブル肥大による悪影響が支配的になり TPS が低下した

と解釈できます。

DML単体の影響

TPC-B は複合的なシナリオだったため、次に DML 単体(SELECT / INSERT / UPDATE / DELETE)と FF の影響を確認します。

UPDATE については、HOT 更新の影響が比較しやすいように、HOT 更新が起こりにくい条件を作った update_nohot も用意しました(インデックスを設定した列を更新し、HOT更新が起こらないようにしています)。

また、UPDATE の代わりに DELETE + INSERT を行う churn と名付けたテストも含めています。

DELETE を単純に「ランダムな aid を消す」という実装にすると、時間が進むにつれて「DELETE がヒットしない(0行削除)」が増える懸念があります。そこで、pgbench_delete_queue (UNLOGGED TABLE) を用意し、DELETE が必ず既存行を削除する実装にしました。

FF と TPS を比較した結果を示します。見やすくするため、各 DML の FF=100 を基準に正規化しています。

DML操作でFFとTPSを比較した結果です

FF を低下させるに従い、概ね以下の結果となりました。

  • SELECT は TPS が著しく落ちました。FF 低下によりテーブルが肥大化し、キャッシュ効率が悪化した影響が大きいと推測しています。
  • UPDATE(HOT更新あり)は FF=90, 95 で TPS 向上が見られました。ただし改善幅は小さめで、FF<=85 では FF=100 を下回りました。
  • UPDATE(HOT更新なし)は、FF を下げるほど TPS が低下しました。HOT 更新のメリットが得られない一方で、テーブル肥大のデメリットは受けるためと考えられます。
  • INSERT はほぼ横ばいでした(少なくとも今回の条件では FF の影響は限定的でした)。
  • DELETE は ±数%程度の揺れに収まり、単回測定の範囲では明確な傾向を断定しにくい結果でした。

これらを踏まえると、FF を低下させて「全体としての性能向上」を狙うには、相当量の UPDATE があり、かつ HOT 更新が十分に見込めるワークロードでない限り難しい(少なくとも今回の条件ではそう見える)と言えそうです。

結論

実際のワークロード次第ではありますが、FF を見直して効果を得られるのは、HOT 更新の改善がボトルネックに効いてくるケースに限られそうです。
また、FF=100 でも HOT 更新は起こりうることを考えると、確証が持てない段階ではデフォルト(FF=100)で様子を見る判断も現実的だと思われます。

こぼれ話

今回の検証では AI (ChatGPT) との共同作業で行いました。AI は実験計画と検証コードを素早く準備し、検証をサポートしてくれました。また、私だけでは思いつかなかったような観点もフォローしてくれました。

ただ一方で、検証としては致命的なミスを犯すこともあり、それ故に違和感のある結果が出たとしても、無理やり説明をつけて正当化しようとする場面もありました。

AI は頼りになる味方ですが、それでも出力結果に対して批判的にチェックすべきということを忘れてはならない、と改めて感じる機会となりました。

信念を持て

伝えたいこと

退職エントリーに代えて、退職に際してメンバーに伝えたことを、文章として整えて残しておきます。

この記事の主題はひとつです。信念を持て。そしてその信念は、固定された正解ではなく、経験を通して見つけ、磨き、更新していくものだということです。

入社から退職までの9年間、いくつかの内製システムの開発・運用に、リーダーとして関わってきました。

私が考えるリーダーの役目は、リーダーシップとマネジメントの両方を使い、メンバーの力を引き出し、組織の目標に貢献することです。
(参考:リーダーシップとマネジメントの違いについては別の記事でまとめています)
組織(チーム)とマネジメントとリーダーシップ - ぱと隊長日誌

ただ、その役目を果たす過程では、迷う局面が必ず来ます。利害が衝突する、正解がない、時間がない、辛い。そういう場面で最後に支えになるのは、ノウハウ以上に「自分は何を大事にするのか」という「信念(軸)」でした。

そして今はAIの進化が著しく、仕事の前提が大きく変わり続けています。だからこそ「やり方」はアップデートしつつ、「何を大事にするか」という信念(軸)は手放さないでほしい。そう思います。

私の信念

参考までに、私が大切にしている信念を紹介します。

1. 真摯であれ

プロとしての矜持を持ち、誠実に向き合うこと。誠実な行動は、信頼を少しずつ積み重ねます。信頼があるからこそ、新しいチャレンジを後押ししてくれたり、失敗したときに手を差し伸べてくれたりします。

ここで言う「真摯」は、具体的な作法というより精神に近いものです。自分が取ろうとしている行動や発言は、プロとしての矜持に基づくものだろうか。その問いを、節目節目で自分に向けられるかどうかが大切だと思っています。

(関連:過去にもう少し踏み込んで書きました)
「真摯さ」とは何か - ぱと隊長日誌
志あるチームであれ - ぱと隊長日誌

2. 情報は可能な限りオープンにする

言い換えるなら、情報を握って人を動かさないということです。

もちろん、情報の全てを何でも共有すればよいわけではありません。オープンにしないことが正しい情報もあります。私が「ここはオープンにしない」と判断するのは、たとえば次のようなものです。

  • 「ここだけの話」など、秘密にすることを明示的に求められた話
  • 主観的な見方に基づく他者批判
  • 個人の信頼関係をもとに打ち明けてくれた話

それ以外は、できるだけ共有し、判断材料を揃える。私はそれを心がけてきました。

私がこれを強く意識するようになったのは、情報共有がうまく回らない状況を見た経験があったからです。

情報が十分に共有されないと、メンバーは「何が起きているのかわからない」不安を抱きやすくなります。一方で、リーダー側も「情報が上がってこない」と感じる。そうして互いに様子を見るようになり、気づけば悪循環になりかける。だからこそ、自分のチームが同じ状況に陥らないようにしたいと思いました。

そこで私は、まず自分から情報を出すことを徹底しました。
マネジメントレイヤーが何を考えて、どう動いているのかを遅延なく伝える。
自分の失敗も隠さずさらけ出し、振り返った結果を共有する。そしてそこで終わりにせず、みんなの意見を求めてさらに磨きをかける。
悪いニュースでも報告してくれたら感謝する。責めるのではなく、一緒にネクストアクションを考えて動く。

このやり取りを繰り返すことで、「言っても大丈夫」「言ったほうが前に進む」という空気ができ、結果として、自分がギバー (giver) を続けていれば、周囲もギバーとなり、自然と必要な情報が集まってくる状態を作れました。

実際、悪いニュースほど早い段階で共有されるようになり、解決に向けたネクストアクションを、より早く打てるようになりました。また、大小問わず good job を見つけて取り上げて褒めることで、改善案やその結果の共有が増え、メンバー同士が協力し合う度合いも高まりました。失敗の共有を恐れなくなったとも感じています。

メンバーの行動や成果で気づいた良い点は、朝会などで取り上げて共有するようにもしていました。
(参考:レビューでも気づいた良い点はほめるようにしています)
レビューでほめて後輩を育てる - ぱと隊長日誌

さらに、情報をオープンにする姿勢は、チームの中だけではなく、チーム外のステークホルダーやユーザーに対しても同様にしました。

日頃の信頼関係という前提があったとはいえ、オープンにすることで、システム担当者だけでは解決が難しい課題に対して、協力を申し出てもらえる場面がありました。ユーザー側の業務を変えたり、負担が増えるような話でも、不平不満を言わず受け入れてくれました。時には、こちらから言い出す前に提案してくれたことすらありました。

3. ビジネスをシステムで支える

私たちシステムエンジニアは、システムのプロです。だからこそ、システムの観点で何が適切かを見極める力が重要だと考えています。

ここで言いたいのは「とにかくシステムを作れ」ではありません。システムは手段であり、目的はビジネスの価値(売上・顧客体験・リスク低減・業務継続など)を守り、伸ばすことです。

つまり、プロとして求められるのは「作れること」だけでなく、

  • そもそもシステム化が最適か(運用・ルール・教育で満たせないか)
  • どの品質を優先するか(早さ、正確さ、保守性、コスト)
  • 誰が困っていて、何がボトルネックか

を見極め、必要な手段を選び取ることだと思っています。

4. 意見の違いを認めて前へ進む (agree to disagree)

ステークホルダーやメンバーと議論すれば、信念や前提の違いでぶつかることがあります。そのとき大事なのは「勝つこと」ではなく、組織全体として何が最適かを探し、必要なら落としどころを作って前に進めることです。

私はこれを、自分への“おまじない”として「agree to disagree」と呼んでいました(厳密な英語の用法とは違うかもしれません)。

たとえば、アーキテクチャや手法の選定は、定量的に比較できれば理想ですが、必ずしもそうはなりません。各自の経験やプライドで固執が生まれることもある。それでも、どちらかに決めなければ課題解決は進みません。

また、チーム間で対立が起きることもあります。各チームには抱えている業務や背景があり、安易に承諾できない事情がある。けれど、ビジネスの価値を高めたいという点では一致していることが多い。

だから私は、議論が熱くなったときほど、まずこう問い直していました。
「我々が目指していることは何か?」
ここで主語を「我々」にするのがポイントです。

そのうえで、次の順番を意識していました。

  1. 双方の意見が対立している点と、合意できている点(ゴール)を切り分ける
  2. 合意できている点(ゴール)を中心に議論する
  3. 双方が納得できる着地点を見出す

そして、着地点を作ったあとは態度を決める必要があります。
もし自分の方針に寄せたなら、成功したら互いの成功として喜ぶ。失敗したら素直に認め、繰り返さないための振り返りを実施する。
もし相手の方針に寄せたなら、成功したら素直に称賛する。仮に失敗しても相手だけを批判せず、自分も責任を共有する。
いずれであっても、最後までやり抜く覚悟を持つこと。そうやって初めて、「合意」は前へ進む力になります。

信念は絶対ではありません。竹のように、しなやかさが必要な場面もあります。そして、たとえ譲歩することがあっても、それは前に進むための建設的な折り合いであり、「信念を捨てること」とは別物だと私は考えています。

おわりに

ここに書いた信念は、私のものです。誰かにそのまま当てはめたいわけではありません。ただ、迷う局面で立ち戻る「信念(軸)」があると、判断の速度も、折れにくさも変わります。

そしてその信念は、一度作って終わりではありません。経験を通して発見し、言葉にし、更新していく。

これから先、環境も技術もさらに変わっていくはずです。だからこそ「やり方」をアップデートしつつ、「何を大事にするか」は手放さないでほしい。そんな気持ちでまとめました。

自分の軸を育ててくれた本

プロとして矜持を持つことが大切だと気づかせてくれた本

リーダーシップとは何かを見直すことになった本

※新訳版も出版されていましたので、併記しています。

「与える人」になることが、長期的には良い結果をもたらすと自信を持たせてくれた本