そーだいなるらくがき帳

そーだいが自由気侭に更新します。

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

ERROR:  canceling statement due to conflict with recovery
DETAIL:  User query might have needed to see row versions that must be removed.

これは字の如く、レプリケーション側でWALの反映時にコンフリクトして強制的にクエリを殺されています。 理由は下記のスライドがわかりやすいです。

www.slideshare.net

詳細は公式ドキュメント読むと良いです。

26.5. ホットスタンバイ

追記(2017/12/28)

俺達のヒーロー @sawada_masahiko くんが完璧に補足してくれたのでちゃんと知りたい人はこっちを見ましょう。

qiita.com

なぜコンフリクトするのか?

PostgreSQLはSELECTでもロックを取ります。 なのでSELECT中にプライマリ側で対象行に更新があるとロック競合してWALの反映が待たされます。 するとWALがいつまで経っても反映されないのでmax_standby_streaming_delayの時間(Default 30秒)が過ぎてWALの更新が待たされてる場合にクエリが殺されます。

クエリを殺されないためには?

なのでmax_standby_streaming_delay=-1とすることでWALの反映をずっと待たせることができます。 しかしプライマリ側でVACUUMが実行されるとスタンバイ側でWALが反映されます。 そこでhot_standby_feedbackを有効化することでVACUUMを待たせる事ができます。 この2つを設定することでスタンバイ側のクエリが殺されること無く、WALの反映を待たせることができます。

問題はないの?

問題として長時間のクエリが実行されるとWALがずっと待たされるためディスク肥大化の可能性があります。

では肥大化するとディスク圧迫だけが問題でしょうか? 大きな問題としてWALの循環時にプライマリ側に合わせてスタンバイ側もWALを循環してしまう=スレーブが壊れてしまうということがあります。

ただしWALアーカイブモードを有効にしている場合は修復することができます。

スタンバイ側のrecovery.confに、restore_command= 'scp <アーカイブ先> <スタンバイ側のpg_xlogディレクトリ>'と指定していると 次のような動作をします。

  1. プライマリ「スタンバイに届いてないWALすてちゃった」
  2. スタンバイ「でもアーカイブ残ってるから、そっちから勝手に欠けてる分いただくぜ」

これによって不足分を自動に補ってくれるのですが アーカイブモードを有効にしてない場合は死 にます。

コンフリクトの解決策とレプリケーションスロット

そこで9.3未満問題を解決したのが9.4から出てくるレプリケーションスロット(フィジカル)です。 レプリケーションスロットをプライマリ側に作っとくと、スタンバイがそのスロットに対して「自分の状況」をPushします。

この「自分の状況」には次のものがあります。

以上を通知しており、3.を受け取ったプライマリは、「この行古いけど、スタンバイ君がまだ必要って言ってるからVACUUMやめとこ」って判断することが出来るので良きに計らってくれるわけです。 さらに2.の情報も持っているのでWAL循環問題も防げるので今回のようなスタンバイのコンフリクト問題の多くを防ぐことが出来る仕組みとなっています。

9.3未満を使っている方はこの機会にぜひ、バージョンアップをご検討ください。