Go 用 Amazon QLDB ドライバー — クックブックリファレンス - Amazon Quantum Ledger Database (Amazon QLDB)

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

Go 用 Amazon QLDB ドライバー — クックブックリファレンス

このリファレンスガイドでは、Go 用の Amazon QLDB ドライバーの一般的なユースケースについて説明します。Go コード例を示して、ドライバーを使用した基本的な作成、読み取り、更新、削除 (CRUD) オペレーションの実行方法を解説します。Amazon Ion データを処理するためのコード例も含まれています。さらに、このガイドでは、トランザクションをべき等にし、一意性の制約を実装するためのベストプラクティスを取り上げています。

ドライバーのインポート

次のコード例では、ドライバーとその他の必要な AWS パッケージをインポートします。

import ( "github.com/amzn/ion-go/ion" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" )
注記

この例では、Amazon Ion パッケージ (amzn/ion-go/ion) もインポートします。このリファレンスでいくつかのデータオペレーションを実行するときに Ion データを処理するには、このパッケージが必要です。詳細については、「Amazon Ion の操作」を参照してください。

ドライバーのインスタンス化

次のコード例では、指定した台帳名に接続するドライバーのインスタンスを、指定した AWS リージョンに作成します。

awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) driver, err := qldbdriver.New( "vehicle-registration", qldbSession, func(options *qldbdriver.DriverOptions) { options.LoggerVerbosity = qldbdriver.LogInfo }) if err != nil { panic(err) }

CRUD オペレーション

QLDB は作成、読み取り、更新、および削除 (CRUD) オペレーションをトランザクションの一部として実行します。

警告

ベストプラクティスとして、書き込みトランザクションを厳密にべき等にしてください。

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

再試行の場合に予期しない結果を避けるために、書き込みトランザクションをべき等にすることをお勧めします。トランザクションは、複数回実行して毎回同じ結果を生成できる場合、idempotent です。

例えば、Person という名前のテーブルにドキュメントを挿入するトランザクションについて考えてみます。トランザクションは、まずドキュメントがテーブル内に既に存在するかどうかをチェックする必要があります。このチェックを行わないと、テーブルに重複するドキュメントができる可能性があります。

QLDB がサーバー側でトランザクションを正常にコミットできるが、レスポンスを待機中にクライアントはタイムアウトするとします。トランザクションがべき等でない場合、再試行時に同じドキュメントが複数回挿入される可能性があります。

インデックスを使用してテーブル全体のスキャンを回避する

インデックス付きフィールドまたはドキュメント ID で等価演算子を使用する WHERE 述語句でステートメントを実行する (例: WHERE indexedField = 123 または WHERE indexedField IN (456, 789)) こともお勧めします。このインデックス付きのルックアップがない場合、QLDB はテーブルスキャンを実行する必要があり、トランザクションタイムアウトやオプティミスティック同時実行制御 (OCC) 競合が発生する可能性があります。

OCC の詳細については、「Amazon QLDB 同時実行モデル」を参照してください。

暗黙的に作成されたトランザクション

QLDBDriver.Execute 関数は、トランザクションのインスタンス (これを使用してステートメントを実行可能) を受け取る Lambda 関数を受け入れます。Transaction のインスタンスは、暗黙的に作成されたトランザクションをラップします。

Transaction.Execute 関数を使用して、Lambda 関数内でステートメントを実行できます。ドライバーは、Lambda 関数が戻ったときに暗黙的にトランザクションをコミットします。

次のセクションでは、基本的な CRUD オペレーションの実行、カスタム再試行ロジックの指定、一意性制約の実装方法について説明します。

テーブルの作成

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") })

インデックスの作成

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE INDEX ON Person(GovId)") })

ドキュメントの読み取り

var decodedResult map[string]interface{} // Assumes that Person table has documents as follows: // { "GovId": "TOYENC486FH", "FirstName": "Brent" } _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'") if err != nil { return nil, err } for result.Next(txn) { ionBinary := result.GetCurrentData() err = ion.Unmarshal(ionBinary, &decodedResult) if err != nil { return nil, err } fmt.Println(decodedResult) // prints map[GovId: TOYENC486FH FirstName:Brent] } if result.Err() != nil { return nil, result.Err() } return nil, nil }) if err != nil { panic(err) }

クエリパラメータの使用

次のコード例では、ネイティブ型のクエリパラメータを使用します。

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") }) if err != nil { panic(err) }

次のコード例では、複数のクエリパラメータを使用します。

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", "TOYENC486FH", "Brent") }) if err != nil { panic(err) }

次のコード例では、クエリパラメータのリストを使用します。

govIDs := []string{}{"TOYENC486FH", "ROEE1", "YH844"} result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", govIDs...) }) if err != nil { panic(err) }
注記

インデックス付きルックアップなしでクエリを実行すると、完全なテーブルスキャンが呼び出されます。この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、クエリのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

ドキュメントの挿入

次のコード例では、ネイティブデータ型を挿入します。

_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if a document with a GovId of TOYENC486FH exists // This is critical to make this transaction idempotent result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert } else { person := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } _, err = txn.Execute("INSERT INTO Person ?", person) if err != nil { return nil, err } } return nil, nil })

このトランザクションは、ドキュメントを Person テーブルに挿入します。挿入する前に、まずドキュメントがテーブル内に既に存在しているかどうかをチェックします。このチェックにより、トランザクションは本質的にべき等になります。このトランザクションを複数回実行しても、意図しない副作用は発生しません。

注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

1 つのステートメントで複数のドキュメントの挿入

1 つの INSERT ステートメントを使用して複数のドキュメントを挿入するために、次のように型 list のパラメータをステートメントに渡すことができます。

// people is a list txn.Execute("INSERT INTO People ?", people)

リストを渡すときには、変数プレースホルダー (?) を二重山括弧 (<<...>>) で囲まないでください。マニュアルの PartiQL ステートメントでは、二重山括弧はバッグと呼ばれる順序付けされていないコレクションを表します。

ドキュメントの更新

次のコード例では、ネイティブデータ型を使用します。

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", "John", "TOYENC486FH") })
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

ドキュメントの削除

次のコード例では、ネイティブデータ型を使用します。

result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("DELETE FROM Person WHERE GovId = ?", "TOYENC486FH") })
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

トランザクションで複数のステートメントの実行

// 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 }

再試行ロジック

ドライバーの Execute 関数には、再試行可能な例外 (タイムアウトや OCC 競合など) が発生した場合にトランザクションを再試行する組み込みの再試行メカニズムがあります。再試行の最大数とバックオフ戦略を設定できます。

デフォルトの再試行制限は 4 であり、デフォルトのバックオフ戦略は 10 ミリ秒のベースを使用する ExponentialBackoffStrategy です。再試行ポリシーは、RetryPolicy のインスタンスを使用して、ドライバーインスタンスごとおよびトランザクションごとに設定できます。

次のコード例では、ドライバーのインスタンスのカスタム再試行制限とカスタムバックオフ戦略を使用して再試行ロジックを指定します。

import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy = qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} driver, err = qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } }

次のコード例では、特定の無名関数のカスタム再試行制限とカスタムバックオフ戦略を使用して再試行ロジックを指定します。この SetRetryPolicy 関数は、ドライバーインスタンスに設定された再試行ポリシーを上書きします。

import ( "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("us-east-1"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy1 := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy1 }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy2 := qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} // Overrides the retry policy set by the driver instance driver.SetRetryPolicy(retryPolicy2) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") }) }

一意性制約の実装

QLDB は現在、一意のインデックスをサポートしていません。しかし、この動作をアプリケーションに実装するのは簡単です。

Person テーブル内の GovId フィールドに対して一意性制約を実装するとします。これを行うには、以下を実行するトランザクションを記述します。

  1. テーブルに指定された GovId を持つ既存のドキュメントがないことをアサートします。

  2. アサーションに合格した場合は、ドキュメントを挿入します。

競合するトランザクションが同時にアサーションに合格すると、一方のトランザクションだけが正常にコミットされます。もう一方のトランザクションは OCC 競合例外で失敗します。

次のコード例は、この一意性制約ロジックを実装する方法を示しています。

govID := "TOYENC486FH" document := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if doc with GovId = govID exists result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", govID) if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert return nil, nil } return txn.Execute("INSERT INTO Person ?", document) }) if err != nil { panic(err) }
注記

この例では、GovId フィールドでインデックスを使用して、パフォーマンスを最適化することをお勧めします。GovId でインデックスをオンにしないと、ステートメントのレイテンシーが大きくなり、OCC 競合例外やトランザクションタイムアウトが発生する可能性があります。

Amazon Ion の操作

以下のセクションでは、Amazon Ion モジュールを使用して Ion データを処理する方法について説明します。

Ion モジュールのインポート

import "github.com/amzn/ion-go/ion"

Ion 型の作成

Go 用 Ion ライブラリでは現在、ドキュメントオブジェクトモデル (DOM) がサポートされていないため、Ion データ型を作成することはできません。しかし、QLDB の操作時には、Go ネイティブ型と Ion バイナリの間で、マーシャルとアンマーシャルを行えます。

Ion バイナリの取得

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalBinary(aDict) if err != nil { panic(err) } fmt.Println(ionBytes) // prints [224 1 0 234 238 151 129 131 222 147 135 190 144 133 71 111 118 73 100 137 70 105 114 115 116 78 97 109 101 222 148 138 139 84 79 89 69 78 67 52 56 54 70 72 139 133 66 114 101 110 116]

Ion テキストの取得

aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalText(aDict) if err != nil { panic(err) } fmt.Println(string(ionBytes)) // prints {FirstName:"Brent",GovId:"TOYENC486FH"}

Ion の詳細については、GitHub にある Amazon Ion のドキュメントを参照してください。QLDB で Ion を操作するコード例については、「Amazon QLDB での Amazon Ion のデータ型の操作」を参照してください。