Memcached使ってみました。単純な例で動作を確認後、python-blog-systemの画像出力部分に適用し、ベンチマークをとりました。
Memcachedとは
分散KVS(キーバリューシステム)の一種です。いわゆるRDB(リレーショナルデータベース)は、スケールアウトに向かない構造をしているために、安いサーバをいっぱい並べても性能がリニアに向上しません。Memcachedでは、トランザクションや、データの耐久性などを保証しないかわりに、大量のデータを、高速にキャッシュすることができます。詳しい説明は本家でどうぞ⇒http://memcached.org/ 概要が知りたければwikipediaがお手軽です。
シンプルな例
まずは、非常にシンプルなコードを書いて動作を確認しました。
- 初回アクセス: 何も表示されません。
キーweatherに対して何も設定されておらず、Noneが返されるため、何も表示されません。 - 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/Python上で動作しているアプリに、Memcachedを組み込んでみました。部分的な適用でしたが、実作業時間で5分程度で組み込むことができ、データの取得にかかる時間を半分未満に短縮することができました。
しかし、GAEのレスポンスタイムは300ms以上かかります。そのため、10msが5msになったところで、その差を実感することは出来ませんでした。今回は、取り出すデータの数が1個であったためこういう結果になってしまいましたが、データ数が多くなればMemcachedを使う恩恵は大きくなるでしょう。例えば、100個のデータを取得する場合であれば、1秒から0.5秒に処理時間を短縮できます。
また、Memcachedに加工済みのデータを保存することで、更なる高速化も図ることもできます。ブログ記事のデータを取り出すのではなく、ブログ記事をHTMLに加工したものを保存すれば、圧倒的な高速化を実現できるはずです。
