このドキュメントの Django のバージョンにはセキュリティ上の脆弱性があるため、すでにサポートが終了されています。新しいバージョンにアップグレードしてください!最新の Django のバージョンのドキュメントはこちら

Django v1.0 documentation

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

revision-up-to:8961 (1.0)

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

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

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

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

# ある URL に対し、キャッシュに該当するページがないか探す
given a URL, try finding that page in the cache
# キャッシュ内にページがある場合
if the page is in the cache:
    # キャッシュされたページを返す
    return the cached page
else:
    # ページを生成する
    generate the page
    # 生成されたページを (次のリクエスト用に) キャッシュに保存
    save the generated page in the cache (for next time)
    # 生成されたページを返す。
    return the generated page

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

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

キャッシュを立ち上げる

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

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

memcached

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

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

memcached 本体のインストールの他に、 memcached の Python バインディングをイ ンストールする必要があります。 memcached には二つのバージョンがあります。以 下の どちらかの モジュールを選んでインストールしてください。

  • 最も高速に memcached をドライブできる選択肢は、 http://gijsbert.org/cmemcache/ から入手できる cmemcache です。 (こ のモジュールは、開発版の Django でしか使えません。 Django 0.96 とそれ以 前のバージョンは下の選択肢を使ってください)。
  • cmemcache を使えないなら、 python-memcached を使ってください。 ftp://ftp.tummy.com/pub/python-memcached/ で入手できます。この URL が有 効でなくなっていたら、 memcached のウェブサイト (http://www.danga.com/memcached/) に行って、 "Client API" セクションか らPython バインディングを入手してください。

Django を memcached と組み合わせて使うには、 CACHE_BACKENDmemcached://ip:port/ に設定します。 ip は memcached デーモンを動か しているホストの IP アドレス、 port はポート番号です。

以下の例では、 memcached をローカルホスト (127.0.0.1) のポート番号 11211 で動かしています:

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

memcached の素晴らしい点の一つは、複数のサーバ間でキャッシュを共有できると いうことです。この機能を利用するには、全てのサーバアドレスをセミコロンで区 切って CACHE_BACKEND に設定します。以下の例では、 IP アドレスが 172.19.26.240 と 172.19.26.242 で、いずれもポート番号 11211 で動作している memcached インスタンスでキャッシュを共有しています:

CACHE_BACKEND = 'memcached://172.19.26.240:11211;172.19.26.242:11211/'

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

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

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

python manage.py createcachetable [cache_table_name]

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

キャッシュ用のテーブルを作成したら、 CACHE_BACKEND 設定を "db://tablename" にします。 tablename はキャッシュ用テーブルの名前 です。以下の例では、キャッシュテーブル名を my_cache_table にしています:

CACHE_BACKEND = 'db://my_cache_table'

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

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

ファイルシステム上にキャッシュしたい内容を置くには、 CACHE_BACKEND"file://" キャッシュ形式を指定します。例え ば、キャッシュデータを /var/tmp/django_cache に置きたいなら、以下の ように設定します:

CACHE_BACKEND = 'file:///var/tmp/django_cache'

先頭にスラッシュが 3 つ連なっていることに注意して下さい。最初の 2 つのスラッ シュや file:// の一部で、最後のスラッシュはディレクトリパス /var/tmp/django_cache の最初の文字です。

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

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

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

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

CACHE_BACKEND = 'locmem:///'

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

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

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

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

CACHE_BACKEND = 'dummy:///'

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

Django 1.0 で新たに登場しました.

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

CACHE_BACKEND = 'path.to.backend://'

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

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

CACHE_BACKEND の引数

これらのキャッシュは全て引数をとれます。引数はクエリ文字列の形式をとります。 使える引数は以下の通りです:

timeout
デフォルトのタイムアウトで、単位は秒です。デフォルト値は 5 分 (300 秒) に設定されています。
max_entries
simple および database バックエンド用の引数で、キャッシュの消去を 行わずに残しておけるエントリの最大数です。デフォルト値は 300 です。
cull_percentage

キャッシュ内のエントリ数が max_entries を越えたときに淘汰されるエン トリの割合です。実際の百分率は 1/cull_percentage で決定されます。従っ て、キャッシュエントリ数が max_entries を越えたときに全体の 1/3 の エントリを淘汰したければ、 cull_percentage=3 と設定します。

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

以下の例では、タイムアウトは 60 に設定されています:

CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=60"

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

In this example, timeout is 30 and max_entries is 400:

CACHE_BACKEND = "memcached://127.0.0.1:11211/?timeout=30&max_entries=400"

サイト単位のキャッシュ

Django 1.0 で変更されました: (previous versions of Django only provided a single CacheMiddleware instead of the two pieces described below).

キャッシュの立ち上げ後、最も簡単なキャッシュの用法はサイト全体のキャッシュ です。設定は設定ファイルの 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_SECONDS -- 各ページのキャッシュ時間を秒単位で 指定します。
  • CACHE_MIDDLEWARE_KEY_PREFIX -- 同じ Django の下にある複数のサ イト間でキャッシュを共有する場合、この値をサイトの名前にするか、 Django インスタンスごとに固有の文字列にして、キャッシュのキー衝突を防ぎます。キー 衝突を気にする必要がない場合は空文字列を設定します。

キャッシュミドルウェアは GET または POST パラメタをもたない全てのページを キャッシュします。オプションとして、 CACHE_MIDDLEWARE_ANONYMOUS_ONLY 設定を True にすると、匿名の リクエスト (すなわちログインユーザ以外からのリクエスト) だけをキャッシュし ます。これは (Django の adminインタフェースを含む) ユーザ固有のページに対す るキャッシュを無効化する際に便利です。ただし、 CACHE_MIDDLEWARE_ANONYMOUS_ONLY を使う場合は、必ず AuthenticationMiddleware を有効に してください。

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

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

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

Django 1.0 で新たに登場しました.

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

ビュー単位のキャッシュ

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

from django.views.decorators.cache import cache_page

def slashdot_this(request):
    ...

slashdot_this = cache_page(slashdot_this, 60 * 15)

Python 2.4 のデコレータ構文を使うと以下のようになります:

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

cache_page は単一の引数をとります。これはキャッシュのタイムアウトを秒で 表したものです。上の例では、 slashdot_this() の出力結果は 15 分間キャッ シュされます。

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

開発版の Django で新たに追加された機能です

ページのキャッシュをもっと細かく制御したいなら、 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 %} に指定してください。

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

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

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

低水準のキャッシュ API

ときに、レンダリングされたページ全体のキャッシュが十分でないことがあります。 例えば、集約的なデータベースのクエリだけをキャッシュすればよいと考えるかも しれません。このような場合のために、低水準のキャッシュ API を使って、オブジェ クトを好きな粒度でキャッシュに保存できます。

キャッシュ API は簡単なものです。キャッシュを表現するモジュールである django.core.cacheCACHE_BACKEND 設定に基づいて生成された cache オブジェクトを公開しています:

>>> from django.core.cache import cache

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

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

timeout_seconds 引数はオプションで、デフォルト値は CACHE_BACKEND 設 定の timeout 引数の値になります (これについては上記を参照してください)。

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

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

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

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

get() には default 引数を指定できます:

>>> cache.get('my_key', 'has_expired')
'has_expired'
Django 1.0 で新たに登場しました.

キーに使う値がまだキャッシュ辞書上に存在しない場合にのみ、 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}

最後に、明示的なキーの削除は delete() で行えます。これは特定のオブジェ クトに対するキャッシュを消去する簡単な方法です:

>>> cache.delete('a')

これだけです。キャッシュにはほとんど制限がありません: 安全に pickle 化でき るオブジェクトならなんでもキャッシュできます。ただし、キーは文字列でなけれ ばなりません。

上流キャッシュ

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

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

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

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

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

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

Vary ヘッダを使う

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

デフォルトでは、 Django のキャッシュシステムはキャッシュキーをリクエストの パス部分、例えば "/stories/2005/jun/23/bank_robbed/" を使って生成します。 これでは、クッキーや言語設定のようなユーザエージェント間の違いにかかわらず、 同じ URL を指すリクエストは全て同じバージョンのキャッシュを使うことになって しまいます。

そこで Vary が登場します。

Django 下のページが、クッキーや言語、ユーザエージェントといったリクエストヘッ ダ上の違いに基づいて、違った内容を出力する場合、 Vary ヘッダを使って、 ページ出力が何に依存しているかをキャッシュメカニズムに教える必要があります。

Django でこれを行うには、以下のような便宜用のビュー関数デコレータ、 vary_on_headers を使います:

from django.views.decorators.vary import vary_on_headers

# Python 2.3 syntax.
def my_view(request):
    ...
my_view = vary_on_headers(my_view, 'User-Agent')

# Python 2.4 decorator syntax.
@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):
    ...

クッキーによるコンテンツの変更はよくあることなので、 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 ヘッダが送信されるように背後で気を配ります。

他にもキャッシュパラメタを操作する方法がいくつかあります。例えば、 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後ろ に置かねばなりません。