Google App Engine上のベスト・プラクティス、その1: Datastore
2010.02.07
Google App Engine上でアプリを作りはじめて約二ヶ月。いろいろと分かって来たこともあるので、自分へのメモも含めてまとめてみる。まずは、Datastoreの話から。
なによりも大切なのはデータベースの設計
あたりまえと言えばあたりまえの話だが、App Engine上でアプリを作る上でもっとも大切なこと(=頭を使うべきところ)は、データベースの設計である。特にリレーショナル・データベース(RDB)上でのアプリ作りに慣れた人には、大きな「発想の転換」が必要なので、ここは注意が必要。
特に絶対にやっては行けないのは、
- 将来RDB上へ移行できるようにレイヤーを作って、その上にアプリを作る
- RDB上に作ったアプリをデータモデルを大幅に変更せずにApp Engine上に移植する
- RDBを前提に設計されたフレームワークをApp Engine上に載せて、その上にアプリを作る
など。App Engineの利点を活用するためには、その特徴を最大に活かすように(データ・モデルだけでなく)アプリケーションそのもののアーキテクチャを最適化すべき。逆の言い方をすれば、それができない状況であればApp Engine上に載せる意味はあまりない。レイヤーをもうけてポータビリティを高めようとか、RDB上に作られたアプリをアーキテクチャの大幅な変更なしに移植しようとしている人がいるのであれば、そもそも「何でApp Engine上にアプリを載せたいのか」という発想が本当に正しいのかどうかから見直すべき。
Datastoreの特性を理解する
Datastoreの特性についてはGoogleから提供されているドキュメントに必要なことは書いてあるので、ここではいちいち説明しないが、それを頭に入れた上での私なりのベスト・プラクティスを箇条書きにするとこんな感じになる。
- ReadとWriteのスピードの違いが桁違いに大きい → 1つのHTTPアクセス中にするputの数はできるだけ少なくしておく。通常は0から1。よほど複雑な場合でも2、3回。5つ以上のputが必要な状況に陥ったとしたらデータ・モデルの設計から見直すべき。何らかの理由でたくさんの数のEntityを更新しなければならない場合(たとえばスキーマ変更のために1000個のEntityを一気に書き換える)などは、TaskQueueを使って順繰りに実行させる。
- Entity Groupはトランザクションのためにある → Entity Groupはできるだけ小さくしておき、トランザクションが不要なところに親子関係は持たせないようにする。エンド・ユーザーから見て親子関係にあるからと言って、必ずしも同じEntity Groupに入れる必要はないのでそこを間違えないように。たとえば、ブログのエントリーと、それに対するコメントが良い例。まずは、親子関係を一切持たせずにモデルを設計して行き、どうしてもアトミックに複数のEntityを同時に変更する必要が出て来た段階で、親子関係を持たせてEntity Groupを作る、という作り方をおすすめする。
- データの正規化は基本的にはしない → エンド・ユーザーから見て一つの「もの」(たとえば商品、ブログエントリー)があった場合、それに付随する情報(たとえば、タイトル、値段、更新日時、作者)をEntityそのもののプロパティとしてもたせておき、Queryなしに一回のgetですべて取得できるようにしておく。これは、RDBにおける「ベスト・プラクティス」とは正反対の方向を向いたものなので、RDB/SQLに慣れた人こそ気をつけて設計すべきである。
- JOINがない → SQLにおけるJOINをApp Engine上で実現しようとすると、queryの結果をもとに複数のqueryをするというnested queryをせざるを得ないが、そもそもJOINが必要となるような状況になったことが、データ・モデルの設計がすでにApp Engine向けのものになっていない証拠なので、「ああ、JOINが使えたら楽なのに」と嘆く前に、今一度データ・モデルの設計から見直すべき。
- Queryはキーの取得だけだととても高速 → ReadはWriteと比べて早いとは言え、数百のEntityをQueryで取得するとそれなりの時間(千cpu_ms以上)がかかる。ただし、Queryもキーの取得だけだと圧倒的に早いので、「いかにキーの取得だけで済むように設計するか」という部分で頭を絞るべき。場合によっては、keyそのものに情報を埋め込んでおくことで、Entityを取得せずにEntityの情報を得るということも可能なので、ノウハウの一つとして覚えておくと良い。
- キーを使ったEntityの取得はQueryよりはるかに早い → なんらかの方法でキーを取得しておけば、そこから一つのEntityを取得するのは非常に早いので、それを利用して一つのHTTP GETを非常に高速に(100cpu_ms以下で)処理をすることが可能なので、これは頭に入れておくと良い。
- Datastoreへのアクセスは無視できない確率で必ず失敗する(実測値で600回に1回程度)→ それに対応したコードを書いておく。HTMLを直接ブラウザーに返す場合は、サーバー側でretryをするしかないが、AJAXでデータを取得する場合は必ずクライアント側にretryの仕組みを入れておく(3回で十分)。
- App Engineは高レベルなAPIやGQLを提供して既存のアプリケーションの移植を簡単にしている → GoogleがApp Engineに関して言っていることはおおむね正しいが、ここだけは「うそも方便」だと思った方が良い。App Engineの上でスケーラブルなアプリを作りたいのであれば、低レベルのAPIを直接たたくべき。ちなみに、私はGQLは一切使わず、Pythonでdb.Modelを直接操作しているが、APIそのものはとてもストレートで簡単なので、すぐに慣れる。
このあたりさえしっかりおさえておけば、Google App Engine上のアプリの作成は決して難しいものではないので、ぜひともチャレンジしていただきたい。
ちなみに、何か書き忘れたことや私の知らないこともあるかも知れないので、もし何かここに追加すべきものがあると思う方には、ぜひともコメントをいただきたい。ちなみに、「その2」はmemcacheに関して書くことを予定している。
分かりやすいです。ありがとうございます。
put用とget用のエンティティを分けるというのも「なるほど」と思ったので貼っておきますー。
エンティティを2つに分けます - How BuddyPoke Scales on Facebook Using Google App Engine - urekatのスカンク日記3
http://d.hatena.ne.jp/urekat/20100124/1264298323
Posted by: bufferings | 2010.02.08 at 07:25
はじめまして。私はtwitterのようなミニブログと、簡易なSNSと、2つのウェブサイトの立ち上げを計画しています(前者はtwitterと似て非なるものですが)。どちらもオリジナル部分が多いので、どうやらスクラッチで作るのがいいようです。しかし、いまだに制作してくれる人が見つかりません。ウェブの提供地も、制作してくれる人も、どちらも日本でもアメリカでも構わないのですが、以前からブログを拝見していたここで相談をしてみようと思い立ちました(私は技術はまったくの素人で、ブログ内容はちんぷんかんぷんなのですが、興味はあるのでなんとな~くわかったような気になってその概念を楽しんでいます)。お忙しいところ、ありがとうございます。
Posted by: Yoshihiro Doi | 2010.02.08 at 17:39
Datastoreアクセスのリトライに関してはデコレータでどうでしょう?
http://appengine-cookbook.appspot.com/recipe/autoretry-datastore-timeouts/
cookbookは参考に成るコードが沢山有りますね。
Posted by: ryu | 2010.02.09 at 04:18
エントリとは関係ないんですが、
一部のページで読み込んでる http://nama.st/javascripts/ns_shortcut.min.js (ryは何でしょうか?
nama.st が死んでるらしく、それのレスポンス待ちでページの読み込みが非常に遅くなってます(Chrome)。
僕だけですかね?
Posted by: 通りすがり | 2010.02.22 at 04:35