DynamoDBのトランザクション機能を検証してみた
エンジニアの内山です。
今回は DynamoDB についてです。
トランザクション機能の検証
New – Amazon DynamoDB Transactions
https://aws.amazon.com/jp/blogs/aws/new-amazon-dynamodb-transactions/
ついにDynamoDBがトランザクション機能がつきました。
この機能を使う場合は、以下のAPIを利用することになります。
- TransactGetItems (読み取り)
- TransactWriteItems (書き込み)
今回は TransactWriteItems について検証してみました。
さっそくですが、以下のコードでロールバックされるか確認してみました。
func rollbackTest(svc *dynamodb.DynamoDB, tableName string) {
_, err := svc.TransactWriteItems(&dynamodb.TransactWriteItemsInput{
TransactItems: []*dynamodb.TransactWriteItem{
// 更新できる
{
Put: &dynamodb.Put{
TableName: aws.String(tableName),
Item: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String("Item-1"),
},
},
},
},
// 更新エラーとなる
{
Put: &dynamodb.Put{
TableName: aws.String(tableName),
Item: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String("Item-2"),
},
},
ConditionExpression: aws.String("ForceError = :true"), // この条件で更新処理を失敗させる
ExpressionAttributeValues: map[string]*dynamodb.AttributeValue{
":true": {BOOL: aws.Bool(true)},
},
},
},
},
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
log.Println("Error:", awsErr.Code(), awsErr.Message())
}
}
}
以下のように、トランザクションのエラーが出ます。
Error: TransactionCanceledException Transaction cancelled,
please refer cancellation reasons for specific reasons [None, ConditionalCheckFailed]
1個目のアイテムが None
で問題なく通ってますが、
2個目のアイテムが ConditionalCheckFailed
でエラーが起こっているので、
トランザクションがキャンセルされたことになっています。
期待通りですね。
トランザクションが適用される範囲について
トランザクションが適用される範囲は以下のとおりです。
- 同じパーティションキー でも 異なるパーティションキー でもOK
- 同じテーブル でも 異なるテーブル でもOK
- 同じリージョンのみ
- 同じアカウントのみ
- DynamoDB テーブルのみ
制限について
以下のような制限があるので、注意が必要です。
同じアイテムを同時に更新できない
以下のコードで、同じアイテム(Item-1)を同時に更新してみます。
func dupTest(svc *dynamodb.DynamoDB, tableName string) {
dupID := "Item-1"
_, err := svc.TransactWriteItems(&dynamodb.TransactWriteItemsInput{
TransactItems: []*dynamodb.TransactWriteItem{
{
Put: &dynamodb.Put{
TableName: aws.String(tableName),
Item: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(dupID),
},
},
},
},
{
Put: &dynamodb.Put{
TableName: aws.String(tableName),
Item: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(dupID),
},
},
},
},
},
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
log.Println("Error:", awsErr.Code(), awsErr.Message())
}
}
}
以下のようなエラーが発生します。
Error: ValidationException Transaction request cannot include multiple operations on one item
同時に更新できるアイテム数は 10個まで
以下のコードで、11個のアイテムを更新する処理を試してみます。
func lengthTest(svc *dynamodb.DynamoDB, tableName string) {
n := 11
var items []*dynamodb.TransactWriteItem
for i := 0; i < n; i++ {
items = append(items, &dynamodb.TransactWriteItem{
Put: &dynamodb.Put{
TableName: aws.String(tableName),
Item: map[string]*dynamodb.AttributeValue{
"id": {
S: aws.String(fmt.Sprintf("Item-%d", i+1)),
},
},
},
})
}
_, err := svc.TransactWriteItems(&dynamodb.TransactWriteItemsInput{
TransactItems: items,
})
if err != nil {
if awsErr, ok := err.(awserr.Error); ok {
log.Println("Error:", awsErr.Code(), awsErr.Message())
}
}
}
以下のようなエラーが発生します。
Error: ValidationException 1 validation error detected: Value '[com.amazonaws.dynamodb.v20120810.TransactWriteItem@73f1877d, com.amazonaws.dynamodb.v20120810.TransactWriteItem@73e36ffc, com.amazonaws.dynamodb.v20120810.TransactWriteItem@73d5587b, com.amazonaws.dynamodb.v20120810.TransactWriteItem@73c740fa, com.amazonaws.dynamodb.v20120810.TransactWriteItem@7429e581, com.amazonaws.dynamodb.v20120810.TransactWriteItem@741bce00, com.amazonaws.dynamodb.v20120810.TransactWriteItem@740db67f, com.amazonaws.dynamodb.v20120810.TransactWriteItem@73ff9efe, com.amazonaws.dynamodb.v20120810.TransactWriteItem@7380cb75, com.amazonaws.dynamodb.v20120810.TransactWriteItem@6c0b735b, com.amazonaws.dynamodb.v20120810.TransactWriteItem@6bfd5bda]' at 'transactItems' failed to satisfy constraint: Member must have length less than or equal to 10
この制限は意識していないと違反してしまいそうなので、注意が必要ですね。
まとめ
実装されたDynamoDBのトランザクション機能について、実際に動かしながら検証してみました。
今までは、アトミック性を保ちたい場合1レコードに収まるようにデータモデリングする必要がありました。
これがなかなか大変だったのですが、トランザクション機能が実装されたことにより、少しは楽になるのではないかなと思っています。
これからもDynamoDBをどんどん活用していきたいです。