

# LWLock:SubtransSLRU (LWLock:SubtransControlLock)
<a name="wait-event.lwlocksubtransslru"></a>

`LWLock:SubtransSLRU` および `LWLock:SubtransBuffer` 待機イベントは、セッションがサブトランザクション情報の単純最小使用時間 (SLRU) キャッシュへのアクセスを待っていることを示します。これは、トランザクションの可視性と親子関係を決定するときに発生します。
+ `LWLock:SubtransSLRU`: プロセスは、サブトランザクションのシンプルな最も長い時間使われていない (SLRU) キャッシュへのアクセスを待っています。バージョン 13 より前の RDS for PostgreSQL では、この待機イベントは `SubtransControlLock` と呼ばれます。
+ `LWLock:SubtransBuffer`: プロセスは、サブトランザクションのシンプルな最も長い時間使われていない (SLRU) バッファの I/O を待っています。バージョン 13 より前の RDS for PostgreSQL では、この待機イベントは `subtrans` と呼ばれます。

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

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

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

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

**サブトランザクションを理解する** – サブトランザクションは、PostgreSQL のトランザクション内のトランザクションです。ネストされたトランザクションとも呼ばれます。

サブトランザクションは通常、次の場合に作成されます。
+ `SAVEPOINT` コマンド
+ 例外ブロック (`BEGIN/EXCEPTION/END`)

サブトランザクションを使用すると、トランザクション全体に影響を与えることなく、トランザクションの一部をロールバックできます。これにより、トランザクション管理をきめ細かく制御できます。

**実装の詳細** – PostgreSQL は、メイントランザクション内のネストされた構造としてサブトランザクションを実装します。各サブトランザクションは独自のトランザクション ID を取得します。

実装の主な側面。
+ トランザクション ID は `pg_xact` で追跡されます。
+ 親子関係は `PGDATA` の `pg_subtrans` サブディレクトリに保存されます。
+ 各データベースセッションは、最大 `64` のアクティブなサブトランザクションを維持できます
+ この制限を超えると、サブトランザクションオーバーフローが発生します。サブトランザクション情報を取得するために、最も長い時間使われていない (SLRU) キャッシュにアクセスする必要があります

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

サブトランザクション SLRU 競合の一般的な原因は次のとおりです。
+ **SAVEPOINT および EXCEPTION 処理の過剰な使用** – `EXCEPTION` ハンドラーを使用した PL/pgSQL プロシージャは、例外が発生するかどうかにかかわらず、暗黙的なセーブポイントを自動的に作成します。各 `SAVEPOINT` は新しいサブトランザクションを開始します。1 つのトランザクションが 64 を超えるサブトランザクションを蓄積すると、サブトランザクション SLRU オーバーフローがトリガーされます。
+ **ドライバーと ORM の設定** – `SAVEPOINT` の使用は、アプリケーションコードで明示的で行うことも、ドライバー設定を通じて暗黙的に行うこともできます。一般的に使用される ORM ツールやアプリケーションフレームワークの多くは、ネストされたトランザクションをネイティブにサポートしています。以下は一般的な例です。
  + JDBC ドライバーパラメータ `autosave` を `always` または `conservative` に設定すると、各クエリの前にセーブポイントが生成されます。
  + Spring Framework トランザクション定義を `propagation_nested` に設定した場合。
  + `requires_new: true` が設定されている場合の Rails。
  + `session.begin_nested` を使用する場合の SQLAlchemy。
  + ネストされた `atomic()` ブロックを使用する場合の Django。
  + `Savepoint` を使用する場合の GORM。
  + ロールバックレベル設定がステートメントレベルのロールバックに設定されている場合の psqlODBC (例: `PROTOCOL=7.4-2`)。
+ **長時間実行されるトランザクションとサブトランザクションを伴う高同時ワークロード** – 同時ワークロードが高く、長時間実行されるトランザクションとサブトランザクション中にサブトランザクション SLRU オーバーフローが発生すると、PostgreSQL の競合が増加します。これは、`LWLock:SubtransBuffer` および `LWLock:SubtransSLRU` ロックの昇格された待機イベントとして現れます。

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

待機イベントの原因に応じたさまざまなアクションをお勧めします。一部のアクションは即時の打開策を提供しますが、他のアクションは調査と長期的な修正を必要とします。

**Topics**
+ [サブトランザクション使用状況のモニタリング](#wait-event.lwlocksubtransslru.actions.monitor)
+ [メモリパラメータを設定する](#wait-event.lwlocksubtransslru.actions.memory)
+ [長時間のアクション](#wait-event.lwlocksubtransslru.actions.longterm)

### サブトランザクション使用状況のモニタリング
<a name="wait-event.lwlocksubtransslru.actions.monitor"></a>

PostgreSQL バージョン 16.1 以降では、次のクエリを使用して、バックエンドごとのサブトランザクション数とオーバーフローステータスをモニタリングします。このクエリは、バックエンド統計をアクティビティ情報と結合して、サブトランザクションを使用しているプロセスを示します。

```
SELECT a.pid, usename, query, state, wait_event_type,
       wait_event, subxact_count, subxact_overflowed
FROM (SELECT id, pg_stat_get_backend_pid(id) pid, subxact_count, subxact_overflowed
      FROM pg_stat_get_backend_idset() id
           JOIN LATERAL pg_stat_get_backend_subxact(id) AS s ON true
     ) a
JOIN pg_stat_activity b ON a.pid = b.pid;
```

PostgreSQL バージョン 13.3 以降では、サブトランザクションキャッシュプレッシャーについて `pg_stat_slru` ビューをモニタリングします。次の SQL クエリは、Subtrans コンポーネントの SLRU キャッシュ統計を取得します。

```
SELECT * FROM pg_stat_slru WHERE name = 'Subtrans';
```

`blks_read` 値が一貫して増加すると、キャッシュされていないサブトランザクションのディスクアクセスが頻繁になり、SLRU キャッシュプレッシャーの可能性が示されます。

### メモリパラメータを設定する
<a name="wait-event.lwlocksubtransslru.actions.memory"></a>

PostgreSQL 17.1 以降では、`subtransaction_buffers` パラメータを使用してサブトランザクション SLRU キャッシュサイズを設定できます。次の設定例は、サブトランザクションバッファパラメータを設定する方法を示しています。

```
subtransaction_buffers = 128
```

このパラメータは、サブトランザクションコンテンツのキャッシュに使用される共有メモリの量を指定します (`pg_subtrans`)。単位なしで指定した場合、値は `BLCKSZ` バイトのブロックを表し、通常はそれぞれ 8KB です。例えば、値を 128 に設定すると、サブトランザクションキャッシュに 1MB (128 \* 8kB) のメモリが割り当てられます。

**注記**  
このパラメータをクラスターレベルで設定することで、すべてのインスタンスの整合性を維持できます。特定のワークロード要件とインスタンスクラスに適した値をテストして調整します。パラメータの変更を有効にするには、ライターインスタンスを再起動する必要があります。

### 長時間のアクション
<a name="wait-event.lwlocksubtransslru.actions.longterm"></a>
+ **アプリケーションコードと設定の確認** – アプリケーションコードとデータベースドライバーの設定で、明示的および暗黙的な `SAVEPOINT` 使用状況とサブトランザクションの一般的な使用状況の両方を確認します。64 を超えるサブトランザクションを生成する可能性のあるトランザクションを特定します。
+ **セーブポイント使用量の削減** – トランザクションでのセーブポイントの使用を最小限に抑えます。
  + EXCEPTION ブロックを使用して PL/pgSQL の手順と関数を確認します。EXCEPTION ブロックは暗黙的なセーブポイントを自動的に作成し、サブトランザクションのオーバーフローにつながる可能性があります。各 EXCEPTION 句は、実行中に例外が実際に発生したかどうかに関係なく、サブトランザクションを作成します。  
**Example**  

    例 1: 問題のある EXCEPTION ブロックの使用

    次のコード例は、複数のサブトランザクションを作成する問題のある EXCEPTION ブロックの使用を示しています。

    ```
    CREATE OR REPLACE FUNCTION process_user_data()
    RETURNS void AS $$
    DECLARE
        user_record RECORD;
    BEGIN
        FOR user_record IN SELECT * FROM users LOOP
            BEGIN
                -- This creates a subtransaction for each iteration
                INSERT INTO user_audit (user_id, action, timestamp)
                VALUES (user_record.id, 'processed', NOW());
                
                UPDATE users 
                SET last_processed = NOW() 
                WHERE id = user_record.id;
                
            EXCEPTION
                WHEN unique_violation THEN
                    -- Handle duplicate audit entries
                    UPDATE user_audit 
                    SET timestamp = NOW() 
                    WHERE user_id = user_record.id AND action = 'processed';
            END;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql;
    ```

    次の改善されたコード例では、例外処理の代わりに UPSERT を使用することで、サブトランザクションの使用を削減しています。

    ```
    CREATE OR REPLACE FUNCTION process_user_data()
    RETURNS void AS $$
    DECLARE
        user_record RECORD;
    BEGIN
        FOR user_record IN SELECT * FROM users LOOP
            -- Use UPSERT to avoid exception handling
            INSERT INTO user_audit (user_id, action, timestamp)
            VALUES (user_record.id, 'processed', NOW())
            ON CONFLICT (user_id, action) 
            DO UPDATE SET timestamp = NOW();
            
            UPDATE users 
            SET last_processed = NOW() 
            WHERE id = user_record.id;
        END LOOP;
    END;
    $$ LANGUAGE plpgsql;
    ```  
**Example**  

    例 2: STRICT 例外ハンドラー

    次のコード例は、NO\_DATA\_FOUND を使用した EXCEPTION 処理の問題を示しています。

    ```
    CREATE OR REPLACE FUNCTION get_user_email(p_user_id INTEGER)
    RETURNS TEXT AS $$
    DECLARE
        user_email TEXT;
    BEGIN
        BEGIN
            -- STRICT causes an exception if no rows or multiple rows found
            SELECT email INTO STRICT user_email 
            FROM users 
            WHERE id = p_user_id;
            
            RETURN user_email;
            
        EXCEPTION
            WHEN NO_DATA_FOUND THEN
                RETURN 'Email not found';
        END;
    END;
    $$ LANGUAGE plpgsql;
    ```

    次の改善されたコード例では、例外処理の代わりに IF NOT FOUND を使用してサブトランザクションを回避します。

    ```
    CREATE OR REPLACE FUNCTION get_user_email(p_user_id INTEGER)
    RETURNS TEXT AS $$
    DECLARE
        user_email TEXT;
    BEGIN
         SELECT email INTO user_email 
         FROM users 
         WHERE id = p_user_id;
            
         IF NOT FOUND THEN
             RETURN 'Email not found';
         ELSE
             RETURN user_email;
         END IF;
    END;
    $$ LANGUAGE plpgsql;
    ```
  + JDBC ドライバー – `autosave` パラメータを `always` または `conservative` に設定すると、各クエリの前にセーブポイントが生成されます。`never` 設定がアプリケーションに受け入れられるかどうかを評価します。
  + PostgreSQL ODBC ドライバー (psqlODBC) — ロールバックレベル設定 (ステートメントレベルのロールバック用) は、ステートメントのロールバック機能を有効にするための暗黙的なセーブポイントを作成します。トランザクションレベルのロールバックがアプリケーションで許容されるかどうかを評価します。
  + ORM トランザクション設定を確認する
  + セーブポイントを必要としない代替エラー処理戦略を検討する
+ **トランザクション設計の最適化** – 過剰なネストを回避し、サブトランザクションオーバーフロー条件の可能性を減らすために、トランザクションを再構築します。
+ **長時間実行されるトランザクションの削減** – 長時間実行されるトランザクションでは、サブトランザクション情報を長く保持することで、サブトランザクションの問題が悪化する可能性があります。Performance Insights メトリクスをモニタリングし、アイドル状態のトランザクションを自動的に終了するように `idle_in_transaction_session_timeout` パラメータを設定します。
+ Performance Insights メトリクスのモニタリング – `idle_in_transaction_count` (トランザクション状態でアイドル状態のセッション数) や `idle_in_transaction_max_time` (アイドル状態にある最長時間のトランザクションの継続時間) などのメトリクスを追跡して、長時間実行されるトランザクションを検出します。
+ `idle_in_transaction_session_timeout` の設定 – 指定した期間後にアイドル状態のトランザクションを自動的に終了するように、パラメータグループにこのパラメータを設定します。
+ プロアクティブモニタリング – `LWLock:SubtransBuffer` および `LWLock:SubtransSLRU` 待機イベントの発生頻度をモニタリングし、サブトランザクション関連の競合が重大になる前に検出します。