Datastoreでのデータモデル設計メモ
最終更新日 : 2010/12/13 (2010/12/13 より執筆開始)
下滝 亜里 <asatohan at gmail.com>
内容に関するコメント(感想、提案、書き間違いの指摘)は歓迎します。
このドキュメントの内容は、まずはブログで書いています
ドラフトバージョン
はじめに
個人的に趣味で、Google App Engine/Java(実際はScalaを使用)を使い始めて一年ぐらいになります。そこで、このドキュメントでは、Datastoreのこれまでに設計したデータモデルについて少しふりかえりながら、以下を紹介します。
- 設計上の失敗点
- データモデルのパターン(デザインパターンと言う意味ではない)
- データモデルの進化のパターン(プロパティの追加や削除など)
なお、基本的には、JDOを使用し、一部でObjectifyを使ってます。
Webサイトの事例
事例として作ってるWebサイトはGoodsHomeというものです。もともとは、新刊の定期自動チェックのために作ってました。今は、モノ(商品)を中心としてファンやユーザーがつながれるようなソーシャルなサイトにしようと思ってます。
執筆時点で、47のKindを定義しています。
データモデル設計の事例
事例1
この事例では、NewItemというモデルを検討します。このモデルは、商品が新たに見つかった時の情報を表します。新たな商品データがあるかどうかは、タスクを使って定期チェックされます。なお、150,000以上のエンティティがあります。
図のPKは、プライマリキーのことです。PKの値はasinで、これは、amazonの商品IDです。categoryは、商品のカテゴリ(書籍やおもちゃなど)を表しています。dateは、見つかった日時を表します。ReadOnlyは、そのプロパティが読み取りのみ行われることを示します。
このモデルに対する操作は、二つです。
- (a) エンティティの作成
- (b) カテゴリごとに新着順にエンティティの読み取り(select from NewItem where category == '%s' order by date desc)。
特徴は以下です。
- (1) 他のエンティティへの関係を持たない(Key経由でも持たない)。
- (2) 各プロパティは、読み取り専用である。
- (3) 各エンティティは、(特に理由がなければ設計上は)削除されない。
- (4) トランザクションは不要である。
最後に、下記の図は、上記のようなモデルをより一般的に表したものです。
事例2
この事例ではWishTagというモデルを検討します。このモデルは、ある商品を欲しいと思ったユーザー数(正確には思った回数)を表します。facebookの「イイネ!」ボタンと同じような機能です。ユーザーが「欲しい!」ボタンをクリックすると、カウントが一つ増えます。ちなみに、現状、1,800程度のエンティティがあります。
プライマリキーは、asin(amazonの商品ID)です。countはクリック数を表します。
このモデルに対する操作は、三つです。
- (a) エンティティの作成
- (b) countの更新
- (c) countの読み取り
特徴は以下です。
- (1) 他のエンティティへの関係を持たない(Key経由でも持たない)。
- (2) プロパティは、更新/読み取りされる。
- (3) 各エンティティは、(特に理由がなければ設計上は)削除されない。
- (4) トランザクションは必要である。
下記の図は、上記のようなモデルをより一般的に表したものです。
設計上の悩み
設計上の悩みは、どのユーザー(ユーザー登録している者としていない者)がいつクリックしたのかを示す情報がないことです。つまりこのような情報を保存するような仕様への変更が出た時、データモデルをどう変更するのか? を考える必要があります。新たなKindを定義することなく、単にプロパティを追加するような変更では対応できないと考えます。
事例3
この事例では、商品のブックマークを表すモデルを検討します。各ユーザーは、商品IDをもとに、その商品をブックマークできます。このモデルは、Datastoreに慣れていない初期に作ったモデルです。
BookmarksとBookmarkの二つのクラス(Kind)を定義しました(JDOを使用)。Bookmarkは、各商品を対象としたブックマークを表します。asinは、amazonの商品IDです。userIdは、ブックマークしたユーザのIDを表します。Bookmarksは、単にuserIdごとに、Bookmarkの一覧を保持するだけのものです。
なお、JDOを使用しているので上記のようにモデルを表していますが、Datastore上では、実際には、以下のようにデータが保存されますので注意してください(詳しくはGAEのドキュメントを参照)。
このモデルに対する操作は、三つです。
- (a) エンティティの作成
- (b) bookmarksの更新
- (c) bookmarksの読み取り
特徴は以下です。
- (1) エンティティ間の関係がある。1対n(one-to-many)の関係。
- (2) トランザクションは必要である。
-Bookmarks(Parent)
- (3) bookmarks以外の(JDO上の)プロパティがない。
- (4) 各エンティティは、(特に理由がなければ設計上は)削除されない。
-Bookmark(Child)
- (5) 各プロパティは、読み取り専用である。
- (6) 各エンティティは、削除される。
下記の図は、上記のようなモデルをより一般的に表したものです。
設計上の悩み
設計上失敗していると感じるのは、Bookmarksがなぜ必要なのか、という点です。不要な理由はあります。
-性能上の欠点: ブックマークの追加や削除が行われるとindexのプロパティ値の更新が発生する。
そもそも、なぜ現状のようなモデル設計になっているかというと、不慣れだったこともありますが、ユーザーIDをもとにブックマークの一覧どのように保持・取得するかという観点から、現状のモデルを思いついた(それ以外は思いつけなかった)ということがあります。
実は、現状のモデルはバージョン2で、バージョン1では、BookmarkはuserIdを保持していませんでした。変更した理由は、性能上の理由と言うよりは、クエリの行いやすさの理由だった気がします。あるユーザーがその商品をブックマークしているかどうかを
userId == '%s' && asin == '%s'
のようにクエリできるようにしたかったからです。
ちなみに次のバージョンでは、Bookmarksを削除する予定です。それと、ブックマークした日時を保存するプロパティの追加も検討しています。
事例4
この事例のモデルは、タグです。単に商品にタグを付けれる機能です。各商品に対してタグは10つまで付けられる仕様です。JDO使用です。
TagsとTagの二つのクラス(Kind)があります。Tagsは、ある商品に付けられたタグの一覧を保持するだけのものです。asinはamazonの商品IDを表します。Tagは個々のタグを表します。nameは、タグ名を、userIdは、このタグを付けたユーザーのIDを(ない場合もある)、dateはタグが付けられた日時を表します。
このモデルに対する操作は、以下です。
- (a) エンティティの作成
- (b) tagsの更新
- (c) tagsの読み取り
- (d) nameからasinリストの取得
特徴は以下です。
- (1) エンティティ間の関係がある。
- (1.1) 1対n(one-to-many)の関係である。
- (1.2) 双方向の関係である。
- (2) トランザクションは必要である。タグ数の制約を満たす必要がある。
-Tags(Parent)
- (3) tags以外の(JDO上の)プロパティがない。
- (4) 各エンティティは、(特に理由がなければ設計上は)削除されない。
-Tag(Child)
- (5) 各プロパティは、読み取り専用である。
- (6) 各エンティティは、削除される。
- (7) Tags(Parent)への関係をもつ。
下記の図は、上記のようなモデルをより一般的に表したものです。
データモデル設計の考慮点
今回のモデルに限りませんが、設計する上で考慮の必要性が高いように思える順番は以下です。
- (1) トランザクションの必要の有無。
- (2) 性能。といっても、性能の測定や試験はほとんどしていません。
- (3) クエリのやりやすさ。やりやすくなるようにプロパティを持たせます。
- (4) 仕様変更への対応のしやすさ。個人的にはそれほど今のところ考慮して設計していませんが、何度かデータモデルの変更の必要性に出くわしました。変換作業は、結構面倒な印象があります。
- (5) データ容量。データの重複をなくすることを優先しません。課金で対応します。たとえば、私の場合、現状、エンティティ数:10,480,242(1000万)、使用容量:12.03 of 15.00 GBytes、課金:$0.06 です。
付録:データモデルのパターン
付録:データモデルの進化パターン
参考文献とリソース
更新履歴
todo/memo
Low-level APIレベルでのデータストアの理解は必須 -> JDOレベルで誤解に基づいた設計をしてしまう可能性がある。