GAE/PythonのアプリにMemcached組み込んだら、CPU負荷が3分の1になった。
GAEは宇宙最強のデータセンタで動いてる。なら、負荷なんて気にしなくていい。お金さえあればね。ってことで、お金払いたくないので最適化しました。数行の変更で、性能3倍以上になっちゃいましたよ!!!!
最適化対象
ブログに埋め込むパーツです。シンプルですが、とあるまとめサイトに埋めたところ、1日数万リクエストがありました。Memcachedを使わなくても、1日50万PV程度は大丈夫だったのですが、最適化してみました。
Memcached導入による負荷の変化
赤い線がAPIが使ったCPU時間、青い線が全体のCPU時間です。Memcachedを組み込んだとたん、CPU使用量が3分の1になりました。特に、APIのCPU使用量は劇的に減っています。
無料利用分でどれだけつかえるの?
超過しそうなリソースはCPU時間とOutboundの転送量。それぞれ1リクエスト当たり10msと400byteです。
CPU無料枠
6.5時間/10ms = 234万PV
Outbound転送無料枠
1GB/400byte = 250万PV
これなら、1日200万PVは余裕ですね。
Memcached組み込み後のソースコード
※Memcachedを組み込む際には、元のデータの更新にあわせて、Memcachedのキャッシュをクリア(もしくは新しい値を上書き)すること。さもないと古いデータが表示され続けます。
データ読み取り処理(quickGet)
Memcachedにデータがあるか確認します。あれば使います。なければ、DataStoreから取得しMemcachedに保存します。
データ書き換え処理(18行目)
DataStoreに保存したデータと同じものをMemcachedに保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | #!/usr/bin/env python # -*- coding: utf-8 -* from google.appengine.ext import webapp, db from google.appengine.ext.webapp import util from google.appengine.api import memcache import urllib class MainHandler(webapp.RequestHandler): def get(self): if self.request.get('cmd'): page = Page.all().filter("referer =", self.request.get("referer")).get() if self.request.get('cmd') == "like": page.like = page.like + 1 else: page.dislike = page.dislike + 1 page.put() memcache.set(key = self.request.get("referer"), value = page, time = 3600) self.redirect("?done=1&referer=" + urllib.quote(self.request.get("referer"))) elif self.request.referer: if self.request.get("referer"): referer = self.request.get("referer") else: referer = self.request.referer page = quickGet(referer) if not page: page = Page(referer = referer, title = self.request.get("title"), site = self.request.get("site")) page.put() if self.request.get("done"): disabled = "disabled" else: disabled = "" self.response.out.write(u""" <html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head> <body style="margin:0"> <input type="button" onclick="this.disabled = true;location.href='/?cmd=like&referer=%s'" value="いい" %s> %d : <input type="button" onclick="this.disabled = true;location.href='/?cmd=dislike&referer=%s'" value="だめ" %s> %d </body></html> """ % (urllib.quote(referer), disabled, page.like, urllib.quote(referer), disabled, page.dislike)) def quickGet(referer): data = memcache.get(referer) if data == None: data = Page.all().filter("referer =", referer).get() memcache.set(key = referer, value = data, time = 3600) return data class ListHandler(webapp.RequestHandler): def get(self): for page in Page.all().filter("site =", self.request.get("site")).order("-like").filter("like > ", 0).fetch(20): self.response.out.write("""<body style="font-size:8px"><div>%d : %d <a href="%s?phpMyAdmin=cfc2644bd9c947213a0141747c2608b0">%s</a></div></body>""" % (page.like, page.dislike, page.referer, page.title)) class Page(db.Model): site = db.StringProperty() title = db.StringProperty() referer = db.StringProperty() like = db.IntegerProperty(default = 0) dislike = db.IntegerProperty(default = 0) def main(): application = webapp.WSGIApplication([('/', MainHandler), ('/list', ListHandler)], debug=True) util.run_wsgi_app(application) if __name__ == '__main__': main() |
まとめ
Memcachedを使うことで、負荷を3分の1程度まで軽減することができた。負荷が問題になった場合は、Memcachedを導入できるか検討すべきだ。

