PostgreSQL

PostgreSQLのレプリケーションのコンフリクトについて言いたい

経緯

@soudai1025 さんの以下のエントリを読んでいて、

PostgreSQLのレプリケーションのコンフリクトについて

補足したくなり、

とか



とか色々tweetしていると、

とアドバイスをいただいたので、ドキュメントのこの辺の内容をまとめておきます。

なにがコンフリクトするか

スタンバイでは、マスタから転送されるWALを適用しながら、クライアントからのSQLを受付けるので(hot_standby = trueの場合)、よくある例として、スレーブでの参照スレーブが参照しているタプルを物理削除するWAL(VACUUM等で出力される)の適用がコンフリクトします。

その他にも、以下のようなものがあります。

  • マスタでの排他ロック(AccessExclusiveLock)とスタンバイでの参照(AELを取得するとそれに対応するWALがでます)
  • マスタでのデータベース削除と、スタンバイでのそのデータベースに接続するセッション など

(※)
PostgreSQLはSELECTでもロックを取得します。しかし、SELECTで取得されるロックはAccessShareLockというロックで、UPDATE/DELTE/INSERTで取得されるロックであるRowExclusiveLockとはコンフリクトしません。そのため、レプリケーションでのコンフリクトも起きませんし、通常のクエリ処理でもコンフリクトは起きません。

https://www.postgresql.jp/document/9.6/html/explicit-locking.html#table-lock-compatibility

コンフリクトを防ぐ

コンフリクトを防ぐために、マスタはスタンバイを忖度しながら物理削除するタプルを決定する必要があります。
その方法として、以下の方法があります。
1. vacuum_defer_cleanup_age
2. hot_standby_feedbackやレプリケーションスロットの使用

1. vacuum_defer_cleanup_ageパラメータ(トランザクション数を設定する)

これは、マスタがタプルの物理削除を遅延するというものです。例えば、物理削除可能になった(DELETE/UPDATE後、そのタプルをみてるトランザクションが居なくなった)タプルに対して、VACUUMは通常すぐに物理削除しますが、vacuum_defer_cleanup_ageが設定されている場合は、後で物理削除するようになります。
パラメータには遅延するトランザクション数を指定するので、マスタは「直近削除されたタプルではなく、○○トランザクション前に削除されているタプルを物理削除しよう」と判断します。
なので、vacuum_defer_cleanup_ageはマスタ側で設定します。

2. hot_standby_feedback(on/off)やレプリケーションスロットの使用

一方、こちらは、スタンバイが自身の活動状況をマスタに報告することで、マスタは「このタプルはスタンバイが見ている可能性があるから物理削除しないでおいておこう」と判断することができるようになります。
なので、hot_standby_feedbackはスタンバイ側で設定します。

コンフリクトが発生したらどっちを待たせるか

コンフリクトが発生した場合、コンフリクトしているクエリをいつキャンセルするか(もしくはキャンセルしないでWAL適用を待たせる)をmax_standby_streaming_delayで設定することができます。

  • -1 : コンフリクトが発生してもクエリをキャンセルせず、WAL適用をクエリが完了するまで待たせる。
  • 0以上 : コンフリクト後、指定した秒数後にクエリをキャンセル

例えばスタンバイに重い分析系のクエリを投げたり、論理バックアップを取得する時とかで、クエリ処理側を優先したいときには-1にします。

マスタ側のWALがなくなってしまうのは別の問題

物理削除を遅延させた場合、マスタでテーブルが肥大化する可能性があり、スタンバイでのクエリを優先した場合は、スタンバイでWALが溢れる可能性があります。
ただし、どちらの場合でも、マスタでのVACUUMやWALの送信が止まったり、スタンバイのWAL受信が止まったりすることはありません。

マスタ側のWALがなくなってしまうのは別の問題で、例えばWALの転送遅延が発生し、スタンバイに送る前にマスタ側でWALを循環(リサイクル)させてしまい、スタンバイが必要としているWALがなくなってしまう、みたいな時に起きます。
これは、当該エントリで説明されているようにレプリケーションスロットを使うことで対処できます。

おわりに

公式ドキュメントを書くときには、もう少し分かりやすく書くように気をつけよう。