経緯
@soudai1025 さんの以下のエントリを読んでいて、
PostgreSQLのレプリケーションのコンフリクトについて
補足したくなり、
とか細かいけど、実際にコンフリクトするのはスレーブ側でのSELECTとそのSELECTしている行を物理削除するWALの適用(その他にもある)。SELECTのロックとUPDATEのロックは通常のクエリ処理でも競合しないようになっている。
— Masahiko Sawada (@sawada_masahiko) December 27, 2017
PostgreSQLのレプリケーションのコンフリクトについてhttps://t.co/O4kEcoUVR3
とか色々tweetしていると、マスタ側での物理削除を遅らせるために、vacuum_defer_cleanup_ageではマスタ側が、消せるようになった行も少し待って(あとで)物理削除するようなる。レプリケーションスロットとか、hot_standby_feedbackでは、
— Masahiko Sawada (@sawada_masahiko) December 27, 2017
そういうのはエントリー書いてトラバすると色んな人が喜ぶと思います。
— そーだい@初代ALF (@soudai1025) December 27, 2017
またはPostgreSQL公式ドキュメントに書く。
とアドバイスをいただいたので、ドキュメントのこの辺の内容をまとめておきます。
なにがコンフリクトするか
スタンバイでは、マスタから転送されるWALを適用しながら、クライアントからのSQLを受付けるので(hot_standby = trueの場合)、よくある例として、スレーブでの参照とスレーブが参照しているタプルを物理削除するWAL(VACUUM等で出力される)の適用がコンフリクトします。
その他にも、以下のようなものがあります。
- マスタでの排他ロック(AccessExclusiveLock)とスタンバイでの参照(AELを取得するとそれに対応するWALがでます)
- マスタでのデータベース削除と、スタンバイでのそのデータベースに接続するセッション
など
(※)
PostgreSQLはSELECTでもロックを取得します。しかし、SELECTで取得されるロックはAccessShareLockというロックで、UPDATE/DELTE/INSERTで取得されるロックであるRowExclusiveLockとはコンフリクトしません。そのため、レプリケーションでのコンフリクトも起きませんし、通常のクエリ処理でもコンフリクトは起きません。
コンフリクトを防ぐ
コンフリクトを防ぐために、マスタはスタンバイを忖度しながら物理削除するタプルを決定する必要があります。
その方法として、以下の方法があります。
- vacuum_defer_cleanup_age
- 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がなくなってしまう、みたいな時に起きます。
これは、当該エントリで説明されているようにレプリケーションスロットを使うことで対処できます。
おわりに
公式ドキュメントを書くときには、もう少し分かりやすく書くように気をつけよう。