Djangoのキャッシュフレームワーク
動的なWebサイトの基本的なトレードオフは、動的であるということです。 ユーザーがページを要求するたびに、Webサーバーは、データベースクエリからテンプレートのレンダリング、ビジネスロジックまで、あらゆる種類の計算を行って、サイトの訪問者に表示されるページを作成します。 これは、処理オーバーヘッドの観点から、標準のファイルシステムからファイルを読み取るサーバーの配置よりもはるかに高価です。
ほとんどのWebアプリケーションでは、このオーバーヘッドは大した問題ではありません。 ほとんどのWebアプリケーションはwashingtonpost.com
またはslashdot.org
ではありません。 それらは、まあまあのトラフィックを持つ単純な中小規模のサイトです。 ただし、トラフィックが中程度から多いサイトでは、できるだけ多くのオーバーヘッドを削減することが不可欠です。
そこで、キャッシングが登場します。
何かをキャッシュするということは、高価な計算の結果を保存して、次回計算を実行する必要がないようにすることです。 これが動的に生成されたWebページでどのように機能するかを説明するいくつかの擬似コードを次に示します。
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ヘッダーを介して)提供できるキャッシュのタイプです。
キャッシュの設定
キャッシュシステムには、少量のセットアップが必要です。 つまり、キャッシュされたデータがどこに存在するかを指定する必要があります。データベース、ファイルシステム、または直接メモリのいずれかです。 これは、キャッシュのパフォーマンスに影響を与える重要な決定です。 はい、一部のキャッシュタイプは他のタイプよりも高速です。
キャッシュ設定は、設定ファイルの:setting: `CACHES` 設定に含まれます。 :setting: `CACHES` で使用可能なすべての値の説明は次のとおりです。
Memcached
Djangoによってネイティブにサポートされる最速で最も効率的なタイプのキャッシュである Memcached は、完全にメモリベースのキャッシュサーバーであり、元々LiveJournal.comで高負荷を処理するために開発され、その後DangaInteractiveによってオープンソース化されました。 FacebookやWikipediaなどのサイトで使用され、データベースへのアクセスを減らし、サイトのパフォーマンスを劇的に向上させます。
Memcachedはデーモンとして実行され、指定された量のRAMが割り当てられます。 キャッシュ内のデータを追加、取得、削除するための高速インターフェイスを提供するだけです。 すべてのデータはメモリに直接保存されるため、データベースやファイルシステムの使用によるオーバーヘッドはありません。
Memcached自体をインストールした後、Memcachedバインディングをインストールする必要があります。 利用可能なPythonMemcachedバインディングがいくつかあります。 最も一般的な2つは、 python-memcached と pylibmc です。
DjangoでMemcachedを使用するには:
- 設定 :setting: `バックエンド ` に
django.core.cache.backends.memcached.MemcachedCache
またdjango.core.cache.backends.memcached.PyLibMCCache
(選択したmemcachedバインディングによって異なります) - 設定 :setting: `LOCATION ` に
ip:port
値、ここでip
MemcachedデーモンのIPアドレスであり、port
Memcachedが実行されているポート、またはunix:path
値、ここでpath
MemcachedUnixソケットファイルへのパスです。
この例では、Memcachedはpython-memcached
バインディングを使用して、ローカルホスト(127.0.0.1)ポート11211で実行されています。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
この例では、Memcachedは、python-memcached
バインディングを使用してローカルUnixソケットファイル/tmp/memcached.sock
を介して利用できます。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': 'unix:/tmp/memcached.sock',
}
}
pylibmc
バインディングを使用する場合は、unix:/
プレフィックスを含めないでください。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '/tmp/memcached.sock',
}
}
Memcachedの優れた機能の1つは、複数のサーバー間でキャッシュを共有できることです。 これは、複数のマシンでMemcachedデーモンを実行できることを意味し、プログラムは、各マシンでキャッシュ値を複製する必要なしに、マシンのグループをシングルキャッシュとして扱います。 この機能を利用するには、すべてのサーバーアドレスをに含めます :setting: `LOCATION ` 、セミコロンまたはコンマ区切りの文字列として、またはリストとして。
この例では、キャッシュは、IPアドレス172.19.26.240および172.19.26.242で実行されているMemcachedインスタンスで共有され、両方ともポート11211で共有されます。
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:11212',
'172.19.26.244:11213',
]
}
}
Memcachedの最後のポイントは、メモリベースのキャッシュには欠点があることです。キャッシュされたデータはメモリに保存されるため、サーバーがクラッシュするとデータが失われます。 明らかに、メモリは永続的なデータストレージを目的としていないため、唯一のデータストレージとしてメモリベースのキャッシュに依存しないでください。 間違いなく、Djangoキャッシングバックエンドの none は永続ストレージに使用する必要があります。これらはすべて、ストレージではなくキャッシングのソリューションを目的としていますが、メモリベースのキャッシングは特に重要であるため、ここで指摘します。一時的。
データベースキャッシング
Djangoは、キャッシュされたデータをデータベースに保存できます。 これは、高速でインデックスが適切なデータベースサーバーがある場合に最適に機能します。
データベーステーブルをキャッシュバックエンドとして使用するには:
- 設定 :setting: `バックエンド ` に
django.core.cache.backends.db.DatabaseCache
- 設定 :setting: `LOCATION ` に
tablename
、データベーステーブルの名前。 この名前は、データベースでまだ使用されていない有効なテーブル名である限り、任意の名前にすることができます。
この例では、キャッシュテーブルの名前はmy_cache_table
です。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
'LOCATION': 'my_cache_table',
}
}
キャッシュテーブルの作成
データベースキャッシュを使用する前に、次のコマンドでキャッシュテーブルを作成する必要があります。
python manage.py createcachetable
これにより、Djangoのデータベースキャッシュシステムが期待する適切な形式のテーブルがデータベースに作成されます。 テーブルの名前はから取られています :setting: `LOCATION ` 。
複数のデータベースキャッシュを使用している場合、:djadmin: `createcachetable` はキャッシュごとに1つのテーブルを作成します。
複数のデータベースを使用している場合、:djadmin: `createcachetable` は、データベースルーターのallow_migrate()
メソッドを監視します(以下を参照)。
:djadmin: `migrate` と同様に、:djadmin:` createcachetable` は既存のテーブルにアクセスしません。 欠落しているテーブルのみが作成されます。
実行するSQLを出力するには、実行するのではなく、createcachetable --dry-run
オプションを使用します。
複数のデータベース
複数のデータベースでデータベースキャッシュを使用する場合は、データベースキャッシュテーブルのルーティング命令も設定する必要があります。 ルーティングの目的で、データベースキャッシュテーブルは、django_cache
という名前のアプリケーションでは、CacheEntry
という名前のモデルとして表示されます。 このモデルはモデルキャッシュに表示されませんが、モデルの詳細はルーティングの目的で使用できます。
たとえば、次のルーターは、すべてのキャッシュ読み取り操作をcache_replica
に送信し、すべての書き込み操作をcache_primary
に送信します。 キャッシュテーブルはcache_primary
にのみ同期されます。
class CacheRouter:
"""A router to control all database cache operations"""
def db_for_read(self, model, **hints):
"All cache read operations go to the replica"
if model._meta.app_label == 'django_cache':
return 'cache_replica'
return None
def db_for_write(self, model, **hints):
"All cache write operations go to primary"
if model._meta.app_label == 'django_cache':
return 'cache_primary'
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"Only install the cache model on primary"
if app_label == 'django_cache':
return db == 'cache_primary'
return None
データベースキャッシュモデルのルーティング方向を指定しない場合、キャッシュバックエンドはdefault
データベースを使用します。
もちろん、データベースキャッシュバックエンドを使用しない場合は、データベースキャッシュモデルのルーティング命令を提供することを心配する必要はありません。
ファイルシステムのキャッシュ
ファイルベースのバックエンドは、各キャッシュ値を個別のファイルとしてシリアル化して保存します。 このバックエンドセットを使用するには :setting: `バックエンド ` に"django.core.cache.backends.filebased.FileBasedCache"
と :setting: `LOCATION ` 適切なディレクトリに。 たとえば、キャッシュされたデータを/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
が読み取りおよび書き込み可能であることを確認してください。
ローカルメモリキャッシュ
設定ファイルで別のキャッシュが指定されていない場合、これがデフォルトのキャッシュです。 インメモリキャッシュの速度の利点が必要であるが、Memcachedを実行する機能がない場合は、ローカルメモリキャッシュバックエンドを検討してください。 このキャッシュはプロセスごと(以下を参照)でスレッドセーフです。 使用するには、 :setting: `バックエンド ` に"django.core.cache.backends.locmem.LocMemCache"
。 例えば:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
キャッシュ :setting: `LOCATION ` 個々のメモリストアを識別するために使用されます。 1つしかない場合locmem
キャッシュ、あなたは省略できます :setting: `LOCATION ` ; ただし、複数のローカルメモリキャッシュがある場合は、それらを分離しておくために、少なくとも1つに名前を割り当てる必要があります。
キャッシュは、最も使用頻度の低い(LRU)カリング戦略を使用します。
各プロセスには独自のプライベートキャッシュインスタンスがあることに注意してください。これは、プロセス間のキャッシュが不可能であることを意味します。 これは明らかに、ローカルメモリキャッシュが特にメモリ効率が良くないことを意味するため、本番環境にはおそらく適切ではありません。 開発に最適です。
バージョン2.1での変更:古いバージョンでは、LRUではなく疑似ランダムカリング戦略が使用されます。
ダミーキャッシング(開発用)
最後に、Djangoには、実際にはキャッシュしない「ダミー」キャッシュが付属しています。これは、何もせずにキャッシュインターフェイスを実装するだけです。
これは、さまざまな場所でヘビーデューティーキャッシュを使用する本番サイトがあり、キャッシュしたくない開発/テスト環境で、コードを特殊なケースに変更する必要がない場合に役立ちます。 ダミーキャッシュを有効にするには、 :setting: `バックエンド ` そのようです:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
カスタムキャッシュバックエンドの使用
Djangoには、すぐに使用できる多数のキャッシュバックエンドのサポートが含まれていますが、カスタマイズされたキャッシュバックエンドを使用したい場合もあります。 Djangoで外部キャッシュバックエンドを使用するには、Pythonインポートパスを :setting: `バックエンド ` の :setting: `CACHES` そのような設定:
CACHES = {
'default': {
'BACKEND': 'path.to.backend',
}
}
独自のバックエンドを構築している場合は、標準のキャッシュバックエンドをリファレンス実装として使用できます。 コードはDjangoソースのdjango/core/cache/backends/
ディレクトリにあります。
注:ホストがそれらをサポートしていないなど、本当に説得力のある理由がなければ、Djangoに含まれているキャッシュバックエンドに固執する必要があります。 それらは十分にテストされており、使いやすいです。
キャッシュ引数
各キャッシュバックエンドには、キャッシュ動作を制御するための追加の引数を与えることができます。 これらの引数は、:setting: `CACHES` 設定の追加キーとして提供されます。 有効な引数は次のとおりです。
:setting: `タイムアウト ` :キャッシュに使用するデフォルトのタイムアウト(秒単位)。 この引数のデフォルトは
300
秒(5分)です。TIMEOUT
をNone
に設定すると、デフォルトでキャッシュキーが期限切れになることはありません。0
の値を指定すると、キーはすぐに期限切れになります(事実上「キャッシュしない」)。:setting: `オプション ` :キャッシュバックエンドに渡す必要のあるオプション。 有効なオプションのリストはバックエンドごとに異なり、サードパーティライブラリによってサポートされているキャッシュバックエンドは、オプションを基盤となるキャッシュライブラリに直接渡します。
独自のカリング戦略を実装するキャッシュバックエンド(つまり、
locmem
、filesystem
、database
バックエンド)は、次のオプションを尊重します。MAX_ENTRIES
:古い値が削除される前にキャッシュで許可されるエントリの最大数。 この引数のデフォルトは300
です。CULL_FREQUENCY
:MAX_ENTRIES
に達したときにカリングされるエントリの割合。 実際の比率は1 / CULL_FREQUENCY
なので、CULL_FREQUENCY
を2
に設定して、MAX_ENTRIES
に達したときにエントリの半分をカリングします。 この引数は整数である必要があり、デフォルトは3
です。CULL_FREQUENCY
の0
の値は、MAX_ENTRIES
に達したときにキャッシュ全体がダンプされることを意味します。 一部のバックエンド(特にdatabase
)では、これによりのカリングがはるかに高速になりますが、キャッシュミスが多くなります。
Memcachedバックエンドはのコンテンツを渡します :setting: `オプション ` クライアントコンストラクターへのキーワード引数として、クライアントの動作のより高度な制御を可能にします。 使用例については、以下を参照してください。
:setting: `KEY_PREFIX ` :Djangoサーバーが使用するすべてのキャッシュキーに自動的に含まれる(デフォルトで先頭に追加される)文字列。
詳細については、キャッシュドキュメントを参照してください。
:setting: `VERSION ` :Djangoサーバーによって生成されたキャッシュキーのデフォルトのバージョン番号。
詳細については、キャッシュドキュメントを参照してください。
:setting: `KEY_FUNCTION ` プレフィックス、バージョン、およびキーを最終的なキャッシュキーに構成する方法を定義する関数への点線のパスを含む文字列。
詳細については、キャッシュドキュメントを参照してください。
この例では、ファイルシステムバックエンドは60秒のタイムアウトで構成されており、最大容量は1000アイテムです。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
'LOCATION': '/var/tmp/django_cache',
'TIMEOUT': 60,
'OPTIONS': {
'MAX_ENTRIES': 1000
}
}
}
オブジェクトサイズの制限が2MBのpython-memcached
ベースのバックエンドの構成例を次に示します。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'server_max_value_length': 1024 * 1024 * 2,
}
}
}
バイナリプロトコル、SASL認証、およびketama
動作モードを有効にするpylibmc
ベースのバックエンドの構成例を次に示します。
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
'LOCATION': '127.0.0.1:11211',
'OPTIONS': {
'binary': True,
'username': 'user',
'password': 'pass',
'behaviors': {
'ketama': True,
}
}
}
}
サイトごとのキャッシュ
キャッシュを設定したら、キャッシュを使用する最も簡単な方法は、サイト全体をキャッシュすることです。 次の例のように、'django.middleware.cache.UpdateCacheMiddleware'
と'django.middleware.cache.FetchFromCacheMiddleware'
を:setting: `MIDDLEWARE` 設定に追加する必要があります。
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
ノート
いいえ、それはタイプミスではありません。「更新」ミドルウェアがリストの最初にあり、「フェッチ」ミドルウェアが最後になっている必要があります。 詳細は少しわかりにくいですが、完全なストーリーが必要な場合は、以下のミドルウェアの順序を参照してください。
次に、次の必要な設定をDjango設定ファイルに追加します。
- :setting: `CACHE_MIDDLEWARE_ALIAS` –ストレージに使用するキャッシュエイリアス。
- :setting: `CACHE_MIDDLEWARE_SECONDS` –各ページをキャッシュする秒数。
- :setting: `CACHE_MIDDLEWARE_KEY_PREFIX` –キャッシュが同じDjangoインストールを使用する複数のサイト間で共有されている場合は、これをサイトの名前、またはこのDjangoインスタンスに固有の他の文字列に設定して防止しますキーの衝突。 気にしない場合は、空の文字列を使用してください。
FetchFromCacheMiddleware
は、ステータス200のGETおよびHEAD応答をキャッシュします。ここで、要求ヘッダーと応答ヘッダーが許可されます。 クエリパラメータが異なる同じURLのリクエストへの応答は、一意のページと見なされ、個別にキャッシュされます。 このミドルウェアは、HEAD要求が対応するGET要求と同じ応答ヘッダーで応答されることを想定しています。 その場合、HEADリクエストに対してキャッシュされたGET応答を返すことができます。
さらに、UpdateCacheMiddleware
は、各 HttpResponse にいくつかのヘッダーを自動的に設定します。
Expires
ヘッダーを現在の日付/時刻に定義された:setting: `CACHE_MIDDLEWARE_SECONDS` を加えたものに設定します。Cache-Control
ヘッダーを設定して、ページの最大経過時間を指定します。これも、:setting: `CACHE_MIDDLEWARE_SECONDS` 設定からです。
ミドルウェアの詳細については、ミドルウェアを参照してください。
ビューが独自のキャッシュ有効期限を設定する場合(つまり、 Cache-Control
ヘッダーにmax-age
セクションがある場合、ページは:setting: `CACHE_MIDDLEWARE_SECONDS` ではなく、有効期限までキャッシュされます。 django.views.decorators.cache
のデコレータを使用すると、ビューの有効期限を簡単に設定したり( cache_control()デコレータを使用)、ビューのキャッシュを無効にしたり( never_cache()を使用)できます。デコレータ)。 これらのデコレータの詳細については、他のヘッダーの使用セクションを参照してください。
:setting: `USE_I18N` がTrue
に設定されている場合、生成されるキャッシュキーにはアクティブな言語の名前が含まれます- Djangoの検出方法も参照してください言語設定)。 これにより、自分でキャッシュキーを作成しなくても、多言語サイトを簡単にキャッシュできます。
キャッシュキーには、:setting: `USE_L10N` がTrue
に設定されている場合はアクティブな言語、の場合は現在のタイムゾーンも含まれます。 :setting: `USE_TZ` はTrue
に設定されます。
ビューごとのキャッシュ
- 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
は、キャッシュタイムアウト(秒単位)という1つの引数を取ります。 上記の例では、my_view()
ビューの結果が15分間キャッシュされます。 (読みやすくするために、60 * 15
と表記していることに注意してください。 60 * 15
は900
に評価されます。つまり、15分に60秒/分を掛けたものになります。)
サイトごとのキャッシュと同様に、ビューごとのキャッシュはURLからキーオフされます。 複数のURLが同じビューを指している場合、各URLは個別にキャッシュされます。 URLconfが次のようになっている場合は、my_view
の例を続けます。
urlpatterns = [
path('foo/<int:code>/', my_view),
]
次に、/foo/1/
と/foo/23/
へのリクエストは、ご想像のとおり、別々にキャッシュされます。 ただし、特定のURL(/foo/23/
など)が要求されると、そのURLへの後続の要求はキャッシュを使用します。
cache_page
は、オプションのキーワード引数cache
を受け取ることもできます。これは、ビューをキャッシュするときに(:setting: `CACHES` 設定から)特定のキャッシュを使用するようにデコレータに指示します。結果。 デフォルトでは、default
キャッシュが使用されますが、任意のキャッシュを指定できます。
@cache_page(60 * 15, cache="special_cache")
def my_view(request):
...
ビューごとにキャッシュプレフィックスを上書きすることもできます。 cache_page
は、オプションのキーワード引数key_prefix
を取ります。これは、ミドルウェアの:setting: `CACHE_MIDDLEWARE_KEY_PREFIX` 設定と同じように機能します。 これは次のように使用できます。
@cache_page(60 * 15, key_prefix="site1")
def my_view(request):
...
key_prefix
引数とcache
引数は一緒に指定できます。 NSkey_prefix
引数と :setting: `KEY_PREFIX ` 下で指定 :setting: `CACHES` 連結されます。
URLconfでビューごとのキャッシュを指定する
前のセクションの例では、cache_page
がmy_view
関数を変更するため、ビューがキャッシュされるという事実をハードコーディングしています。 このアプローチは、ビューをキャッシュシステムに結合しますが、いくつかの理由で理想的ではありません。 たとえば、別のキャッシュのないサイトでビュー機能を再利用したり、キャッシュせずにビューを使用したい人にビューを配布したりできます。 これらの問題の解決策は、ビュー関数自体の隣ではなく、URLconfでビューごとのキャッシュを指定することです。
これを行うのは簡単です。URLconfで参照するときは、ビュー関数をcache_page
でラップするだけです。 以前の古いURLconfは次のとおりです。
urlpatterns = [
path('foo/<int:code>/', my_view),
]
同じことですが、my_view
がcache_page
でラップされています。
from django.views.decorators.cache import cache_page
urlpatterns = [
path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]
テンプレートフラグメントのキャッシュ
さらに細かく制御したい場合は、cache
テンプレートタグを使用してテンプレートフラグメントをキャッシュすることもできます。 テンプレートにこのタグへのアクセスを許可するには、テンプレートの上部に{% load cache %}
を配置します。
{% cache %}
テンプレートタグは、指定された時間、ブロックのコンテンツをキャッシュします。 少なくとも2つの引数が必要です。秒単位のキャッシュタイムアウトと、キャッシュフラグメントを指定する名前です。 タイムアウトがNone
の場合、フラグメントは永久にキャッシュされます。 名前はそのまま使用されます。変数は使用しないでください。 例えば:
{% load cache %}
{% cache 500 sidebar %}
.. sidebar ..
{% endcache %}
フラグメント内に表示される動的データによっては、フラグメントの複数のコピーをキャッシュしたい場合があります。 たとえば、サイトのすべてのユーザーに対して、前の例で使用したサイドバーの個別のキャッシュコピーが必要な場合があります。 これを行うには、1つ以上の追加の引数(フィルターの有無にかかわらず変数)を{% cache %}
テンプレートタグに渡して、キャッシュフラグメントを一意に識別します。
{% load cache %}
{% cache 500 sidebar request.user.username %}
.. sidebar for logged in user ..
{% endcache %}
:setting: `USE_I18N` がTrue
に設定されている場合、サイトごとのミドルウェアキャッシュはアクティブな言語を尊重します。 cache
テンプレートタグの場合、テンプレートで使用可能な変換固有の変数の1つを使用して、同じ結果を得ることができます。
{% load i18n %}
{% load cache %}
{% get_current_language as LANGUAGE_CODE %}
{% cache 600 welcome LANGUAGE_CODE %}
{% trans "Welcome to example.com" %}
{% endcache %}
テンプレート変数が整数値に解決される限り、キャッシュタイムアウトはテンプレート変数にすることができます。 たとえば、テンプレート変数my_timeout
が値600
に設定されている場合、次の2つの例は同等です。
{% cache 600 sidebar %} ... {% endcache %}
{% cache my_timeout sidebar %} ... {% endcache %}
この機能は、テンプレートでの繰り返しを回避するのに役立ちます。 タイムアウトを変数に1か所で設定し、その値を再利用することができます。
デフォルトでは、キャッシュタグは「template_fragments」と呼ばれるキャッシュを使用しようとします。 そのようなキャッシュが存在しない場合は、デフォルトのキャッシュの使用にフォールバックします。 using
キーワード引数で使用する代替キャッシュバックエンドを選択できます。これは、タグの最後の引数である必要があります。
{% cache 300 local-thing ... using="localcache" %}
構成されていないキャッシュ名を指定すると、エラーと見なされます。
- django.core.cache.utils.make_template_fragment_key(fragment_name, vary_on=None)
キャッシュされたフラグメントに使用されるキャッシュキーを取得する場合は、make_template_fragment_key
を使用できます。 fragment_name
は、cache
テンプレートタグの2番目の引数と同じです。 vary_on
は、タグに渡されるすべての追加引数のリストです。 この関数は、キャッシュされたアイテムを無効化または上書きする場合に役立ちます。次に例を示します。
>>> from django.core.cache import cache
>>> from django.core.cache.utils import make_template_fragment_key
# cache key for {% cache 500 sidebar username %}
>>> key = make_template_fragment_key('sidebar', [username])
>>> cache.delete(key) # invalidates cached template fragment
低レベルキャッシュAPI
場合によっては、レンダリングされたページ全体をキャッシュしてもあまり効果がなく、実際には不便なやり過ぎです。
たとえば、サイトにビューが含まれている場合、その結果はいくつかの高価なクエリに依存しており、その結果はさまざまな間隔で変化します。 この場合、サイトごとまたはビューごとのキャッシュ戦略が提供するフルページキャッシュを使用することは理想的ではありません。これは、結果全体をキャッシュしたくないためです(一部のデータは頻繁に変更されるため)。ただし、めったに変更されない結果をキャッシュする必要があります。
このような場合、Djangoはシンプルな低レベルのキャッシュAPIを公開します。 このAPIを使用して、任意のレベルの粒度でオブジェクトをキャッシュに格納できます。 文字列、辞書、モデルオブジェクトのリストなど、安全にピクルスできるPythonオブジェクトをキャッシュできます。 (最も一般的なPythonオブジェクトはpickle化できます。pickle化の詳細については、Pythonのドキュメントを参照してください。)
キャッシュへのアクセス
- django.core.cache.caches
:setting: `CACHES` 設定で構成されたキャッシュには、dictのようなオブジェクト
django.core.cache.caches
を介してアクセスできます。 同じスレッド内の同じエイリアスに対して繰り返し要求すると、同じオブジェクトが返されます。>>> from django.core.cache import caches >>> cache1 = caches['myalias'] >>> cache2 = caches['myalias'] >>> cache1 is cache2 True
指定されたキーが存在しない場合、
InvalidCacheBackendError
が発生します。スレッドセーフを提供するために、キャッシュバックエンドの異なるインスタンスがスレッドごとに返されます。
- django.core.cache.cache
ショートカットとして、デフォルトのキャッシュは
django.core.cache.cache
として利用できます。>>> from django.core.cache import cache
このオブジェクトは
caches['default']
と同等です。
基本的な使い方
基本的なインターフェースは次のとおりです。
- cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None)
>>> cache.set('my_key', 'hello, world!', 30)
- cache.get(key, default=None, version=None)
>>> cache.get('my_key') 'hello, world!'
key
はstr
である必要があり、value
は任意の選択可能なPythonオブジェクトにすることができます。
timeout
引数はオプションであり、デフォルトでは、:setting: `CACHES` 設定(上記で説明)の適切なバックエンドのtimeout
引数になります。 これは、値をキャッシュに保存する必要がある秒数です。 timeout
にNone
を渡すと、値が永久にキャッシュされます。 0
のtimeout
は、値をキャッシュしません。
オブジェクトがキャッシュに存在しない場合、cache.get()
はNone
を返します。
>>> # Wait 30 seconds for 'my_key' to expire...
>>> cache.get('my_key')
None
リテラル値None
をキャッシュに保存しないことをお勧めします。これは、保存されたNone
値と、戻り値 [ X197X]。
cache.get()
はdefault
引数を取ることができます。 これは、オブジェクトがキャッシュに存在しない場合に返す値を指定します。
>>> cache.get('my_key', 'has expired')
'has expired'
- cache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None)
キーがまだ存在しない場合にのみキーを追加するには、add()
メソッドを使用します。 set()
と同じパラメーターを取りますが、指定されたキーがすでに存在する場合、キャッシュの更新を試みません。
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
add()
がキャッシュに値を格納したかどうかを知る必要がある場合は、戻り値を確認できます。 値が保存されている場合はTrue
を返し、それ以外の場合はFalse
を返します。
- cache.get_or_set(key, default, timeout=DEFAULT_TIMEOUT, version=None)
キーの値を取得したい場合、またはキーがキャッシュにない場合に値を設定したい場合は、get_or_set()
メソッドがあります。 get()
と同じパラメーターを取りますが、デフォルトは、単に返されるのではなく、そのキーの新しいキャッシュ値として設定されます。
>>> cache.get('my_new_key') # returns None
>>> cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'
呼び出し可能オブジェクトをデフォルト値として渡すこともできます。
>>> import datetime
>>> cache.get_or_set('some-timestamp-key', datetime.datetime.now)
datetime.datetime(2014, 12, 11, 0, 15, 49, 457920)
- cache.get_many(keys, version=None)
キャッシュに一度だけヒットする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_many(dict, timeout)
複数の値をより効率的に設定するには、set_many()
を使用して、キーと値のペアのディクショナリを渡します。
>>> cache.set_many({'a': 1, 'b': 2, 'c': 3})
>>> cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}
cache.set()
と同様に、set_many()
はオプションのtimeout
パラメーターを取ります。
サポートされているバックエンド(memcached)では、set_many()
は挿入に失敗したキーのリストを返します。
- cache.delete(key, version=None)
delete()
を使用してキーを明示的に削除できます。 これは、特定のオブジェクトのキャッシュをクリアする簡単な方法です。
>>> cache.delete('a')
- cache.delete_many(keys, version=None)
一度に多数のキーをクリアしたい場合、delete_many()
はクリアするキーのリストを取得できます。
>>> cache.delete_many(['a', 'b', 'c'])
- cache.clear()
最後に、キャッシュ内のすべてのキーを削除する場合は、cache.clear()
を使用します。 これには注意してください。 clear()
は、アプリケーションによって設定されたキーだけでなく、すべてをキャッシュから削除します。
>>> cache.clear()
- cache.touch(key, timeout=DEFAULT_TIMEOUT, version=None)
バージョン2.1の新機能。
cache.touch()
は、キーの新しい有効期限を設定します。 たとえば、キーを更新して今から10秒後に有効期限が切れるようにするには:
>>> cache.touch('a', 10)
True
他の方法と同様に、timeout
引数はオプションであり、デフォルトでは:setting: `CACHES` 設定の適切なバックエンドのTIMEOUT
オプションになります。
touch()
は、キーが正常にタッチされた場合はTrue
を返し、それ以外の場合はFalse
を返します。
- cache.incr(key, delta=1, version=None)
- cache.decr(key, delta=1, version=None)
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
ノート
incr()
/ decr()
メソッドはアトミックであることが保証されていません。 アトミックなインクリメント/デクリメントをサポートするバックエンド(特にmemcachedバックエンド)では、インクリメントおよびデクリメント操作はアトミックになります。 ただし、バックエンドがインクリメント/デクリメント操作をネイティブに提供しない場合は、2段階の取得/更新を使用して実装されます。
- cache.close()
キャッシュバックエンドによって実装されている場合は、close()
を使用してキャッシュへの接続を閉じることができます。
>>> cache.close()
ノート
close
メソッドを実装していないキャッシュの場合、これはノーオペレーションです。
キャッシュキーのプレフィックス
サーバー間、または本番環境と開発環境間でキャッシュインスタンスを共有している場合、あるサーバーによってキャッシュされたデータが別のサーバーによって使用される可能性があります。 キャッシュされたデータの形式がサーバー間で異なる場合、問題の診断が非常に困難になる可能性があります。
これを防ぐために、Djangoはサーバーが使用するすべてのキャッシュキーにプレフィックスを付ける機能を提供します。 特定のキャッシュキーが保存または取得されると、Djangoは自動的にキャッシュキーの前に :setting: `KEY_PREFIX ` キャッシュ設定。
各Djangoインスタンスが異なることを確認することによって :setting: `KEY_PREFIX ` 、キャッシュ値に衝突がないことを確認できます。
キャッシュのバージョン管理
キャッシュされた値を使用する実行中のコードを変更する場合、既存のキャッシュされた値を削除する必要がある場合があります。 これを行う最も簡単な方法は、キャッシュ全体をフラッシュすることですが、これにより、まだ有効で有用なキャッシュ値が失われる可能性があります。
Djangoは、個々のキャッシュ値をターゲットにするためのより良い方法を提供します。 Djangoのキャッシュフレームワークには、システム全体のバージョン識別子があり、 :setting: `VERSION ` キャッシュ設定。 この設定の値は、キャッシュプレフィックスおよびユーザー提供のキャッシュキーと自動的に組み合わされて、最終的なキャッシュキーを取得します。
デフォルトでは、キーリクエストにはサイトのデフォルトのキャッシュキーバージョンが自動的に含まれます。 ただし、プリミティブキャッシュ関数にはすべて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 '%s:%s:%s' % (key_prefix, version, key)
パーツをさまざまな方法で組み合わせる場合、または最終キーに他の処理を適用する場合(たとえば、キーパーツのハッシュダイジェストを取得する場合)、カスタムキー関数を提供できます。
NS :setting: `KEY_FUNCTION ` キャッシュ設定は、のプロトタイプに一致する関数へのドットパスを指定しますmake_key()
その上。 提供されている場合、このカスタムキー機能は、デフォルトのキー結合機能の代わりに使用されます。
キャッシュキーの警告
最も一般的に使用される本番キャッシュバックエンドであるMemcachedは、250文字を超える、または空白や制御文字を含むキャッシュキーを許可しません。そのようなキーを使用すると、例外が発生します。 キャッシュポータブルコードを奨励し、不快な驚きを最小限に抑えるために、memcachedでエラーを引き起こすキーが使用された場合、他の組み込みキャッシュバックエンドは警告(django.core.cache.backends.base.CacheKeyWarning
)を発行します。
より広い範囲のキーを受け入れることができる本番バックエンド(カスタムバックエンド、またはmemcached以外の組み込みバックエンドの1つ)を使用していて、警告なしにこのより広い範囲を使用したい場合は、 [を無音にすることができます。 X214X] :setting: `INSTALLED_APPS` のいずれかのmanagement
モジュールにこのコードが含まれています。
import warnings
from django.core.cache import CacheKeyWarning
warnings.simplefilter("ignore", CacheKeyWarning)
代わりに、組み込みバックエンドの1つにカスタムキー検証ロジックを提供する場合は、それをサブクラス化し、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パスを使用します :setting: `バックエンド ` あなたの一部 :setting: `CACHES` 設定。
ダウンストリームキャッシュ
これまでのところ、このドキュメントでは、独自のデータのキャッシュに焦点を当ててきました。 しかし、別のタイプのキャッシングもWeb開発に関連しています。それは、「ダウンストリーム」キャッシュによって実行されるキャッシングです。 これらは、リクエストがWebサイトに到達する前でもユーザーのページをキャッシュするシステムです。
ダウンストリームキャッシュの例を次に示します。
- ISPは特定のページをキャッシュする場合があるため、 https://example.com/ からページをリクエストした場合、ISPはexample.comに直接アクセスしなくてもページを送信します。 example.comのメンテナは、このキャッシュについての知識がありません。 ISPはexample.comとWebブラウザの間に位置し、すべてのキャッシュを透過的に処理します。
- Django Webサイトは、パフォーマンスのためにページをキャッシュするSquid Web Proxy Cache( http://www.squid-cache.org/ )などのプロキシキャッシュの背後にある場合があります。 この場合、各リクエストは最初にプロキシによって処理され、必要な場合にのみアプリケーションに渡されます。
- Webブラウザもページをキャッシュします。 Webページが適切なヘッダーを送信する場合、ブラウザは、Webページに再度アクセスして変更されているかどうかを確認することなく、そのページへの後続の要求にローカルのキャッシュコピーを使用します。
ダウンストリームキャッシュは効率を大幅に向上させますが、危険が伴います。多くのWebページのコンテンツは認証や他の多くの変数に基づいて異なり、純粋にURLに基づいてページを盲目的に保存するキャッシュシステムは、誤ったデータや機密データを後続のデータに公開する可能性があります。それらのページへの訪問者。
たとえば、Webメールシステムを運用していて、「受信トレイ」ページのコンテンツは明らかにどのユーザーがログインしているかによって異なります。 ISPが盲目的にサイトをキャッシュした場合、そのISPを介してログインした最初のユーザーは、サイトへの後続の訪問者のためにユーザー固有の受信ボックスページをキャッシュします。 それはクールではない。
幸い、HTTPはこの問題の解決策を提供します。 指定された変数に応じてキャッシュの内容を異なるようにダウンストリームキャッシュに指示し、特定のページをキャッシュしないようにキャッシュメカニズムに指示するために、多数のHTTPヘッダーが存在します。 次のセクションでは、これらのヘッダーのいくつかを見ていきます。
Varyヘッダーの使用
Vary
ヘッダーは、キャッシュキーを構築するときにキャッシュメカニズムが考慮すべき要求ヘッダーを定義します。 たとえば、Webページのコンテンツがユーザーの言語設定に依存する場合、そのページは「言語によって異なる」と言われます。
デフォルトでは、Djangoのキャッシュシステムは、要求された完全修飾URL("https://www.example.com/stories/2005/?order_by=author%22
など)を使用してキャッシュキーを作成します。 つまり、Cookieや言語設定などのユーザーエージェントの違いに関係なく、そのURLへのすべてのリクエストは同じキャッシュバージョンを使用します。 ただし、このページがCookie、言語、ユーザーエージェントなどのリクエストヘッダーの違いに基づいて異なるコンテンツを生成する場合は、Vary
ヘッダーを使用してキャッシュメカニズムに次のことを伝える必要があります。ページ出力はそれらに依存します。
Djangoでこれを行うには、次のように、便利な django.views.decorators.vary.vary_on_headers()ビューデコレータを使用します。
from django.views.decorators.vary import vary_on_headers
@vary_on_headers('User-Agent')
def my_view(request):
...
この場合、キャッシュメカニズム(Django独自のキャッシュミドルウェアなど)は、一意のユーザーエージェントごとにページの個別のバージョンをキャッシュします。
Vary
ヘッダーを手動で設定するのではなくvary_on_headers
デコレータを使用する利点(response['Vary'] = 'user-agent'
などを使用)は、デコレータがVary
ヘッダー(すでに存在している可能性があります)。最初から設定して、すでにそこにあるものをオーバーライドする可能性はありません。
vary_on_headers()
に複数のヘッダーを渡すことができます。
@vary_on_headers('User-Agent', 'Cookie')
def my_view(request):
...
これは、ダウンストリームキャッシュが両方で変化するように指示します。つまり、ユーザーエージェントとCookieの各組み合わせが独自のキャッシュ値を取得します。 たとえば、ユーザーエージェントMozilla
とCookie値foo=bar
を使用したリクエストは、ユーザーエージェントMozilla
とCookie値foo=ham
。
Cookieによって異なることが非常に一般的であるため、 django.views.decorators.vary.vary_on_cookie()デコレータがあります。 これらの2つのビューは同等です。
@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()を直接使用することもできます。 この関数は、Vary header
を設定または追加します。 例えば:
from django.shortcuts import render
from django.utils.cache import patch_vary_headers
def my_view(request):
...
response = render(request, 'template_name', context)
patch_vary_headers(response, ['Cookie'])
return response
patch_vary_headers
は、最初の引数として HttpResponse インスタンスを取り、2番目の引数として大文字と小文字を区別しないヘッダー名のリスト/タプルを取ります。
Varyヘッダーの詳細については、 公式のVary仕様を参照してください。
キャッシュの制御:他のヘッダーの使用
キャッシュに関するその他の問題は、データのプライバシーと、データをキャッシュのカスケードのどこに保存するかという問題です。
ユーザーは通常、独自のブラウザーキャッシュ(プライベートキャッシュ)とプロバイダーのキャッシュ(パブリックキャッシュ)の2種類のキャッシュに直面します。 パブリックキャッシュは複数のユーザーによって使用され、他の誰かによって制御されます。 これは機密データに問題を引き起こします。たとえば、銀行口座番号をパブリックキャッシュに保存したくない場合です。 したがって、Webアプリケーションには、どのデータがプライベートでどれがパブリックであるかをキャッシュに伝える方法が必要です。
解決策は、ページのキャッシュを「プライベート」にする必要があることを示すことです。 Djangoでこれを行うには、 cache_control()ビューデコレータを使用します。 例:
from django.views.decorators.cache import cache_control
@cache_control(private=True)
def my_view(request):
...
このデコレータは、適切なHTTPヘッダーをバックグラウンドで送信します。
キャッシュ制御設定「private」と「public」は相互に排他的であることに注意してください。 デコレータは、「private」を設定する必要がある場合(およびその逆)に「public」ディレクティブが削除されるようにします。 2つのディレクティブの使用例は、プライベートエントリとパブリックエントリの両方を提供するブログサイトです。 パブリックエントリは、任意の共有キャッシュにキャッシュできます。 次のコードは、 patch_cache_control()を使用します。これは、キャッシュ制御ヘッダーを手動で変更する方法です( 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キャッシングの詳細については、 RFC 7234 を参照してください)。 たとえば、Djangoのサーバー側キャッシュフレームワークを使用しない場合でも、 max-age ディレクティブを使用して、一定時間ビューをキャッシュするようにクライアントに指示できます。 :
from django.views.decorators.cache import cache_control
@cache_control(max_age=3600)
def my_view(request):
...
( do がキャッシングミドルウェアを使用する場合、max-age
は:setting: `CACHE_MIDDLEWARE_SECONDS` 設定の値ですでに設定されています。 その場合、 cache_control()デコレータのカスタムmax_age
が優先され、ヘッダー値が正しくマージされます。)
有効なCache-Control
応答ディレクティブは、cache_control()
で有効です。 さらにいくつかの例を示します。
no_transform=True
must_revalidate=True
stale_while_revalidate=num_seconds
既知のディレクティブの完全なリストは、 IANAレジストリにあります(すべてが応答に適用されるわけではないことに注意してください)。
ヘッダーを使用してキャッシュを完全に無効にする場合、 never_cache()は、ブラウザーや他のキャッシュによって応答がキャッシュされないようにヘッダーを追加するビューデコレーターです。 例:
from django.views.decorators.cache import never_cache
@never_cache
def myview(request):
...
MIDDLEWAREの順序
キャッシングミドルウェアを使用する場合は、:setting: `MIDDLEWARE` 設定内の適切な場所に各半分を配置することが重要です。 これは、キャッシュミドルウェアがキャッシュストレージを変更するヘッダーを知る必要があるためです。 ミドルウェアは、可能な場合は常にVary
応答ヘッダーに何かを追加します。
UpdateCacheMiddleware
は応答フェーズで実行され、ミドルウェアは逆の順序で実行されるため、リストの一番上にあるアイテムは応答フェーズで last を実行します。 したがって、UpdateCacheMiddleware
がVary
ヘッダーに何かを追加する可能性のある他のミドルウェアのの前に表示されることを確認する必要があります。 次のミドルウェアモジュールはそうします:
SessionMiddleware
はCookie
を追加しますGZipMiddleware
はAccept-Encoding
を追加しますLocaleMiddleware
はAccept-Language
を追加します
一方、FetchFromCacheMiddleware
は、ミドルウェアが最初から最後に適用される要求フェーズで実行されるため、リストの一番上にあるアイテムは、要求フェーズで最初を実行します。 FetchFromCacheMiddleware
は、他のミドルウェアがVary
ヘッダーを更新した後にも実行する必要があるため、FetchFromCacheMiddleware
は after である必要があります。