30.4. WALの設定

データベースの性能に影響するようなWALに関連した設定パラメータが複数あります。 本節では、その使い方を説明します。 サーバ設定パラメータの設定方法についての詳細は19章サーバの設定を参照してください。

チェックポイントは、一連のトランザクションにおいて、そのチェックポイント以前に書かれた全ての情報によりヒープとインデックスファイルがすでに更新されていることを保証する場所です。 チェックポイントの時刻において、全てのダーティページデータはディスクにフラッシュされ、特殊なチェックポイントレコードがログファイルに書き込まれます。 (変更されたレコードは以前にWALフラッシュされています。) クラッシュした時、クラッシュからの復旧処理は最後のチェックポイントレコードを見つけ、ログの中でどのレコード(これはredoレコードと呼ばれています)から復旧処理がREDOログ操作を開始すべきかを決定します。 このチェックポイント以前になされたデータの変更は、すでにディスク上にあることが保証されています。 従って、チェックポイント後、redoレコード内のそのチェックポイント以前のログセグメントは不要となり、再利用または削除することができます (WALアーカイブが行われる場合、このログセグメントは削除もしくは再利用される前に保存されなければなりません)。

チェックポイント処理は、全てのダーティデータページをディスクへ書き出すため、大きなI/O負荷を発生させます。 チェックポイント処理においては、I/Oはチェックポイント開始時に始まり、次のチェックポイントが開始する前に完了するように調節されます。 これは、チェックポイント処理中の性能劣化を極力抑える効果があります。

サーバのチェックポインタプロセスは、自動的にチェックポイントを時々実行します。 checkpoint_timeout秒が経過するか、またはmax_wal_sizeに達するか、どちらかの条件が最初に満たされるとチェックポイントが開始されます。 デフォルトの設定では、それぞれ5分と1GBとなっています。 前回のチェックポイント以降書き出すWALがない場合、checkpoint_timeoutが経過したとしても新しいチェックポイントが飛ばされます。 WALアーカイブ処理を使用しており、かつ、データ損失の可能性を限定するためにファイルのアーカイブ頻度に対する下限を設定したい場合、このチェックポイントパラメータではなくarchive_timeoutパラメータを調節しなければなりません。 また、CHECKPOINT SQLコマンドで強制的にチェックポイントを作成することもできます。

checkpoint_timeoutまたはmax_wal_size、あるいはその両者を減少させると、チェックポイントはより頻繁に行われます。 これにより、やり直しに要する処理量が少なくなるので、クラッシュ後の修復は高速になります。 しかし、変更されたデータページの吐き出しがより頻繁に行われることにより増大するコストとバランスを考えなければなりません。 full_page_writesが設定されている(デフォルトです)場合、他に考慮しなければならない点があります。 データページの一貫性を保証するために、各チェックポイント後の最初に変更されるデータページは、そのページ全体の内容がログに保存されることになります。 このような場合、チェックポイントの間隔を少なくすることは、WALログへの出力を増加させ、間隔を短くする目的の一部を無意味にします。 また、確実により多くのディスクI/Oが発生します。

チェックポイントはかなり高価なものです。 1番の理由は、この処理は現時点の全てのダーティバッファを書き出す必要があること、2番目の理由は、上記のようにその後に余計なWALの書き込みが発生することです。 そのため、チェックポイント用のパラメータを高くし、チェックポイントがあまりにも頻発することがないようにすることを勧めます。 簡単なチェックポイント用のパラメータの健全性検査として、checkpoint_warningパラメータを設定することができます。 チェックポイントの発生間隔がcheckpoint_warning秒未満の場合、max_wal_sizeの増加を勧めるメッセージがサーバのログに出力されます。 このメッセージが稀に現れたとしても問題にはなりませんが、頻出するようであれば、チェックポイントの制御パラメータを増加させるべきです。 max_wal_sizeを十分高く設定していないと、大規模なCOPY転送などのまとまった操作でこうした警告が多く発生するかもしれません。

ページ書き出しの集中による入出力システムの溢れを防ぐために、チェックポイント期間のダーティバッファの書き出しは一定の期間に分散されます。 この期間はcheckpoint_completion_targetにより制御され、チェックポイント間隔の割合として指定されます。 I/Oの割合は、チェックポイントの起動時からcheckpoint_timeout秒が経過した時、あるいはmax_wal_sizeを超えた時、このどちらかが発生するとすぐに、チェックポイントが完了するように調整されます。 デフォルトの0.5という値では、PostgreSQLは、次のチェックポイントが始まるまでのおよそ半分の時間で各チェックポイントが完了するものと想定できることになります。 通常の操作においてほぼ最大のI/Oスループットに近いようなシステムでは、チェックポイントにおけるI/O負荷を減らすためにcheckpoint_completion_targetを増やすことを勧めます。 この欠点は、延長されたチェックポイントがリカバリ時に影響をあたえることです。 リカバリ時に使用できるように、より多くのWALセグメントを保持する必要があるためです。 checkpoint_completion_targetを最大の1.0に設定することもできますが、より低く抑えること(おそらく最大で0.9)が最善です。 チェックポイントには、ダーティバッファを書き出す以外の活動も含まれているからです。 1.0という設定は、ある時点でチェックポイントが完了しなくなるという結果に陥ります。 これは必要なWALセグメント数が想定以上に変動することになり、性能の劣化が発生することになります。

LinuxおよびPOSIXプラットフォームでは、チェックポイントによって書かれたページを、設定したバイト数の後にディスクに吐き出すようにcheckpoint_flush_afterを使ってOSに強制させることができます。 この設定がない場合はこのページはOSのページキャッシュに保持されるかもしれず、チェックポイントの最後にfsyncが発行された際の速度低下を招きます。 この設定は、しばしばトランザクションの遅延を減少させるのに役立ちます。 しかし、とりわけワークロードがshared_buffersよりも大きく、かつOSのページキャッシュよりも小さい場合には性能上不利になることもあります。

pg_xlogディレクトリ内のWALセグメントファイルの数は、min_wal_sizemax_wal_size、それに前回のチェックポイントで生成されたWALの量に依存します。 古いログセグメントファイルが不要になると、削除または再利用(連番のうち、今後利用される予定の番号に名前が変更されます)されます。 ログの出力レートが短期間にピークを迎えたためにmax_wal_sizeを超えた場合、この制限以下になるまで不要なセグメントファイルが削除されます。 この制限以下になると、次のチェックポイントまでは、システムは見積もりを満たすだけのWALファイルを再利用します。 この見積は、前回のチェックポイントの際に使用されたWALファイルの移動平均に基づいています。 もし実際の使用量が見積もりを上回ると、移動平均は直ちに増加します。 これにより、平均需要というよりは、ピーク時の需要をある程度満たすことができるわけです。 min_wal_sizeは、今後のために再利用されるWALファイル数の最小値を設定します。 システムがアイドル状態にあり、WALの使用量を見積った結果、少ないWALしか必要ないとなったとしても、こうした量のWALファイルは必ず再利用されます。

max_wal_sizeとは無関係に、WALファイルのうち、常に最新の(wal_keep_segments + 1)個が維持されます。 また、WALアーカイブを利用している場合は、古いセグメントは、アーカイブされるまでは削除も再利用もされません。 WALが生成されるペースにWALのアーカイブ処理が追いつかなかったり、archive_commandが連続して失敗すると、事態が解決するまでWALファイルはpg_xlogの下に蓄積されていきます。 レプリケーションスロットを使用しているスタンバイサーバが低速だったり、失敗すると、同じ現象が起きます(26.2.6. レプリケーションスロットを参照のこと)

アーカイブからのリカバリもしくはスタンバイモードにおいて、サーバでは定期的に通常運用でのチェックポイント処理と似た再開始点処理を行います。これは、すでに再生されたWALを再度読み込む必要がないよう、ディスクに現在の状態を強制的に書き込み、pg_controlファイルを更新します。またpg_xlogディレクトリの中の古いログセグメントを再利用できるようにします。 再開始点処理はチェックポイントレコードに対してしか実施されないので、マスタ側のチェックポイント処理よりも発生頻度が多いということはありません。 再開始点は、最後の再開始点より少なくともcheckpoint_timeout秒が経過しているか、あるいはmax_wal_sizeを超えそうな場合に起動されます。 しかし、再開始点が実施できるための制約事項により、リカバリの際には1回のチェックポイント分のWALを上限に、max_wal_sizeを超えてしまいがちです。 (どのみちmax_wal_sizeはハードリミットではないので、ディスクスペースを使い尽くしてしまわないように、常に十分な余裕を持っておくべきです)

よく使われる2つの内部用WAL関数があります。 XLogInsertRecordXLogFlushです。 XLogInsertRecordは共有メモリ上のWALバッファに新しいレコードを挿入します。 新しいレコードを挿入する余地がない時は、XLogInsertRecordは、満杯になったWALバッファを書き込み(カーネルキャッシュに移動)しなければいけません。 これは望ましいことではありません。 なぜなら、データベースへの低レベルの変更(例えば行の挿入)の度にXLogInsertRecordが呼ばれますが、そのような場合には変更を受けたページに対して排他ロックがかかっており、それゆえこの操作は可能な限り高速に実行されなければなりません。 さらに悪いことには、WALバッファへの書き込みの際に、さらに時間がかかる、強制的な新しいログセグメントの生成が必要となるかもしれません。 通常、WALの書き込み、吐き出しはXLogFlush要求で実施されます。 これはたいていの場合、トランザクションコミットの際に永続的な記憶領域にトランザクションレコードが吐き出されることを保証するために行われます。 ログ出力が大量に行われるシステムでは、XLogInsertRecordによって必要となる書き込みを防ぐほどにはXLogFlush要求が頻繁に起こらないかもしれません。 そういうシステムでは、wal_buffersパラメータを変更してWALバッファの数を増やしてください。 full_page_writesが設定され、かつ、システムが高負荷状態である場合、wal_buffersを高くすることで、各チェックポイントの直後の応答時間を滑らかにすることができます。

commit_delayパラメータは、XLogFlush内でロックを取得してからグループコミット上位者が何マイクロ秒休止するかを定義します。一方、グループコミット追従者は上位者の後に並びます。 すべてが上位者の結果として生ずる同期操作によりフラッシュされるように、この遅延は他のサーバプロセスがそれらのコミットレコードをWALバッファに追加することを許容します。 fsyncが有効でないか、またはcommit_siblingsより少ない他のセッションがその時点で活動しているトランザクションであれば休止は行われません。 他の何らかのセッションが直ぐにでもコミットするという起こりそうにない時の休止を避けるものです。 いくつかのプラットフォームにおいて、休止要求の分解能は10ミリ秒で、1から10000マイクロ秒の間のcommit_delayの設定は、どの値でも同じ効果となることを覚えておいてください。 いくつかのプラットフォームで、休止操作はパラメータによって要求された時間よりわずかに長くなることも覚えておいてください。

commit_delayの目的は、それぞれのフラッシュ操作のコストを並列にコミット中のトランザクションに(潜在的にはトランザクションの待ち時間と引き換えに)分散させることにあり、うまく設定を行うためには、まずそのコストを測る必要があります。 そのコストが高ければ高いほど、トランザクションのスループットがある程度向上するという意味において、commit_delayの効果がより増すことが期待できます。 pg_test_fsyncプログラムは、一つのWALフラッシュが必要とするマイクロ秒単位の平均時間を計測するために使用可能です。 プログラムが報告する単一の8kb書き込み操作のあとのフラッシュ平均時間の2分の1の値は、しばしばcommit_delayの最も効果的な設定です。 従って、この値は特定の作業負荷のための最適化を行うときに使用するための手始めとして推奨されます。 WALログが高遅延の回転ディスクに格納されているときは、commit_delayのチューニングは特に有効ですが、半導体ドライブまたはバッテリー・バックアップされている書き込みキャッシュ付きのRAIDアレーのような、特に同期時間が高速な格納メディア上であっても大きなメリットがある場合があります。 しかし、このことは、代表的作業負荷に対してきちんと検証しておくべきです。 commit_siblingsの高い値は、これらの状況で使用すべきで、一方より小さなcommit_siblingsの値は高遅延メディア上でしばしば有用です。 余りにも高い値のcommit_delayを設定すると、トランザクション遅延を増加させかねないことになり、トランザクションの総スループットが低下します。

commit_delayが(デフォルトの)ゼロに設定されても、グループコミットが起こることがあります。 しかし、それぞれのグループは前回のフラッシュ操作(あった場合)が発生していた期間中に、それぞれのコミットレコードをフラッシュする必要に至ったセッションのみから成ります。 クライアントが多い状況では、gangway effectが起こる傾向があり、そのためcommit_delayがゼロであってもグループコミットの効果が著しく、従って、commit_delayを明示的に設定しても役立ちません。 commit_delayの設定は(1)複数の同時にコミット中のトランザクションが存在すること、そして(2)コミット頻度によりある程度までスループットが制限されている場合に役立ちます。 しかし、回転待ち時間が長い場合、この設定はわずか二つのクライアントにおいてさえトランザクションスループットを向上させる効果があるかもしれません(言いかえれば、一つの兄弟(sibling)トランザクションを所有する単一のコミット中のクライアントです)。

wal_sync_methodパラメータはPostgreSQLがカーネルに対してWAL更新のディスクへの書き込みを要求する方法を決定します。 fsync_writethroughを除き、どういう設定でも信頼性は同じはずです。fsync_writethroughは他のオプションがそうしないときでも、時々ディスクキャッシュの書き出しを強制することができます。 しかしながら、プラットフォームによってどれが一番速いのかがまったく違います。 pg_test_fsyncプログラムを使って異なるオプションの速度テストを行うことができます。 ちなみに、このパラメータはfsyncが無効になっている場合は役に立ちません。

wal_debug設定パラメータを有効にすることで、XLogInsertRecordXLogFlushというWAL呼び出しは毎回サーバログにログが残ります (このパラメータをサポートするようにPostgreSQLをコンパイルする必要があります)。 将来このオプションはより一般的な機構に置き換わる可能性があります。