Try T.M Engineer Blog

多摩市で生息するエンジニアが「アウトプットする事は大事だ」と思って初めたブログ

DynamoDBについて調べてみたことを書いてみた その2

前回書いた記事「DynamoDBについて調べてみたことを書いてみた その1」の続きの話です。

kodak.hatenablog.com

今回は、前回書けなかったLSIとGSIについて書こうと思ったのですが、その前に前回の記事の補足を色々と書いていたら意外と濃い内容になってしまったので、今回は補足だけに留めておこうかと思います。

前回のおさらい

前回、HashとRangeという考え方の話をしました。

Hashは、Hash値ごとにデータを分散して格納しているので、一意であることが望ましいカラム
Rangeは、Hashの抽出範囲を決めるため(検索用)のカラム

でした。
じゃあ、HashとRange以外の値はというと・・・?「重要ではない」のです。「重要ではない」この意味は、テーブルを作成するコマンドを見るとわかります。

# dynamodbでテーブルを作成するコマンド
$aws dynamodb create-table \
    --table-name Music \
    --attribute-definitions \
        AttributeName=Artist,AttributeType=S \
        AttributeName=SongTitle,AttributeType=S \
    --key-schema \
        AttributeName=Artist,KeyType=HASH \
        AttributeName=SongTitle,KeyType=RANGE \
    --provisioned-throughput \
        ReadCapacityUnits=1,WriteCapacityUnits=1

create-tableを見てみると、書かれている内容は以下の通り。

・ --table-name : テーブル名を指定します。
・ --key-schema : テーブルのプライマリキーを構成する属性を指定します。プライマリーキーはHash、HashとRangeの組み合わせの2つから作ります。
・ --attribute-definitions : テーブルとインデックスのキースキーマを記述する属性のリストを定義します。多くは、--key-schemaと同じものが記載されるはずです。
・ --provisioned-throughput : 指定したテーブルのスループットを指定します。(こちらの説明はまた今度)

RDBであれば、テーブルを作る時に格納するカラム名を事前にすべて定義する必要がありました。
しかし、DynamoDBは定義しません。定義しないということは、テーブルから見た時にHashとRange以外にどのような値を持つデータが格納されているのかわからないという事になります。
つまりテーブルにとってHashとRange以外は「重要ではない」という事になります。

このあたりのデータに対する扱い方はRDBとかなり変わってくるので興味深いです。 (ちなみに、DynamoDBはRDBでいうカラムをAttributeと呼ぶのですが、この場では言い辛いので引き続きカラムと呼ぶことにします)

Range Key = Sort Key?

Rangeで指定されたカラムは、Range Keyとなります。Hashで指定されたカラムは、Hash Keyです。このRange Keyは、なんと自動でソートしてくれるのです(デフォルト昇順)
試しに以下のようなデータをテーブルに格納してみると、"A"->"Z"->"a"->"z"、"ひらがな"->"漢字"という順で並び替えてくれました。

Artist(Hash) SongTitle(Range)
浜崎あゆみ A
浜崎あゆみ SEASONS
浜崎あゆみ a
浜崎あゆみ evolution
GLAY ここではない、どこかへ
GLAY とまどい
GLAY 生きてく強さ

このRange Keyが自動でソートしてくれる機能は、かなり重要です。というのも、上で書いたとおりDynamoDBはHashとRange以外は、テーブルから見て何が格納されているかわかりません。
ということは、RDBで言うorder byが使えません。order byに代わるのは、このRange Keyのみです。

ソートをプログラムで書くのは、以外と面倒だったりするのでRange Keyを上手くつかわないといけません。

問題

ここで問題です。
以下のようなテーブルがあるとき、何をHashに何をRangeに選びますか? ちなみに、更新日時を指定してデータを取得したいという要件があるとします。

タイトル 更新日 更新時間 ラベル コメント
○○を追加 2019-03-13 23:50:20:10 □□日記 ○○が追加されました、是非御覧ください。
△△を更新 2019-03-14 21:50:40:11 □□スケジュール △△が更新されました、是非御覧ください。
○○を追加 2019-03-14 19:50:35:35 □□日記 ○○が追加されました、是非御覧ください。
△△を更新 2019-03-11 15:50:20:40 □□スケジュール △△が更新されました、是非御覧ください。
○○を追加 2019-03-11 02:50:19:35 □□日記 ○○が追加されました、是非御覧ください。
○○を追加 2019-03-16 05:50:13:35 □□日記 ○○が追加されました、是非御覧ください。
△△を更新 2019-03-16 01:05:30:02 □□スケジュール △△が更新されました、是非御覧ください。

・・・・悩みますよね。

まずは、Rangeを考えます。
「更新日時を指定してデータを取得したいという要件がある」とのことですが、更新日と更新時間が2つのカラムに分かれています。2つのカラムに対して、Rangeを指定するのは不可能なので、この場合「更新日+更新時間」の力技カラム(Number型)を作ります。そして、そのカラムに対してRangeを指定します。

次に、Hashを考えます。
この時、どういった結果が欲しいかを考えます。更新日時を中心に考えるのか、ラベルを中心に考えるのか、それにより結果が変わってきます。

■更新日をHashで考えた時のソート結果

タイトル 更新日(Hash) 更新時間 ラベル コメント 力技カラム(Range)
○○を追加 2019-03-13 23:50:20:10 □□日記 ○○が追加されました、是非御覧ください。 2019031323502010
○○を追加 2019-03-11 02:50:19:35 □□日記 ○○が追加されました、是非御覧ください。 2019031102501935
△△を更新 2019-03-11 15:50:20:40 □□スケジュール △△が更新されました、是非御覧ください。 2019031115502040
○○を追加 2019-03-14 19:50:35:35 □□日記 ○○が追加されました、是非御覧ください。 2019031419503535
△△を更新 2019-03-14 21:50:40:11 □□スケジュール △△が更新されました、是非御覧ください。 2019031421504011
△△を更新 2019-03-16 01:05:30:02 □□スケジュール △△が更新されました、是非御覧ください。 2019031601053002
○○を追加 2019-03-16 05:50:13:35 □□日記 ○○が追加されました、是非御覧ください。 2019031605501335

■ラベルをHashで考えた時のソート結果

タイトル 更新日 更新時間 ラベル(Hash) コメント 力技カラム(Range)
○○を追加 2019-03-11 02:50:19:35 □□日記 ○○が追加されました、是非御覧ください。 2019031102501935
○○を追加 2019-03-13 23:50:20:10 □□日記 ○○が追加されました、是非御覧ください。 2019031323502010
○○を追加 2019-03-14 19:50:35:35 □□日記 ○○が追加されました、是非御覧ください。 2019031419503535
○○を追加 2019-03-16 05:50:13:35 □□日記 ○○が追加されました、是非御覧ください。 2019031605501335
△△を更新 2019-03-11 15:50:20:40 □□スケジュール △△が更新されました、是非御覧ください。 2019031115502040
△△を更新 2019-03-14 21:50:40:11 □□スケジュール △△が更新されました、是非御覧ください。 2019031421504011
△△を更新 2019-03-16 01:05:30:02 □□スケジュール △△が更新されました、是非御覧ください。 2019031601053002

どうでしたでしょうか? RDBと考え方が全然違いますよね。なかなかHashやRangeが決まらないなぁという時は、力技カラムを作ってそれをHashやRangeにする方法も1つです。
DynamoDBってRDBと考え方が全然違うんだなぁという雰囲気を掴んでもらえれば幸いです。

次回こそっLSIとGSIについて書こうと思います。