DynamoDBメモ
感想
テーブル数は少なく
RCUとWCUを管理しやすくなる
Partitionさえ別れていれば負荷が分散される
Schemaless、属性は複数の意味を持ちうる/持たせられる
RDB的な複数のテーブルのデータも1つのテーブルにおさめられる
Tables, Items and Attributes
Items 他のDatabaseにおける Rows Recourds Tuples などに相当
テーブル内で一意になる Identifier ( Primary Key ) を持つ
PKは複合キーでも表現できる (例: Artist Table で、 Artist と Song Title )
PK以外の Attributes については Schemaless なので事前に定義不要
Attributes 他のDatabaseにおける Columns に相当
基本的に Scalar 値 (単一の値)。String Number など
ネストすることもできる (階層の上限は32まで)
Map List など
プライマリキー
テーブル内の各項目を一意にするため (一般的なPK)
単一で、テーブル内の項目を一意にするキー (よく使うのはおそらく id とか)
内部的にはキーの値をHash化して、どのPartitionに格納されるかが決まる
いわゆる複合キー
PKの Attributes は scalar のみ (String Number or Binary )
セカンダリインデックス
PK以外でTableにクエリを行うことができるようになる
Indexは元のテーブルのデータが更新されたときには、自動で更新される
Indexに、元のテーブルからどの Attributes を コピー (射影) するかを設定できる
最低限は、元のテーブルの PK
Quota
Control Plane いわゆる DDL
Data Plane いわゆる DML
C
PutItem
BatchWriteItem
25件まで
R
GetItem
PKで取得
BatchGetItem
100件まで
Query
Index に対しても実行できる
Scan
全件取得
U
UpdateItem
条件つきUpdateが可能
D
DeleteItem
BatchWriteItem
25件まで
Transactions
トランザクションをサポートしている
TransactWriteItems
Put, Update or Delete
All or nothing が保証される
TransactGetItems
: とか # はテーブル名にしないほうが良さそう
Data Type
Scalar Types , Document Types or Set Types のいずれかにカテゴライズされる
Scalar Types
Number String Binary Boolean null
Doument Types
ネスト構造
List や Map はこちらにカテゴライズ
Set Types
Scalar value の Set
中身は、Scalar Type しかサポートされない
Number
精度が重要な場合は文字列にするべき
epoch time を渡すことで timestamp として扱うこともできる
String
PK 以外のStringはサイズ制限はない ( Max item size の 400kB まで)
順番は、UTF-8 String encoding された順
"a" (0x61) は "A" (0x41) より大きい
ISO8601に従って timestamp としてあつかうこともできる
分散保存している関係上、書き込みが成功したからといって即時に全ての保存先に反映されるわけではない
書き込みがおこなわれてから即読込をしたときに、書き込んだ内容が反映されているとは限らないという話
↑で、最新の書き込み内容が取得できることを保証することもできるが、デメリットもそれなりにある
ネットワーク遅延などがあると 500 error
レイテンシが大きくなる傾向
GSIでは使用不可
よりおおくのCapacityを使用
On-demand と Provisiond (Default) の2種類のキャパシティタイプを提供している
LSI は、元テーブルのCapacityを継承する On-demand と Provisiond の切り替えをしたい場合は、24時間に1回のみ切り替えられる
リクエスト毎課金
直近のトラフィックによって、Workloadを自動で調節
以下に当てはまる場合によさそう
Workloadがわからない新しいテーブル
予想できないアプリケーションのトラフィックがある
使用した分だけの料金
なるほどわからん?
Peak Traffic and Scaling Properties
例えば Application の Traffic パターンが 25,000 ~ 50,000 / s の 読み取り一貫性がある読込だった場合
ピーク値が 50,000 なので、自動的に 100,000 / s まで捌けるように RRU を確保する
直近の Trafficのピーク が 100,000 / s だった場合には 200,000 / s まで捌けるように RRU を確保する
初期 Capacity
新規のテーブルの場合 直近のTraffic が 2,000 WCU or 6,000 RCU として算出される
RCUとWCUを自前で管理する
オートスケーリングも使用できる
以下に当てはまる場合に良さそう
アプリケーションのトラフィックを予測できる
トラフィックが一定 もしくは、増減が緩やか
例えば 6 RCU, 6 WCU のテーブルがあったとして
読み取り一貫性がある読込を秒間 24KB まで (4KB * 6 RCU)
結果整合性読込を秒間 48KB まで
書き込みを秒間 6KB まで (1KB * 6 WCU)
RCU・WCUを超える大量の読込・書き込みを行おうとする場合 HTTP 400 ProvisionedThroughputExceededException が返ってくる
オートスケーリング
RCU と WCU の上限と下限を設定できる
目標使用率も設定できる
Reserved Capacity
前払いして安くなる
クエリ
RDB では柔軟なクエリができるが、コストが高くスケールしない
NoSQLでは、ある決まった方法でのクエリは効率的だが、それ以外の方法はコストが高く遅くなる
設計
RDB では、実装の詳細やパフォーマンスを気にせず柔軟に設計できる。クエリ最適化は、基本的にはSchemaの設計にかかわらない (正規化は重要)
DynamoDB では、よく使われる重要なクエリをローコストで素早くできるようにSchemaを設計する。データ構造はユースケースに紐付いた形になる。
NoSQL の設計のキーコンセプト
基本的に、ユースケースが固まって必要なクエリがわかるまでは設計すべきではない。
テーブル数はできるだけ少なくするべき
何故?
設計のアプローチ
システムが提供するべきクエリのパターンを特定する
アプリケーションのアクセスパターンを前もって把握する
DataSize
どのくらいのサイズのデータを保存し、どのくらいのサイズのデータを1度のクエリで取得するか?を把握する
データのパーティショニングを決定するため
Data shape
RDB のように、クエリ時に、データの形を変えるのではなく、必要なデータをそのまま格納する
スピードと、スケーラビリティーのため
Data velocity
ピークのクエリロード
I/Oのキャパシティを最適化するため
クエリの要件を決めた後は
関連するデータをひとつにまとめる
参照のローカリティを高めるため
できるだけテーブル数をすくなくする
例外は、大量の時系列データが関係している場合や、アクセスパターンが大きくことなるデータセット
ソート順を使用する
関連する項目が、キー設計によってソート順が一緒になるようになっていれば、効率的にクエリができる。
クエリを分散させる
パーティション間で、トラフィックが均等に分散するようにキーを設計する
※ホットスポットを避ける
GSIを利用する
DynamoDB supports your access patterns using the throughput that you provisioned as long as the traffic against a given partition does not exceed 3,000 RCUs or 1,000 WCUs.
ん? 1パーティションにくるトラフィックが 3,000 RCU/s or 1,000 WCU/s 超えたら新しくパーティション分割するってことかな?
バーストキャパシティ
RCU/WCUを使い切っていなければ5分まで貯めてくれる
通常のRCUとWCUより優先して貯めているものを使用してくれる
Hot Partition が発生してしまった場合、1つのPartitionのRCU が 3,000 以上 or WCUが 1,000 以上になってしまった場合に、Throttleing が発生してしまう
Hot Partition が発生してしまっても、テーブルに設定している RCU/WCU を超えない限りは、うまく動くためのしくみ
例
テーブルは 400 WCU で Partition が4つの場合
それぞれの Partition に 100 WCU ずつ配分される
1 - 3 は 50WCU しか消費しないが、HotPartitionの4は150WCU 必要
Adptiveキャパシティによって、自動的にPartition4のWCU が増えることで、Throttleされることがない状態にできる
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/images/adaptive-capacity.png
一般的にに LSI より GSI を使うべき
クエリ結果の一貫性が必要な場合をのぞく
頻繁にクエリをしない属性に Index をつけない
Storage料金とI/Oのコストがかかるだけ
必要な属性のみ射影する
Storage料金と、クエリ速度のため
射影されるデータが1kByte以下だったら、1kByteまでは射影する属性を増やしてしまって良い
1kByte -> 1WCU
書き込みのコスト > 読込のコスト
Indexに不要な属性を含めるより、クエリしてしまったほうが安いケースも
頻繁に呼び出されるクエリを最適化する
クエリの結果に含める必要がある属性は、射影する
※ 書き込みとの兼ね合いになる
時々にしか呼び出されないクエリは、しばしば必須のクエリになりうることを頭に入れておく
Item-Collection Sizeを気にすること
Item-Collection Size は、同じ Partition キー を持つテーブル内の全ての項目とそのLSI
上限 10 GB
SparseなIndexを利用する
テーブルの一部のItemが持つ属性をIndexにすると、Indexへの書き込みがすくなくなり、Indexのサイズが小さくなりクエリが高速になる。
ときにはあえて、属性を消すことで、IndexをSparseに保つ
例
注文管理の例 Order
Partition Key CustomerId Sort Key OrderId
配送済みかどうかを isOpen 属性で管理
配送完了したら isOpen 属性を消す
GSI で、CustomerId (partition key) isOpen (sort key) を設定すると、isOpen 属性が存在する(未配送なデータ)のみ GSI に存在する状態になる
Order はたくさんあっても、 isOpen が少量の場合に、効率的にクエリ、Indexing できる
ソート順をあらわせる OrderOpenDate 属性を追加して、配送したら属性を消すという方法にするとソートもできて望ましい
基本的に GSI は Sparse
対象の属性がある項目のみ GSI に保存されるので
一部の項目のみが持つ属性を Index にすることで、Sparse にできる
GSI は 1テーブル最大20 だが、実践上ではそれ以上の属性について Indexingできる
RDBと異なり、Schemaが固定されていないので、1テーブルに様々な情報を詰め込める
項目ごとに同じ属性名でも実際には異なる情報を詰めることもできる
例
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/images/OverloadGSIexample.png
全てのItemで、属性名を統一する
Sort Key に よって中身(意味) が変わる
元テーブルの Sort Key を Partition Key に、attribute を Sort Key にして GSI を作ることで、様々な attribute についてクエリできる状態になる
Partition Key Employee_Name Sort Key Murphy, John で検索
Partition Key Warehouse_01 で、Warehouse_01 で働いている人を全部取得
圧縮する
1項目を複数の項目に分割する
S3に保存する
Application 側でトランザクション頑張る
S3のオブジェクトの識別子を規約にしたがうようにする
ある一定期間でテーブルを作る
期間の終わり際に、次の期間のテーブルを作る
期間完了時には、新しく作ったテーブルに書き込み先を変える
テーブル名に期間を入れると良い
以前の期間のWCUを下げる
RCUも不要なら下げる
Adjacency Lists Design Pattern (隣接リストデザインパターン)
For existing applications, analyze query logs to find out how people are currently using the system and what the key access patterns are.
どのようなパターンでデータにアクセスされているか?を網羅的に把握する
基本的にはTable数は少なくする
例外としては、時系列データや、アクセスパターンが大きく異なるデータ
GSI1
GSI Overloading
GSI2
Applicationのアクセスパターンに対応
HotPartitionが生まれる可能性がある
Sharding ( 0 - N )
code:sharding
ItemsPerRCU = 4kB / AvgItemSize
PartitionMaxReadRate = 3K * ItemsPerRCU
N = MaxRequiredIO / PartitionMaxReadRate
クエリ
複合キーのテーブルについて、AWSコンソール上だと Partition Key のみのクエリができないが、AWS CLI などからは可能 keyConditionExpression で条件のプレースホルダを設定
expressionAttributeValues でプレースホルダに当てはまる値を設定
code:shell
aws dynamodb query \
--table-name <table-name> \
--key-condition-expression "id = :id" \
--expression-attribute-values '{ ":id": { "S": "id01" }}'
code:shell
aws dynamodb query \
--table-name <table-name> \
--key-condition-expression "ArtistName = :name and SongTitle = :title" \
--expression-attribute-values '{ ":name": { "S": "John Doe" }, ":title": { "S": "My Song" }}'
参考資料
ここにある内のいくつかは読んでみたほうがよさそう