

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

# Recomendações de driver do Amazon QLDB
<a name="driver.best-practices"></a>

**Importante**  
Aviso de fim do suporte: os clientes existentes poderão usar o Amazon QLDB até o final do suporte em 31/07/2025. Para obter mais detalhes, consulte [Migrar um Amazon QLDB Ledger para o Amazon](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/) Aurora PostgreSQL.

Esta seção descreve as melhores práticas para configurar e usar o driver Amazon QLDB para qualquer linguagem compatível. Os exemplos de código fornecidos são especificamente para Java.

Essas recomendações se aplicam à maioria dos casos de uso comuns, mas um tamanho não serve para todos. Use as recomendações a seguir conforme achar adequado para seu aplicativo.

**Topics**
+ [Configurando o objeto QldbDriver](#driver.best-practices.configuring)
+ [Tentar novamente com exceções](#driver.best-practices.retrying)
+ [Otimizar o desempenho](#driver.best-practices.performance)
+ [Como executar várias instruções por transação](#driver.best-practices.multiple-statements)

## Configurando o objeto QldbDriver
<a name="driver.best-practices.configuring"></a>

O objeto `QldbDriver` gerencia as conexões com seu ledger mantendo um pool de *sessões* que são reutilizadas em todas as transações. Uma [sessão](driver-session-management.md) representa uma única conexão com o ledger. O QLDB suporta uma transação em execução ativa por sessão.

**Importante**  
Para versões mais antigas do driver, a funcionalidade de agrupamento de sessões ainda está no `PooledQldbDriver` objeto em vez de `QldbDriver`. Se você estiver usando uma das versões a seguir, substitua qualquer menção de `QldbDriver` por `PooledQldbDriver` pelo restante deste tópico.  


****  

| Driver | Versão | 
| --- | --- | 
| Java | 1.1.0 ou mais cedo | 
| .NET | 0.1.0-beta | 
| Node.js | 1.0.0-rc.1 ou mais cedo | 
| Python | 2.0.2 ou mais cedo | 
O objeto `PooledQldbDriver` está obsoleto na versão mais recente dos drivers. Recomendamos atualizar para a versão mais recente e converter todas as instâncias de `PooledQldbDriver` para`QldbDriver`.

**Configurar QldbDriver como um objeto global**

Para otimizar o uso de drivers e sessões, certifique-se de que exista apenas uma instância global do driver na instância do seu aplicativo. Por exemplo, em Java, você pode usar frameworks de *injeção de dependência*, como [Spring](https://spring.io/), [Google Guice](https://github.com/google/guice) ou [Dagger](https://dagger.dev/). O exemplo de código a seguir mostra como configurar `QldbDriver` como singleton.

```
@Singleton
public QldbDriver qldbDriver (AWSCredentialsProvider credentialsProvider,
                                    @Named(LEDGER_NAME_CONFIG_PARAM) String ledgerName) {
    QldbSessionClientBuilder builder = QldbSessionClient.builder();
    if (null != credentialsProvider) {
        builder.credentialsProvider(credentialsProvider);
    }
    return QldbDriver.builder()
            .ledger(ledgerName)
            .transactionRetryPolicy(RetryPolicy
                .builder()
                .maxRetries(3)
                .build())
            .sessionClientBuilder(builder)
            .build();
}
```

**Configurar as tentativas de nova tentativa**

O driver repete transações automaticamente quando ocorrem exceções transitórias comuns (como `SocketTimeoutException` ou `NoHttpResponseException`). Para definir o número máximo de tentativas de repetição, você pode usar o parâmetro `maxRetries` do objeto da configuração `transactionRetryPolicy` ao criar uma instância do `QldbDriver`. (Para versões mais antigas do driver, conforme listado na seção anterior, use o parâmetro `retryLimit` de `PooledQldbDriver`.)

O valor padrão de `maxRetries` é `4`.

Erros do lado do cliente, como não é possível tentar `InvalidParameterException` novamente. Quando eles ocorrem, a transação é abortada, a sessão é retornada ao pool e a exceção é lançada para o cliente do driver.

**Configurar o número máximo de sessões e transações simultâneos**

O número máximo de sessões de ledger usadas por uma instância de `QldbDriver` para executar transações é definido por seu parâmetro `maxConcurrentTransactions`. (Para versões mais antigas do driver, conforme listado na seção anterior, isso é definido pelo parâmetro `poolLimit` de `PooledQldbDriver`.)

Esse limite deve ser maior que zero e menor ou igual ao número máximo de conexões HTTP abertas que o cliente da sessão permite, conforme definido pelo AWS SDK específico. Por exemplo, em Java, o número máximo de conexões é definido no [ClientConfiguration](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html#getMaxConnections--)objeto.

O valor padrão de `maxConcurrentTransactions` é a configuração máxima de conexão do seu AWS SDK.

Ao configurar o `QldbDriver` em seu aplicativo, considere as seguintes considerações de escalabilidade:
+ Seu pool sempre deve ter pelo menos tantas sessões quanto o número de transações em execução simultânea que você planeja ter.
+ Em um modelo multiencadeado em que um thread do supervisor delega aos threads do trabalhador, o driver deve ter pelo menos tantas sessões quanto o número de threads do trabalhador. Caso contrário, no pico de carga, os threads estarão esperando na fila por uma sessão disponível.
+ O limite de sessões ativas simultâneas por ledger é definido em [Cotas e limites no Amazon QLDB](limits.md#limits.fixed). Certifique-se de não ter configurado mais do que esse limite de sessões simultâneas a serem usadas em um único ledger em todos os clientes.

## Tentar novamente com exceções
<a name="driver.best-practices.retrying"></a>

Ao tentar novamente as exceções que ocorrem no QLDB, considere as recomendações a seguir.

**Tentando novamente OccConflictException**

As exceções de conflito de *controle de simultaneidade otimista (OCC)* ocorrem quando os dados que a transação está acessando são alterados desde o início da transação. O QLDB lança essa exceção ao tentar confirmar a transação. O driver repete a transação até quantas vezes `maxRetries` estiver configurada.

Para obter mais informações sobre OCC e as práticas recomendadas de uso de índices para limitar conflitos de OCC, consulte [Modelo de simultaneidade do Amazon QLDB](concurrency.md).

**Tentando novamente com outras exceções fora do QldbDriver**

Para repetir uma transação fora do driver quando exceções personalizadas definidas pelo aplicativo são lançadas durante o runtime, você deve encapsular a transação. Por exemplo, em Java, o código a seguir mostra como usar a biblioteca [Reslience4J](https://resilience4j.readme.io/) para repetir uma transação no QLDB.

```
private final RetryConfig retryConfig = RetryConfig.custom()
        .maxAttempts(MAX_RETRIES)
        .intervalFunction(IntervalFunction.ofExponentialRandomBackoff())
        // Retry this exception
        .retryExceptions(InvalidSessionException.class, MyRetryableException.class)
        // But fail for any other type of exception extended from RuntimeException
        .ignoreExceptions(RuntimeException.class)
        .build();

// Method callable by a client
public void myTransactionWithRetries(Params params) {
    Retry retry = Retry.of("registerDriver", retryConfig);

    Function<Params, Void> transactionFunction = Retry.decorateFunction(
            retry,
            parameters ->  transactionNoReturn(params));
    transactionFunction.apply(params);
}

private Void transactionNoReturn(Params params) {
    try (driver.execute(txn -> {
            // Transaction code
        });
    }
    return null;
}
```

**nota**  
Tentar novamente uma transação fora do driver QLDB tem um efeito multiplicador. Por exemplo, se `QldbDriver` estiver configurada para repetir três vezes e a lógica de repetição personalizada também tentar três vezes, a mesma transação poderá ser repetida até nove vezes.

**Tornar as transações idempotentes**

Como prática recomendada, torne suas transações de gravação idempotentes para evitar efeitos colaterais inesperados no caso de tentativas repetidas. Uma transação é *idempotente* se puder ser executada várias vezes e produzir resultados idênticos a cada vez.

Para saber mais, consulte [Modelo de simultaneidade do Amazon QLDB](concurrency.md#concurrency.idempotent).

## Otimizar o desempenho
<a name="driver.best-practices.performance"></a>

Para otimizar o desempenho ao executar transações usando o driver, considere as seguintes considerações:
+ A operação `execute` sempre faz no mínimo três `SendCommand` chamadas de API para o QLDB, incluindo os seguintes comandos:

  1. `StartTransaction`

  1. `ExecuteStatement`

     Esse comando é invocado para cada instrução partiQL que você executa no bloco `execute`.

  1. `CommitTransaction`

  Considere o número total de chamadas de API que são feitas quando você calcula a workload geral do seu aplicativo.
+ Em geral, recomendamos começar com um gravador de thread único e otimizar as transações agrupando várias declarações em lotes em uma única transação. Maximize as cotas de tamanho da transação, tamanho do documento e número de documentos por transação, conforme definido em [Cotas e limites no Amazon QLDB](limits.md#limits.fixed).
+ Se o agrupamento em lotes não for suficiente para grandes cargas de transações, você pode tentar o multiencadeamento adicionando gravadores adicionais. No entanto, você deve considerar cuidadosamente os requisitos de seu aplicativo para sequenciamento de documentos e transações e a complexidade adicional que isso introduz.

## Como executar várias instruções por transação
<a name="driver.best-practices.multiple-statements"></a>

Conforme descrito na [seção anterior](#driver.best-practices.performance), você pode executar várias declarações por transação para otimizar o desempenho do seu aplicativo. No exemplo de código a seguir, você consulta uma tabela e, em seguida, atualiza um documento nessa tabela dentro de uma transação. Você faz isso passando uma expressão lambda para a operação `execute`.

------
#### [ Java ]

```
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
// set your UPDATE to filter on vin and insured, and check if you updated something or not.
public static boolean InsureCar(QldbDriver qldbDriver, final String vin) {
    final IonSystem ionSystem = IonSystemBuilder.standard().build();
    final IonString ionVin = ionSystem.newString(vin);

    return qldbDriver.execute(txn -> {
        Result result = txn.execute(
                "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE",
                ionVin);
        if (!result.isEmpty()) {
            txn.execute("UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin);
            return true;
        }
        return false;
    });
}
```

------
#### [ .NET ]

```
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
// set your UPDATE to filter on vin and insured, and check if you updated something or not.
public static async Task<bool> InsureVehicle(IAsyncQldbDriver driver, string vin)
{
    ValueFactory valueFactory = new ValueFactory();
    IIonValue ionVin = valueFactory.NewString(vin);

    return await driver.Execute(async txn =>
    {
        // Check if the vehicle is insured.
        Amazon.QLDB.Driver.IAsyncResult result = await txn.Execute(
            "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", ionVin);

        if (await result.CountAsync() > 0)
        {
            // If the vehicle is not insured, insure it.
            await txn.Execute(
                "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", ionVin);
            return true;
        }
        return false;
    });
}
```

------
#### [ Go ]

```
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
// set your UPDATE to filter on vin and insured, and check if you updated something or not.
func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) {
    insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {

        result, err := txn.Execute(
            "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)
        if err != nil {
            return false, err
        }

        hasNext := result.Next(txn)
        if !hasNext && result.Err() != nil {
            return false, result.Err()
        }

        if hasNext {
            _, err = txn.Execute(
                "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin)
            if err != nil {
                return false, err
            }
            return true, nil
        }
        return false, nil
    })
    if err != nil {
        panic(err)
    }

    return insured.(bool), err
}
```

------
#### [ Node.js ]

```
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
// set your UPDATE to filter on vin and insured, and check if you updated something or not.
async function insureCar(driver: QldbDriver, vin: string): Promise<boolean> {

    return await driver.executeLambda(async (txn: TransactionExecutor) => {
        const results: dom.Value[] = (await txn.execute(
            "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)).getResultList();

        if (results.length > 0) {
            await txn.execute(
                "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin);
            return true;
        }
        return false;
    });
};
```

------
#### [ Python ]

```
# This code snippet is intentionally trivial. In reality you wouldn't do this because you'd
# set your UPDATE to filter on vin and insured, and check if you updated something or not.

def do_insure_car(transaction_executor, vin):
    cursor = transaction_executor.execute_statement(
        "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin)
    first_record = next(cursor, None)
    if first_record:
        transaction_executor.execute_statement(
            "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin)
        return True
    else:
        return False

def insure_car(qldb_driver, vin_to_insure):
    return qldb_driver.execute_lambda(
        lambda executor: do_insure_car(executor, vin_to_insure))
```

------

A operação `execute` do driver inicia implicitamente uma sessão e uma transação nessa sessão. Cada instrução que você executa na expressão lambda é encapsulada na transação. Depois que todas as instruções são executadas, o driver confirma automaticamente a transação. Se alguma instrução falhar após o limite de repetição automática ser esgotado, a transação será abortada.

**Propagar exceções em uma transação**

Ao executar várias instruções por transação, geralmente não recomendamos que você capture e engula exceções dentro da transação.

Por exemplo, em Java, o programa a seguir captura qualquer instância de `RuntimeException`, registra o erro e continua. Esse exemplo de código é considerado uma prática ruim porque a transação é bem-sucedida mesmo quando a instrução `UPDATE` falha. Portanto, o cliente pode presumir que a atualização foi bem-sucedida quando não foi.

**Atenção**  
Não use esse exemplo de código. É fornecido para mostrar um exemplo de antipadrão que é considerado uma má prática.

```
// DO NOT USE this code example because it is considered bad practice
public static void main(final String... args) {
    ConnectToLedger.getDriver().execute(txn -> {
        final Result selectTableResult = txn.execute("SELECT * FROM Vehicle WHERE VIN ='123456789'");
        // Catching an error inside the transaction is an anti-pattern because the operation might
        // not succeed.
        // In this example, the transaction succeeds even when the update statement fails.
        // So, the client might assume that the update succeeded when it didn't.
        try {
            processResults(selectTableResult);
            String model = // some code that extracts the model
            final Result updateResult = txn.execute("UPDATE Vehicle SET model = ? WHERE VIN = '123456789'",
                    Constants.MAPPER.writeValueAsIonValue(model));
        } catch (RuntimeException e) {
            log.error("Exception when updating the Vehicle table {}", e.getMessage());
        }
    });
    log.info("Vehicle table updated successfully.");
}
```

Em vez disso, propague (aumente) a exceção. Se alguma parte da transação falhar, deixe a operação `execute` abortar a transação para que o cliente possa lidar com a exceção adequadamente.