DynamoDBのベスト・プラクティス

事前に必要な知識

リレーショナルではない

アプリケーション側でデータの整合性を担保する

キャパシティーユニット

4KBを1秒あたり1回の強力な整合性のある読み込み
or
4KBを1秒あたり2回の結果整合性のある読み込み

制限

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Limits.html

パーティション

10GB
3000ReadCapacityUnits
1000WriteCapacityUnits

項目サイズ

400KB

項目数

制限なし

裏側の動作

データ

複数のサーバーに配置する

パーティション

  • 作成時
( readCapacityUnits / 3,000 ) + ( writeCapacityUnits / 1,000 ) = initialPartitions (rounded up)
read capacity units / partitions = read capacity units per partition
write capacity units / partitions = write capacity units per partition

パーティションが増えるほどスループットが落ちる

  • 10GBオーバー時
超えたパーティションのみを2つに分割する

※分割されたパーティションは半分のスループットになる

スループットの変更時

  • 対応できない場合
パーティションを2倍にする

パーティションが増えるほどスループットが落ちる

  • 減らした場合
パーティションの数を減らさない

※最初から大きめにしない

スループットを超えた場合

ProvisionedThroughputExceeded例外が発生する

※再試行 or UpdateTable(スループットを増やす)する

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Programming.Errors.html#Programming.Errors.RetryAndBackoff

クエリ

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/QueryAndScan.html

Query

プライマリキー or セカンダリインデックスを使用

Scan

テーブル or セカンダリインデックスのすべての項目の読み込み
※一度に 1 つのパーティションしか読み込めない

結果整合性のある読み込みがデフォルトで実行
1024KB / 4KB / 2 = 256の読み込みオペレーション

並列スキャン

テーブル or セカンダリインデックスを複数のセグメントに論理的に分割して、
複数のアプリケーションワーカーがセグメントに対して並列スキャン
  • Segments
特定のワーカーがスキャンするセグメント
  • TotalSegments
並列スキャンの対象となるセグメントの合計数

※アプリケーションで使用されるワーカーの数と同じにする

ローカルセカンダリインデックス

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html

テーブルと同じパーティションキーと異なるソートキーで構成されるインデックス
local secondary indexを作成してQuery or Scan
非キー属性に頻繁にアクセスする場合に設定する
書き込みや更新が多数になる場合はKEYS_ONLYを射影することでサイズを最小限にする

※インデックスに射影されていない属性も取り出せる
※テーブルの一部またはすべての属性がコピーされる
※リクエストした属性が射影されていない場合はテーブルから項目全体を読み込む ※テーブル毎に最大5つ
※最大10GB
※ストレージとプロビジョニング済みのスループットを消費する

グローバルセカンダリインデックス

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html

テーブルのプライマリキーとは異なるプライマリキーによって構成されるインデックス
クエリではベーステーブルから属性をフェッチできません
非キー属性にアクセスすることはできません
キー値は一意である必要がありません
テーブルのパーティションキーとソートキーは必ずインデックスに射影されます

テーブルのベストプラクティス

パーティションキー

個別の値が多数含まれ、できるだけランダムかつ均一に値がリクエストされるようにする
2014-07-09.1
...
2014-07-09.200

急激に増大するキャパシティーは控えめに使用する

データアップロード時に書き込みアクティビティを分散する

パーティションキーとレンジキーの組み合わせ毎にアップロードして分散する

パーティションキーAとレンジキーAのアップロード
パーティションキーAとレンジキーBのアップロード
パーティションキーBとレンジキーAのアップロード
パーティションキーBとレンジキーBのアップロード

※できるだけ多くのサーバーに分散したアップロードをする

時系列データへのアクセスパターンを理解する

すべての項目を単一のテーブルに格納せずに月単位または週単位のテーブルにする
古い項目はS3にバックアップして、テーブル毎削除する

人気の高い項目をキャッシュに格納する

まずキャッシュを見て、なければ初めてDynanoDBに問い合わせる

プロビジョニングされたスループットを調整するときにワークロードの均一性を考慮する

バルクロード時に必要なスループットと通常のスループットは合わせる。

※バルクロード時だけ、スループットを上げて、後で下げるとパーティションが2倍に増えて、スループットが悪化する可能性大

大規模環境でのアプリケーションのテスト

データが増えて、パーティションが増え、スループットが悪化したときのことを考えておく
高いスループット設定をしてから、下げると同じ状況を作り出せる

スループットに対するストレージの比率は大規模と同じようにする
※そもそもパーティション当たり10GBを超えないようにする


項目のベストプラクティス

大規模な設定属性の代わりに 1 対多のテーブルを使用する

なんでも1つのテーブルに格納しない

パーティションキーはID等にする

複数テーブルの使用による多様なアクセスパターンのサポート

表示頻度・アクセス頻度によってテーブルを分ける

パーティションキーはID等にする

大量の属性値を圧縮する

長い文字列は圧縮してから格納する

※圧縮しても400KB超える可能性がある場合には最初からS3に格納する

Amazon S3 に大量の属性値を格納する

画像(DynamoDBにはURL)
長い文字列(DynamoDBにはS3のオブジェクトID)

※S3のオブジェクトIDは最大1024バイト
※&等非推奨の文字はS3のオブジェクトIDに使用しない

http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingMetadata.html

大量の属性を複数の項目に分割する

XxxxChunksテーブルの作成
親テーブルにはChunkCount,ChunkVersionを置いておく
XxxxChunksテーブルのIDは${親ID}#${ChunkVersion}#${ChunkSequenceNo}

※BatchGetItem・BatchWriteItemを使用する


クエリとスキャンのベストプラクティス

読み込みアクティビティの急激な増大の回避

Scan(Query)時にLimitパラメータを指定して読み込みオペレーションの数を減らす

重要なトラフィック用と記録用にテーブルを分けて1時間毎に分担する
※重要なものをそうでないものを分ける

ミッションクリティカルなテーブルとシャドウテーブルに同時に書き込んでおく
※重要なリクエストと重要でないリクエストをテーブルを分けて対応する

並列スキャンの利用

並列スキャンで高速化

※あくまでデータサイズが大きく、読み込みスループットが十分にある場合で高速化したい場合です
スループットが多量に消費されるので要注意

並列スキャンが適している場合

テーブルのサイズが20GB以上
プロビジョニングされている読み込みスループットが完全に使用されていない
シーケンシャルScanオペレーションでは遅すぎる

TotalSegments の選択

1セグメント/2GBにしてみる

※アプリケーションのワーカー数とセグメント数は合わせる

  • スループットを消費しないうちにScanリクエストが制限される場合
TotalSegmentsの値を増やす
  • Scan リクエストによって、プロビジョニングされたスループットが必要以上に消費される場合
TotalSegmentsの値を減らします

ローカルセカンダリインデックスのベストプラクティス

インデックスの使用は控えめにする

頻繁にクエリを行わない属性では、local secondary index を作成しない

※使用されていないインデックスは、ストレージおよび I/O コストを増大させる

頻繁に更新されず、多数の属性でクエリが行われるテーブルは複数のインデックスを作成する

※readが多く行われ、書き込みは頻繁には発生しないテーブル

多量の書き込みアクティビティが発生するテーブルにはインデックスを設定しない

インデックスが頻繁に変わるため、高コストになってしまう

※インデックスを設定したいものはデータを別のテーブルにコピーする
※インデックスを設定するなら、KEYS_ONLYでもよい

インデックスのサイズは可能な限り小さくする

インデックスが小さいほど高パフォーマンス
頻繁にリクエストを行う属性だけを射影する

ソートキーが少数のデータにだけ存在する項目にインデックスを設定する

writeがreadより多い場合

  • インデックスのエントリーサイズを1KB以下にする
KEYS_ONLYにする

※追加コストが発生しない

  • ALL
異なるソートキーによってテーブルを並べ替えるようにする場合のみ指定する

項目コレクションの拡張の監視

テーブル and インデックスで同じパーティションキーを持つすべての項目が10GBを超えないか

※10GB超えるとItemCollectionSizeLimitExceededExceptionが発生してしまう

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/LSI.html#LSI.ItemCollections.SizeLimit

グローバルセカンダリインデックス のベストプラクティス

ワークロードが均一になるようにキーを選択する

値の数が多いパーティションキーとソートキーを選択します

グローバルセカンダリインデックス を使用したすばやい検索

小さいグローバルセカンダリインデックスにする

※インデックスに射影するテーブル属性の数が少ないものにする

結果整合性のある読み込みレプリカを作成する

テーブルに書き込んでからそのデータがインデックスに反映されるまでの間に短い伝達遅延がある
スループットの高低で、複数のグローバルセカンダリインデックスを作成する

http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/specifying-conditions.html

参考

docs.aws.amazon.com

http://aws.typepad.com/sajp/2016/12/should-your-dynamodb-table-be-normalized-or-denormalized.html