Django のキャッシュで Google App Engine の Memcache API を使う

Django のキャッシュで Google App Engine の Memcache API を使う

2008/05/29 1:48am

Google App Engine ブログ新しい動きがあった。トピックは以下の通り。

  1. アカウント数の制限を撤廃
  2. 課金の料金体系について
  3. 画像操作のための API
  4. Memcache API

どれひとつとっても、重大な発表ばかりだ。プラットホームとしての Google App Engine に注目している方は 1. 2. が特に気になるだろうし、利用者からすれば、3. 4. は待ち焦がれていた機能だろう。

重いページをキャッシュする

このブログを Google App Engine に移行して一週間が経った。しかし、Admin Console でログを確認してみると、ところどころで警告が出ている。


"GET /index.xml HTTP/1.1" 200 117413 - -
...
This request used a high amount of CPU, and was roughly 5.5 times over the average request CPU limit. High CPU requests have a small quota, and if you exceed this quota, your app will be temporarily disabled.

どうやら、フィードの生成が重いようである。

考えてみれば、フィードがリクエストされるたびに、この重い処理を繰り返す必要はない。どうせ、記事の更新があるまでは生成される内容は同じだし、記事の更新をリアルタイムに反映する必要もないだろう。キャッシュしてしまおう。

The Django cache framework

Django には簡単に使えるキャッシュ・フレームワーク和訳)が組込まれており、memcached もサポートされている。

今回は Django のキャッシュ・フレームワークが memcached の代わりに GAE の Memcache API を使うようにすることで、コンテンツをキャッシュしてみよう。

Note

以下で紹介する方法は、Google App Engine Helper for Django に投稿されたパッチに基づいている。この機能が Google App Engine Helper for Django に取り込まれる日も近いだろう。

この修正は簡単で、main.py に以下の行を追加するだけである。

# Install App Engine memcache backend for cache framework
from google.appengine.api import memcache
sys.modules['memcache'] = memcache

変更は一目瞭然。トップレベルの memcache モジュールを GAE の memcache モジュールにしてしまう。

あとは、Django のキャッシュ・フレームワークを設定するだけだ。settings.pyCACHE_BACKEND の指定を追加しよう。GAE の Memcache API では接続先や負荷分散を指定する必要はないので、ホストの部分は空にしておく。

CACHE_BACKEND = 'memcached:///'

とりあえず、フィードをキャッシュしたかったので cache_page デコレータで、フィードを生成している View をキャッシュするようにした。

from django.views.decorators.cache import cache_page, never_cache

@cache_page
def archive_tag(request, slug, page=1):
  ...

たったこれだけで、初回リクエスト以降、重いフィード生成の処理をキャッシュしてくれるようになった。

KeyError

**(2008.5.29 追記)**memcached.Client#get が KeyError を返すことがあるようだ。キーが存在しない場合は None を返すはずなので、バグかもしれない。

ただ、公開直後の時点では KeyError が頻発し、エラーページが表示されてしまう。以下のようなコードで、

def silent_missing_key_error(func):
    def inner(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except KeyError:
            logging.exception("KeyError in google.appengine.api.memcache")
            return None
    return inner

from django.core.cache.backends.memcached import CacheClass as MemcacheCache
MemcacheCache.get = silent_missing_key_error(MemcacheCache.get)
MemcacheCache.get_many = silent_missing_key_error(MemcacheCache.get_many)

KeyError が起こった場合はログに残して、None を返すようにしている。