echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS】DynamoDBのキーとソートとフィルタについて

きっかけ

先日、とあるサーバーレスアプリを作っていた際、DynamoDBについて下記のツイートをしました。

そうしたら、下記の突っ込みリプを頂きました。

プライマリーキーではなくパーティションキーなのです
このキーの値によってデータが配置されるノードが決まります。同一ノード内ではある程度検索ができる(レンジキーやローカルインデックスではソートや絞り込みができる)わけですねー
ホットキーがタブーなのも特定のノードに処理が偏るからですね

というか確かに、DynamoDBのキーの扱いについて、認識がイマイチあいまいだなあ...と思う部分があったので、この機会にちょっと調べてみました、

主要なキー群(プライマリインデックス)

DynamoDBの主要なキーには、下記の3種類があります。

  • パーティションキー
    • DynamoDBのデータをどのパーティションに配置するかを決定するキー。
    • キーの形式は「ハッシュ型(HASH)」
    • 使用可能な検索は「完全一致」のみ
    • パーティションキーは必須
  • ソートキー
    • パーティションキーが同一の複数データについて、ソートや範囲検索を行うためのキー
    • キーの形式は「レンジ型(RANGE)」
    • 使用可能な検索は「完全一致」「前方一致(BeginWith)」及び「範囲検索」(値の大小など)
    • ソートキーは任意(なくてもよい)
    • ソートキーだけでの検索は不可(必ずパーティションキーとセット)
      • これが結構面倒だったりする
  • プライマリキー

※DynamoDBの「パーティション」の概念、及び「なぜパーティションキーが"完全一致"固定なのか」などについては、下記公式ページが参考になります。

パーティションとデータ分散(AWS公式ページ)】 https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/HowItWorks.Partitions.html

設計するときに注意すること

上記「パーティションキー」と「ソートキー」を踏まえ、DynamoDBテーブルを設計する際は、下記の点に注意する必要があります。

  • パーティションキー」には、テーブル内の情報の主属性を一意に特定できる値を設定する。
  • 「ソートキー」には、テーブル内の同一主属性のデータについて、並び替え、または範囲指定するような値を指定する

※例:会員制Webサイトのアクセスログを記録するテーブルの場合

てか、DynamoDBはパーティションキーの設計が本当に肝になるので、開発時は慎重に検討する必要があります。

その他のキー群(セカンダリインデックス)

プライマリインデックス(主に「パーティションキー」と「ソートキー」)は上記の通りですが、場合によっては下記のようなケースが出てきます。

  • パーティションキー&ソートキー以外の組み合わせでも検索をかけたい
  • ソートキー1個ではソート条件が足りない

※例えば、先述の「会員制Webサイトのアクセスログを記録するテーブル」の場合、下記のケースが出てくると思います。

  • ある会員がアクセスしたサイト内ページ(「購買履歴」「会員情報閲覧」など)で検索をかけたい
  • アクセスしたデバイスでも並び替えを行いたい

そんな「プライマリインデックスだけじゃ足りない」時に必要になるのが「セカンダリインデックス」です。

セカンダリインデックスには下記の2種類があり、どちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。

そしてどちらも「プライマリとは別で作成できる、パーティションキー(またはパーティションキーとソートキーのペア)」です。

では何が違うかというと、以下の点が異なります。

  • グローバルセカンダリインデックス
  • ローカルセカンダリインデックス
    • プライマリとは別で作成できる、パーティションキーとソートキーのペア
      • ソートキーは必須(というか、ソートキーを増やすのが目的)
    • 「プライマリと異なるのはソートキーのみ」という点(=パーティションキーは同じ)
    • ローカルセカンダリインデックスを作成することで、プライマリのソートキーとは別の項目でのソートも可能

具体的な例

例えば下記「GameScores」テーブルがあったとして、この構成だと例えば「各ゲーム別のハイスコアラーの確認(昔のゲームでデモプレイ後に出てくるアレ)」ができません。
パーティションキーは「UserId」、ソートキーは「GameTitle」
f:id:Makky12:20200214202943p:plain

その場合、下記構成のグローバルセカンダリインデックスを作成すれば、それが実現できます。

f:id:Makky12:20200214203012p:plain

また「あるプレーヤーのハイスコアを出したプレイ履歴」を閲覧したい場合は、下記構成のローカルセカンダリインデックスを作成すればOKです。

【参考】
※どちらもAWS公式サイト(上記「GameScores」テーブル画像も、下記サイトのものです)

セカンダリインデックスを作成する際の注意

  • 処理速度の低下
    • セカンダリインデックスを作成すると、プライマリ以外にもセカンダリにも処理を行うため、スループットが低下する(特に書き込み)。
    • RDBで言えば「別途コピーテーブルを作成し、そちらにも各種処理を行う」イメージに近い
    • ストレージ容量も追加で必要になる
  • 個数の制限
    • セカンダリインデックスは、20個までという制限がある。(さすがに20個作成することはなかなかないけど)
    • 大量にセカンダリインデックスが必要な場合は、そもそもテーブル設計を見直す必要がある。(RDBでいう「正規化」)

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~

serverless.connpass.com

なお当日は

  • スピーカー・登壇者側は人生初
  • 大トリ
  • 当日は業務終了後に名古屋から直行予定

と、あわただしい感じですが、皆さま、どうぞよろしくお願いします。
※発表では、下記について触れる予定です

  • AWS
  • Infrastructure as Code(IaC)&Serverless Framework
  • Athena&QuickSight

それではまた。