きっかけ
先日、とあるサーバーレスアプリを作っていた際、DynamoDBについて下記のツイートをしました。
ここに来て、DynamoDBの構成を変えなきゃいけない可能性が。てかプライマリキー、完全一致検索しかできないの?
— Masaki Suzuki@フリーランスクラウドエンジニア (@makky12) 2020年2月4日
そうしたら、下記の突っ込みリプを頂きました。
プライマリーキーではなくパーティションキーなのです
このキーの値によってデータが配置されるノードが決まります。同一ノード内ではある程度検索ができる(レンジキーやローカルインデックスではソートや絞り込みができる)わけですねー
ホットキーがタブーなのも特定のノードに処理が偏るからですね
というか確かに、DynamoDBのキーの扱いについて、認識がイマイチあいまいだなあ...と思う部分があったので、この機会にちょっと調べてみました、
主要なキー群(プライマリインデックス)
DynamoDBの主要なキーには、下記の3種類があります。
- パーティションキー
- ソートキー
- プライマリキー
※DynamoDBの「パーティション」の概念、及び「なぜパーティションキーが"完全一致"固定なのか」などについては、下記公式ページが参考になります。
【パーティションとデータ分散(AWS公式ページ)】 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html
設計するときに注意すること
上記「パーティションキー」と「ソートキー」を踏まえ、DynamoDBテーブルを設計する際は、下記の点に注意する必要があります。
- 「パーティションキー」には、テーブル内の情報の主属性を一意に特定できる値を設定する。
- 「ソートキー」には、テーブル内の同一主属性のデータについて、並び替え、または範囲指定するような値を指定する
※例:会員制Webサイトのアクセスログを記録するテーブルの場合
- パーティションキー:会員ID
- ソートキー:アクセス日時
てか、DynamoDBはパーティションキーの設計が本当に肝になるので、開発時は慎重に検討する必要があります。
その他のキー群(セカンダリインデックス)
プライマリインデックス(主に「パーティションキー」と「ソートキー」)は上記の通りですが、場合によっては下記のようなケースが出てきます。
- パーティションキー&ソートキー以外の組み合わせでも検索をかけたい
- ソートキー1個ではソート条件が足りない
※例えば、先述の「会員制Webサイトのアクセスログを記録するテーブル」の場合、下記のケースが出てくると思います。
- ある会員がアクセスしたサイト内ページ(「購買履歴」「会員情報閲覧」など)で検索をかけたい
- アクセスしたデバイスでも並び替えを行いたい
そんな「プライマリインデックスだけじゃ足りない」時に必要になるのが「セカンダリインデックス」です。
セカンダリインデックスには下記の2種類があり、どちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。
そしてどちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。
では何が違うかというと、以下の点が異なります。
具体的な例
例えば下記「GameScores」テーブルがあったとして、この構成だと例えば「各ゲーム別のハイスコアラーの確認(昔のゲームでデモプレイ後に出てくるアレ)」ができません。
※ パーティションキーは「UserId」、ソートキーは「GameTitle」
その場合、下記構成のグローバルセカンダリインデックスを作成すれば、それが実現できます。
- パーティションキー:GameTitle
- ソートキー:TopScore
また「あるプレーヤーのハイスコアを出したプレイ履歴」を閲覧したい場合は、下記構成のローカルセカンダリインデックスを作成すればOKです。
- パーティションキー:UserId(プライマリと同じ)
- ソートキー:TopScoreDateTime
【参考】
※どちらもAWS公式サイト(上記「GameScores」テーブル画像も、下記サイトのものです)
セカンダリインデックスを作成する際の注意
- 処理速度の低下
- 個数の制限
query()メソッドとsort()メソッド
DynamoDBで複数データの取得によく使われるのが、以下の2つのメソッドです。
- query() メソッド
- scan() メソッド
どちらも「DynamoDBからデータを取り出す」点は同じですが、処理内容に下記の違いがあります。
- query():「KeyConditionExpression」の条件でデータをフィルタリング→条件に合致したデータを取り出す→返却
- scan():全データを取り出す→「FilterExpression」の条件でデータをフィルタリング→条件に合致したデータを返却
つまり、scan()はフィルタリングの有無に関わらずまず全データを取り出すため、下記のことが言えます。(もちろんデータ量次第ですが)
- query()メソッドより、処理に時間がかかる
- query()メソッドより、課金額が多くなる
- DynamoDBの読み込み時の課金対象は「読み込まれたデータ(上で言う「取り出されたデータ」)」なので
なので、極力下記の方針にした方が良いです。
※公式サイトや各種ブログにも書かれている通りです。
- DynamoDBからのデータの取り出しは、極力query()メソッドを使う
- 決め打ちで1データのみ取得する場合、getItem()メソッドを使う
- scan()メソッドは、全データの取得が必須な場合のみ使う
- インデックス構成では賄いきれない
- 皇族処理の関係上、全データの取得が必須な場合... など
まとめ
- プライマリキー(パーティションキーとソートキー)の構成を、よく検討する
- DynamoDBは、本当にこれが重要
- 場合によってはセカンダリキーの使用も検討する
- うまく使えば、データの扱いが便利になる。(ただし多用は避ける)
- セカンダリキーが多量に必要な場合、まずテーブル設計を見直す
- データ取得は極力query()やgetItem()メソッドで行う。scan()を多用しない。
と言ったとこでしょうか。
てか、これ書きながら自分でも「業務で使ってるあのテーブル、設計ガバガバじゃねえか!」とか思い返してたりするんですけどね...
というか、(DynamoDBに限らず)時間をおいて見返すと「設計がむちゃくちゃ」とか「1から設計し直したい...」と思うものが、本当に多いです。
まだまだスキルアップしないとですね。
告知
私事ですが、下記「Serverless Meetup Tokyo #16」イベントにて、スピーカーをさせて頂くことになりました。
※開催日時:2020/2/27(木) 19:00~
なお当日は
- スピーカー・登壇者側は人生初
- 大トリ
- 当日は業務終了後に名古屋から直行予定
と、あわただしい感じですが、皆さま、どうぞよろしくお願いします。
※発表では、下記について触れる予定です
- AWS
- Infrastructure as Code(IaC)&Serverless Framework
- Athena&QuickSight
それではまた。