概要
GetItemとBatchGetItem、TransactGetItemそれぞれの違いをまとめてみました。
実際に使用したコードも記載しているので参考になればと思います。
Write / Delete / Put / Update もそれぞれのリクエストの違いは同様です。
データベースの設定やリクエストクライアントの作成は以下の記事を参照ください。
KotlinでDynamoDBに接続 - その1
KotlinでDynamoDBに接続 - その2
KotlinでDynamoDBに接続 - その3
NoSQLとRDBMS
Transact系のリクエストの前にDynamoDBの特性であるNoSQLとRDBMSの違いから復習します。
RDBMS:ACID(Atomicity、Consistency、Isolation、Durability)
トランザクションにおいて持つべき4つの特性を表しています。
Atomicity:原子性 / 不可分性
トランザクションに含まれる個々の処理が「すべて実行される」か「一つも実行されない」のどちらかの状態になるという性質
Consistency:一貫性 / 整合性
トランザクションの前後でデータの整合性が保たれ、矛盾の無い状態が常時継続される性質
Isolation:独立性 / 隔離性
トランザクション実行中の処理過程が外部から遮断され、他の処理などに対し影響を受けない・与えない性質
Durability:永続性 / 耐久性
トランザクションの結果が記録され、失われることがないという性質
NoSQL:BASE(Basically Available、Soft-State、Eventual Consistency)
ACIDに対し可用性や性能を重視した分散システムの特性です。
Basically Available
基本的にいつでも利用可能であり、一つの処理中に他の処理を行うことができる性質
Soft-State
常時整合性を保っている必要はなく、一時的に一貫性のない状態を許容する性質
Eventual Consistency
処理完了後結果的には整合性が保証される性質
GetItem vs BatchGetItem vs TransactGetItem
GetItem
公式説明
デフォルトでは、GetItem オペレーションは、結果整合性のある読み込みを提供します。
結果整合性のある読み込みがアプリケーションで受け入れられない場合は、ConsistentRead を使用します。
このオペレーションは標準の読み込みよりも時間を要することがありますが、常に最後に更新された値を返します。
BatchGetItem
公式説明
BatchGetItem オペレーションはプライマリキーを使用して複数のテーブルの複数の項目の属性を返します。
1 回のオペレーションで取得できる項目の最大数は 100 です。
また、取得される項目の数は1 MB のサイズ制限によって制限されます。テーブルのプロビジョニングされたスループットを超えたことや内部処理が失敗したことが原因でレスポンスサイズの制限を超えた場合または結果の一部だけが返された場合、DynamoDB は UnprocessedKeys 値を返すので取得する次の項目からオペレーションを再試行できます。
DynamoDB はこの制限を適用するために、ページごとに返される項目の数を自動的に調整します。例えば100 個の項目を取得するようにリクエストしても、個々の項目のサイズが 50 KB の場合返されるのは 20 個の項目だけなので適切な UnprocessedKeys 値を設定して結果の次のページを取得できます。必要に応じてアプリケーションに独自のロジックを組み込んで、結果のページを 1 つのセットにまとめることができます。
TransactGetItem
ACID特性を指向した読み込み処理
公式説明
TransactGetItems は単一のアカウントおよびリージョン内の 1 つ以上のテーブルから複数のアイテムをアトミックに取得する同期操作です。
TransactGetItems 呼び出しには、最大 100 個の TransactGetItem オブジェクトを含めることができます。各オブジェクトにはアカウントおよびリージョンのテーブルから取得する項目を指定する Get 構造体が含まれます。
TransactGetItems への呼び出しでは複数の AWS アカウントまたはリージョンのテーブルからアイテムを取得できません。
またトランザクション内のアイテムの合計サイズは 4 MB を超えることはできません。
アプリ実装
設定関連
// スキーマ作成
private val userSchema: TableSchema<User> =
TableSchema.builder(User::class.java).newItemSupplier { User() }
.addAttribute(String::class.java) { a ->
a.name("GroupName").getter(User::userId).setter { user, s -> user.userId = s }
.tags(StaticAttributeTags.primaryPartitionKey())
}.addAttribute(String::class.java) { a ->
a.name("CognitoIdentityId").getter(User::password)
.setter { user, s -> user.password = s }
.tags(StaticAttributeTags.primarySortKey())
}.build()
// クライアント作成
private var enhancedClient: DynamoDbEnhancedAsyncClient =
DynamoDbEnhancedAsyncClient.builder().dynamoDbClient(
DynamoDbAsyncClient.builder().region(
Region.AP_NORTHEAST_1
).credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(BuildConfig.accessKeyId, BuildConfig.secretAccessKey)
)
).build()
).build()
// テーブルインスタンス作成
private var table1: DynamoDbAsyncTable<User> = enhancedClient.table("LoginAppUser", userSchema)
private var table2: DynamoDbAsyncTable<User> = enhancedClient.table("LoginAppUser", userSchema)
// キー作成
val key1 = Key.builder()
.partitionValue("abc") // パーティションキー指定
.sortValue("123") // ソートキー指定
.build()
val key2 = Key.builder()
.partitionValue("abc") // パーティションキー指定
.sortValue("123") // ソートキー指定
.build()
val key3 = Key.builder()
.partitionValue("abc") // パーティションキー指定
.sortValue("123") // ソートキー指定
.build()
GetItem
fun getItemByRequest(): User? {
// key1に一致するデータを取得するリクエスト
val getItemRequest: GetItemEnhancedRequest =
GetItemEnhancedRequest.builder().consistentRead(true).key(key1).build()
// table1からデータ取得
val user: CompletableFuture<User>? = table1.getItem(getItemRequest)
return user?.get(10L, TimeUnit.SECONDS) // 10秒でタイムアウト
}
BatchGetItem
fun batchGetItemByRequest(): MutableList<User> {
val batchGetItemRequest: BatchGetItemEnhancedRequest =
BatchGetItemEnhancedRequest.builder().readBatches(
ReadBatch.builder(User::class.java)
.mappedTableResource(table1) // table1からデータ取得
.addGetItem(
// key1に一致するデータを取得
GetItemEnhancedRequest.builder().key(key1).build()
).addGetItem(
// key2に一致するデータを取得
GetItemEnhancedRequest.builder().key(key2).build()
)
.mappedTableResource(table2) // table1からデータ取得
.addGetItem(
// key3に一致するデータを取得
GetItemEnhancedRequest.builder().key(key3).build()
).build()
).build()
val batchResults: BatchGetResultPagePublisher? =
enhancedClient.batchGetItem(batchGetItemRequest)
val userList: MutableList<User> = ArrayList()
batchResults?.subscribe { page ->
page.resultsForTable(table1).forEach { user: User -> userList.add(user) }
page.resultsForTable(table2).forEach { user: User -> userList.add(user) }
}?.exceptionally { null }
return userList
}
BatchGetItem
fun transactGetItemByRequest(): MutableList<User> {
val transactGetItemRequest: TransactGetItemsEnhancedRequest =
TransactGetItemsEnhancedRequest.builder()
// table1からkey1に一致するデータを取得
.addGetItem(table1, GetItemEnhancedRequest.builder().key(key1).build())
// table1からkey2に一致するデータを取得
.addGetItem(table1, GetItemEnhancedRequest.builder().key(key2).build())
// table2からkey3に一致するデータを取得
.addGetItem(table2, GetItemEnhancedRequest.builder().key(key3).build())
.build()
val transactResults: CompletableFuture<MutableList<Document>>? =
enhancedClient.transactGetItems(transactGetItemRequest)
val userList: MutableList<User> = ArrayList()
transactResults?.get(10L, TimeUnit.SECONDS)?.forEach { user: Document ->
run {
userList.add(user.getItem(table1))
userList.add(user.getItem(table2))
}
}
return userList
}
まとめ
これまで曖昧な理解のままノーマルなリクエストとBatch系、Transact系を使っていたのでこの機会に再勉強しました。
ACID特性が必要な場合にはTransact系を使い、その他の場合単発でのリクエストはノーマル、まとめて処理を行う場合にはBatch系を使っていきます。
そもそもACID特性が必要な場合にはRDBMSを使うのでは、、、
ACID特性が必要ない場合には可用性や性能を重視した分散システムを使い、必要に応じてTransact系のリクエストを使用できるDynamoDBは素晴らしいですね。