MENU

DynamoDBのテーブル設計をするとき、自分に問いかけていること

DynamoDBをいじり始めてかれこれ一年くらい。見よう見まねで騙し騙しやってきたが、色々と痛い目を見てわかってきたこともある。転んで生傷つくりながら、テーブル設計をする際に考えるようになったことを、備忘録的に記述していく。

オートスケールの話はしない(わからない)。インフラ専門部隊がいないなら、オンデマンドがいいよ。人的コストより多分安いよ。

目次

ドキュメント

なにはともあれ、公式のドキュメントについて存在を知っておく→「DynamoDB のベストプラクティス - Amazon DynamoDB

こんな記事を読んでいる時間があるなら、公式のドキュメントを読むべきだ。でも多分読めない。自分も今でも読めていない。ここに書かれているのは本当に日本語だろうか、と真剣に思う。まぁ教科書なんていうのはだいたい、わかってから読むとわかるもんである。

それでも通して読むことでなんとなく親しみがわくのが人間なので、頑張って何度も読み、わからないところは飛ばし、読めるところだけでも、最後まで読む、それを繰り返すことが大事、だとは思う。だんだん読めるところが増えてくるし、自分も少しずつ増えてきた。でもまだ全部はとても読めていない。

それでもできることはある。以下では、今の自分の理解を、なんとか頑張って記述していく。

RDBの常識はDynamoDBの非常識

まず最初に断っておかなくてはいけないが、RDBについて経験がある者は(だいたいあると思うが)、「DynamoDBはRDBではない」と10回唱えてから学ぶ姿勢が大事だ。RDBで良しとされていることがDynamoDBでは悪手で、逆にRDBにおけるご法度がDynamoDBのセオリーだったりするので、「RDBでいうところの」なんて考えるくらいならDBに関する知識ゼロのほうがマシとすら思う。

実際には、RDBと比較して考えるほうが理解は深まるだろう。本記事でもRDBについて触れた記述はある。しかし、それらはほとんどRDBとの違いを強調したものだ。RDBとは根本的な思想が違う(したがってRDBの常識が通用しない)ということを念頭に置いたうえで、DynamoDBを学びたい。

DynamoDBのテーブル設計はつまりキー設計

DynamoDBのテーブル設計をするとは、つまりキー設計をすることだ。キー設計では、以下を設定する。

  • パーティションキーのみ or パーティションキー + ソートキー
  • グローバルセカンダリインデックスは使う?
  • ローカルセカンダリインデックスは使う?

これらが、求められる要件から決まる。求められる要件とは、どんな検索がなぜ必要なのか、だ。どんな検索、とはつまり具体的に投げるクエリである。なぜ、とはクエリの結果の精度とリアルタイム性を決めるためだ。自分は、次のような問いかけをして考えている。

  • 何の情報を元にアイテムを特定するか?
  • 一覧(リスト)は必要か?
  • 集計はあるか?
  • 複数の異なるタイプの情報が同時に必要か?(joinしたくなるか?)
  • 原子性は必要か?
  • リアルタイム性は必要か?

一つ一つ見ていく。

何の情報を元にアイテムを特定するか?

まず最初に考えるべきことは、DBの情報を引き出す時、引き出すために使える情報はなにか?ということだ。たとえばユーザーの情報を取得したい時、ユーザーIDをキーにして情報を取得する、といった具合。この場合、ユーザーIDをパーティションキーにするのがが一案となる。

一覧(リスト)は必要か?

DynamoDBでウキウキしながら設計を始めた時、最初に引っかかるのは一覧じゃないだろうか。RDBではselect * fromでできることが、DynamoDBでは難しい。特に、キーをパーティションキーのみにした場合、純粋にKVSとなる。

対応としては、だいたい以下。

  • パーティションキー + ソートキー型にする
  • なんとかセカンダリインデックスを使う
  • めっちゃクエリ投げる
  • 一覧は諦める

それぞれ見ていく。

パーティションキー + ソートキーで頑張る

許されるなら、まずパーティションキー + ソートキーにしたい。これだけでもだいぶ対応できる。たとえば特定のユーザーのコメントについて集めるならば、ユーザーIDをパーティションキー、コメントした日時をソートキーに持てば、ユーザーIDからそのユーザーのコメント一覧を得られる。

セカンダリインデックスで頑張る

他にも一覧が必要な場合、簡単なのは、グローバルセカンダリインデックス(GSI)を利用するのが楽。たとえば、特定のスレッドのコメントを一覧化したい場合、パーティションキーが既にユーザーIDで使われているのであれば、GSIとして、スレッドIDみたいなのをパーティションキーにしてクエリを投げれば良い。

ただし、GSIは内部的には検索用の別テーブルを用意するということなので、当然その分コスト(キャパシティユニット)がかかる

ソートキーだけ別にすればよい場合、ローカルセカンダリインデックス(LSI)が使える。LSIはGSIより低コストだが、テーブル作成時にしか使えないので、あらかじめ仕様が練られていないと使えない。で、目まぐるしく仕様が変わり続けるプロジェクトにばかり携わっている自分は使えたことがない。

一覧性とスケールのしやすさはトレードオフ

これらの方法のいずれにも言える注意点は、一つのパーティションに情報が集中してしまうことだ。パーティションを分散させることによりスケールメリットが得られるのがDynamoDBの良さなので、この設計はDynamoDBのメリットを殺しかねない設計、ということになる。

つまり、一覧性とスケールのしやすさはトレードオフの関係にある。妥協点を探りながら設計することになる。

……流行らないサービスにとっては、スケールメリット以前の問題なんだけどね。。。負荷の心配とかしてみたいもんですわ。。。

めっちゃクエリ投げる

パーティションは分散させたい……でも一覧も取得したい……と思ったら、力技。

めっちゃクエリ投げる一覧として取得したい分全部のクエリ投げる。並列で投げる。投げまくる。とにかくほしいアイテムのキーを投げまくる。

クエリを投げまくるので、パフォーマンスとか気にするとアプリケーション側がたいへん。一覧取得用のアイテムを作ってGetする、とか謎の工程も必要になるかもしれない。

一覧は諦める

究極の対応、一覧は諦める。これができるなら、それが一番いいと思います。。。いや、真面目に。

集計はあるか?

サービスにはよくランキングやコメント数の表示などあるが、集合論を基礎とするRDBが得意とする集計作業が、DynamoDBは苦手。PVみたいにインクリメントで表現できるようなものは、アトミックカウンタなんか使えるかもしれないし(使ったことないけど)、まぁ一ユーザーのコメント数くらいならば、まとめて取得して数を数えるくらいしてもいいかもしれない。が、ランキングとかマッチングになってくるとつらい。ソート条件が複雑だったりすると、手に負えない可能性もある。

それでもまぁ、非同期処理で時間をかければできそうならなんとなりなるかもしれないが、もしリアルタイム性まで求められると、RDBでいいんじゃないですかねー、という結論になるかと思う

複数の異なるタイプの情報が同時に必要か?(joinしたくなるか?)

RDBで言うところのjoinさせたい、と思うような情報の取得の仕方が必要そうならば、DynamoDBの設計難度が上がる。

RDBでは、情報は重複がないように正規化させるのがセオリーだが、DynamoDBは違う。DynamoDBは情報を一箇所に集める(局在化させる)のがセオリーだ。このRDBとDyamoDBの違いを端的に表現しているのは、ベストプラクティスのドキュメントにある「DynamoDB アプリケーションではできるだけ少ないテーブルを維持する必要があります。設計が優れたアプリケーションでは、必要なテーブルは 1 つのみです。」という文言だろう(僕が最も衝撃を受けた部分でもある)。一つのテーブルに種々の情報を打ち込むのが基本で、もっといえば一つのアイテムにもできる限り関連する情報を集めるのが基本

そうすると、アイテム同士で重複する情報が更新された時の問題が起きる。たとえばコメント一つとっても、コメント自体の情報と、コメントを書いたユーザーの情報(名前とか)があると思うが、コメントのアイテムにユーザー情報を格納していた場合、ユーザーが情報を変更したら、そのユーザーのコメントしたアイテムすべての情報も更新しなくてはならない。

それが嫌ならば、コメントのアイテムでのユーザー情報はユーザーIDのみに留めておき、ユーザーの情報については別途クエリを投げる、ということになるのだが、何度もクエリを投げることになり、あまりスマートという感じではない。

まぁでもそれが楽ではある。複数情報を同時に更新となると、原子性の問題もでてくるが、DynamoDBは基本的に原子性について求めるべきではない。だから、複数のアイテムを同時に書き込むような処理が必要な時は、非同期処理で、裏で最終的に整合するように処理を完遂するような設計が良い……のだけれど、当然システム全体としてみると煩雑になる。しんどい。

原子性は必要か?

原子性について補足すると、DynamoDBはトランザクション処理ができない……と言われていたりするが、今どきは10アイテムまでならできる。なので、必ずしもこれがために不可ってことはない。

ただpython用ライブラリのboto3でも低レベルなAPIじゃないと使えなったりするし(2019年4月現在)、あまり嬉しくない要件であることはたしか。できれば原子性については割り切りたい

リアルタイム性は必要か?

アイテムを更新した結果が、瞬時に適用される。結果は常に一貫している(一貫性)。当たり前のようだが、DynamoDBでは必ずしもそうではない。DynamoDBは一つのアイテムが内部的には複数箇所で保存されていて、そのうちの1つを見に行く仕掛けなので、更新が常に全体に反映されているとは限らないわけだ。複数箇所を見に行くことで結果が一貫するようにする「強力な整合性」を求めることもできるのだが(GSIでは使えない)、それくらい厳しい精度を求められるような要件では、DynamoDBを使うかどうか自体を注意深く検討したほうがよい。

それと、前述のように裏の非同期処理で人知れず更新をどんどん適用していくようなシステムだと、更新が完全に反映されるまで時間がかかる。その時間がどれくらいかかるのかはなかなか保証できないだろう。リアルタイム性とは保証できる処理時間だが、システム的にリアルタイム性は低くなりがちである。

DynamoDBは簡単ではない

以上がDynamoDBの設計時に自分がしている問いかけなのだが、検索の便利さとスケールメリット、システムの複雑さなどあちらを立てればこちらが立たずのトレードオフで、またそれが決まれば苦労しないわっていうビジネス上のユースケースの決定を早い段階で迫られるため(これが一番つらい)、だいぶ苦労する

DynamoDBの記事を見ていて時折見かけるのが、「DynamoDBはRDBと違い仕様変更に対し柔軟に対応できる」というような記述だが、これは嘘とは言わないが、だからDynamoDBのほうが(RDBより)簡単なんだ、と短絡的に考えると痛い目に合うと思う。確かにちょっとしたデータの属性の変更くらいであれば、RDBより柔軟に対応できるのはそうだと思うけれど、RDBのような柔軟な検索ができないので、甘えを許さないという意味でキー設計の厳しさはRDB以上だ。実際、AWSのドキュメントには

DynamoDB の場合は対照的に、答えが必要な質問が分かるまで、スキーマの設計を開始しないでください。ビジネス上の問題とアプリケーションのユースケースを理解することが不可欠です。

DynamoDB に合わせた NoSQL 設計 - Amazon DynamoDB

とある。対照的、とはもちろんRDBと比較しての話だ。まぁつまり、ここまでに問いかけたように、「どんなクエリがなぜ必要なのか」をハッキリさせろ、ということだ。RDBの設計とは別方面の厳しさがあると思う。

いやまぁ、RDBだってパフォーマンス考えれば、投げられるクエリを考えてインデックス貼って……とかしないといけないだろうけれど、そこそこの規模までなら、とにかく情報を引っ張ってくる、ということは、だいたいなんとかなるはずだ(それがクソクエリと呼ばれるものだったとしても)。DynamoDBの場合、RDBのクソクエリを遥かに凌駕するクエリを大量に生み出さねばならないか、最悪詰む(頼みのGSI、LSIには上限がある)。

ログ分析は別の手段を用意する

以上のように、DynamoDBは検索性がよろしくないので、あらゆる方向からログを分析するような用途にはつらい。平たく言うと、集計には向きません。なので、ログ分析なんかのためのインフラは別途用意すべき。

楽なのは、CloudWatchの活用とか、とにかくS3に突っ込んでAthenaで解析することだと思う。ただ、ログならともかく、分析のため、とデータについてもS3に保存すると、S3とDynamoDBの整合性の問題も出てくる。さすがに複数のサービスを跨ぐとトランザクション処理というわけにもいかないから、一時的に失敗しても時間を置いて成功するまで続けるか失敗しても帳尻合わせられるような非同期処理にしようとか、定期的にデータベースをメンテナンスするようなバッチもあったほうがいいかな、という風になってきて、だんだんとインフラが複雑になっていくわけだ。まぁでも、このあたりの話はRDBを使っても、検索はElasticsearchで、なんてすると同じような問題を抱えることにはなる。

RDBかDynamoDBかは、DB設計ではなくシステム設計として考える

ここまで色々とDynamoDBの苦しみを書いてきたが、逆に言うと、検索要件さえハッキリしていれば使いやすいといえる。キー設計さえブレなければ、仕様の変更にも柔軟に対応できるし、jsonが基本だから階層構造のデータを表現しやすいのは大きなメリットだ。json投げればそれがそのままデータです、ってのは本当にわかりやすくて良い。

まぁでも、一番はシステムの中でデータベースとしてアプリケーションと切り離した設計をしやすい、というところかと思う。主眼がデータモデルではなくて、サービスでのデータの使われ方、という感じ。サービスのユースケースが大事で、アプリケーションとデータベースを繋ぐAPI設計、システム設計が肝。考え方としては、いわゆるマイクロサービス的。RDBを使いたいシステムとはシステム設計の考え方そのものが異なるわけで、RDBかDynamoDBか、みたいな巷の議論はそもそも違うんじゃなかろうか、と思う。

好きです

その他、予約語めっちゃ多いとかfloat使えないとかDecimalかよとか、実際に使い始めると色々な罠で一々躓きますが、なんだかんだ言って好きです。フィジカルコンピューティングみたいなのとも相性いいと思います。いつかハードウェア絡めたシステムで使いたい。。。

(2020/02/23 追記)とか言ってたら本当に使う機会が出てきて人生言ったことが本当になるなぁとつくづく思います。でも最近はむしろFirestoreの優しさに抱かれています。DynamoDBはほんとストイックだと思うよ。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次