Python
AWS
DynamoDB
WCU

DynamoDBの基礎まとめ ― 書き込み(WCU)検証

DynamoDBの基礎まとめ ― 料金体系とプロビジョニング
の続きとして、実際のWCUがどう発生するか検証したいと思います。

テーブルを作成

実際のSampleTableテーブルを作って、WCUを検証してみます。
image1.png
ユーザの購入履歴を想定するSampleTableを作成し、useridをPartition Key、updatedatetimeをSort Keyとします。Partition Key + Sort KeyでPrimary Keyを構成します。

サンプルデータを投入
image2.png

書き込み(WCU)

まず書き込みのほうで、DynamoDBはデータを書き込むためのアクションを基本二つ用意しています。
テーブルへのデータの書き込みを参考

アクション 説明
PutItem テーブルに単一項目を書き込みます(Primary Key指定必須)。
BatchWriteItem 同時に複数の項目に書き込むアクション。

PythonでLambda関数

パターン1:新規レコード挿入

それぞれデータサイズ1KB以下と1KB越え2回実施

回数 userid updatedatetime itemid itemstatus
1回目 U00001 2018-04-09 09:01:00 IT006 good
2回目 U00001 2018-04-09 09:02:00 IT007 985バイトの長い文字列
import boto3
import logging

dynamoDB = boto3.resource("dynamodb")
table = dynamoDB.Table("SampleTable")

def lambda_handler(event, context):
    try:
        result = table.put_item(
            Item={
                "userid" : "U00001",
                "updatedatetime" : "2018-04-09 09:01:00",
                "itemid" : "IT006",
                "itemnumber" : 10,
                "itemprice" : 100,
                "itemstatus" : "good"
            }
        )
        print(result)
    except Exception as e:
        logging.error("type : %s", type(e))
        logging.error(e)

挿入結果
image11.png

パターン2:項目値更新

更新対象行のデータサイズ1KB未満で更新後サイズ不変
更新対象行のデータサイズ1KB以上で更新後サイズが1KB未満
更新対象行のデータサイズ1KB未満で更新後サイズが1KB以上
3回実行

回数 userid updatedatetime 更新項目 更新前 更新後
1回目 U00001 2018-04-09 09:01:00 itemnumber 10 11
2回目 U00001 2018-04-09 09:01:00 itemstatus good 985バイトの長い文字列
3回目 U00001 2018-04-09 09:02:00 itemstatus 985バイトの長い文字列 good
import boto3
import logging

dynamoDB = boto3.resource("dynamodb")
table = dynamoDB.Table("SampleTable")

def lambda_handler(event, context):
    try:
        result = table.update_item(
            Key={
                "userid" : "U00001",
                "updatedatetime" : "2018-04-09 09:01:00"
            },
            UpdateExpression="set itemnumber = :n",
            ExpressionAttributeValues={
                ":n" : 11
            }
        )
        print(result)
    except Exception as e:
        logging.error("type : %s", type(e))
        logging.error(e)

更新結果
image21.png

パターン3:複数の項目同時更新

DynamoDBは1行ずつの挿入と更新が基本ですので、SQLのように一括更新ができませんでした。
以下のコードを実行してみました。updateのKey条件にPartition Keyのみ指定しても、思うようにすべてのU00001のitemnumberが11になることになりません。
The provided key element does not match the schemaエラーが出ます。

type : <class 'botocore.exceptions.ClientError'>

[ERROR] 2018-05-02T13:16:47.583Z    10f0238f-4e0b-11e8-b306-09566ca178d3    type : <class 'botocore.exceptions.ClientError'>
[ERROR] 2018-05-02T13:16:47.619Z    10f0238f-4e0b-11e8-b306-09566ca178d3    An error occurred (ValidationException) when calling the UpdateItem operation: The provided key element does not match the schema

import boto3
import logging

dynamoDB = boto3.resource("dynamodb")
table = dynamoDB.Table("SampleTable")

def lambda_handler(event, context):
    try:
        result = table.update_item(
            Key={
                "userid" : "U00001",
            },
            UpdateExpression="set itemnumber = :n",
            ExpressionAttributeValues={
                ":n" : 11
            }
        )
        print(result)
    except Exception as e:
        logging.error("type : %s", type(e))
        logging.error(e)

一括挿入/更新はBatchWriteItemアクションを使いますが、結果的に1行ずつの挿入と更新になります。

回数 userid updatedatetime itemstatus 処理
1回目 U00004 インクリメント good 挿入
2回目 U00004 インクリメント 985バイトの長い文字列 更新
import boto3
import logging

dynamoDB = boto3.resource("dynamodb")
table = dynamoDB.Table("SampleTable")

def lambda_handler(event, context):
    try:
        with table.batch_writer() as batch:
            for i in range(5):
                result = batch.put_item(
                    Item={
                        "userid" : "U00004",
                        "updatedatetime" : "2018-04-09 09:0" + str(i) + ":00",
                        "itemid" : "IT00" + str(i + 1),
                        "itemnumber" : i + 1,
                        "itemprice" : 100 * (i + 1),
                        "itemstatus" : "good"
                    }
                )
                print(i)
                print(result)
    except Exception as e:
        logging.error("type : %s", type(e))
        logging.error(e)

挿入/更新結果
3-1
image31.png

3-2
image32.png

CloudWatchでWCUダッシュボートを作成

消費されたWCUを確認するには、CloudWatch -> Dashboards -> Create Dashboard -> Add metric graphで対象テーブルの
ConsumedWriteCapacityUnits
ProvisionedWriteCapacityUnits
グラフを作成します。

image.png

回数 消費WCU 説明
1-1 1WCU 1KB未満のデータサイズを挿入したため、1KBに切り上げし、1WCU消費
1-2 2WCU 1KB以上のデータサイズを挿入したため、2KBに切り上げし、2WCU消費
2-1 1WCU 1KB未満のデータサイズに対する更新、1KBに切り上げし、1WCU消費
2-2 2WCU 1KB未満のデータサイズに対する更新だが、更新後1KB以上となるため、2KBに切り上げし、2WCU消費
2-3 2WCU 1KB以上のデータサイズに対して更新だが、更新後1KB未満でも、1KB未満のデータサイズを5回挿入するが、1回ずつ1KBに切り上げ、合計5KBの5WCU消費更新量が1KB以上とみなし、2WCU消費
3-1 5WCU 1KB未満のデータサイズを5回挿入するが、1回ずつ1KBに切り上げ、合計5KBの5WCU消費
3-1 10WCU 1KB以上のデータサイズを5回更新するが、1回ずつ2KBに切り上げ、合計10KBの10WCU消費

考察

DynamoDBのWCU計算の特徴から、1レコードのデータサイズを1KB未満(あるいはKBに比例する)に抑えるのがよく、プロビジョニングする際にも挿入・更新レコード数/秒で算出するので良いです。