TECH BLOG
技術ブログ

ARTICLE

  • 2022-02-18 PostgreSQL

    クエリの競合と hot_standby_feedback

クエリの競合について

スタンバイを hot_standby = on としているストリーミングレプリケーション環境では、クエリの競合(コンフリクト)というものが発生します。
クエリの競合が発生する条件は様々ですが、例えば、マスタで実行された VACUUM によって、スタンバイで参照しようとしていたデータブロックが削除(移動)してしまった場合に発生します。他にも、マスタで A テーブルに対して DROP TABLE コマンドを実行した際に、スタンバイで A テーブルに対して SELECT が実行されていると競合が発生し、スタンバイのクエリが中断される可能性があります。
クエリ競合の詳細は、公式ドキュメントに詳しく説明がありますので、ご参照ください。

PostgreSQL 13.1文書 - 26.5.2. 問い合わせコンフリクトの処理 -

https://www.postgresql.jp/document/13/html/hot-standby.html#HOT-STANDBY-CONFLICT

競合の発生状況の確認

また、どれだけクエリの競合が発生していたかを保持している pg_stat_database_conflicts というビューも用意されています。
実際に SELECT 文で見てみますと下記のような感じでした。

=# SELECT * FROM pg_stat_database_conflicts;
   datid   |  datname   | confl_tablespace | confl_lock | confl_snapshot | confl_bufferpin | confl_deadlock
-----------+------------+------------------+------------+----------------+-----------------+----------------
     13593 | postgres   |                0 |          0 |              0 |               0 |              0
     13592 | template0  |                0 |          0 |              0 |               0 |              0
         1 | template1  |                0 |          0 |              0 |               0 |              0

詳細は例によってドキュメントをご参照ください。

PostgreSQL 13.1文書 - 27.2. 統計情報コレクタ -

https://www.postgresql.jp/document/13/html/monitoring-stats.html#MONITORING-PG-STAT-DATABASE-CONFLICTS-VIEW

ちなみに、マスタでの DROP TABLE コマンドとスタンバイのクエリが競合した際には、confl_lock がカウントアップされます。
マスタでの VACUUM とスタンバイのクエリが競合した際には、confl_snapshot がカウントアップされます。
confl_bufferpin がカウントアップされている場合には、共有バッファ(shared_buffers)が足りていない可能性がありますので、ご注意ください。

hot_standby_feedback パラメータについて

このクエリの競合を防止するパラメータとして hot_standby_feedback というものがあります。
hot_standby_feedback を on にすると、スタンバイで実行されているクエリの情報をマスタへ送信されるようになります。

フィードバックが送られていることを確かめたい場合には、ログレベルを DEBUG2 にしてください。スタンバイからマスタへのフィードバックメッセージがログに出力されるようになります。
具体的には下記のようなログメッセージです。

2022-02-17 14:12:45.058 JST [2238] DEBUG: sending hot standby feedback xmin 24451 epoch 0 catalog_xmin 0 catalog_xmin_epoch 0

スタンバイでクエリがキャンセルされなくなる仕組み

マスタが VACUUM 時に、このスタンバイからフィードバックされた情報を確認します。
そして、スタンバイで参照される可能性のあるデータブロックを削除(移動)しないようにします。
その結果、スタンバイでのクエリと競合することがなくなり、クエリもキャンセルされなくなるという仕組みです。

hot_standby_feedback を on にしても、発生するクエリのキャンセルについて

hot_standby_feedback を on にしても、スタンバイでクエリのキャンセルが発生する可能性があります。
その理由は、hot_standby_feedback を on にすることで、抑止できるクエリのキャンセルは「データブロックの削除(移動)に伴うクエリの競合による」ものに限定されるからです。
具体的には、下記のようなメッセージが表示されるクエリのキャンセルを抑止できます。

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

例えば、マスタで行った DROP TABLE コマンドと、スタンバイの SELECT が競合した場合には、hot_standby_feedback が on であっても、スタンバイの SELECT がキャンセルされる可能性があります。

マスタの DROP TABLE によるスタンバイの SELECT がキャンセルされる

実際に下記のような手順で再現できます。

  1. スタンバイで SELECT 実行
    standby=# show hot_standby_feedback;
    hot_standby_feedback 
    ----------------------
    on
    (1 行)
  • hot_standby_feedback が on となっていることを確認します。
standby=# SELECT * FROM test_table CROSS JOIN (SELECT pg_sleep(100000)) as sleep_second;
  • test_table に対する SELECT 文を実行します。
  • pg_sleep(100000) している SELECT と CROSS JOIN させているので、SELECT は100,000 秒間実行されます。
  1. マスタで DROP TABLE 実行

    master=# DROP TABLE test_table;
    DROP TABLE
  2. スタンバイで SELECT がキャンセルされるまで待つ
    しばらく待つと下記のようなメッセージが出力されて、SELECT が終了します。

ERROR: canceling statement due to conflict with recovery
DETAIL: User was holding a relation lock for too long.

  • 無事(?)に SELECT がキャンセルされました。

クエリがキャンセルされるまでの時間について

ちなみに、クエリがキャンセルされるまでの時間はパラメータ「max_standby_streaming_delay」によります(アーカイブ WAL からリカバリする場合は「max_standby_archive_delay 」)。
クエリが実行されてから、max_standby_streaming_delay 分の時間を待つのではなく、WAL レコードが到着してから max_standby_streaming_delay 分の時間が経過したらキャンセルが発生します。
max_standby_streaming_delay のデフォルトは 30 秒です。

standby=# show max_standby_streaming_delay;
 max_standby_streaming_delay 
-----------------------------
 30s
(1 行)

スタンバイサーバが高負荷なときなど、WAL レコードがスタンバイに到着しているものの 、そのリカバリが遅れている状況もあります。
例えば、WAL レコードのリカバリが 30 秒以上遅れている場合、スタンバイでクエリを実行した瞬間にクエリがキャンセルされる可能性もあります。

補足)hot_standby_feedback を on にするデメリット

マスタで発生した VACUUM と、スタンバイでのクエリの競合を防げるというのであれば、hot_standby_feedback は常に on の方がよいのではないか、と考えたくなるのですが、hot_standby_feedback を on にすることで弊害もあります。
具体的には「マスタで廃止行が溜まりやすくなる = ディスクサイズの消費量が増加」という可能性があります。
繰り返しとなりますが、hot_standby_feedback を on にすると、VACUUM 時にスタンバイで参照する可能性のあるデータブロックの削除(移動)が保留となります。
そのため、hot_standby_feedback が off であれば VACUUM 時に削除されて再利用できるはずの領域が、hot_standby_feedback が on ですと再利用できない場合があります(=別に領域を確保する必要があるので、ディスクサイズの消費量が増加します)。

おわりに

以上、クエリの競合と hot_standby_feedback の説明でした。
ストリーミングレプリケーションは便利ですが、設定によってはスタンバイでクエリがキャンセルされる可能性があることを知っておいていただければと思います。

CATEGORY

ARCHIVE

データベースの
パフォーマンス・チューニングのご相談は
株式会社インサイトまで
お気軽にお問い合わせください。

CONTACT