GAE/PythonでMemcachedを使ってみた

火曜日, 1月 25th, 2011 by

Memcached使ってみました。単純な例で動作を確認後、python-blog-systemの画像出力部分に適用し、ベンチマークをとりました。

Memcachedとは

分散KVS(キーバリューシステム)の一種です。いわゆるRDB(リレーショナルデータベース)は、スケールアウトに向かない構造をしているために、安いサーバをいっぱい並べても性能がリニアに向上しません。Memcachedでは、トランザクションや、データの耐久性などを保証しないかわりに、大量のデータを、高速にキャッシュすることができます。詳しい説明は本家でどうぞ⇒http://memcached.org/ 概要が知りたければwikipediaがお手軽です。

シンプルな例

まずは、非常にシンプルなコードを書いて動作を確認しました。

  1. 初回アクセス: 何も表示されません。
    キーweatherに対して何も設定されておらず、Noneが返されるため、何も表示されません。
  2. 2回目以降: snowingと表示されます。
    初回アクセス時に、キーweatherに”snowing”が設定さたので、snowingと表示されます。

ソースコード

読み出し⇒書き込みという順序になっているので、初回アクセス時は何も表示されませんが、3600秒以内に再アクセスするとsnowingと表示されます。

  • 6行目: Memcachedからデータを取り出して表示
  • 7行目: Memcachedにデータを保存

以下のソースコードは実際にGAE/Pythonで動作します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.api import memcache

class MainHandler(webapp.RequestHandler):
  def get(self):
    self.response.out.write(memcache.get("weather"))
    memcache.set(key = "weather", value="snowing", time=3600)

def main():
  application = webapp.WSGIApplication([('/', MainHandler)], debug=True)
  util.run_wsgi_app(application)

if __name__ == '__main__':
  main()

Memcacheパターン

Memcache パターンはMemcachedを使う場合のお手本のようなものです。Memcacheパターンは以下のように動作します。

  • Memcachedにデータがあれば、その値を返す。
  • Memcachedにデータがなければ、DataStoreなどから取得して、Memcashedに保存して、その値を返す。

擬似コード

擬似コードで書くと以下のようになります。(Googleのドキュメントより転載)

1
2
3
4
5
6
7
8
def get_data():
  data = memcache.get("key")
  if data is not None:
    return data
  else:
    data = self.query_for_data()
    memcache.add("key", data, 60)
    return data

実際に適用してみた

python-blog-systemに組み込んでみた。適用対象は、機能はシンプルだけど、負荷の大きい画像出力部分にしてみました。;

適用前

ベースとなるソースコードです。画像を表示するたびにDatastoreから画像を取得します。

1
2
3
4
5
class ImageHandler(AuthHandler):
  def get(self, key):
    image = Image.get(key)
    self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
    self.response.out.write(image.image)

適用後

quickGetという関数を作成し、DataStoreの代わりに呼び出すようにしました。quickGet関数は、Memcachedに画像があるかどうか確認し、無ければDataStoreから取得します。取得した画像はMemcachedに保存し、2度目以降のアクセスに備えます。 ( ※quickGetは、画像だけでなくすべてのデータに対応しています。動的言語の強みですね。 )

1
2
3
4
5
6
7
8
9
10
11
class ImageHandler(AuthHandler):
  def get(self, key):
    image = quickGet(key)
    self.response.headers['Content-Type'] = image.contentType.encode('utf-8')
    self.response.out.write(image.image)
def quickGet(key):
  data = memcache.get(key)
  if data == None:
    data = db.get(key)
    memcache.set(key = key, value = data, time=3600)
  return data

性能評価

では、どれぐらい性能を改善できたのでしょうか。

測定方法

timeitやprofileモジュールを使うのが正攻法だとおもうのですが、単純に、処理前と処理後の時間の差分を測定してみました。

  • slowGet
    毎回DataStoreにアクセスするオリジナル版
  • quickGet
    Memcachedを使った高速化版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ImageHandler2(AuthHandler):
  def get(self, key):
    for i in range(20):
      start = datetime.datetime.now()
      quickGet(key)
      end = datetime.datetime.now()
      self.write("<div>%s</div>" % (end - start))
    self.write("<br>")
    for i in range(20):
      start = datetime.datetime.now()
      slowGet(key)
      end = datetime.datetime.now()
      self.write("<div>%s</div>" % (end - start))

def slowGet(key):
  return db.get(key)

def quickGet(key):
  data = memcache.get(key)
  if data == None:
    data = db.get(key)
    memcache.set(key = key, value = data, time=3600)
  return data

開発環境での実行結果
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#quickGet
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
0:00:00.001000
0:00:00
0:00:00
#slowGet
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
0:00:00.001000
[cc]

<strong>GAE環境</strong>
明らかにMemcached版の方が早いです
[cc]
#quickGet
0:00:00.005764
0:00:00.006300
0:00:00.005913
0:00:00.004031
0:00:00.004950
0:00:00.005300
0:00:00.004968
0:00:00.004644
0:00:00.004859
0:00:00.004386
0:00:00.004912
0:00:00.003805
0:00:00.004547
0:00:00.004249
0:00:00.004069
0:00:00.004359
0:00:00.004716
0:00:00.004357
0:00:00.004450
0:00:00.003709
#slowGet
0:00:00.009259
0:00:00.011274
0:00:00.010659
0:00:00.016932
0:00:00.010952
0:00:00.011551
0:00:00.009306
0:00:00.015085
0:00:00.009747
0:00:00.010543
0:00:00.010175
0:00:00.008038
0:00:00.008064
0:00:00.008558
0:00:00.009136
0:00:00.009290
0:00:00.007938
0:00:00.008104
0:00:00.008364
0:00:00.008199

GAE上でのMemcachedとDataStoreの比較
Memcachedは平均4.71ms、DataStoreは10.06msでした。もっと差がつくのかと思っていましたが、意外と差は小さいです。

GAE / memcached VS datastore

GAE / memcached VS datastore

まとめ

 GAE/Python上で動作しているアプリに、Memcachedを組み込んでみました。部分的な適用でしたが、実作業時間で5分程度で組み込むことができ、データの取得にかかる時間を半分未満に短縮することができました。
 しかし、GAEのレスポンスタイムは300ms以上かかります。そのため、10msが5msになったところで、その差を実感することは出来ませんでした。今回は、取り出すデータの数が1個であったためこういう結果になってしまいましたが、データ数が多くなればMemcachedを使う恩恵は大きくなるでしょう。例えば、100個のデータを取得する場合であれば、1秒から0.5秒に処理時間を短縮できます。
 また、Memcachedに加工済みのデータを保存することで、更なる高速化も図ることもできます。ブログ記事のデータを取り出すのではなく、ブログ記事をHTMLに加工したものを保存すれば、圧倒的な高速化を実現できるはずです。

Facebook comments:

comments

Leave a Reply


Get Adobe Flash player
single