さて、一時はどうなるかと思いましたが現在は「CPU Quota」も表示される事無く動いています。
今日はこの状況を乗り切る際に行った「Google App Engine」のパフォーマンスチューニングtipsを3つほどご紹介。
webapp.WSGIApplicationのdebugフラグはFalseにすべし
いきなり当たり前で申し訳ないですが、実はこのフラグがTrueかFalseかというだけで「CPU Quota」が出る頻度が極度に異なります。実際に同じ症状の方もいらっしゃる様なので、おそらく間違いありません。
また公開する様なシステムでは、パスワード等も管理される事になるかと思いますが、debug=Trueだとスタックトレースに変数の値が表示されてしまい無茶苦茶危険です。気を付けましょう。
なお、Tagtterではユーザのパスワードは一切保存していません。
時間の掛かる既知な処理はキャッシュする
これはGoogle App Engineだけに言える話ではないですが、Tagtterではユーザの存在確認の為にtwitter.comへアクセスしています。但し、タグが追加される度、voteされる度にこれを行ってしまうとtwitter.comにも負荷を掛ける事になります。
そこでTagtterではTagtter内に存在するユーザに対してはtwitter.comへのアクセスを行わないようになっています。
確かにGoogle App Engineの処理能力は良いのですが、いかんせん制限が厳しかったりします。余談になりますが現状Tagtterのデータ全件をXMLエクスポートする処理は「CPU Quota」で動きません。現在はデータの種別毎にバックアップしています。
Templateにはクラスオブジェクトは渡さない
Django Templateには、db.Modelのインスタンスを渡せて非常に便利なのですが、このDBインスタンスをTemplateの中から扱うと非常に遅くなります。またdb.Modelに持ったgeneratorをTemplateから使うと更にパフォーマンスが落ちます。
例えばTagtterではユーザに対して付けられたタグをManyToManyで管理しており、モデルの一部を抜粋すると
class TagtterTag(db.Model):
name = db.StringProperty(required=True)
... snip ...
class TagtterMembership(db.Model):
user = db.ReferenceProperty(TagtterUser)
tag = db.ReferenceProperty(TagtterTag)
... ship ...
class TagtterUser(db.Model):
name = db.StringProperty(required=True)
... snip ...
def tags(self):
return (x.tag for x in self.tagttermembership_set)
この様なコードになるのですが、user.tagsをTemplate側から呼び出すとかなりパフォーマンスダウンになります。また、Google App EngineではCPU占有度により「CPU Quota」させる仕組みがあるのですが、実はこれはTemplate内の呼び出しにも適応される為
application → template → model procedure
という処理を行うと、1処理(1値参照)あたりのCPU使用回数が増える事になります。つまり上記Tagtterのtagsページでは全てのタグが表示されますがこれを
template_values = {
'tags' : TagtterTag.all(),
}
この様にしてしまうとTemplate側からModelのメソッドを呼び出す事になり、現状のデータ件数だと100%エラーが発生してしまいます。Google App EngineではCPU占有状態が5秒程続くとエラーになる様で、このブリッジ状態だけでもパフォーマンスダウンに繋がるって事になります。
これを回避する為には、applicationにてTemplateで使う値を全て保持してしまう事が最も効果的でした。
つまり全てdictとして渡してあげるのです。変にTemplate側で「タグの数が0でない物を表示」とするのではなく
all_tags = []
for tag in TagtterTag.all():
if tag.size():
size = tag.size()
all_tags.append({
'name' : tag.name,
'title' : tag.title,
'size' : size,
'fontsize' : font_size(size),
})
... ship ...
template_values = {
... ship ...
'tags' : all_tags,
... ship ...
}
この様にTemplate側からdb.Modelメソッドを呼び出させないようにするのです。
これで、現状エラーも出なくなりました。要するに極力db.Modelインスタンスにアクセスしない様にするってのが味噌ですね。こんな
他にもパフォーマンスチューニングの方法は幾らでもあるかと思います。Tagtterで言えばクライアント側の処理は殆どパフォーマンスチューニングされていないのが現状です。
不定期ですが時間を見付けてやって行きたいと思います。
何かご要望などあれば、Feature Requestまで。