

# Lock:advisory
<a name="apg-waits.lockadvisory"></a>

`Lock:advisory`イベントは、PostgreSQL アプリケーションがロックを使用して複数のセッション全体のアクティビティを調整するときに発生します。

**Topics**
+ [関連するエンジンのバージョン](#apg-waits.lockadvisory.context.supported)
+ [Context](#apg-waits.lockadvisory.context)
+ [原因](#apg-waits.lockadvisory.causes)
+ [アクション](#apg-waits.lockadvisory.actions)

## 関連するエンジンのバージョン
<a name="apg-waits.lockadvisory.context.supported"></a>

この待機イベント情報は、Aurora PostgreSQL バージョン 9.6 以降に関連しています。

## Context
<a name="apg-waits.lockadvisory.context"></a>

PostgreSQL アドバイザリロックは、ユーザーのアプリケーションコードによって明示的にロックおよびロック解除を実行するアプリケーションレベルの協調的ロックです。アプリケーションは PostgreSQL アドバイザリロックを使用して、複数のセッションにまたがるアクティビティを調整できます。通常のオブジェクトレベルまたは行レベルのロックとは異なり、アプリケーションはロックのライフタイムを完全に制御できます。詳細については、PostgreSQL ドキュメントの [Advisory Locks (アドバイザリロック)](https://www.postgresql.org/docs/12/explicit-locking.html#ADVISORY-LOCKS) を参照してください。

アドバイザリロックは、トランザクションが終了する前に解放されるか、トランザクション間のセッションで保持されます。これは、`CREATE INDEX`ステートメントによって取得されたテーブルへのアクセス排他ロックなど、暗黙のうちにシステムで強制されるロックには当てはまりません。

アドバイザリロックの取得 (ロック) およびリリース (ロック解除) に使用される関数の説明については、「PostgreSQL のドキュメント」の[アドバイザリロックの関数](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS)を参照してください。

アドバイザリロックは、通常の PostgreSQL ロックシステムの上に実装され、`pg_locks`システムビューで表示できます。

## 原因
<a name="apg-waits.lockadvisory.causes"></a>

このロックタイプは、明示的に使用するアプリケーションによって排他的に制御されます。クエリの一部として各行に対して取得されるアドバイザリロックは、ロックの急増や、長期的な蓄積を引き起こすことがあります。

これらの効果は、クエリが返すよりも多くの行でロックを取得する方法でクエリが実行されると発生します。アプリケーションは最終的にすべてのロックを解放する必要がありますが、返されない行でロックが取得された場合、アプリケーションはすべてのロックを見つけることができません。

PostgreSQL のドキュメントの「[アドバイザリロック](https://www.postgresql.org/docs/12/explicit-locking.html#ADVISORY-LOCKS)」からの例を紹介します。

```
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100;
```

この例では、`LIMIT`節がクエリの出力を停止できるのは、内部で行が選択され、その ID 値がロックされた後のみです。これは、データ量の増加により、プランナーが開発中にテストされなかった別の実行プランを選択した場合に突然発生することがあります。この場合の構築アップは、アプリケーションがロックされた各ID値に明示的に`pg_advisory_unlock`を呼び出すことによって発生します。ただし、この場合、返されなかった行において取得されたロックのセットを見つけることはできません。ロックはセッションレベルで取得されるため、トランザクションの終了時に自動的に解放されません。

ブロックされたロック試行のスパイクは、意図しない競合が原因の可能性があります。このような競合では、アプリケーションの無関係な部分が、誤って同じロック ID スペースを共有します。

## アクション
<a name="apg-waits.lockadvisory.actions"></a>

アドバイザリロックのアプリケーション使用状況を確認し、アプリケーションフロー内のいつどこで各タイプのアドバイザリロックが取得および解放されるのか、詳しく説明します。

セッションが取得したロックが多すぎるか、長時間実行しているセッションがロックを早期に解放しないために、ロックの蓄積が遅くなっているかどうかを調べます。`pg_terminate_backend(pid)`を使用してセッションを終了すると、セッションレベルロックの遅い蓄積を修正できます。

アドバイザリロックを待機中のクライアントが`pg_stat_activity`、`wait_event_type=Lock`、`wait_event=advisory`に表示されます。同じ`pid`の`pg_locks`システムビューへのクエリを実行し、`locktype=advisory`と`granted=f`を検索することで、特定のロック値を取得できます。

`pg_locks`に対して`granted=t`を持つ同じアドバイザリロックへのクエリを実行することで、ブロックしているセッションを特定することができます。

```
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;
```

すべてのアドバイザリロック API 関数には、1 つの`bigint`引数または2つの`integer`引数の 2 組の引数があります。
+ `bigint` の引数が 1 つの API 関数では、上位 32 ビットが `pg_locks.classid`、下位 32 ビットが `pg_locks.objid` となります。
+ `integer`が2つある API 関数の場合、第 1 引数は`pg_locks.classid`、第 2 引数は`pg_locks.objid`となります。

`pg_locks.objsubid`値はどの API フォームが使用されたかを示し、`1`は 1 つの`bigint`引数、`2`は 2 つの`integer`引数を意味します。