「AWS無料相談会」をオンラインで開催中

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をどんどん活用していきたいです。