DynamoDBのput_item()やupdate_item()でConditionalCheckFailedExceptionが発生したとき、キャパシティユニットが消費されるか確認してみた

DynamoDBテーブルで更新条件を満たさない場合のキャパシティユニット消費について確認してみました。
2023.09.28

DynamoDBテーブルでデータの追加や更新をするとき、条件を指定することがよくあります。

  • データがすでに存在するなら、更新しない
  • 新しいデータの場合だけ、更新したい
  • など

この条件を満たさない場合は、ConditionalCheckFailedExceptionのエラーが発生します。 そこで、ふと気になったのです。条件を満たさないとき(エラー発生したとき)、キャパシティユニットは消費されるのか?と。

というわけで、試してみました。

おすすめの方

  • 掲題について知りたい方
  • DynamoDBテーブルの条件指定操作の方法を知りたい方

適当なDynamoDBテーブルを用意する

適当なDynamoDBテーブルを用意します。インデックスも存在しません。

DynamoDBテーブルを準備する

実験用のスクリプトを作成し、実行する

それぞれ、作成・更新・削除を「条件指定なし」「条件指定あり(エラー発生する)」で試します。

app.py

import boto3
from botocore.exceptions import ClientError

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("boto3-test")


def main():
    put_item1()
    put_item2()

    update_item1()
    update_item2()

    delete_item1()
    delete_item2()


def put_item1():
    resp = table.put_item(
        Item={"todoId": "t0001", "name": "りんご飴を買う"}, ReturnConsumedCapacity="TOTAL"
    )
    print(resp["ConsumedCapacity"])


def put_item2():
    try:
        table.put_item(
            Item={"todoId": "t0001", "name": "わたがしを買う"},
            ReturnConsumedCapacity="TOTAL",
            ConditionExpression="attribute_not_exists(todoId)",
        )
        print(resp["ConsumedCapacity"])
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            print(e)
            # not raise
        else:
            raise


def update_item1():
    resp = table.update_item(
        Key={"todoId": "t0001"},
        UpdateExpression="set #name = :name",
        ExpressionAttributeNames={
            "#name": "name",
        },
        ExpressionAttributeValues={
            ":name": "たこやきを買う",
        },
        ReturnConsumedCapacity="TOTAL",
    )
    print(resp["ConsumedCapacity"])


def update_item2():
    try:
        resp = table.update_item(
            Key={"todoId": "t0001"},
            UpdateExpression="set #name = :name",
            ConditionExpression="attribute_not_exists(todoId)",
            ExpressionAttributeNames={
                "#name": "name",
            },
            ExpressionAttributeValues={
                ":name": "かき氷を買う",
            },
            ReturnConsumedCapacity="TOTAL",
        )
        print(resp["ConsumedCapacity"])
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            print(e)
            # not raise
        else:
            raise


def delete_item1():
    resp = table.delete_item(
        Key={"todoId": "t0001"},
        ReturnConsumedCapacity="TOTAL",
    )
    print(resp["ConsumedCapacity"])


def delete_item2():
    try:
        resp = table.delete_item(
            Key={"todoId": "t0001"},
            ConditionExpression="attribute_exists(todoId)",
            ReturnConsumedCapacity="TOTAL",
        )
        print(resp["ConsumedCapacity"])
    except ClientError as e:
        if e.response["Error"]["Code"] == "ConditionalCheckFailedException":
            print(e)
            # not raise
        else:
            raise


if __name__ == "__main__":
    main()

スクリプトを実行する

下記でスクリプトを実行します。少なくとも3つのキャパシティユニットが消費されました。

$ python app.py

{'TableName': 'boto3-test', 'CapacityUnits': 1.0}
An error occurred (ConditionalCheckFailedException) when calling the PutItem operation: The conditional request failed
{'TableName': 'boto3-test', 'CapacityUnits': 1.0}
An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: The conditional request failed
{'TableName': 'boto3-test', 'CapacityUnits': 1.0}
An error occurred (ConditionalCheckFailedException) when calling the DeleteItem operation: The conditional request failed

エラー発生の場合は、Responseを受け取れないので、消費したキャパシティユニットが不明です。 そのため、CloudWatchメトリクスを確認します。

CloudWatchメトリクスで消費キャパシティユニットを確認する

CloudWatchメトリクスで次のメトリクスを確認します。

  • ConsumedWriteCapacityUnits

消費されたキャパシティユニット数は、6になっている

キャパシティユニットの消費合計数は、6でした。 このうち、3つのキャパシティユニットは、条件指定が無い場合の操作です。 つまり、条件指定でエラー発生した場合でも、キャパシティユニットは消費されています。

さいごに

DynamoDBテーブルのアイテムに対する操作で条件を指定し、エラー発生した場合のキャパシティユニット消費数を確認してみました。 「条件が不一致ならキャパシティユニットが表示されない!」と思っていたので、新たな発見でした。 どなたかの参考になれば幸いです。

参考