Django のキャッシュフレームワーク

revision-up-to:17812 (1.4)

動的な Web サイトの根本的なトレードオフ要因とは、まさに動的であるということ そのものです。ユーザがページをリクエストするたびに、サーバはデータベースへ のクエリからテンプレートのレンダリングやビジネスロジックといった全ての計算 を実行して、サイト訪問者が見るページを生成します。これは、処理のオーバヘッ ドという観点から考えると、ファイルシステムからファイルを読み出すタイプの標 準的なサーバ設計よりもはるかに高くつきます。

ほとんどの Web アプリケーションでは、このオーバヘッドはたいしたものではあり ません。ほとんどの Web アプリケーションは washingtonpost.com や slashdot.org とは違って、小規模から中規模のサイトであり、トラフィックもそこ そこにすぎません。しかし、中規模以上のサイトや高いトラフィックをさばかねば ならないサイトでは、可能なかぎりオーバヘッドを削るのは基本です。

そこでキャッシュが登場します。

コンテンツのキャッシュとは、コストのかかる計算で、かつ一度計算したら再度計 算する必要のないものの結果を保存することです。以下の疑似コードは、動的に生 成されるWeb ページで、キャッシュがどのように動作するかを説明しています:

ある URL に対し、キャッシュに該当するページがないか探す
キャッシュ内にページがある場合:
    キャッシュされたページを返す
それ以外:
    ページを生成する
    生成されたページを (次のリクエスト用に) キャッシュに保存
    生成されたページを返す。

Django には堅牢なキャッシュシステムが付属しており、動的なページを保存して、 リクエストの度に最計算しなくてもよいようになっています。利便性のため、 Django には様々な粒度でのキャッシュを提供しており。特定のビューだけをキャッ シュしたり、生成に時間を要する部分だけをキャッシュしたり、サイト全体をキャッ シュしたりできます。

Django は Squid のような「上流の」キャッシュ や、ブラウザベースのキャッシュともうまく協調できます。こうした類のキャッシュ は直接制御できませんが、サイトのどの部分をどのようにキャッシュすべきかを (HTTP ヘッダを介して) ヒントとして与えられます。

キャッシュを立ち上げる

キャッシュシステムを使うには、少し設定が必要です。例えば、キャッシュデータ をどこに置くか、データベース上か、ファイルシステム上か、それともメモリ上か を指定せねばなりません。これはキャッシュのパフォーマンスに影響する重要な決 定です; そう、あるキャッシュ方式が別の方式より高速な場合もあるのです。

キャッシュの選択は設定ファイルの CACHES 設定で行います。 CACHES に設定できる値を以下に示します。

キャッシュの設定は Django 1.3 で変わりました。 Django 1.2 以前では CACHE_BACKEND という文字列でキャッシュの設定を行っていました。 この設定は CACHES という辞書に置き換えられました。

Memcached

Django で利用できるキャッシュの中でも断然高速で、もっとも高効率である Memcached は、完全なメモリベースのキャッシュフレームワークです。 Memcached はもともと LiveJournal.com の高負荷を低減するために開発され、その後 Danga Interactive でオープンソース化されました。 Memcached は Slashdot や Wikipedia で使われており、データベースアクセスを低減して、サイトのパフォー マンスを劇的に向上させます。

Memcached はデーモンとして動作し、指定された量の RAM の割り当てを受けます。 Memcached の役割は、キャッシュ内に任意のデータを追加し、取り出したり削除したりするた めのインタフェース、それも 超稲妻迅い インタフェースを提供することにあり ます。全てのデータは直接メモリ上に保存されるので、データベースやファイルシ ステムの使用によるオーバヘッドがなくなります。

Memcached 本体のインストールの他に、 memcached の Python バインディングをイ ンストールする必要があります。 いくつかの Python バインディングがあります。 一般的なのは python-memcachedpylibmc です。

Django 1.0 と 1.1 では cmemcache バインディングも利用できましたが、 cmemcache がメンテナンスされていないため、 1.2 で 推奨されないライブラリ になりました。 cmemcache のサポートは Django 1.4 で無くなります。
pylibmc のサポートを追加しました。

Django と Memcached を組み合わせて使うには

  • BACKENDdjango.core.cache.backends.memcached.MemcachedCachedjango.core.cache.backends.memcached.PyLibMCCache を 設定します(選んだ Memcached バインディングによります)。
  • LOCATIONip:port を設定します。 ip は Memcached デーモンが動作している IP アドレスを、 port は、Memcached が動作しているポート番号を設定します。 あるいは、 unix:path を設定します。 path は Unix ソケットファイルのパスを設定します。

次の例は、 Memcached が localhost (127.0.0.1) の 11211 番ポートで動作していて、 python-memcached バインディングを利用している場合のものです。:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

次の例は、 Memcached が /tmp/memcached.sock の Unix ソケットを通じて利用可能で、 python-memcached バインディングを利用している場合のものです。:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
    }
}

memcached の素晴らしい点の一つは、複数のサーバ間でキャッシュを共有できると いうことです。この機能を利用するには、全てのサーバアドレスをセミコロンで区 切るか、リストで LOCATION に設定します。

以下の例では、 IP アドレスが 172.19.26.240 と 172.19.26.242 で、いずれもポー ト番号 11211 で動作している memcached インスタンスでキャッシュを共有してい ます:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
        ]
    }
}

以下の例では、 IP アドレスが 172.19.26.240 (ポート番号 11211) 、 172.19.26.242 (ポート番号 11212) 、 172.19.26.244 (ポート番号 11213) で 動作している Memcached インスタンスでキャッシュを共有しています。:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': [
            '172.19.26.240:11211',
            '172.19.26.242:11211',
            '172.19.26.244:11213',
        ]
    }
}

メモリベースのキャッシュには一つだけ短所があります: キャッシュデータはメモ リ上に保存されるので、サーバクラッシュ時に失われることがあります。いうまで もなく、メモリは永続的なデータ保存場所ではありません。ですから、データの保 存場所を確保するのにメモリベースのキャッシュに依存してはなりません。実際に は、 Django のキャッシュバックエンドは どれも、永続的な記憶装置にはなりえません – キャッシュバックエンドは記憶 装置ではなく、あくまでもキャッシュ用です – ただ、メモリベースのキャッシュ は特に一時性が高いため、ここで注意しておきます。

データベースを使ったキャッシュ

データベーステーブルをキャッシュバックエンドに使うには、まず以下のコマンド を実行してデータベース上にキャッシュテーブルを作成します:

python manage.py createcachetable [cache_table_name]

[cache_table_name] は作成したいデータベーステーブルの名前です。 (この名 前は、現在データベースで既に使われていない有効なテーブル名なら何でも構いま せん。) このコマンドはデータベース中に Django のデータベースキャッシュシス テム向けの適切な形式のテーブルを生成します。

キャッシュ用のテーブルを作成したら、 BACKEND 設定を "django.core.cache.backends.db.DatabaseCache" にし、 LOCATION 設定を tablename にします。 tablename はキャッシュ用テーブルの名前です。 以下の例では、キャッシュテーブル名を my_cache_table にしています:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

データベースを使ったキャッシュは、設定ファイルで指定しているのと同じデータベースを利用します。 キャッシュ用のテーブルを別のデータベースにすることはできません。

データベースのキャッシュがうまく働くのは、高速でインデクス構築のよくできた データベースサーバを使っている場合です。

データベースを使ったキャッシュと、マルチデータベース

データベースを使ったキャッシュをマルチデータベースで使っている場合には、 データベースキャッシュテーブルのルーティングを設定しなければなりません。 ルーティングのためのキャッシュテーブルは CacheEntry という名前のモデルとして、 アプリケーション名は django_cache という名前として扱われます。 このモデルはモデルキャッシュには現れませんが、ルーティングのために使えます。

例えば、このルーターはすべてのキャッシュの読み取り操作を cache_slave へ、 すべての書き込み操作を cache_master へルーティングします。 キャッシュテーブルは cache_master にだけ同期します。:

class CacheRouter(object):
    """すべてのデータベースキャッシュ操作をコントロールするルーター"""

    def db_for_read(self, model, **hints):
        "すべてのキャッシュ読み込み操作をスレーブへ"
        if model._meta.app_label in ('django_cache',):
            return 'cache_slave'
        return None

    def db_for_write(self, model, **hints):
        "すべてのキャッシュ読み込み操作をマスターへ"
        if model._meta.app_label in ('django_cache',):
            return 'cache_master'
        return None

    def allow_syncdb(self, db, model):
        "キャッシュモデルはマスターにだけ同期"
        if model._meta.app_label in ('django_cache',):
            return db == 'cache_master'
        return None

データベースキャッシュモデルへルーティングの指示をしなかった場合には、キャッシュバックエンド は default データベースを利用します。

もちろん、データベースを使ったキャッシュを使わない場合には、データベースキャッシュモデルへの ルーティング指示について心配する必要はありません。

ファイルシステムを使ったキャッシュ

ファイルシステム上にキャッシュしたい内容を置くには、 BACKEND"django.core.cache.backends.filebased.FileBasedCache" と指定します。 キャッシュデータを /var/tmp/django_cache に置きたいなら、以下の ように設定します:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
    }
}

Windowsの場合には、ドライブレターを次の例のようにパスの先頭に置きます。:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

ディレクトリパスは常に絶対パス指定。すなわち、ファイルシステムのルートから 始まるパス名を指定せねばなりません。パスの末尾にスラッシュを追加するかどう かは問題にはなりません。

この設定の指し示すパスが実在し、 Web サーバを動かしているシステムユーザから 読み書き可能であるようにしてください。上の例でいうなら、サーバを apache というユーザで動かしている場合、 /var/tmp/django_cache ディレクトリ が実在して、 apache によって読み書き可能かどうかをチェックしてください。

各キャッシュは別々のファイルに、 Python の pickle モジュールを使って シリアライズされた (“pickleされた”) オブジェクト として保存されます。 各ファイル名はファイルシステムで利用できる安全な文字にエスケープされたキャッシュキーです。

ローカルメモリ上のキャッシュ

メモリを使ったキャッシュの恩恵を受けたい一方で、 memcached を動かせない状況 にある場合には、ローカルメモリを使ったキャッシュバックエンドを検討してみて ください。このキャッシュはマルチプロセスセーフかつスレッドセーフです。使う には、 BACKEND"django.core.cache.backends.locmem.LocMemCache" と指定します。 以下のようにして使います:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake'
    }
}

キャッシュの LOCATION は個別のメモリストアを識別するために 使われます。ローカルメモリ上のキャッシュを一つだけ使う場合には、 LOCATION は省略できます。ローカルメモリ上のキャッシュを複数 使う場合にはキャッシュを分けておくために少なくとも一つには名前を割り当てておく必要があります。

各サーバプロセスは、個別にキャッシュインスタンスを保持するので、プロセス間 でキャッシュは共有されません。このことから、明らかに、ローカルメモリキャッ シュのメモリ効率は非常に悪いといえます。おそらく実運用環境向きとではないで しょう。

ダミーキャッシュ (開発用)

最後に、 Django には「ダミーの」キャッシュが付いてきます。このキャッシュは 実際にはなにもしません – 単に何もしないキャッシュインタフェースを実装して いるだけです。

ダミーキャッシュが便利になるのは、そこかしこで様々なキャッシュを使っている ような実運用サイトを構築していて、開発/テスト環境ではキャッシュを行いたく ないような場合です。開発環境ではダミーキャッシュによってキャッシュしなくな りますが、実運用環境はそのままにしておけます。ダミーキャッシュを有効にする には、次のように BACKEND を設定します:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

カスタムのキャッシュバックエンドを使う

Django には、すぐに使えるキャッシュバックエンドがいくつもありますが、カスタ マイズしたバックエンドや自作のバックエンドを使いたい場合もあるでしょう。 外部のキャッシュバックエンドを Django と組み合わせたいなら、以下のように、 Python の import パスを CACHE_BACKENDBACKEND で指定します:

CACHES = {
    'default': {
        'BACKEND': 'path.to.backend',
    }
}

自作のバックエンドを作成しているなら、リファレンス実装として標準のキャッシュ バックエンドを使うとよいでしょう。キャッシュバックエンドは Django のソース 中の django/core/cache/backends/ ディレクトリ下にあります。

注意: 運用ホスト上でサポートされていないなど、本当に特別な理由がないかぎり、 Django 組み込みのキャッシュバックエンドを使った方がよいでしょう。組み込みの バックエンドは良くテストされており、とても簡単に扱えます。

Cache の引数

エンジンと名前のほかに、キャッシュの振る舞いをコントロールするためにキャッシュバックエンドは どれも引数をとれます。引数は CACHES のキーで指定します。 使える引数は以下の通りです:

  • TIMEOUT: デフォルトのタイムアウトで、単位は秒です。 デフォルト値は5 分 (300 秒) に設定されています。

  • OPTIONS: キャッシュバックエンドへ渡したい オプションです。 使えるオプションは、バックエンドごとに様々です。

    locmemfilesystemdatabase といったキャッシュバックエンドは それぞれ独自の淘汰方法を持っていて、次のオプションに従います。

    • MAX_ENTRIES: いくつまでキャッシュエントリを保持するかの設定です。 この設定を超えると古いものから削除されます。 デフォルト値は 300 です。

    • CULL_FREQUENCY: キャッシュエントリ数が MAX_ENTRIES に 達したときにどのくらいのキャッシュエントリを削除するかを分数で指定します。 実際の割合は 1/CULL_FREQUENCY です。 つまり、CULL_FREQUENCY2 に設定すると、 MAX_ENTRIES に達した場合に半分のキャッシュを削除します。

      CULL_FREQUENCY0 を指定すると、キャッシュエントリ数が MAX_ENTRIES に到達した時に全てのキャッシュエントリを廃棄します。 の設定は、キャッシュミスの増加と引き換えに、淘汰処理を 劇的に 高速化します。

    サードパーティのライブラリを使ったキャッシュバックエンドはライブラリの オプションを背後のライブラリにじかにオプションを渡します。 結果として、有効なオプションのリストは使うライブラリに依存します。

  • KEY_PREFIX: Djangoサーバが使うキャッシュキーに 自動的に付与される文字列です(デフォルトでは前につきます)。 頭につけられる文字列です。

    詳細は キャッシュのドキュメント を参照してください。

  • VERSION: Djangoサーバが生成するキャッシュキーに使われる デフォルトのバージョン番号です。

    詳細は キャッシュのドキュメント を参照してください。

  • KEY_FUNCTION ドットで区切られた関数のパスを設定します。関数でキーの頭につけられる文字とバージョンを 最終的にどのように構成するかを定義します。

    詳細は キャッシュのドキュメント を参照してください。

ファイルシステムを使ったキャッシュを、デフォルトの タイムアウトが 60 秒で、 最大のキャッシュエントリ保持数が 1000 の設定です。:

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': '/var/tmp/django_cache',
        'TIMEOUT': 60,
        'OPTIONS': {
            'MAX_ENTRIES': 1000
        }
    }
}

不正な引数や引数値は暗黙のうちに無視されます。

サイト単位のキャッシュ

キャッシュを立ち上げたら、サイト全体をキャッシュするのが最も簡単な使い方で す。設定は設定ファイルの MIDDLEWARE_CLASSES'django.middleware.cache.UpdateCacheMiddleware''django.middleware.cache.FetchFromCacheMiddleware' を追加するだけです。 例えば以下のようにします:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

Note

ミドルウェアの順番は、これで間違っていません。 “Update” ミドルウェ アがリストの先頭で、 “Fetch” ミドルウェアが末尾です。詳しいからくりは ちょっとややこしいのですが、後述の MIDDLEWARE_CLASSES の順番 で説明 しています。

次に、以下の必須の設定を Django 設定ファイルに追加します:

  • CACHE_MIDDLEWARE_ALIAS – キャッシュを保存するストレージの エイリアスを指定します。
  • CACHE_MIDDLEWARE_SECONDS – 各ページのキャッシュ時間を秒単位で 指定します。
  • CACHE_MIDDLEWARE_KEY_PREFIX – 同じ Django の下にある複数のサ イト間でキャッシュを共有する場合、この値をサイトの名前にするか、 Django インスタンスごとに固有の文字列にして、キャッシュのキー衝突を防ぎます。キー 衝突を気にする必要がない場合は空文字列を設定します。

キャッシュミドルウェアはリスエストとレスポンスのヘッダで許可された場合、 GET と HEAD リクエストへのレスポンスをキャッシュします。 同じ URL で別のパラメータがついたリクエストは、ユニークなものとして認識し、 別々にキャッシュされます。 オプションとして CACHE_MIDDLEWARE_ANONYMOUS_ONLYTrue に設定すると、アノニマスリクエスト(ログインしていないユーザのリクエスト)のみ がキャッシュされます。ユーザに特異なページのキャッシュを無効にする簡単で効果的な 設定です( Django の管理画面を含みます)。 CACHE_MIDDLEWARE_ANONYMOUS_ONLY の設定を使う場合には、 AuthenticationMiddleware を有効にするのを忘れないでください。 キャッシュミドルウェアは HEAD リクエストに対しての返答のレスポンスヘッダと GET リクエストへのレスポンスヘッダが同じであることを期待しています。 上記の場合 GET レスポンスのキャッシュを HEAD リクエストへ返します。

加えて、キャッシュミドルウェアは自動的に以下のヘッダを HttpResponse に追加します:

  • 「新鮮な」(キャッシュされていない) ページをリクエストされた場合には、 Last-Modified ヘッダを現在の date/time に設定します。
  • Expires ヘッダを現在時刻と CACHE_MIDDLEWARE_SECONDS を加算した 値に設定します。
  • CACHE_MIDDLEWARE_SECONDS に基づき、 Cache-Control ヘッダにページ の最長寿命を設定します。

ミドルウェアの詳細は ミドルウェア を参照してください。

ビューの中でキャッシュの有効期限を設定した場合 (Cache-Control ヘッダの max-age セクションを設定した場合)、ページは CACHE_MIDDLEWARE_SECONDS の設定値ではなくビューで設定した有効期限の下で キャッシュされます。 django.views.decorators.cache モジュールのデコレー タを使えば、ビューの有効期限を設定 (cache_control デコレータ) したり、 ビューのキャッシュを抑制 (never_cache デコレータ) できます。これらのデ コレータについては Vary ヘッダ以外のヘッダを使ったキャッシュ制御 を参照してください。

リリースノートを参照してください

USE_I18NTrue に設定した場合、アクティブな language 名付きのキャッシュキーが生成されます。 – 言語設定の検出メカニズム も参照してください。 マルチリンガルなサイトも自分でキーを生成すること無く容易にキャッシュできます。

リリースノートを参照してください

アクティブな languageUSE_L10NTrue に設定されたときもキャッシュキーに含まれます。 また USE_TZTrue に設定された場合には 現在のタイムゾーン も含まれます。

ビュー単位のキャッシュ

django.views.decorators.cache.cache_page()

キャッシュフレームワークをもう少し低い粒度で使うには、個々のビューの出力を キャッシュします。 django.views.decorators.cache には関数デコレータ cache_page があり、自動的にビューからの応答をキャッシュします。 使い方は簡単です:

from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
    ...

cache_page は単一の引数をとります。これはキャッシュのタイムアウトを秒で 表したものです。上の例では、 my_view() の出力結果は 15 分間キャッシュ されます。(上の例では、可読性のために、 60 * 15 と書いています。 60 * 15900 、すなわち 60 秒を 15 回です)

サイト単位のキャッシュのように、ビュー単位のキャッシュは URL 単位です。 複数の URL が同じビューに対してある場合には各URLごとに別々にキャッシュされます。 my_view を例にしましょう。 URLConf が以下のような場合には:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

/foo/1//foo/23/ へのリクエストは期待通り別々にキャッシュされます。 いったん固有の URL (たとえば /foo/23/ )へのリクエストがあると、同じ URL への リクエストに対してはキャッシュが使われます。

cache_page はオプションのキーワード引数をとれます。 cache 引数はビューの結果をキャッシュする際に特定のキャッシュを指定できます( CACHES の設定のもの)。 デフォルトでは default キャッシュが使われますが、望みのキャッシュを指定できます:

@cache_page(60 * 15, cache="special_cache")
def my_view(request):
    ...

ビュー単位でキャッシュのプリフィックスの設定を上書きすることもできます。 cache_pagekey_prefix というキーワード引数をとれます。 key_prefix はミドルウェアに対する CACHE_MIDDLEWARE_KEY_PREFIX と同様に機能します。次のように使います:

@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
    ...

二つの設定は組み合わせて使用できます。 cache key_prefix を 指定すると、設定されているキャッシュエイリアスのキャッシュのプリフィックスを 上書きしたものを取得できます。

URLconf でビュー単位のキャッシュを指定する

ここまでのセクションに登場した例は my_view 関数自身を cache_page で 置き換えていたので、ビューがキャッシュされるようにハードコードされていました。 この方法はビューとキャッシュシステムを密結合してしまうため、いくつかの理由において 理想的ではありません。 実際は、ビュー関数をほかのキャッシュしないサイトで再利用したくなるかもしれませんし、 キャッシュを使わないかもしれない人々にビュー関数を配布したいかもしれません。 このような問題を解決するには、ビュー関数自身ではなく、 URLconf でビュー単位の キャッシュを設定します。

URLconf への設定は簡単にできます: URLconf でビュー関数を cache_page で ラップするだけです。もともとの URLconf がこうなっているとします:

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', my_view),
)

cache_pagemy_view をラップするとこうなります。:

from django.views.decorators.cache import cache_page

urlpatterns = ('',
    (r'^foo/(\d{1,2})/$', cache_page(60 * 15)(my_view)),
)

テンプレートの部分的キャッシュ

ページのキャッシュをもっと細かく制御したいなら、 cache テンプレートタグ を使って、テンプレートの一部分だけをキャッシュできます。 cache タグをテ ンプレート内で使えるようにするには、テンプレートの冒頭に {% load cache %} を挿入しておきます。

{% cache %} テンプレートタグは、タグで囲まれたブロックの内容を、指定さ れた時間だけキャッシュします。 cahce タグは、必須の引数として、キャッシュ の有効期限 (秒単位) とキャッシュされた部分に付ける名前をとります。例えば、 以下のように使います:

{% load cache %}
{% cache 500 sidebar %}
    .. sidebar ..
{% endcache %}

キャッシュ部分内で使われる変数に応じて、複数のキャッシュコピーを保存したい 場合もあるでしょう。例えば、上の例で挙げたサイドバー部分を、ユーザ毎に個別 にキャッシュしたい場合には、 {% cache %} テンプレートタグに追加の引数を 指定して、キャッシュをユーザ毎に一意に識別できるようにします:

{% load cache %}
{% cache 500 sidebar request.user.username %}
    .. sidebar for logged in user ..
{% endcache %}

キャッシュ変数は、複数指定しても全く問題ありません。必要なだけの引数を {% cache %} に指定してください。

USE_I18NTrue にセットされている場合、 サイト単位のミドルウェアキャッシュは アクティブな言語を優先しますcache テンプレート内で同じ結果を取得するために、テンプレートタグは 翻訳用の変数 を使えます。

{% load i18n %}
{% load cache %}

{% get_current_language as LANGUAGE_CODE %}

{% cache 600 welcome LANGUAGE_CODE %}
    {% trans "Welcome to example.com" %}
{% endcache %}

キャッシュのタイムアウトはテンプレート変数でも指定できます。ただし、テンプ レート変数は整数値でなければなりません。例えば、テンプレート変数 my_timeout600 にセットしていれば、以下の二つの例は同じ効果をも たらします:

{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}

この機能を使えば、テンプレートで何度もタイムアウトを編集しなくても、一箇所 で変数を宣言しておいて再利用できるので便利です。

低水準のキャッシュ API

ページ全体のキャッシュがあまり有効でなく、ともするとやりすぎでかえって不便 な場合があります。

恐らく実際には、いくつかのコストの高い、結果の更新頻度が違うクエリーを含んでいるでしょう。 こういう場合には、サイト単位や、ビュー単位のキャッシュが提供するページ全体のキャッシュは 理想的ではありません。(いくつかのデータは頻繁に更新されるので)全部の結果はキャッシュしたく なくて、それでも変更が少ない結果はキャッシュしたいでしょうから。

こういう場合のために、 Django は低レベルなキャッシュ API を公開しています。 この API は好きな粒度でオブジェクトをキャッシュに格納するのに使えます。 安全に pickle できる Python オブジェクトであれば、文字列でも辞書でも、モデルオブジェクトの リストでも、なんでもキャッシュできます(たいていの Python オブジェクトは pickle できます。 pickle に関して、より詳しくは Python のドキュメントを参照してください)。

キャッシュを表現するモジュールである django.core.cacheCACHES'default' エントリ設定に基づいて生成された cache オブジェクトを公開しています:

>>> from django.core.cache import cache

基本となるインタフェースは set(key, value, timeout)get(key) です:

>>> cache.set('my_key', 'hello, world!', 30)
>>> cache.get('my_key')
'hello, world!'

timeout 引数はオプションで、デフォルト値は CACHESdefault バックエンド設定の timeout 引数の値になります (これについては上記を参照してください)。 キャッシュに保存する秒数です。

オブジェクトがキャッシュの中になければ、 cache.get()None を返し ます:

# Wait 30 seconds for 'my_key' to expire...

>>> cache.get('my_key')
None

リテラルで None をキャッシュにしまうことは賛成できません。 なぜなら None が保持されていたのか、キャッシュに見つからなくて None が 返されたかの区別ができないでしょうから。

cache.get() には default 引数を指定できます。 default には、オ ブジェクトがキャッシュの中にないときに返す値を指定します:

>>> cache.get('my_key', 'has_expired')
'has_expired'

キーに使う値がまだキャッシュ辞書上に存在しない場合にのみ、 add() メソッ ドを使ってください。 add() メソッドは、 set() と同じ引数をとります が、指定したキーがすでに存在する場合には、キャッシュを更新しません:

>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'

add() によってキャッシュにデータが保存されたどうかを知りたければ、戻り 値をチェックしてください。戻り値が True なら、保存されています。そうで ないときは False を返します。

キャッシュを一度しかアクセスしない get_many() インタフェースもあります。 get_many() は指定した全てのキーのうち、キャッシュ内に実在する (そして期 限切れでない) ものの入った辞書を返します:

>>> cache.set('a', 1)
>>> cache.set('b', 2)
>>> cache.set('c', 3)
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
リリースノートを参照してください

cache.set() のように、 set_many() はオプションで timeout 引数をとります。

キーは delete() で明示的に削除できます。特定のオブジェクトのキャッシュをクリアする 簡単な方法です。

>>> cache.delete('a')
リリースノートを参照してください

一度にたくさんのキーをクリアしたい場合には、 delete_meny() にクリアしたいキーの リストを渡します。

>>> cache.delete_many(['a', 'b', 'c'])
リリースノートを参照してください

最後に、すべてのキーをキャッシュから削除したい場合には、 cache.clear() を 使います。注意が必要なのは、 clear() はキャッシュから すべてを 削除することです。 あなたのアプリケーションがセットしたキーにとどまりません。

>>> cache.clear()

既に登録済みのキーを増減したい場合には、それぞれ incr()decr() メソッド を使います。デフォルトではキャッシュの値は 1 ずつ増減されます。増減の値は引数で与えられます。 もし、存在しないキーに対して増減しようとした場合には ValueError を送出します:

>>> cache.set('num', 1)
>>> cache.incr('num')
2
>>> cache.incr('num', 10)
12
>>> cache.decr('num')
11
>>> cache.decr('num', 5)
6

Note

incr()/decr() メソッドはアトミックが保証されません。 アトミックな増減をサポートしているバックエンド( memcached バックエンドとか)は 増減の操作はアトミックになります。 一方、増減の操作をネイティブに提供していないバックエンドは取得と更新という2ステップで 実装されるでしょう。

キャッシュキーのプリフィックス

リリースノートを参照してください

複数のサーバ間でキャッシュインスタンスを共有している場合や本番環境と 開発環境で共有している場合には、あるサーバのキャッシュデータを他の サーバに使われてしまうことがあります。 キャッシュデータのフォーマットがサーバ間でつがう場合には突き止めるのが 非常に難しい問題を引き起こしがちです。

この問題を避けるために、すべてのキャッシュキーにプリフィックスをつけられます。 個別のキャッシュキーを保存するときや取得するときに キャッシュの KEY_PREFIX に設定された 値を Django が自動でプリフィックスをつけます。

各 Django インスタンスの KEY_PREFIX を確実に別のものに設定しておくことで、キャッシュが衝突することを避けられます。

キャッシュバージョン

リリースノートを参照してください

キャッシュの値を利用するコードを変更した場合、既存のキャッシュを きれいにする必要があるかもしれません。 一番簡単な方法は前キャッシュをフラッシュすることですが、まだ使える 有効なキャッシュをロスしてしまいます。

Django は個別のキャッシュをターゲットによりより手段を用意しています。 Django のキャッシュフレームワークは VERSION で設定できるシステムワイドなバージョン識別子を持っています。 この設定の値はユーザが指定したキーとキャッシュプリフィックスと自動的に 組み合わされて最終的なキャッシュキーが生成されます。

デフォルトでは、すべてのキーリクエストは自動的にサイトのデフォルト キャッシュキーバージョンが含まれます。 しかし、基本的なキャッシュ関数は、 set と get でキャッシュキーバージョンを 指定できる世に version 引数をとれます。:

# Set version 2 of a cache key
>>> cache.set('my_key', 'hello world!', version=2)
# Get the default version (assuming version=1)
>>> cache.get('my_key')
None
# Get version 2 of the same key
>>> cache.get('my_key', version=2)
'hello world!'

特定キーのバージョンは incr_version()decr_version() メソッドを 使うことで増減できます。 他のキーには影響を与えずに、特定キーを新しいバージョンに押し出せます。前の例に続けると:

# Increment the version of 'my_key'
>>> cache.incr_version('my_key')
# The default version still isn't available
>>> cache.get('my_key')
None
# Version 2 isn't available, either
>>> cache.get('my_key', version=2)
None
# But version 3 *is* available
>>> cache.get('my_key', version=3)
'hello world!'

キャッシュキーの変換

リリースノートを参照してください

ここまでの2つのセクションで解説した通り、ユーザの与えたキャッシュキーは そのままでは利用されません。キャッシュプリフィックスとキャッシュキー バージョンと組み合わせて、最終的なキャッシュキーが与えられます。 デフォルトでは3つの部分がコロンで区切られた文字列が与えられます。

def make_key(key, key_prefix, version):
return ‘:’.join([key_prefix, str(version), smart_str(key)])

別の方法で組み合わせたり、(キーの部分を八種ダイジェストしたりと)他の 処理をキーに加えたい場合には、カスタムキー関数を定義できます。

キャッシュの設定で KEY_FUNCTION に 上で紹介した make_key() と引数を あわせた関数へのパスをドットで区切られたパスで指定します。 設定がされていれば、デフォルトのキー組み合わせ関数の代わりに利用されます。

キャッシュキーに関する注意

リリースノートを参照してください

Memcached は一番一般的に本番環境のキャッシュバックエンドで使われていますが、 250文字以上のキャッシュキーや、空白・制御文字を含めたキーは許されません(使用 すると例外が発生します)。 キャッシュのコードをポータブルにするために、また好ましくないサプライズを最小限 にするために、他のビルトインキャッシュバックエンドは、 memcached で利用すると エラーになるキーが使われた場合には、ウォーニングを出します ( django.core.cache.backends.base.CacheKeyWarning )。

いろいろなキーを受け付けるキャッシュバックエンド(カスタムバックエンドや memcached 以外のビルトインバックエンド)を使っていて、ウォーニングなしでいろいろなキーを利用したい 場合には、 CacheKeyWarning をおとなしくさせられます。 INSTALLED_APPS に設定しているうちの一つの management モジュール に、次のコードを追加します。:

import warnings

from django.core.cache import CacheKeyWarning

warnings.simplefilter("ignore", CacheKeyWarning)

ビルトインバックエンドでキーのバリデーションロジックを交換したい場合には、 バックエンドを継承して validate_key メソッドを上書きして、 カスタムのキャッシュバックエンドを使う に従うだけです。 実際に locmem バックエンドで行う場合には、このコードをモジュールに 追加します。:

from django.core.cache.backends.locmem import LocMemCache

class CustomLocMemCache(LocMemCache):
    def validate_key(self, key):
        """Custom validation, raising exceptions or warnings as needed."""
        # ...

...ドットで区切られたこのクラスの Python のパスを CACHES 設定の BACKEND 部分に設定します。

上流キャッシュ

ここまでは、 自分の データに対するキャッシュについて説明してきました。し かし、 Web 開発にはもう一つのタイプのキャッシュが関係してきます。それ は 「上流 (upstream)」のキャッシュ機構で行われているキャッシュです。上流の キャッシュは、ユーザのリクエストが Web サイトに到達する前ですらページのキャッ シュを行います。

上流キャッシュの例をいくつか示します:

  • ISP がある程度のページをキャッシュしていることもありますので、 http://example.com/ へリクエストしたページを ISP は example.com へ アクセスせずに返してくることがあります。 example.com のメンテナは、このキャッシュについて知りません。 ISP は example.com とブラウザの間にいて、すべてのキャッシュハンドリングを 透過的に行います。
  • ページをキャッシュしてパフォーマンスを向上させるために、 Django Web サイトを Squid Web プロキシ (http://www.squid-cache.org/) の背後に置 けます。この場合、リクエストはまず Squid でさばかれ、必要な時にのみア プリケーションに渡されるようになります。
  • Web ブラウザもページをキャッシュします。 Web ページが適切なヘッダを送 信すると、ブラウザは以後の同じページへのリクエストにはローカルの (キャッ シュされた) コピーを使うようになります。

上流のキャッシュは効率を高める良い方法ではありますが、危険もはらんでいます: 多くの Web ページのコンテンツは認証に応じて異なる内容になります。また、その 他の変数も入ります。キャッシュシステムが純粋に URL だけに基づいてページを 盲目的に保存してしまうと、同じページを後から見た訪問者に対して正しくない情 報や機密の情報を晒してしまいます。

例えば、 Web ベースの email システムを操作しているとしましょう。”inbox” ページのコンテンツはいうまでもなくログインしているユーザ固有のものです。 ある ISP が盲目的にサイトをキャッシュしてしまうと、その ISP を経由して最初 にログインしたユーザは自分の inbox ページをキャッシュしてしまい、以降に そのサイトを訪れたユーザが閲覧できるようになってしまいます。これはよろしく ありません。

幸運にも、 HTTP にはこうした問題に対する解決策があります。すなわち、キャッ シュ機構に指定の変数に基づいてコンテンツのキャッシュを行うよう指示したり、 キャッシュメカニズムが特定のページをキャッシュしないように指示したりする 一連の HTTP ヘッダがあるのです。

Vary ヘッダを使う

Vary ヘッダは、キャッシュ機構がキャッシュキーを生成するときに、どのリク エストヘッダを考慮すべきかを定義しています。例えば、 Web ページのコンテンツ が言語設定に依存している場合、ページは「言語によって変化 (vary)」します。

Django 1.3 でクエリを含むフルリクエストパスがキャッシュキーの 生成に使われるようになりました。 Django 1.2 ではパス部分のみでした。

デフォルトでは、 Django のキャッシュシステムはキャッシュキーをリクエストの パス部分とクエリ、例えば /stories/2005/?order_by=author" を使って生成します。 この場合、クッキーや言語設定のようなユーザエージェント間の違いにかかわらず、 同じ URL を指すリクエストは全て同じバージョンのキャッシュを使います。 しかし、クッキーや言語、ユーザエージェントといったリクエストヘッダ上の違い に基づいて、違った内容を出力する場合、 Vary ヘッダを使って、ページ出力 が何に依存しているかをキャッシュメカニズムに教える必要があります。

Django で Vary ヘッダを設定するには、以下のような便宜用のビュー関数デコ レータ、 vary_on_headers を使います:

from django.views.decorators.vary import vary_on_headers

@vary_on_headers('User-Agent')
def my_view(request):
    ...

上の場合では、 (Django 自体のキャッシュミドルウェアのような) キャッシュメカ ニズムは個々のユーザエージェント固有の別のバージョンをキャッシュします。

Vary ヘッダを (response['Vary'] = 'user-agent' のような操作で) 手動 で変更せずに、 vary_on_headers デコレータを使う利点は、デコレータが (す でに存在するかもしれない) Vary ヘッダをスクラッチから作るのではなく、き ちんと追加処理を行う点にあります。

vary_on_headers() には複数のヘッダを渡せます:

@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
    # ...

これは上流キャッシュに 両方 の変化を伝えます。つまり、ユーザエージェントとクッキー の組み合わせでキャッシュを取得します。例えば、ユーザエージェントが Mozilla で クッキーの値が foo=bar は、ユーザエージェントが Mozilla で、 クッキーの値が foo=ham のリクエストと別のものとして考慮されます。

クッキーによるコンテンツの変更はよくあることなので、 vary_on_cookie デコレータも用意されています。従って、以下の二つのビューは同じ振舞いをします:

@vary_on_cookie
def my_view(request):
    ...

@vary_on_headers('Cookie')
def my_view(request):
    ...

vary_on_headers に渡すヘッダは大小文字を区別しないので注意してください。 "User-Agent""user-agent" と同じです。

ヘルパ関数 django.utils.cache.patch_vary_headers() も直接使えます:

from django.utils.cache import patch_vary_headers
def my_view(request):
    # ...
    response = render_to_response('template_name', context)
    patch_vary_headers(response, ['Cookie'])
    return response

patch_vary_headers は第一引数に HttpResponse インスタンスをとり、ヘッ ダ名のリストまたはタプルを第二引数にとります。

Vary ヘッダの詳細は 公式の Vary の仕様 を参照してください。

Vary ヘッダ以外のヘッダを使ったキャッシュ制御

キャッシュの利用で起きるもう一つの問題は、データのプライバシーと、カスケー ド接続したキャッシュのどこにデータを保存すべきかという疑問です。

通常、ユーザの目に触れるのは二種類のキャッシュ、すなわち自分のブラウザのキャッ シュ (プライベートのキャッシュ) と、ページプロバイダ側のキャッシュ (公開の キャッシュ) です。公開のキャッシュは複数のユーザによって利用されており、別 のユーザがその内容を制御することもあります。これは、注意の必要なデータを扱 う際には問題になります: 例えば、銀行のアカウント番号を公開キャッシュに保存 して欲しくはないでしょう。つまり、 Web アプリケーションにはどのデータがプラ イベートで、どのデータが公開なのかを区別する方法が必要なのです。

この問題の解決策は、ページキャッシュが「プライベート」であると示すことです。 Django では、 cache_control ビューデコレータを使ってこれを実現します。 例えば:

from django.views.decorators.cache import cache_control
@cache_control(private=True)
def my_view(request):
    # ...

このデコレータは、適切な HTTP ヘッダが送信されるように背後で気を配ります。

「プライベート」と「パブリック」キャッシュ制御は排他的であることを覚えておいてください。 デコレータは「プライベート」がセットされるべきである場合には「パブリック」ディレクティブ を取り除きます(逆も同じです)。プライベートとパブリックなエントリの両方を持つブログサイトで 二つのディレクティブを使う例です。パブリックなエントリは共有キャッシュとしてキャッシュされます。 コードに登場する patch_cache_controll はキャッシュ制御ヘッダをマニュアルで修正 します(内部的には cache_control デコレータを呼び出します)。:

from django.views.decorators.cache import patch_cache_control
from django.views.decorators.vary import vary_on_cookie

@vary_on_cookie
def list_blog_entries_view(request):
    if request.user.is_anonymous():
        response = render_only_public_entries()
        patch_cache_control(response, public=True)
    else:
        response = render_private_and_public_entries(request.user)
        patch_cache_control(response, private=True)

    return response

他にもキャッシュパラメタを操作する方法がいくつかあります。例えば、 HTTP を 使うアプリケーションは以下のような操作を行えます:

  • ページの最大キャッシュ回数を定義できます。
  • キャッシュされているコンテンツの新たなバージョンがないか常に調べ、変 更がないときに限ってキャッシュを送信するように設定できます (キャッシュ によっては、サーバ上のページが変更されていても、単にキャッシュコピー の有効期限が切れていないという理由でキャッシュされた内容を配信するこ とがあります)。

Django では、ビュー関数デコレータの cache_control を使って、キャッシュ パラメタを設定します。以下の例では、 cache_control を使って、アクセス ごとにコンテンツの再検証を行い、キャッシュされたバージョンの最大保存期限を 3600 秒に設定しています:

from django.views.decorators.cache import cache_control

@cache_control(must_revalidate=True, max_age=3600)
def my_view(request):
    # ...

有効な Cache-Control HTTP ディレクティブは全て cache_control() に 使えます。利用できるディレクティブを示します:

  • public=True
  • private=True
  • no_cache=True
  • no_transform=True
  • must_revalidate=True
  • proxy_revalidate=True
  • max_age=num_seconds
  • s_maxage=num_seconds

Cache-Control HTTP ディレクティブの説明は Cache-Control の仕様 を参照し てください。

(キャッシュミドルウェアは常にキャッシュヘッダの最長寿命 (max-age) を CACHE_MIDDLEWARE_SETTINGS の設定値に設定するので注意してください。カス タムの max_agecache_control デコレータで使うと、デコレータの設 定が優先され、ヘッダの値は正しくマージされます。)

ヘッダを使ってキャッシュを抑制したい場合には、 django.views.decorators.cache.never_cache を使ってください。このデコレー タは、応答コンテンツがブラウザやその他のキャッシュ機構によってキャッシュさ れないようにヘッダを追加します:

from django.views.decorators.cache import never_cache

@never_cache
def myview(request):
    # ...

その他の最適化

Django には、アプリケーションのパフォーマンスを最適化する上で役立つミドルウェ アが他にもいくつかあります:

  • django.middleware.http.ConditionalGetMiddleware を使うと、条件付 き GET をサポートできるようになり、 ETag および Last-Modified ヘッダを使えるようになります。
  • django.middleware.gzip.GZipMiddleware は gzip 圧縮を扱えるブラウ ザ (最近のほとんどのブラウザが対応しています) に対して、コンテンツを gzip で圧縮します。

MIDDLEWARE_CLASSES の順番

キャッシュ関連のミドルウェアを使う場合、 MIDDLEWARE_CLASSES 設定中の 正しい場所に配置することが重要です。というのも、キャッシュミドルウェアは キャッシュストレージ上のコンテンツとの差異を検出するために、どのヘッダが変 更されたかを調べる必要があるからです。ミドルウェアは通常、必要に応じて Vary レスポンスヘッダに情報を付加します。

UpdateCacheMiddleware はレスポンスフェイズに動作します。レスポンスフェ イズのミドルウェアは逆順に処理されるので、リストの先頭のミドルウェアは 最後 に呼び出されます。従って、 UpdateCacheMiddleware は、 Vary ヘッダに何らかの情報を付加するミドルウェアよりも 手前 に追加せねばなりま せん。以下のミドルウェアが、 Vary ヘッダを操作します:

  • SessionMiddlewareCookie を追加します。
  • GZipMiddlewareAccept-Encoding を追加します。
  • LocaleMiddlewareAccept-Language を追加します。

一方、 FetchFromCacheMiddleware はリクエストフェイズに動作します。リク エストフェイズでは、ミドルウェアは先頭から末尾に向けて処理されるので、 リストの先頭にあるミドルウェアが 最初 に呼び出されます。 FetchFromCacheMiddleware もまた、 Vary ヘッダを操作するミドルウェア よりも後に呼び出さねばならないので、 FetchFromCacheMiddleware後ろ に置かねばなりません。