DynamoDBのテーブル設計時に気を付けること

DynamoDBを使いだして、DynamoDBの仕様やNoSQLへの理解不足ゆえに、あーこれ設計ミスったなーとか、設計段階で考慮すべきだった問題が噴出してきたので、知見をまとめてみます。

テーブル設計より先にデータ利用シーンを洗い出す

AWSの公式ドキュメントには、次のように書いてあります。
NoSQL 設計では、RDBMS 設計とは異なる考え方が必要です。RDBMS の場合は、アクセスパターンを考慮せずに正規化されたデータモデルを作成できます。その後、新しい質問とクエリの要件が発生したら、そのデータモデルを拡張することができます。各タイプのデータを独自のテーブルに整理できます。
NoSQL 設計は異なります。
DynamoDB の場合は対照的に、答えが必要な質問が分かるまで、スキーマの設計を開始しないでください。ビジネス上の問題とアプリケーションのユースケースを理解することが不可欠です。
DynamoDB アプリケーションではできるだけ少ないテーブルを維持する必要があります。設計が優れたアプリケーションでは、必要なテーブルは 1 つのみです。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-general-nosql-design.html#bp-general-nosql-design-concepts
RDBの場合は、最初にシステムで必要になるデータ項目をしっかり洗い出し、テーブルを設計してしまえば、後はアプリ側でSQLを使って自在にデータにアクセスできるので利用シーン(データの使い方)を気にする必要はあまりありません。

しかし、NoSQLDBの場合はデータへのアクセスが柔軟ではないので、利用シーン(なんのデータをキーにアクセスできて、なんのデータが必要か)が洗い出してからでないとテーブル設計を開始してはいけません。

非正規化を検討する

DynamoDBはSQLでいうところのJOINができないため、正規化してテーブルを増やしていくと、取得時にN+1問題のような問題がおきパフォーマンスもプログラムの保守性も低下します。

とはいえ、なんでも一つのテーブルに持たせるとI/Oが集中するので、頻繁に更新される属性があれば別テーブルに切り出すことも必要でしょう。

日付と時間は属性を分けて持つ

タイムスタンプ用のカラムを持たせることはRDBでもよくやりますが、DynamoDBでは日付型がないので、UNIX時間を数値で持つか、文字列でyyyy-mm-dd hh:mm:ssのようなフォーマットで持つかになります。

が、その通りやると確かにタイムスタンプは残りますが、運用後に「データを日別や時間帯で抜きたい」となった時に困ることになります。

DynamoDBではクエリを使って範囲検索をする場合、ソートキーを設定する必要がありますが、セカンダリインデックスを後から設定しようと思っても、日付時刻の属性だけだとソートキーに設定できません。

あらかじめ日付と時刻に分けて属性を持っておけば、日付をパーティションキー、時刻をソートキーというようにグローバルセカンダリインデックス(GSI)を設定し、クエリで日別データを抜き出すことができるようになります。

大量レコードの集計処理をしない

DynamoDBにはSQLでいうところの集計クエリが使えないので、集計したい項目をすべて取り出してプログラム側で集計を行う必要があります。

DynamoDBには、1度のクエリで読み出せる結果のサイズは1MBまでという制約があります。

1MB以上読みだしたい場合は、再度クエリを投げる必要があり、全件取得をプログラムで書こうとすると、ループ処理になってしまい直感的でありませんし、オーバーヘッドも増えます。

大量の項目を取得して集計する必要があるようなトランザクションデータの持ち方は避けた方がよいです。
設計時に犯したしくじりは、次回の設計をよりよいものにしてくれると信じて…。
今回の設計で生まれた負債を保守する作業に戻ります…(‘A`)