【S2お茶会】AWS DynamoDB ⼊⾨ (10/2)

s2お茶会
気づけば今年もあと2ヶ月ちょっとです。

今週は弊社の高野が「AWS DynamoDB ⼊⾨」というテーマで話しました。

AWS DynamoDB ⼊⾨


昨今、何でもかんでもオンラインでデータを保存することが多いと思います。
その際に選択肢が RDB しかないというのも時代遅れなので、そろそろ NoSQL を本格的に導⼊していきたいと思いながら⼆の⾜を踏んでいたのですが、最近新規で開発したサービスで、まず RDB で実装しきってスケジュール的な安⼼を確保しつつ、その後で全てを DynamoDB に換装するということをやりました。

運⽤はこれからなのでまだ何とも⾔えませんが、少なくともやりたいことは⼤体できたので、今後は NoSQL も選択肢に入れていきたいと思っています。
そこで今回は、NoSQL ドキュメントデータベースサービスであるところのDynamoDB について、実際使ってみた感想を交えながら紹介します。

NoSQL とは


DynamoDB の前にまず NoSQL とは何でしょうか。
NoSQL とは “Not only SQL” という意味で、簡単に一言で言ってしまえば、RDB 以外のデータストアの仕組みのことをいいます。
出てきた当初は、読んで字のごとく “No SQL” という意味だったそうですが、否定するのはよくないと、最近では “Not only” と言われているらしいです。

NoSQLにはいくつかのデータベースの分類があって、キーを指定して値をとる KVS、キー・バリュー型データベースや、MongoDBなどに代表される、JSONに自動でインデックスを当てはめてくれるドキュメント型データベース、データ同士の関係性を取得できるグラフ型データベース、MemcachedRedis にメモリを送るインメモリなどなどがあります。
詳しくは公式ドキュメントをご覧になってみてください。

今回紹介する DynamoDB はキーバリュー型、及びドキュメント型の NoSQLデータです。

DynamoDB とは


公式からの引用によると、
Amazon DynamoDB は、規模に関係なく数ミリ秒台のパフォーマンスを実現する、key-value およびドキュメントデータベースです。完全マネージドマルチリージョン、マルチマスターで耐久性があるデータベースで、セキュリティ、バックアップおよび復元と、インターネット規模のアプリケーション⽤のメモリ内キャッシュが組み込まれています。DynamoDB は、1 ⽇に 10 兆件以上のリクエストを処理することができ、毎秒2,000 万件を超えるリクエストをサポートします。

Amazon DynamoDB
とあります。
ただし、お金をかけなければ機能に制限があるので注意です。

テーブル作成


DynamoDBにもテーブルがあります。これはRDBで呼ばれるテーブルと同じ単位です。
NoSQL なので、基本的に value 部分の構造は不要ですが、 Primary KeyIndex に相当するものはテーブルごとに定義する必要があります。


パーティションキー

テーブルを作成する時に、色々な用語が出てくるのですが、まずパーティションキーというものが出てきます。
実際にはそうではないのですが、パーティションキー1つにつき1つの物理DBが割り当てられるとイメージすると理解しやすいです。

ユーザー情報を格納するテーブルのパーティションキーを、例えばユーザーID にすると、そのユーザー専⽤の DB が⽤意されるイメージです(ユーザーAが登録すれば USER_A DB が作られ、ユーザーBが登録すれば USER_B DB が作られる。 これは物理的にです)
この意識を持っておくだけで、これから先の話がわかりやすくなります。

パーティションキーを適切に設定することによってパフォーマンスを担保します。
また、次に話すソートキーが指定されていない場合、これがプライマリキーになります。そのため、使いづらいからといってパーティションキーを適当に固定値で設定し、全て同じパーティションキーを振るというのは意味がないのでご注意ください。


ソートキー

これは RDB でいうところの PRIMARY KEY (partition_key , sort_key) に相当します。同⼀パーティションに⼊りながら、その中で特定のレコードを探したい場合や、実際にソートをしたい場合に役⽴ちます。
同じパーティションに複数の情報を入れたい場合、必ずこのソートキーが必要です。
ただし、複合でプライマリーキーとなることに注意です。sort_key が重複しないよう注意しましょう。

一例ですが、

partition_key: ユーザーID
sort_key: 投稿⽇時
value: 投稿内容 (JSON)

このような時、sort_key に設定してある投稿日時と一致する投稿があると、同じレコードとして上書きされてしまうので、そうならないように気をつけましょう。
こうしておくと、あるユーザーの投稿内容を⽇時順に並べて(asc, desc 共に)出⼒できます。また、ソートキーに対しては範囲指定のクエリも実⾏できます。

テーブルを作成する時は、パーティションキーソートキーを入力すれば大丈夫です。

インデックス


DynamoDB を RDB のように扱いたい時は、インデックスを意識しないといけません。インデックスにも二種類あって、一つが Local Secondary Index、もう一つが Global Secondary Index です。

Local Secondary Index(LSI)

テーブルの同⼀パーティション中に設定するインデックスです。つまり、同じデータベース中に設定する、RDBでいうところのいわゆるインデックスと同じものになります。

テーブル作成時にしか設定できず、後からは追加できません。これが DynamoDB を取っ付きにくいものにしている最大の要因だと個人的には思っています。
また、LSIを設定している場合のみ、1パーティションあたり 10GB の制限が付きます。

効果としては、ほとんどソートキーと同じなのですが、プライマリキーとはならないので重複することができます。

partition_key: ユーザーID
sort_key: 投稿⽇時
ls1_1: いいね数
ls1_2: RT数
value: 投稿内容(json)

このようなテーブルがあった時、これを使ってあるユーザーの投稿をいいね数RT数 で絞り込んだり並べ替えたりできるようになりました。
先ほども言いましたが、LSI を設定せずにテーブルを作成してしまった場合、後から要件が足されても、LSI では対応できなくなってしまうのでご注意ください。



Global Secondary Index(GSI)

パーティションキーを別途設定したリードレプリカ的なデータベース郡を作成できるのが GSI になります。
例えば先ほどのテーブルで、全ユーザーの投稿中で「いいね数」が多い投稿を⼀覧したい時などがあると思いますが、そんな時にパーティションキーを別途設定したリードレプリカ的なデータベース郡を作成することが可能です。

GSI は最⼤20個まで作成できます。
さらに LSI とは違い、テーブル作成後でもインデックスを追加することが可能です。

GSI を作成する時には

  • パーティションキー
  • ソートキー
  • 射影される属性

の3つを設定しなければなりません。

このパーティションキーによっても物理的に DB が分かれるというイメージを持っておいてください。

射影というのが見慣れないかもしれませんが、射影で元テーブルから何の値をGSIテーブルにコピーするかを指定します。最低限 GSI に使う情報と、元テーブルの PRIMARY KEY(パーティション+ソートキー)が射影されるようになっているので、検索で得られた情報から元テーブルを参照することができます。

GSI の使い所についてはこちらに分かりやすい例がまとまってあるのでご覧になってみてください。

ここまで説明したところでやっとオペレーションの説明に入ります。

オペレーション


Get

テーブルによって一意に決まるレコードを取り出します。


Put, Update

テーブルに対して追加・更新を⾏います。
同⼀ Primary Key に対して、

  • Put: 送信した内容で上書き
  • Update: 送信した内容のみを追記

となるので注意してください。

{"id": 1, "name": "hoge"}
Put {"id": 1, "age": 20} => {"id": 1, "age": 20} // name が消える
Update {"id": 1, "age": 20} => {"id": 1, "name": "hoge", "age": 20}


Delete

テーブルに対してレコードの削除ができます。


Query, Scan

テーブルや GSI に対して検索を実⾏します。

Query はパーティションキーを指定し、そのパーティション内で検索が実⾏可能です。
ソートキーや LSI 以外のフィルターも実行できますが、パーティションキー内のレコード全てをスキャンすることになる、これは RDB で⾔うところのSeq scan(indexを使わずに上から順番にスキャンしていく)に当たるため遅いです。
ソートキーによる昇順、降順での並び替えがができます。

これに対して Scan の方は大技で、全パーティション横断での検索が実行されます。全部のパーティションに対して、全て Seq scan が実行されるので、非常に重いです。
もしリアルタイムでこれを実⾏しなければならないなら、GSI の作成や設計し直しを検討するのがいいと思います。
先ほどから何度も言っていますが、パーティションキーごとにデータベースが物理的に分かれているので、Scanは使わないようにしましょう。じゃないと DynamoDB の良さは生かせません。

ページングには、前回読み込んだ時の最後の partition key + sort key (+ 同⼀のフィルター条件)が必要になります。
そのため OFFSET によるページングはできないので注意してください。
そもそも RDB の OFFSET も OFFSET + LIMIT 読み込んだ上で切り捨ててるだけなので同じことをすれば可能ですが。

トランザクション


ROLLBACK やトランザクションの BEGIN はないのですが、オールオアナッシングな一括での書き込みや読み込みはサポートされています。
書き込む時に条件が指定でき、その条件を用いながら一つのグループにまとめあげ、今から書き込もうとしている状態が、書き込むべきではない状態になったらエラーとするような使い方がトランザクションです。
DynamoDBの中での一連の作業をトランザクションにおさめるという使い方しかできないので、使うには工夫が必要です。

LOCK もちょっと⾯倒で、ロックが取れるまで待つ。というRDBのようなロックの仕組みは、ライブラリを探して拾ってくるか⾃分で実装しなければいけません。
ロックが取れないときエラーにしてもいいのであれば、こちらを参考にしてください。

ストリーム


変更内容を Lambda に送信できます。
設定次第ですが、対象レコードの old , new のオブジェクトを受信できるので、それをトリガーに画像をリサイズしたりできます。

これを受けて、検索⽤に RDB に⼊れることもできるので、管理機能からの複雑なクエリなども簡単に⾏えるようにできます。費⽤⾯では⾟くなりますが、パフォマーンスは良いと思いました。

料⾦

プランには、オンデマンドとプロビジョニングの2系統があります。
料金がかかるのは、読み込み/書き込みのリクエストとデータストレージあたりが主で、それ以外は大抵の場合、気にしなくて大丈夫です。

オンデマンドは利⽤した分を⽀払う形です。
案件の規模にもよりますが、意外と安く済みそうな印象。
ちなみにオンデマンドでは上限がないので、うっかり大量に情報を取得するような処理をすると、ものすごい金額が課金されてしまうので注意です。

プロビジョニングは予め使⽤する分を予約しておき、その時間分を⽀払う形です。

どちらかというとオンデマンドの方が使いやすく、お得な気がします。
とはいえ、簡単に切り替えできるので、用途によって使い分けるのがいいと思います。
ただし、テーブルごとに指定することになるので、テーブルの数だけ設定しなければなりません。そこだけ気をつけるようにしてください。


以上で紹介は終わりです。
なかなか使いこなすのは難しいと思うので、まずは RDB との併用をおすすめします。


今週のお茶菓子