

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

Os eventos de espera `LWLock:SubtransSLRU` e `LWLock:SubtransBuffer` indicam que uma sessão está aguardando para acessar o cache simples utilizado com menor frequência (SLRU) para informações de subtransação. Isso ocorre ao determinar a visibilidade da transação e os relacionamentos pai-filho.
+ `LWLock:SubtransSLRU`: um processo está aguardando para acessar o cache simples menos utilizado recentemente (SLRU) para uma subtransação. No RDS para PostgreSQL antes da versão 13, esse evento de espera é chamado de `SubtransControlLock`.
+ `LWLock:SubtransBuffer`: um processo está aguardando a E/S em um buffer simples menos utilizado recentemente (SLRU) para uma subtransação. No RDS para PostgreSQL antes da versão 13, esse evento de espera é chamado de `subtrans`.

**Topics**
+ [Versões compatíveis do mecanismo](#wait-event.lwlocksubtransslru.supported)
+ [Contexto](#wait-event.lwlocksubtransslru.context)
+ [Possíveis causas do maior número de esperas](#wait-event.lwlocksubtransslru.causes)
+ [Ações](#wait-event.lwlocksubtransslru.actions)

## Versões compatíveis do mecanismo
<a name="wait-event.lwlocksubtransslru.supported"></a>

Essas informações de eventos de espera são compatíveis com todas as versões do RDS para PostgreSQL.

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

**Noções básicas sobre subtransações**: subtransação é uma transação dentro de uma transação no PostgreSQL. Também é conhecida como transação aninhada.

Normalmente, as subtransações são criadas quando você usa:
+ `SAVEPOINT`Comandos da 
+ Blocos de exceção (`BEGIN/EXCEPTION/END`)

As subtransações permitem reverter partes de uma transação sem afetar toda ela. Isso oferece controle refinado sobre o gerenciamento de transações.

**Detalhes da implementação**: o PostgreSQL implementa subtransações como estruturas aninhadas nas transações principais. Cada subtransação recebe seu próprio ID.

Principais aspectos de implementação:
+ Os IDs de transação são monitorados em `pg_xact`.
+ Os relacionamentos pai-filho são armazenados no subdiretório `pg_subtrans` em `PGDATA`.
+ Cada sessão do banco de dados pode manter até `64` subtransações ativas.
+ Exceder esse limite causa estouro de subtransação, o que requer acesso ao cache simples utilizado com menor frequência (SLRU) para informações de subtransação.

## Possíveis causas do maior número de esperas
<a name="wait-event.lwlocksubtransslru.causes"></a>

As causas comuns de contenção de SLRU por subtransação são as seguintes:
+ **Uso excessivo do tratamento de SAVEPOINT e EXCEPTION**: os procedimentos PL/pgSQL com manipuladores `EXCEPTION` criam automaticamente pontos de salvamento implícitos, independentemente da ocorrência de exceções. Cada `SAVEPOINT` inicia uma nova subtransação. Quando uma única transação acumula mais de 64 subtransações, ela aciona um estouro de SLRU de subtransação.
+ **Configurações de driver e ORM**: o uso de `SAVEPOINT` pode ser explícito no código da aplicação ou implícito nas configurações do driver. Muitas ferramentas de ORM e frameworks de aplicações geralmente usadas comportam transações aninhadas de forma nativa. Veja aqui alguns exemplos comuns:
  + O parâmetro do driver JDBC `autosave`, se definido como `always` ou `conservative`, gera pontos de salvamento antes de cada consulta.
  + Definições de transação do Spring Framework quando configuradas como `propagation_nested`.
  + Trilhos quando `requires_new: true` está definido.
  + SQLAlchemy quando `session.begin_nested` é usado.
  + Django quando blocos `atomic()` aninhados são usados.
  + GORM quando `Savepoint` é usado.
  + psqlODBC quando a configuração do nível de reversão é definida como reversão em nível de instrução (por exemplo, `PROTOCOL=7.4-2`).
+ **Altas workloads simultâneas com transações e subtransações de longa execução**: quando ocorre um estouro de SLRU de subtransação durante workloads altamente simultâneas e transações e subtransações de longa execução, o PostgreSQL sofre uma maior contenção. Isso se manifesta como eventos de espera elevados para bloqueios `LWLock:SubtransBuffer` e `LWLock:SubtransSLRU`.

## Ações
<a name="wait-event.lwlocksubtransslru.actions"></a>

Recomenda-se ações distintas, dependendo dos motivos do evento de espera. Algumas ações fornecem alívio imediato, enquanto outras exigem investigação e correção no longo prazo.

**Topics**
+ [Monitorar o uso de subtransações](#wait-event.lwlocksubtransslru.actions.monitor)
+ [Configurar parâmetros de memória](#wait-event.lwlocksubtransslru.actions.memory)
+ [Ações de longo prazo](#wait-event.lwlocksubtransslru.actions.longterm)

### Monitorar o uso de subtransações
<a name="wait-event.lwlocksubtransslru.actions.monitor"></a>

Para as versões 16.1 e posteriores do PostgreSQL, use a consulta a seguir para monitorar as contagens de subtransações e o status de estouro por backend. Essa consulta une estatísticas de backend com informações de atividades para mostrar quais processos estão usando subtransações:

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

Para as versões 13.3 e posteriores do PostgreSQL, monitore a visualização `pg_stat_slru` da pressão do cache de subtransação. A consulta SQL a seguir recupera estatísticas de cache SLRU para o componente Subtrans:

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

Um valor `blks_read` consistentemente crescente indica acesso frequente ao disco para subtransações não armazenadas em cache, sinalizando uma possível pressão no cache de SLRU.

### Configurar parâmetros de memória
<a name="wait-event.lwlocksubtransslru.actions.memory"></a>

Para o PostgreSQL 17.1 e versões posteriores, é possível configurar o tamanho do cache de SLRU de subtransações usando o parâmetro `subtransaction_buffers`. O seguinte exemplo de configuração mostra como definir o parâmetro de buffer de subtransações:

```
subtransaction_buffers = 128
```

Esse parâmetro especifica a quantidade de memória compartilhada utilizada para armazenar conteúdo de subtransação (`pg_subtrans`). Quando especificado sem unidades, o valor representa blocos de `BLCKSZ` bytes, normalmente 8 KB cada. Por exemplo, definir o valor como 128 aloca 1 MB (128 x 8 kB) de memória para o cache de subtransações.

**nota**  
É possível definir esse parâmetro em nível de cluster para que todas as instâncias permaneçam consistentes. Teste e ajuste o valor para melhor atender aos requisitos específicos da workload e à classe de instância. É necessário reinicializar a instância do gravador para que a alteração do parâmetro tenha efeito.

### Ações de longo prazo
<a name="wait-event.lwlocksubtransslru.actions.longterm"></a>
+ **Examinar o código e as configurações da aplicação**: analise as configurações do código da aplicação e do driver do banco de dados quanto ao uso explícito e implícito de `SAVEPOINT` e ao uso de subtransações em geral. Identifique transações que podem geram mais de 64 subtransações.
+ **Reduzir o uso de pontos de salvamento**: minimize o uso de pontos de salvamento nas transações:
  + Analise os procedimentos PL/pgSQL e funções com blocos EXCEPTION. Os blocos EXCEPTION criam automaticamente pontos de salvamento implícitos, que podem contribuir para o estouro de subtransações. Cada cláusula EXCEPTION cria uma subtransação, independentemente de uma exceção realmente ocorrer durante a execução.  
**Example**  

    Exemplo 1: uso problemático do bloco EXCEPTION

    O exemplo de código a seguir mostra o uso problemático do bloco EXCEPTION que cria várias subtransações:

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

    O exemplo de código aprimorado a seguir reduz o uso de subtransações usando UPSERT em vez do tratamento de exceções:

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

    Exemplo 2: manipulador de exceções STRICT

    O exemplo de código a seguir mostra o tratamento problemático de EXCEPTION com NO\_DATA\_FOUND:

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

    O exemplo de código aprimorado a seguir evita subtransações usando IF NOT FOUND em vez do tratamento de exceções:

    ```
    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;
    ```
  + Driver JDBC: o parâmetro `autosave`, se definido como `always` ou `conservative`, gera pontos de salvamento antes de cada consulta. Avalie se a configuração `never` seria aceitável para sua aplicação.
  + Driver ODBC do PostgreSQL (psqlODBC): a configuração em nível de reversão (para reversão em nível da instrução) cria pontos de salvamento implícitos para habilitar a funcionalidade de reversão da instrução. Avalie se a reversão em nível de transação ou nenhuma reversão seria aceitável para sua aplicação. 
  + Examinar as configurações de transações do ORM
  + Pense nas estratégias alternativas de tratamento de erros que não exijam pontos de salvamento
+ **Otimizar o design da transação**: reestruture as transações para evitar o agrupamento excessivo e reduzir a probabilidade de condições de estouro de subtransações.
+ **Reduzir as transações de longa duração**: as transações de longa duração podem agravar os problemas com subtransações ao reter as informações delas por mais tempo. Monitore as métricas do Insights de Performance e configure o parâmetro `idle_in_transaction_session_timeout` para encerrar automaticamente as transações ociosas.
+ Monitorar as métricas do Insights de Performance: acompanhe métricas, incluindo `idle_in_transaction_count` (número de sessões inativas no estado da transação) e `idle_in_transaction_max_time` (duração da transação ociosa mais longa) para detectar transações de longa duração.
+ Configurar `idle_in_transaction_session_timeout`: defina esse parâmetro em seu grupo de parâmetros para encerrar automaticamente transações ociosas após um período especificado.
+ Monitoramento proativo: monitore altas ocorrências de eventos de espera `LWLock:SubtransBuffer` e `LWLock:SubtransSLRU` para detectar contenções relacionadas a subtransações antes que elas se tornem críticas.