Building Scalable Web Apps with App Engineを見たメモ
Google I/O 2008 - Building Scalable Web Apps with App Engine を見たメモ
- 書き込みはコストが高い 0:06
- アプリケーションのパフォーマンスを決めるのは、書き込み処理の実装方法
- 読み込み処理は桁違いに速いので、あまり重要ではない
- Entity Groupを使わずにID生成しよう 0:40
- 書き込み処理が少なければID採番用のBlogIndexを親entityにしてEntityGroup(EG)を形成
- 書き込み処理の集中するCommentsは、BlogIndexのEGに入れない
- UserIndexのEGに入れて、timestampとUserIndexを組み合わせてGUIDを作る→txの競合をなくせる
- スケールするアプリのコツ 0:42
- テキスト検索の方法は(全文検索など)? 0:48
- バックグラウンドジョブは?
- まだないが、検討中
- EGはどう使う? 0:52
- 複数のentityを同じtxに入れたい場合に使う。txの単位を表す。
- pagingしている間のrepeatable readはどう確保される? 0:56
- pagingしている間は確保されない。pagingの都度に最新のページ内容を取得している
- 当初内容でrepeatable readを確保したければ、アプリ側で別テーブルを作るなどの対処が必要
Working with Google App Engine Modelsを見たメモ
Working with Google App Engine Modelsを見たメモ
Bigtableにできないこと
- joinできない
- (非正規化するか、ループを回して自前でjoinするしかないか?)
- (「正規化するな、JOIN済みのでっかいテーブルを作れ」)
- (List Propertyを使う)
- (merge join、つまり1テーブル内のjoinは行える)
- 集約関数がない(group byできない)
- count()やmax()も使えない(全件カウントできない)
- しかし毎回対象データをすべて取得してループで集計するのは効率がよくない
- (そもそも最大1000件の制限がある)
- そこで集約したい値は、カウンターとなるオブジェクトで集計する 00:34
- (書き込みが集中しないように複数オブジェクトに分散させるsharding counter)
- 関数やストアドプロシージャはない
- 比較演算子が限られている
- LIKE使えない
- (前方一致ならできる)
- LIKE使えない
- (ORも使えない)
- Many to Manyの表現方法
- List Propertyを使い、キーのリストを持たせる
From Spark Plug to Drive Train: Life of an App Engine Requestを見たメモ
From Spark Plug to Drive Train: Life of an App Engine Request
<Google App Engine Stackの構成(引用元)>
Life of a request
- Staticコンテンツへのリクエスト
- Staticコンテンツ:appengine-web.xmlでstatic要素に指定したコンテンツ
- アプリケーションコード等とは物理的に分離され配置されている
- Front Endが直接処理し、App Serverは関与しない
- Dynamicコンテンツへのリクエスト 0:17
- App Server
- App Master
- アプリを管理してスケジュール
- Front Endにアプリの情報を伝える
- 処理の流れ
- Design Motivations 0:27
- The Numbers 0:36
<"Open For Questions"のトラフィック推移(引用元)>
- Questions 0:39
-
- Datastore上のデータの物理的(地理的)位置を制御できるか?
- できない。
- Datastore上のデータの物理的(地理的)位置を制御できるか?
-
- アプリが受けられる負荷に上限はあるか?
- 現状、safety limitが設けられている。それを超える負荷を扱いたい場合は、app engineチームに連絡してほしい。案件ごとにsafety limitを解除できる。
- アプリが受けられる負荷に上限はあるか?
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をハッシュテーブルで直接取得する。個別のクエリは実行されず、高速に処理される。