💡

DynamoDBで開発する上で最低限知っておきたいこと

2023/11/04に公開

ここ1年間ずっとDynamoDBを採用して開発している者の覚書になります。
※完全な入門記事ではないです。
最新の仕様は公式を参照してください。

データ取得は1MB単位

scanやqueryでデータを取得する時、条件に合致する全データを取得できる訳ではありません。
データ取得は常に1MBの単位となります。
条件に合致するデータが1MB以上存在する場合はLastEvaluatedKeyが返却され、それを次の取得時に設定して再取得する必要があります。

整合性のある読み込み

DynamoDBは通常、結果整合性ですが、PKでの読み込みに限り強力な整合性のある読み込みができます(ConsistentRead)。GSIでは出来ません。

DynamoDBでのトランザクション

DynamoDBはトランザクションに対応していますが、他RDBなどのようにテーブルロックはしません。なのでトランザクション処理が衝突した場合はロックを待つようなことはなく、エラーとなります(楽観的排他制御)。また、書き込みは100単位でしか対応していません。100レコード(アイテム)以上書き込みがまたがる場合は設計を考え直す必要があります。

条件付きの書き込み

記事の通りですが、レコード上の値を条件に書き込み(put,update)することができます。
attribute_not_existsを使うことで、特定の値の重複を許さない処理なども実装可能です。

読み込みのコストは4kB単位(オンデマンドモード)

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html
オンデマンドのDynamoDBはRRU、WRU(読み込み、書き込みリクエスト単位)で課金されます。
読み込みに関しては以下の3通り。
・4KB以下の項目の強力な整合性のある読み込みリクエストは1RRU
・4KB以下の項目の結果整合性のある読み込みリクエストは0.5RRU
・4KB以下の項目のトランザクション読み込みリクエストは2RRU
4KBを超える場合、4KB単位でRRUが増えていきます。

これら計算は1テーブルから取得するごとに行われます(そもそもRDBのような結合ができないです)。つまり、Aテーブルから取得して、Aテーブルのデータに紐づくBテーブルのデータを取得するようなケースでは、それぞれで4KB単位のRRU計算が行われます(最低1RRU)。そのため、DynamoDBではAテーブル、Bテーブルの内容を1テーブルに詰め込んでRRUを抑えることで総コストを減らす戦略もあります(自分はあまりしたことないですが)。

書き込みは1KB単位(詳細は略)。

batchGetの存在と注意点

データ取得の方法にはqueryやscan以外に、batchGetでキーを複数指定して一挙に取得する方法もあります(指定の最大数は100で取得量が1MBの制限があることには注意)。個別にクエリを投げるより性能面でメリットがあります。

ただし、batchGetは指定したキーごとに4KBのRRU計算が行われます。つまり、10個のキーを指定してbatchGetする場合は最低でも5RRU消費します。
なので、取得データに多少無駄ができてもscanやqueryのRRUコスト<batchGetのRRUコストになるケースがありえます。マスタデータや使用者数が極めて限られたデータ(管理者用のテーブルなど)などがそれに当たるかと思います。

ちなみに、指定したキーに重複があるとエラーになることにも注意してください。

スロットリング

https://repost.aws/ja/knowledge-center/on-demand-table-throttling-dynamodb
テーブルへの読み込み、書き込みが集中し過ぎるとスロットリングが発生してエラーとなることがあります。
これは、プロビジョニングだけでなくオンデマンドでも存在することに注意が必要です。

30 分以内に前回のトラフィックのピークの 2 倍を超えると、スロットリングが発生する可能性

一応、オンデマンドモードは前回(30分単位)のトラフィックのピークが考慮されて「オンデマンド」にピークが変動します。スロットリングが発生して直ちに問題のあるアプリで無ければ、この仕様に甘えて良いのかもしれません。

テーブルとパーティションキー(PK)、また読み込みか書き込みかで許容量が異なります。厳密な制限の数値については上記記事を参照してください。
設計でよく考えないといけないのは同PKに対する書き込み処理です。PKが同一でありSKでレコードを分散させるようなデータ構造をとっていて、それらに対して同時書き込み数が多い場合はスロットリングの発生確率が高まります。このようなケースでは書き込み時のPKをUUIDにしてばらつかせるべきでしょう。
また、作成されるデータをすべてリストとして表示し、全ユーザーが毎日そのページに行き着くようなケースも注意すべきです。なるべく「すべて表示」することはせずlimitで件数を絞り、ユーザーのアクションに応じて量を調整する仕様にした方が良いでしょう。

スロットリングが実運用で頻発するケースではプロビジョニングモードを検討するべきだとは思います。

dynamo-easyというライブラリを紹介

https://github.com/shiftcode/dynamo-easy
最後に私が使用しているライブラリについて紹介です。※typescript製です。
以下の例の通り、直感的かつ簡潔に読み込み・書き込みができて便利です。

sampleStore
  .scan()
  .whereAttribute('hogehoge').equals('fuga')
  .exec()

上記コードのexec()だと先述した1MB単位の制限を受けますが、これはexecFecthAll()とするだけで解決されます(返却されたLastEvaluatedKeyをライブラリが再帰的にリクエストしてくれます)。

唯一の懸念点は最終メンテが2020年であることです(2023年11月現在)。

Discussion