

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# Amazon QLDB ドライバーの推奨事項
<a name="driver.best-practices"></a>

**重要**  
サポート終了通知: 既存のお客様は、07/31/2025 のサポート終了まで Amazon QLDB を使用できます。詳細については、[「Amazon QLDB 台帳を Amazon Aurora PostgreSQL に移行する](https://aws.amazon.com/blogs/database/migrate-an-amazon-qldb-ledger-to-amazon-aurora-postgresql/)」を参照してください。

このセクションでは、サポートされている言語の Amazon QLDB ドライバーを設定および使用するためのベストプラクティスについて説明します。提供されているコード例は Java 専用です。

これらの推奨事項は、ほとんどの一般的なユースケースに適用されますが、1 つのサイズがすべてのサイズに適合するとは限りません。アプリケーションに適した以下の推奨事項を使用してください。

**Topics**
+ [QldbDriver オブジェクトの設定](#driver.best-practices.configuring)
+ [例外発生時の再試行](#driver.best-practices.retrying)
+ [パフォーマンスの最適化](#driver.best-practices.performance)
+ [トランザクションごとに複数のステートメントを実行する](#driver.best-practices.multiple-statements)

## QldbDriver オブジェクトの設定
<a name="driver.best-practices.configuring"></a>

この `QldbDriver` は、トランザクション間で再利用される*セッション*のプールを維持することによって、台帳への接続を管理します。[セッション](driver-session-management.md)は、台帳との 1 つの接続を表します。QLDB ではセッションごとに 1 つのトランザクションがアクティブに実行されます。

**重要**  
古いドライバーバージョンのセッションプール機能は、現在も、`QldbDriver` ではなく `PooledQldbDriver` にあります。次のいずれかのバージョンを使用している場合、このトピックに記載の `QldbDriver` は、`PooledQldbDriver` に置き換えてください。  


****  

| ドライバー | バージョン | 
| --- | --- | 
| Java | 1.1.0 以前 | 
| .NET | 0.1.0-beta | 
| Node.js | 1.0.0-rc.1 以前 | 
| Python | 2.0.2 以前 | 
`PooledQldbDriver` オブジェクトは、ドライバーの最新バージョンでは廃止されています。最新バージョンにアップグレードして、`PooledQldbDriver` のすべてのインスタンスを `QldbDriver` に変換することをお勧めします。

**QldbDriver をグローバルオブジェクトとして設定する**

ドライバーとセッションの使用を最適化するには、アプリケーションインスタンスにドライバーのグローバルインスタンスが 1 つのみあるようにします。例えば Java では、[Spring](https://spring.io/)、[Google Guice](https://github.com/google/guice)、[Dagger](https://dagger.dev/) などの*依存関係インジェクション*フレームワークを使用できます。次のコード例は、`QldbDriver` をシングルトンとして設定する方法を示しています。

```
@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();
}
```

**再試行を設定する**

ドライバーは、一般的な一時例外 (`SocketTimeoutException` や `NoHttpResponseException` など) が発生すると、自動的にトランザクションを再試行します。`QldbDriver` のインスタンスを作成するときに `transactionRetryPolicy` 設定オブジェクトの `maxRetries` パラメータを使用すると、最大再試行回数を設定できます。(前のセクションで説明した古いドライバーバージョンの場合は、`PooledQldbDriver` の `retryLimit` パラメータを使用します)。

`maxRetries` の初期値は `4` です。

`InvalidParameterException` などのクライアント側のエラーは再試行できません。これらが発生すると、トランザクションが中止され、セッションがプールに戻され、例外がドライバーのクライアントにスローされます。

**同時セッションと同時トランザクションの最大数を設定する**

トランザクションを実行する `QldbDriver` インスタンスによって使用される台帳セッションの最大数は、その `maxConcurrentTransactions` パラメータで定義します。(前のセクションで説明した古いドライバーバージョンの場合は、`PooledQldbDriver` の `poolLimit` パラメータで定義します)。

この制限は、特定の AWS SDK で定義されているように、0 より大きく、セッションクライアントが許可するオープン HTTP 接続の最大数以下である必要があります。たとえば Java では、接続の最大数は [ClientConfiguration](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/ClientConfiguration.html#getMaxConnections--) オブジェクトで設定されます。

のデフォルト値`maxConcurrentTransactions`は、 AWS SDK の最大接続設定です。

アプリケーションで `QldbDriver` を設定するときは、スケーリングに関する以下の点を考慮してください。
+ プールには常に、同時に実行する予定のトランザクションの数と同数以上のセッションが必要です。
+ スーパバイザースレッドがワーカースレッドに委任するマルチスレッドモデルでは、そのドライバーにワーカースレッドの数と同数以上のセッションが必要です。それ以外の場合、ピーク負荷時に、スレッドはセッションが使用可能になるまで待機中になります。
+ 台帳ごとの同時アクティブセッションのサービス制限は、「[Amazon QLDB でのクォータと制限](limits.md#limits.fixed)」で定義されています。すべてのクライアントで 1 つの台帳に使用される同時セッションの数は、この制限を超えないように設定してください。

## 例外発生時の再試行
<a name="driver.best-practices.retrying"></a>

QLDB で例外が発生したときに再試行する場合は、以下の推奨事項を考慮してください。

**OccConflictException 発生時の再試行**

*オプティミスティック同時実行制御* (OCC) の競合例外は、トランザクションでアクセスしているデータがトランザクションの開始以降に変更された場合に発生します。トランザクションのコミットを試行する間、QLDB はこの例外をスローします。ドライバーは、`maxRetries` に設定されている回数だけトランザクションを再試行します。

OCC について、また、インデックスを使用して OCC 競合を制限するベストプラクティスについては「[Amazon QLDB 同時実行モデル](concurrency.md)」を参照してください。

**その他の例外発生時に QldbDriver の外部で再試行する**

アプリケーション定義のカスタム例外が実行中にスローされたときにこのドライバーの外部でトランザクションを再試行するには、トランザクションをラップする必要があります。Java の例を挙げると、以下のコードは、[Reslience4J](https://resilience4j.readme.io/) ライブラリを使用して 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;
}
```

**注記**  
QLDB ドライバーの外部で行うトランザクションの再試行には乗数効果があります。例えば、`QldbDriver` が 3 回再試行するように設定されていて、カスタム再試行ロジックも 3 回再試行する場合、同じトランザクションを最大 9 回再試行できます。

**トランザクションをべき等にする**

ベストプラクティスとして、再試行の場合に予期しない結果を避けるために、書き込みトランザクションをべき等にしてください。トランザクションは、複数回実行して毎回同じ結果を生成できる場合、*idempotent* です。

詳細については、「[Amazon QLDB 同時実行モデル](concurrency.md#concurrency.idempotent)」を参照してください。

## パフォーマンスの最適化
<a name="driver.best-practices.performance"></a>

このドライバーを使用してトランザクションを実行するときにパフォーマンスを最適化するには、以下の点を考慮してください。
+ `execute` オペレーションは常に、以下のコマンドを含む 3 つ以上の `SendCommand` API コールを QLDB に対して実行します。

  1. `StartTransaction`

  1. `ExecuteStatement`

     このコマンドは、`execute` ブロックで実行する PartiQL ステートメントごとに呼び出されます。

  1. `CommitTransaction`

  アプリケーションの全体的なワークロードを計算するときに行われる API コールの合計数を考慮してください。
+ 一般的に、シングルスレッドのライターから始め、1 つのトランザクション内で複数のステートメントをバッチ処理することでトランザクションを最適化することをお勧めします。「[Amazon QLDB でのクォータと制限](limits.md#limits.fixed)」で定義されているように、トランザクションサイズ、ドキュメントサイズ、トランザクションごとのドキュメント数のクォータを最大に設定します。
+ 大量のトランザクション負荷に対してバッチ処理では不十分な場合は、ライターを追加してマルチスレッドを試すことができます。ただし、ドキュメントとトランザクションの順序付けに関するアプリケーション要件と、これにより生じる複雑さを慎重に検討する必要があります。

## トランザクションごとに複数のステートメントを実行する
<a name="driver.best-practices.multiple-statements"></a>

[前のセクション](#driver.best-practices.performance)で説明したように、トランザクションごとに複数のステートメントを実行して、アプリケーションのパフォーマンスを最適化できます。次のコード例では、トランザクション内で、テーブルをクエリし、そのテーブルのドキュメントを更新しています。そのために、Lambda 式を `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))
```

------

ドライバーの `execute` オペレーションは、セッションとそのセッション内のトランザクションを暗黙で開始します。Lambda 式で実行する各ステートメントは、トランザクションでラップされます。すべてのステートメントが実行されると、ドライバーはトランザクションを自動的にコミットします。自動的な再試行の制限を超えたためにステートメントが失敗すると、トランザクションは中止されます。

**トランザクションでの例外の伝播**

トランザクションごとに複数のステートメントを実行する場合、通常、トランザクション内で例外をキャッチして続行することはお勧めしません。

たとえば、Java の場合、次のプログラムは `RuntimeException` のインスタンスをキャッチしてエラーをログに記録し、続行します。このコード例は、`UPDATE` ステートメントが失敗してもトランザクションが成功するため、推奨されません。クライアントは、更新が失敗しても成功したと思い込む可能性があります。

**警告**  
このコード例は、使用しないでください。これは、推奨されない悪いパターンの例として提供されています。

```
// 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.");
}
```

その代わりに例外を伝播 (バブルアップ) します。トランザクションのいずれかの部分が失敗した場合は、`execute` オペレーションにトランザクションを中止させて、クライアントが適切に例外を処理できるようにします。