複数ノードにまたがる更新トランザクションを同時に実行した場合など、デッドロックが発生する場合があります。例えば、以下のような順番で更新を実施した場合にデッドロックが発生します。
1) トランザクション1が、データノード1上の行1を更新します。
2) トランザクション2が、データノード2上の行2を更新します。
3) トランザクション1が、データノード2上の行2を更新します。
4) トランザクション2が、データノード1上の行1を更新します。
3)でトランザクション1はトランザクション2が更新した行がCOMMITされるのを待ち、4)でトランザクション2はトランザクション1が更新した行がCOMMITされるのを待ちます。これはデッドロックです。
PostgreSQLには、デッドロックを自動で検出して一方を自動でエラーにする機能がありますが、この機能はデッドロックが同一ノード内で発生した場合に有効です。
上記のケースでは、データノード1上ではトランザクション2がトランザクション1を待っているだけで、データノード2上ではトランザクション1がトランザクション2を待っているだけです。つまり、各データノードではデッドロックが発生していないため、このようなデッドロックは自動で検出されません。デッドロックが検出されないことで、トランザクション1とトランザクション2は永久的に待たされることになります。この状態になった場合、pg_cancel_backend()などで一方のトランザクションを手動でキャンセルする必要があります。
この問題を解決するため、アプリケーションがノードをまたいだデッドロックを発生させる可能性がある場合には、各データノード上でlock_timeoutの機能を使用することによりデッドロックを解消してください。ロック獲得の待機時間がlock_timeoutで指定した時間を超えた場合にトランザクションがエラーになるため、ノードをまたがるデッドロックが発生した場合にも、自動でトランザクションをエラーにできます。ただし、デッドロックが発生していない場合でも指定した時間を超えて待機した場合にエラーになることに注意してください。
lock_timeoutをデータノード上のpostgresql.confに設定すると、データノードに接続するすべてのセッションに影響を与える可能性があるため推奨されません。下記のいずれかの方法を使用してください。
中央管理ノードから接続するすべての業務でlock_timeoutの要件が同一である場合には、中央管理ノードでPGXNODEのoptionsにlock_timeoutを設定することができます。下記は、datanode1とdatanode2のlock_timeoutを10sに設定する例です。
postgres=# ALTER PGXNODE datanode1 options (ADD options '-c lock_timeout=10s'); ALTER PGXNODE postgres=# ALTER PGXNODE datanode2 options (ADD options '-c lock_timeout=10s'); ALTER PGXNODE
業務ごとにlock_timeoutの要件が異なる場合には、業務ごとにユーザーを作成し、そのユーザーのlock_timeoutパラメータを設定することができます。下記は、全ノードに業務1用のユーザーapp1userを作成し、app1userのlock_timeoutを全ノードで10sに設定する例です。
postgres=# SET pgx_ddl_target_node = 'ALLNODES'; SET postgres=# CREATE USER app1user; CREATE USER postgres=# ALTER USER app1user SET lock_timeout = '10s'; ALTER ROLE postgres=# RESET pgx_ddl_target_node; RESET