Memcachedで負荷が3分の1/GAE無料枠で1日200万PV

火曜日, 3月 1st, 2011 by

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を導入できるか検討すべきだ。

Facebook comments:

comments

Leave a Reply


Get Adobe Flash player
single