Django Cache
GitHub概要
django/django
The Web framework for perfectionists with deadlines.
スター85,495
ウォッチ2,277
フォーク33,123
作成日:2012年4月28日
言語:Python
ライセンス:BSD 3-Clause "New" or "Revised" License
トピックス
appsdjangoframeworkmodelsormpythontemplatesviewsweb
スター履歴
データ取得日時: 2025/10/22 10:04
ライブラリ
Django Cache
概要
Django Cacheは、Djangoフレームワークに組み込まれた包括的なキャッシングシステムです。複数のキャッシュバックエンドをサポートし、サイト全体、ビュー単位、テンプレートフラグメント単位でのキャッシング機能を提供します。
詳細
Django Cacheは、動的Webアプリケーションのパフォーマンスを大幅に向上させるために設計された、柔軟で強力なキャッシングフレームワークです。主要なキャッシュバックエンドとして、Local Memory(locmem)、Database、File-based、Memcached(PyMemcache、PyLibMC)、Redis、Dummyキャッシュをサポートしています。3つのレベルのキャッシングが可能で、ページ全体をキャッシュするサイト全体キャッシュ、個別のビュー出力をキャッシュするビューレベルキャッシュ、テンプレートの特定部分をキャッシュするテンプレートフラグメントキャッシュがあります。cache_page装飾子、vary_on_headers、cache_control装飾子など、豊富な装飾子により細かい制御が可能です。キャッシュキーの生成、バージョニング、タイムアウト設定、キャッシュ無効化など、エンタープライズレベルの機能を提供します。非同期APIも導入され、async/awaitパターンでの使用もサポートしています。
メリット・デメリット
メリット
- フレームワーク統合: Djangoに完全統合されており、設定と使用が簡単
- 複数バックエンド: Memcached、Redis、データベース、ファイルベースなど豊富な選択肢
- 柔軟なキャッシュレベル: サイト全体、ビュー、テンプレートフラグメント単位での制御
- 豊富な装飾子: cache_page、vary_on_headers、never_cacheなどの便利な装飾子
- キャッシュキー管理: バージョニング、プレフィックス、カスタムキー生成をサポート
- 非同期サポート: async/awaitパターンでのキャッシュ操作が可能
- 開発環境サポート: DummyキャッシュやLocal Memoryで開発時の利便性を提供
デメリット
- 設定の複雑さ: 大規模プロジェクトでは複数キャッシュの設定が複雑になる場合がある
- バックエンド依存: 選択したバックエンドの制限や特性に依存する
- デバッグの困難さ: キャッシュが有効な状態でのデバッグが困難になる場合がある
- メモリ使用量: Local Memoryキャッシュはプロセス固有でメモリ効率が悪い
- ファイルベースの性能: File-basedキャッシュは書き込み操作で性能低下が顕著
- キーの制限: Memcachedなど一部バックエンドではキーの文字数や文字種に制限がある
主要リンク
- Django Cache Framework公式ドキュメント
- Django Cache Settings
- Django Cache API Reference
- Django Cache Decorators
- Django Template Fragment Caching
- Django Cache Versioning
書き方の例
基本的なキャッシュ設定
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379',
'OPTIONS': {
'db': '1',
},
'TIMEOUT': 300, # 5分のデフォルトタイムアウト
'KEY_PREFIX': 'myapp',
'VERSION': 1,
},
'sessions': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
'TIMEOUT': 86400, # 24時間
},
'localcache': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
'TIMEOUT': 300,
'OPTIONS': {
'MAX_ENTRIES': 1000,
}
}
}
# キャッシュミドルウェアの設定(サイト全体キャッシュ)
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
]
CACHE_MIDDLEWARE_ALIAS = 'default'
CACHE_MIDDLEWARE_SECONDS = 600
CACHE_MIDDLEWARE_KEY_PREFIX = 'site'
低レベルキャッシュAPI
from django.core.cache import cache, caches
# デフォルトキャッシュの使用
def get_user_profile(user_id):
cache_key = f'user_profile_{user_id}'
# キャッシュから取得を試行
profile = cache.get(cache_key)
if profile is None:
# キャッシュミスの場合、データベースから取得
profile = UserProfile.objects.get(user_id=user_id)
# キャッシュに保存(1時間)
cache.set(cache_key, profile, 3600)
return profile
# 複数キーの操作
def get_multiple_users(user_ids):
cache_keys = [f'user_{uid}' for uid in user_ids]
# 複数のキーを一度に取得
cached_users = cache.get_many(cache_keys)
# キャッシュにないユーザーを特定
missing_ids = []
for uid in user_ids:
if f'user_{uid}' not in cached_users:
missing_ids.append(uid)
# 不足分をデータベースから取得
if missing_ids:
fresh_users = User.objects.filter(id__in=missing_ids)
fresh_data = {f'user_{u.id}': u for u in fresh_users}
# キャッシュに保存
cache.set_many(fresh_data, timeout=1800)
cached_users.update(fresh_data)
return list(cached_users.values())
# 特定のキャッシュバックエンドを使用
session_cache = caches['sessions']
session_cache.set('session_data', session_info, 86400)
ビューレベルキャッシュ
from django.views.decorators.cache import cache_page, never_cache
from django.views.decorators.vary import vary_on_headers, vary_on_cookie
# ページ全体を15分間キャッシュ
@cache_page(60 * 15)
def product_list(request):
products = Product.objects.filter(is_active=True)
return render(request, 'products/list.html', {'products': products})
# 特定のキャッシュバックエンドを使用
@cache_page(60 * 30, cache='localcache')
def heavy_computation_view(request):
result = perform_heavy_computation()
return JsonResponse({'result': result})
# ヘッダーに基づくキャッシュの分離
@vary_on_headers('User-Agent', 'Accept-Language')
@cache_page(60 * 60)
def responsive_view(request):
return render(request, 'responsive.html')
# Cookieに基づくキャッシュの分離(ユーザー固有コンテンツ)
@vary_on_cookie
@cache_page(60 * 10)
def user_dashboard(request):
if request.user.is_authenticated:
data = get_user_specific_data(request.user)
return render(request, 'dashboard.html', {'data': data})
else:
return redirect('login')
# キャッシュを無効化
@never_cache
def real_time_data(request):
current_time = timezone.now()
return JsonResponse({'timestamp': current_time.isoformat()})
テンプレートフラグメントキャッシュ
<!-- templates/product_detail.html -->
{% load cache %}
<div class="product-detail">
<h1>{{ product.name }}</h1>
<!-- 商品説明を30分間キャッシュ -->
{% cache 1800 product_description product.id %}
<div class="description">
{{ product.description|markdown }}
</div>
{% endcache %}
<!-- ユーザー固有のレビューセクション -->
{% cache 600 user_reviews product.id user.id %}
<div class="user-reviews">
{% for review in user_reviews %}
<div class="review">{{ review.content }}</div>
{% endfor %}
</div>
{% endcache %}
<!-- 言語別の価格表示 -->
{% get_current_language as LANGUAGE_CODE %}
{% cache 3600 product_price product.id LANGUAGE_CODE %}
<div class="price">
{% if LANGUAGE_CODE == 'ja' %}
¥{{ product.price_jpy|floatformat:0 }}
{% else %}
${{ product.price_usd|floatformat:2 }}
{% endif %}
</div>
{% endcache %}
</div>
キャッシュ無効化とクリア
from django.core.cache import cache
from django.core.cache.utils import make_template_fragment_key
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
def update(self, request, *args, **kwargs):
response = super().update(request, *args, **kwargs)
# 更新時にキャッシュを無効化
product = self.get_object()
# ビューレベルキャッシュの無効化
cache_key = f'product_detail_{product.id}'
cache.delete(cache_key)
# テンプレートフラグメントキャッシュの無効化
fragment_key = make_template_fragment_key(
'product_description',
[product.id]
)
cache.delete(fragment_key)
# パターンマッチングによる関連キャッシュの削除
pattern_keys = cache.keys(f'product_{product.id}_*')
if pattern_keys:
cache.delete_many(pattern_keys)
return response
def create(self, request, *args, **kwargs):
response = super().create(request, *args, **kwargs)
# 商品リストのキャッシュをクリア
cache.delete('product_list_cache')
return response
# カスタム管理コマンドでキャッシュクリア
# management/commands/clear_cache.py
from django.core.management.base import BaseCommand
from django.core.cache import cache
class Command(BaseCommand):
help = 'Clear all cache'
def add_arguments(self, parser):
parser.add_argument(
'--pattern',
type=str,
help='Clear cache keys matching pattern'
)
def handle(self, *args, **options):
if options['pattern']:
keys = cache.keys(options['pattern'])
cache.delete_many(keys)
self.stdout.write(f'Deleted {len(keys)} cache keys')
else:
cache.clear()
self.stdout.write('All cache cleared')
非同期キャッシュ操作
from django.core.cache import cache
from asgiref.sync import sync_to_async
# 非同期ビューでのキャッシュ使用
async def async_product_detail(request, product_id):
cache_key = f'product_{product_id}'
# 非同期でキャッシュから取得
product = await cache.aget(cache_key)
if product is None:
# データベースから非同期取得
product = await Product.objects.aget(id=product_id)
# 非同期でキャッシュに保存
await cache.aset(cache_key, product, 3600)
return JsonResponse({
'id': product.id,
'name': product.name,
'price': product.price
})
# 非同期での複数キー操作
async def async_multiple_products(request):
product_ids = request.GET.getlist('ids')
cache_keys = [f'product_{pid}' for pid in product_ids]
# 複数キーを非同期で取得
cached_products = await cache.aget_many(cache_keys)
return JsonResponse({
'products': list(cached_products.values())
})
# カスタム非同期キャッシュヘルパー
class AsyncCacheHelper:
@staticmethod
async def get_or_set_async(key, async_callable, timeout=3600):
"""非同期版のget_or_set"""
value = await cache.aget(key)
if value is None:
value = await async_callable()
await cache.aset(key, value, timeout)
return value
@staticmethod
async def invalidate_pattern_async(pattern):
"""パターンマッチングによる非同期削除"""
# 同期メソッドを非同期化
keys = await sync_to_async(cache.keys)(pattern)
if keys:
await cache.adelete_many(keys)
return len(keys)
キャッシュバージョニングとキー管理
from django.core.cache import cache
from django.conf import settings
class VersionedCache:
def __init__(self, version=None):
self.version = version or getattr(settings, 'CACHE_VERSION', 1)
def make_key(self, key, version=None):
version = version or self.version
return f'v{version}:{key}'
def get(self, key, default=None, version=None):
versioned_key = self.make_key(key, version)
return cache.get(versioned_key, default)
def set(self, key, value, timeout=None, version=None):
versioned_key = self.make_key(key, version)
return cache.set(versioned_key, value, timeout)
def delete(self, key, version=None):
versioned_key = self.make_key(key, version)
return cache.delete(versioned_key)
def incr_version(self, key):
"""キーのバージョンを増分"""
return cache.incr_version(key)
# 使用例
vcache = VersionedCache(version=2)
vcache.set('user_data', user_info, 3600)
user_data = vcache.get('user_data')
# バージョンアップによる全キャッシュ無効化
cache.incr_version('user_data') # バージョン3になり、過去のキャッシュは無効化