mixi engineer blog

*** 引っ越しました。最新の情報はこちら → https://medium.com/mixi-developers *** ミクシィ・グループで、実際に開発に携わっているエンジニア達が執筆している公式ブログです。様々なサービスの開発や運用を行っていく際に得た技術情報から採用情報まで、有益な情報を幅広く取り扱っています。

Google App EngineとMemcache API

こんにちは、某Perl界隈のIRCチャンネルでPythonがマイブーム的なKY誤爆をしてしまったtmaesakaです。

先日、以前から興味のあったGoogle App EngineMemcache APIについて少し調べ、こちらに英文で報告したのですが、今日は日本語で要約したまとめを紹介します。

まず軽く前置きですがGoogle App Engine (GAE)とは、Googleが提供しているウェブアプリケーションをGoogleのインフラ上でスケーリングや冗長化など、ある程度のノウハウや資金を要求される面倒な事を気にせずに運営できるプラットフォームです。つまり、典型的なPaaSの例であり、サービスの運営コストをelastic(伸縮)にします。昨今バズワード化しつつあるクラウドコンピューティングの一種でもあります。

GAEのインフラはGoogleより提供されているAPIセットを用いて利用します。その中にはon memory cacheがMemcache APIという形で提供されています。インターフェイスはmemcachedと同様Key/Valueベースのもので、アプリケーションのトータルパフォーマンス向上に役立つAPIです。

このAPIはmemcachedを連想させられるネーミングですが、実際にGAEのドキュメントを読むと、こう記述されています:

The Memcache API has similar features to and is compatible with memcached by Danga Interactive.

つまりmemcached互換で、なおかつ似ていると書かれていているだけです。普通はこの辺で納得するのでしょうが、私はこういった文を見ると調べたくなる性格なので、ちょっと深入りしてみました。

実際にMemcache APIを使うのは簡単で、'memcache' モジュールをGAEパッケージからインポートします:
from google.appengine.api import memcache

あとはアプリケーションから必要に応じて、各種APIメソッドをコールするだけです。

プロトコル違反なKeyでセットしてみる

memcachedのASCIIプロトコルでは、Keyの長さは250バイトまでという制限があります。それ以上の長さのKeyを送信するとmemcachedはERRORを返します。では、GAEはどうでしょう?

遊び気分で300バイトのKeyで適当な値をSetしようとする以下のコードを走らせてみたところ:

from google.appengine.api import memcache

memcache.flush_all()
test_key = 'x' * 300

if not memcache.set(test_key, 'some_val'):
    print 'Failed to set'
    quit()

print "Looks like we're good = " + memcache.get(test_key)

以下のエラーがローカルのApp Serverから返ってきました:

Keys may not be more than 250 bytes in length, received 300 bytes

あらあら、上記のエラーだけを見ると明らかにmemcachedっぽい動作ですが、memcachedに合わせているだけかもしれません。Keyの長さ制限はドキュメントに記述されていないので、このエラーに遭遇したら驚くデベロッパーもいるかもしれませんね(レアなケースですが)。

さて、次はもっとマジメに独特な情報で比較しましょう。

メモリ消費量で比較してみる

memcachedではサーバインスタンスがどれだけのデータ(総バイト数)をキャッシュしているかをstatsコマンドで容易に取得する事が可能です。同様に得られる情報は制限されるものの、Memcache APIでも同じ事が可能です:

from google.appengine.api import memcache

stats = memcache.get_stats()
if stats: print stats['bytes']

ここでトリビアですが、memcachedから取得できるキャッシュサイズは純粋に全てのkeyとvalueを合計した値ではなく、各レコードのオーバヘッド(item構造体のサイズ)を加算した値です。

この値を実際にGoogle上にデプロイしてあるアプリが返す結果と比較してみました:

1 x 128 byte value with a 5 byte key Memcache API: 133 bytes memcached-1.2.6: 184 bytes 64 x 128 byte values with 5 byte keys Memcache API: 8512 bytes memcached-1.2.6: 11776 bytes 128 x 128 byte values with 5 byte keys Memcache API: 17024 bytes memcached-1.2.6: 23552 bytes

なんとGAEは一切オーバヘッドを報告しません。これでMemcache APIのバックエンドに様々な可能性が広がりましたね。例えばネットワーク越しに分散されているGoogle Sparse Hashかもしれないし、アプリケーションのキャッシュマネージメントの事情でstats情報を独立した仕組みで保持しているのかもしれません。次のセクションで分散やマネージメントに関する推理を紹介します。

運用を考えてみる

特定のアプリケーションに関するキャッシュ情報の取得」は言葉にすると簡単に聞こえるものの、実際は簡単ではありません。まず考えなくてはならない事は、アプリケーションがどの様にキャッシュスペースを与えられているのかという点です。

例えばアプリケーションに対して必要に応じた数の専用インスタンスを用意するか、「専用」という概念を捨て、Keyに対しApplication Identifierをappend/prependして、他アプリケーションとキャッシュプールを共有するといったモデルです。どちらのモデルを採用するにしても、キャッシュのstatsという概念はインスタンス毎に存在するものなので、「このアプリケーションがxxx」という情報の管理と保持には独立した仕組みが必要と考えられます。つまりアプリケーション毎にindexが必要だという事です、例えばInverted Indexあたりのデータストラクチャ(appid -> stats_postings)で保持するなど。

上記の様な工夫を行わなければ、statsリクエストに対して毎回、ルックアップと演算がオンザフライで発生する事になり、低効率なシステムとなります。したがって下の層でmemcachedを使っていたとしても、統計管理が独立していれば、オーバヘッド抜きの純粋な値が保持される可能性もあります。GAEが保持と管理が楽な情報だけをstatsインターフェイスで提供している説明にもなります。

  • hits
  • misses
  • byte_hits
  • items
  • bytes
  • oldest_item_age

さいごに

Google App Engineで遊んでいて感動した事は、ドキュメンテーション(英語のやつ)がとてつもなく解り易い事です。あまり賢くなくて挫折屋な私でも、すぐに自立して簡単なコードが書けるレベルまでいけたほどです。

プログラミング言語面では現状、Pythonしかサポートされていませんが、もし今後いろいろな言語、特にRubyあたり(私は書けませんが)が対応されたら実に凄いプロダクトになるんじゃないかな〜、と思いました。Perl対応も完了したらかなり盛り上がるかも。