Django: キャッシュ(memcache)を設定して超高速化を実現する方法

2011年4月18日

Djangoには、キャッシュバックエンドとして様々な機構をサポートしています。 

  • メモリキャッシュ 
  • データベースキャッシュ 
  • ファイルキャッシュ 
また、バックエンドとは別に、キャッシュ化できるデータの単位も様々です。  

  • サイト全体をキャッシュ化 
  • ビュー単位でキャッシュ化 
  • テンプレートの一部をキャッシュ化 
  • オブジェクトの単位でキャッシュ化  
特に「オブジェクト単位」でキャッシュ化できるため、原理的には変数ひとつだけをキャッシュデータ化することも可能で、まさに究極の粒度をもったキャッシュ機能であると言えます。 また、もっと上流のキャッシュ機能とDjangoベースのWebサーバーを連携させることも可能です。例えば、squidなどのProxyベースのキャッシュ機能との連携がそれに該当します。 まさに、なんでもあり、のDjangoキャッシュ機能ですが、今回はその中でも最も高速なメモリベースのキャッシュを中心に、キャッシュの設定方法を見ていきましょう。

メモリベースのキャッシュを使う場合は、「memcache」を利用することになります。Debianなら、C言語で実装されたこのmemcacheと、Pythonからmemcacheを叩くために必要な「python-memcache」をインストールする必要があります。 (ちなみに、Djangoがオリジナルで提供している「localmem」というキャッシュバックエンドもありますが、これは本番環境では利用しない方がよいです。このキャッシュは簡易メモリキャッシュで、プロセスごとにキャッシュを持つ単純な仕組みです。つまり、キャッシュサーバーでは無いのです。従って、Apacheプロセスが複数稼働しているときに同様のオブジェクトを複数のプロセスが別々にメモリ上にキャッシュすることとなり、非効率なので、これがオススメしない理由です。) 

memcacheをインストールしているのに、python-memcacheをインストールし忘れていたが為に、キャッシュ設定していても、実はキャッシュが有効に機能していなかった。。みたいなことが起こりますので、以下のようにきちんとインストールしましょう。 

1
2
3
4
5
6
7
8
$ sudo apt-get install memcached 
$ sudo apt-get install python-memcache 
$ emacs django_jim/proj/jim_pinax/settings.py 
   ... 以下を追加 
   MIDDLEWARE_CLASSES = ( 'django.middleware.cache.UpdateCacheMiddleware', 
                                                  'django.middleware.common.CommonMiddleware', 
                                                  'django.middleware.cache.FetchFromCacheMiddleware', ... ) 
   CACHE_BACKEND = 'memcached://127.0.0.1:11211/?timeout=%d&max_entries=1200' % CACHE_MIDDLEWARE_SECONDS

これで、Djangoでキャッシュ機能を使う場合にmemcacheが呼び出されるようになります。キャッシュを有効にすると、動作が軽快になるだけでなく、処理が省略されることを意味するので、コンテンツを変更しても内容が反映されなくなったり、毎回同じビューの処理を行う訳ではなくなるため、デバッグが難しくなります。そのような場合に、キャッシュをオフにしてデバッグするためにある設定が、 CACHE_BACKEND = 'dummy:///' です。 これで、キャッシュAPIがすべてから関数となり、実質キャッシュが無効化されます。

その他にも、settings.pyでは以下のような項目を定義することが可能です。 

  • CACHE_MIDDLEWARE_SECONDS = 3600           # 秒単位で設定 
  • CACHE_MIDDLEWARE_KEY_PREFIX = 'bbs'     # キャッシュ名の共通prefix 

上記のように、キャッシュクリアの間隔や複数サブドメインでキャッシュオブジェクトを有効化したい場合などに有効な設定がありますので、ケースバイケースで設定しましょう。 


さて、次は、ビュー側でどうやってキャッシュオブジェクトを管理していくかについてです。 前述しましたが、ビュー側でキャッシュ出来るのは、以下の4種類です。 

  1. サイト単位をキャッシュ化 
  2. ビュー単位でキャッシュ化 
  3. テンプレートのある区間をキャッシュ化 
  4. pythonのオブジェクトの単位でキャッシュ化 
上記の内、1.については、Djangoにおいてはあまり用途がありません。 なぜならば、DjangoであつかうビューはそのほとんどがOneToOneであるためです。サイト単位でのキャッシュは、URLの単位でHTMLの内容をキャッシュ化するようなものですので、一部でもユーザーごとのテンプレート変数を使っていると、それが他者でも共有されてしまうのです。 なので、通常利用するのは上記の内4.となります。 

4.は以下のように実装します。 

1
2
3
4
5
6
7
8
9
from django.core.cache import cache 
cache_data = cache.get("key_name") 
if ( cache_data == None ): 
    # キャッシュミスヒット
    cache.set( "key_name", data, getattr(settings, "CACHE_MIDDLEWARE_SECONDS", 3600) )
else:
    # キャッシュヒット
    data = cache_data
...

cache.get("key_name")で、key_nameでキャッシュしたpythonオブジェクトを呼び出します。

キャッシュオブジェクトが無い場合は、cache.set('key_name')でキャッシュオブジェクトをmemcacheに登録します。その際、当該キャッシュオブジェクトの有効期限を上記のように指定することが可能です。 これで、Pythonオブジェクトの単位でキャッシュを管理することが可能となります。 キャッシュを最大限に活用すると、最初の一回目のアクセス以外は飛躍的に表示速度が向上します。ほとんどの場合は、これで事足りるのですが次第に性能を突き詰めていくと、その有効期限が切れる度に重い処理を行う部分がボトルネックとなっていきます。その場合は、やはりスクリプトの処理自体を高速化する必要性が出てきます。 そのような場合は、ビューで実装しているPythonスクリプト部分と、SQL部分のボトルネックを発見し、以下に高速化していくかが焦点となっていきます。そのような場合の高速化手法については、今後解説することとしましょう。