Building Scalable, Complex Apps on App Engineを見たメモ
Building Scalable, Complex Apps on App Engine
List properties 01:40
- List propertyとは何か?
- 複数の値を持てるプロパティ
- 順序付きリスト
- LPのメリット
- one-to-many関係にあるデータをコンパクトに扱える
- tupleやlistのようなデータを簡単に保存できる
- 子entityを扱う必要がない。entityより軽い。
- ただしcomposite indexで使う場合はindex explosionに注意
- サイズが1000のLPを2つ使ってcomposite indexを作ると100万件のindexエントリができあがる
- Microbloggingの例
- たくさんのエントリを多数のユーザーに配布する必要があり、スケーラビリティが要求される
- データ自体はコピーせず、fan-outさせる方が効率的
- RDB的実装:UsersテーブルとMessagesテーブル、およびN:N関連を表すUsersMessagesテーブル間でjoinする
SELECT * FROM Messages INNER JOIN UserMessages USING (message_id) WHERE UserMessages.user_id = 'X';
-
- app engine的実装:メッセージ受信ユーザーのリストを入れたreceiversというLPをMessageに定義する。あとは「receivers = <ユーザー>」で、あるユーザーが受信したメッセージをすべて取得できる
results = db.GqlQuery( "SELECT * FROM Message " "WHERE receivers = :1", me)
indexes = db.GqlQuery( "SELECT __key__ FROM MessageIndex " "WHERE receivers = :1", me) keys = [k.parent() for k in indexes] messages = db.get(keys)
Merge-join 23:10
- app engineはmerge-joinはサポートしている
- Social graphの例
- UsersとFriends
- 「XさんとYさんに共通の友達は?」
- RDB的実装:UsersとFriendsをjoinする
SELECT * FROM Users INNER JOIN Friends f1, Friends f2 ON Users.user_id = f1.user_b_id AND Users.user_id = f2.user_b_id WHERE f1.user_a_id = 'X' AND f2.user_a_id = 'Y' AND f1.user_b_id = f2.user_b_id
-
- app engine的実装:merge joinとLPを組み合わせる
db.GqlQuery( """SELECT * FROM Person WHERE friends = :1 AND friends = :2 AND location = 'San Francisco'""", me, otherguy)
- merge joinのパフォーマンス
- 条件数と結果数に比例。結果が100件以下の場合に最適
- LPと同様のパフォーマンス
- ストレージのオーバーヘッドなし
- 注意点
- 条件間でオーバーラップが多すぎると、zig-zag処理に時間がかかる
- composite indexと組み合わせて使えない:ソートできない
- メモリ上でソートすべし(キーだけでソートするなど)
まとめ
- LPとmerge joinが適している用途
- fan-out(1つのメッセージを多数のユーザーに送る、など)
- 地理情報処理
- relationship graphs(entityのグラフ)
- fuzzy value
- クエリを「範囲指定」から「set membership」テストで置き換えられるか考える
- データの書き込み時にmembershipの値を計算しておき、クエリは「=」だけで実施→速い
Questions
- 各equality filterを書く位置はzig-zag処理の性能に影響するか?
- あまり影響しない。
- 頻繁に変更されるデータに対し、範囲指定検索するにはどうすればよいか? 変更のたびにたくさんのindexを更新したくない。
- 検索用のデータを適宜計算して持たせる。
- オークションサイトの例:価格帯ごとのオークション一覧を表示したい。あるオークションの価格が変化したら、「0〜1000円のオークション」のフラグをそのオークションのLPに追加しておく。範囲指定検索が不要になり、LPに対するequality filterですばやく検索できる
- 以下のクエリの例では、N+1検索の問題が起きないか?
indexes = db.GqlQuery("SELECT __key__ FROM MessageIndex WHERE receivers = :1", me) keys = [k.parent() for k in indexes] messages = db.get(keys)
-
- 起きない。indexesを取得する際には1回のクエリが走るが、messagesの取得は個々のキーからentityをハッシュテーブルで直接取得する。個別のクエリは実行されず、高速に処理される。