

# Lock:tuple
<a name="wait-event.locktuple"></a>

`Lock:tuple` イベントは、バックエンドプロセスがタプルのロックを取得するのを待っているときに発生します。

**Topics**
+ [サポート対象エンジンバージョン](#wait-event.locktuple.context.supported)
+ [Context](#wait-event.locktuple.context)
+ [待機時間が増加する原因の可能性](#wait-event.locktuple.causes)
+ [アクション](#wait-event.locktuple.actions)

## サポート対象エンジンバージョン
<a name="wait-event.locktuple.context.supported"></a>

この待機イベント情報は、RDS for PostgreSQL のすべてのバージョンでサポートされています。

## Context
<a name="wait-event.locktuple.context"></a>

イベント`Lock:tuple`は、バックエンドがタプルのロック取得を待っている間、別のバックエンドが同じタプルで競合するロックを保持していることを示します。次の表では、セッションが`Lock:tuple`イベントを生成するシナリオを示します。


|  時間  |  セッション 1  |  セッション 2  |  セッション 3  | 
| --- | --- | --- | --- | 
|  t1  |  トランザクションをスタートします。  |    |    | 
|  t2  |  行 1 を更新します。  |    |    | 
|  t3  |    |  行 1 を更新します。セッションは、タプルの排他ロックを取得してから、セッション1がコミットまたはロールバックによってロックを解放するのを待機します。  |    | 
|  t4  |    |    |  行 1 を更新します。セッションは、セッション 2 がタプルの排他ロックを解放するのを待機します。  | 

または、ベンチマークツール`pgbench`を使用してこの待機イベントをシミュレートすることができます。多数の同時セッションを構成して、カスタムSQLファイルでテーブル内の同じ行を更新します。

競合するロックモードの詳細については、「PostgreSQL のドキュメント」の「[明示的なロック](https://www.postgresql.org/docs/current/explicit-locking.html)」を参照してください。`pgbench`の詳細については、「PostgreSQL のドキュメント」の「[pgbench](https://www.postgresql.org/docs/current/pgbench.html)」を参照してください。

## 待機時間が増加する原因の可能性
<a name="wait-event.locktuple.causes"></a>

このイベントが通常よりも頻繁に表示される場合、パフォーマンスの問題を示している可能性があり、典型的な原因は次のとおりです。
+ `UPDATE`または`DELETE`ステートメントの実行により、同じタプルへの競合するロックを取得しようとしている同時セッションが多数あります。
+ 同時実行数の多いセッションでは、`FOR UPDATE`または`FOR NO KEY UPDATE`ロックモードを使用して`SELECT`ステートメントを実行します。
+ さまざまな要因により、アプリケーションまたは接続プールが同じオペレーションを実行するため、より多くのセッションを開きます。新しいセッションが同じ行を変更しようとすると、DB ロードのスパイクが発生して`Lock:tuple`が表示されることがあります。

詳細については、「PostgreSQL のドキュメント」の「[行レベルのロック](https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-ROWS)」を参照してください。

## アクション
<a name="wait-event.locktuple.actions"></a>

待機イベントの原因に応じたさまざまなアクションをお勧めします。

**Topics**
+ [アプリケーションロジックを調査する](#wait-event.locktuple.actions.problem)
+ [ブロッカーセッションを見つける](#wait-event.locktuple.actions.find-blocker)
+ [同時実行数が多いときにそれを減らす](#wait-event.locktuple.actions.concurrency)
+ [ボトルネックのトラブルシューティング](#wait-event.locktuple.actions.bottlenecks)

### アプリケーションロジックを調査する
<a name="wait-event.locktuple.actions.problem"></a>

ブロッカーセッションが長い間`idle in transaction`ステートメントにあったかどうかを確認します。その場合は、短期的な解決策としてブロッカーセッションの終了を検討してください。`pg_terminate_backend` 関数を使用することもできます。この関数の詳細については、「PostgreSQL のドキュメント」の「[サーバーシグナリング関数](https://www.postgresql.org/docs/13/functions-admin.html#FUNCTIONS-ADMIN-SIGNAL)」を参照してください。

長期的な解決策としては、以下を実行してください。
+ アプリケーションロジックを調整します。
+ `idle_in_transaction_session_timeout` パラメータを使用します。このパラメータは、指定された時間より長くアイドル状態であったオープントランザクションを持つセッションを終了させます。詳細については、PostgreSQL ドキュメントの 「[Client Connection Defaults (クライアント接続のデフォルト)](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-IDLE-IN-TRANSACTION-SESSION-TIMEOUT)」 を参照してください。
+ 可能な限りオートコミットを使用します。詳細については、PostgreSQL ドキュメントの 「[Client authentication (クライアント認証)](https://www.postgresql.org/docs/current/ecpg-sql-set-autocommit.html)」 を参照してください。

### ブロッカーセッションを見つける
<a name="wait-event.locktuple.actions.find-blocker"></a>

`Lock:tuple`の待機イベントが発生している間に、どのロックが互いに依存しているかを探して、ブロッカーとブロックされたセッションを特定します。詳細については、PostgreSQL wiki にの「[依存関係情報のロック](https://wiki.postgresql.org/wiki/Lock_dependency_information)」を参照してください。

次の例では、`tuple`でフィルタリングして`wait_time`で順序付けし、すべてのセッションへのクエリを実行しています。

```
SELECT blocked_locks.pid AS blocked_pid,
         blocking_locks.pid AS blocking_pid,
         blocked_activity.usename AS blocked_user,
         blocking_activity.usename AS blocking_user,
         now() - blocked_activity.xact_start AS blocked_transaction_duration,
         now() - blocking_activity.xact_start AS blocking_transaction_duration,
         concat(blocked_activity.wait_event_type,':',blocked_activity.wait_event) AS blocked_wait_event,
         concat(blocking_activity.wait_event_type,':',blocking_activity.wait_event) AS blocking_wait_event,
         blocked_activity.state AS blocked_state,
         blocking_activity.state AS blocking_state,
         blocked_locks.locktype AS blocked_locktype,
         blocking_locks.locktype AS blocking_locktype,
         blocked_activity.query AS blocked_statement,
         blocking_activity.query AS blocking_statement
    FROM pg_catalog.pg_locks blocked_locks
    JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid
    JOIN pg_catalog.pg_locks blocking_locks
        ON blocking_locks.locktype = blocked_locks.locktype
        AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE
        AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
        AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
        AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
        AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
        AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
        AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
        AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
        AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
        AND blocking_locks.pid != blocked_locks.pid
    JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid
    WHERE NOT blocked_locks.GRANTED;
```

### 同時実行数が多いときにそれを減らす
<a name="wait-event.locktuple.actions.concurrency"></a>

`Lock:tuple`イベントは、特にワークロードの多い時間帯に、コンスタントに発生する可能性があります。このような状況では、非常にビジーな行への同時実行数を減らすことを検討してください。多くの場合、キューまたはブールロジックを制御する行の数行だけで、これらの行は非常にビジーになります。

ビジネス要件、アプリケーションロジック、およびワークロードタイプに応じて、さまざまなアプローチを使用することで、競合を減らすことができます。例えば、次のオペレーションを実行できます。
+ テーブルとデータロジックを再設計し、競合を減らします。
+ アプリケーションロジックを変更して、行レベルで高い競合を減らします。
+ 行レベルのロックを活用してクエリを再設計します。
+ `NOWAIT`節はリトライオペレーションで使用します。
+ 楽観的かつハイブリッドロックロジックの同時実行制御の使用を検討します。
+ データベースの隔離レベルの変更を検討してください。

### ボトルネックのトラブルシューティング
<a name="wait-event.locktuple.actions.bottlenecks"></a>

`Lock:tuple`は、CPU の枯渇や Amazon EBS 帯域幅の最大使用率などのボトルネックを発生させることがあります。ボトルネックを減らすには、次のアプローチを検討します。
+ インスタンスクラスタイプをスケールアップします。
+ リソースを大量に消費するクエリを最適化します。
+ アプリケーションロジックを変更します。
+ ほとんどアクセスされないデータをアーカイブします。