

# 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)
+ [컨텍스트](#wait-event.lwlocksubtransslru.context)
+ [대기 증가의 가능한 원인](#wait-event.lwlocksubtransslru.causes)
+ [작업](#wait-event.lwlocksubtransslru.actions)

## 지원되는 엔진 버전
<a name="wait-event.lwlocksubtransslru.supported"></a>

이 대기 이벤트 정보는 모든 RDS for PostgreSQL 버전에서 지원됩니다.

## 컨텍스트
<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`는 새 하위 트랜잭션을 시작합니다. 단일 트랜잭션에 64개 이상의 하위 트랜잭션이 누적되면 하위 트랜잭션 SLRU 오버플로가 트리거됩니다.
+ **드라이버 및 ORM 구성** - `SAVEPOINT`는 애플리케이션 코드에서 명시적으로 사용하거나 드라이버 구성을 통해 암시적으로 사용할 수 있습니다. 일반적으로 사용되는 많은 ORM 도구 및 애플리케이션 프레임워크는 기본적으로 중첩된 트랜잭션을 지원합니다. 다음은 몇 가지 일반적인 예제입니다.
  + JDBC 드라이버 파라미터 `autosave`는 `always` 또는 `conservative`로 설정하면 각 쿼리 전에 저장점을 생성합니다.
  + `propagation_nested`로 설정된 경우 Spring Framework 트랜잭션 정의입니다.
  + `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`)를 캐싱하는 데 사용되는 공유 메모리의 양을 지정합니다. 단위 없이 지정하면 값은 일반적으로 각각 8KB인 `BLCKSZ`바이트 블록을 나타냅니다. 예를 들어, 값을 128로 설정하면 하위 트랜잭션 캐시에 1MB(128 \$1 8kB)의 메모리가 할당됩니다.

**참고**  
모든 인스턴스가 일관되게 유지되도록 클러스터 수준에서 이 파라미터를 설정할 수 있습니다. 특정 워크로드 요구 사항 및 인스턴스 클래스에 적합하도록 값을 테스트하고 조정합니다. 파라미터 변경 사항을 적용하려면 인스턴스를 재부팅해야 합니다.

### 장기적인 조치
<a name="wait-event.lwlocksubtransslru.actions.longterm"></a>
+ **애플리케이션 코드 및 구성 검사** - 애플리케이션 코드 및 데이터베이스 드라이버 구성을 검토하여 일반적으로 명시적 및 암시적 `SAVEPOINT` 사용량과 하위 트랜잭션 사용량을 모두 확인합니다. 64개 이상의 하위 트랜잭션을 생성할 가능성이 있는 트랜잭션을 식별합니다.
+ **저장점 사용량 감소** - 트랜잭션에서 저장점 사용을 최소화합니다.
  + EXCEPTION 블록을 사용하여 PL/pgSQL 프로시저 및 함수를 검토합니다. EXCEPTION 블록은 암시적 저장점을 자동으로 생성하여 하위 트랜잭션 오버플로에 기여할 수 있습니다. 각 EXCEPTION 절은 실행 중에 실제로 예외가 발생하는지 여부에 관계없이 하위 트랜잭션을 생성합니다.  
**Example**  

    예제 1: 문제가 있는 예외 블록 사용

    다음 코드 예제는 여러 하위 트랜잭션을 생성하는 문제가 있는 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\$1DATA\$1FOUND를 사용한 문제가 있는 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`의 높은 발생을 모니터링하고 이벤트가 중요해지기 전에 하위 트랜잭션 관련 경합을 감지할 때까지 기다립니다.