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

[DynamoDBの基礎まとめ ― 料金体系とプロビジョニング](https://qiita.com/tanj/items/d083b07e728ef1764a18)
の続きとして、実際のWCUがどう発生するか検証したいと思います。

##テーブルを作成
実際のSampleTableテーブルを作って、WCUを検証してみます。
![image1.png](https://qiita-image-store.s3.amazonaws.com/0/191079/2c96bc97-d1bb-31b1-fc53-dc4c694c2b09.png)
ユーザの購入履歴を想定するSampleTableを作成し、useridをPartition Key、updatedatetimeをSort Keyとします。Partition Key + Sort KeyでPrimary Keyを構成します。

サンプルデータを投入
![image2.png](https://qiita-image-store.s3.amazonaws.com/0/191079/0829ad90-45df-9685-1e36-d4a2558aeeac.png)

##書き込み(WCU)

まず書き込みのほうで、DynamoDBはデータを書き込むためのアクションを基本二つ用意しています。
[テーブルへのデータの書き込み](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/SQLtoNoSQL.WriteData.html)を参考

|アクション|説明|
|---|---|
|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バイトの長い文字列|

```python
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](https://qiita-image-store.s3.amazonaws.com/0/191079/dd831e14-9b28-cb47-5a22-e792bd06cc7d.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|

```python
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](https://qiita-image-store.s3.amazonaws.com/0/191079/3eb778f9-e22f-e4b7-8d68-bba3ab9299ce.png)


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

```text
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

```

```python
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バイトの長い文字列|更新|

```python
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](https://qiita-image-store.s3.amazonaws.com/0/191079/37f7a4ab-2b83-a28a-e25b-c2b6d3bb5e6a.png)

3-2
![image32.png](https://qiita-image-store.s3.amazonaws.com/0/191079/1cedbbc7-1f4d-f4e1-fac3-5d78fa5be633.png)


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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/191079/8fea39a0-ef53-e449-a065-f9369bd4a98a.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に比例する）に抑えるのがよく、プロビジョニングする際にも挿入・更新レコード数/秒で算出するので良いです。