Кэширование в Django
Кэширование значительно улучшает производительность Django приложений, снижает нагрузку на базу данных и ускоряет отклик для пользователей. Django предоставляет гибкую систему кэширования с поддержкой различных бэкендов.
Настройка кэша в settings.py
1# Базовые настройки кэширования
2CACHES = {
3 'default': {
4 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
5 'LOCATION': 'redis://127.0.0.1:6379/1',
6 'OPTIONS': {
7 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
8 'CONNECTION_POOL_KWARGS': {
9 'max_connections': 50,
10 'retry_on_timeout': True,
11 },
12 'SOCKET_CONNECT_TIMEOUT': 5,
13 'SOCKET_TIMEOUT': 5,
14 },
15 'KEY_PREFIX': 'myapp',
16 'TIMEOUT': 300, # 5 минут по умолчанию
17 },
18 'session': {
19 'BACKEND': 'django.core.cache.backends.redis.RedisCache',
20 'LOCATION': 'redis://127.0.0.1:6379/2',
21 'KEY_PREFIX': 'session',
22 'TIMEOUT': 86400, # 24 часа
23 },
24 'database': {
25 'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
26 'LOCATION': 'cache_table',
27 'TIMEOUT': 600, # 10 минут
28 },
29 'local': {
30 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
31 'LOCATION': 'unique-snowflake',
32 'TIMEOUT': 300,
33 }
34}
35
36# Настройки сессий с кэшем
37SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
38SESSION_CACHE_ALIAS = 'session'
39
40# Кэширование шаблонов
41TEMPLATE_LOADERS = (
42 ('django.template.loaders.cached.Loader', (
43 'django.template.loaders.filesystem.Loader',
44 'django.template.loaders.app_directories.Loader',
45 )),
46)
Установка и настройка Redis
1# requirements.txt или pyproject.toml
2django-redis==5.4.0
3redis==5.0.1
4
5# Настройка Redis в Django
6CACHES = {
7 'default': {
8 'BACKEND': 'django_redis.cache.RedisCache',
9 'LOCATION': 'redis://127.0.0.1:6379/1',
10 'OPTIONS': {
11 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
12 'PARSER_CLASS': 'redis.connection.HiredisParser',
13 'CONNECTION_POOL_CLASS': 'redis.connection.BlockingConnectionPool',
14 'CONNECTION_POOL_CLASS_KWARGS': {
15 'max_connections': 50,
16 'timeout': 20,
17 },
18 'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
19 },
20 'KEY_PREFIX': 'myapp',
21 'TIMEOUT': 300,
22 }
23}
24
25# Настройка для продакшена
26CACHES = {
27 'default': {
28 'BACKEND': 'django_redis.cache.RedisCache',
29 'LOCATION': [
30 'redis://redis1:6379/1',
31 'redis://redis2:6379/1',
32 'redis://redis3:6379/1',
33 ],
34 'OPTIONS': {
35 'CLIENT_CLASS': 'django_redis.client.ShardClient',
36 'CONNECTION_POOL_CLASS': 'redis.connection.BlockingConnectionPool',
37 'CONNECTION_POOL_CLASS_KWARGS': {
38 'max_connections': 100,
39 'timeout': 20,
40 },
41 },
42 }
43}
Настройка Memcached
1# Установка зависимостей
2pip install python-memcached
3
4# Настройка в settings.py
5CACHES = {
6 'default': {
7 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
8 'LOCATION': [
9 '127.0.0.1:11211',
10 '127.0.0.1:11212',
11 ],
12 'OPTIONS': {
13 'MAX_ENTRIES': 10000,
14 'CULL_FREQUENCY': 3,
15 },
16 'KEY_PREFIX': 'myapp',
17 'TIMEOUT': 300,
18 }
19}
20
21# Настройка с Unix socket
22CACHES = {
23 'default': {
24 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
25 'LOCATION': 'unix:/tmp/memcached.sock',
26 }
27}
Базовые операции с кэшем
1from django.core.cache import cache
2from django.core.cache import caches
3
4# Получение кэша по умолчанию
5cache.set('my_key', 'my_value', timeout=300)
6value = cache.get('my_key')
7cache.delete('my_key')
8cache.clear()
9
10# Работа с конкретным кэшем
11session_cache = caches['session']
12session_cache.set('user_session', user_data, timeout=86400)
13
14# Проверка существования ключа
15if cache.get('my_key') is None:
16 # Ключ не существует, создаем его
17 cache.set('my_key', 'new_value', timeout=300)
18
19# Увеличение счетчика
20cache.incr('counter', delta=1)
21cache.decr('counter', delta=1)
22
23# Установка значения только если ключ не существует
24cache.add('unique_key', 'value', timeout=300)
25
26# Получение нескольких ключей
27values = cache.get_many(['key1', 'key2', 'key3'])
28
29# Установка нескольких ключей
30cache.set_many({
31 'key1': 'value1',
32 'key2': 'value2',
33 'key3': 'value3'
34}, timeout=300)
Кэширование в views
1from django.core.cache import cache
2from django.views.decorators.cache import cache_page
3from django.utils.decorators import method_decorator
4from django.views.generic import ListView
5from django.shortcuts import render
6import hashlib
7import json
8
9# Кэширование функции-представления
10@cache_page(60 * 15) # 15 минут
11def expensive_view(request):
12 # Этот view будет кэшироваться на 15 минут
13 data = perform_expensive_operation()
14 return render(request, 'template.html', {'data': data})
15
16# Кэширование с параметрами
17def cached_view_with_params(request, category_id):
18 cache_key = f'category_{category_id}_data'
19 data = cache.get(cache_key)
20
21 if data is None:
22 # Данные не в кэше, получаем из БД
23 data = Category.objects.get(id=category_id).get_expensive_data()
24 cache.set(cache_key, data, timeout=600)
25
26 return render(request, 'category.html', {'data': data})
27
28# Кэширование class-based view
29@method_decorator(cache_page(60 * 30), name='dispatch')
30class CachedListView(ListView):
31 model = Article
32 template_name = 'article_list.html'
33 context_object_name = 'articles'
34
35# Кэширование с динамическим ключом
36def user_specific_view(request):
37 user_id = request.user.id
38 cache_key = f'user_{user_id}_dashboard_data'
39
40 data = cache.get(cache_key)
41 if data is None:
42 data = generate_user_dashboard_data(request.user)
43 cache.set(cache_key, data, timeout=300)
44
45 return render(request, 'dashboard.html', {'data': data})
46
47# Кэширование с версионированием
48def versioned_cache_view(request):
49 version = cache.get('data_version', 1)
50 cache_key = f'data_v{version}'
51
52 data = cache.get(cache_key)
53 if data is None:
54 data = fetch_latest_data()
55 cache.set(cache_key, data, timeout=3600)
56
57 return render(request, 'data.html', {'data': data})
58
59# Инвалидация кэша при обновлении данных
60def update_data_and_invalidate_cache(request):
61 # Обновляем данные
62 data = update_some_data()
63
64 # Инвалидируем кэш
65 cache.delete('cached_data_key')
66
67 # Или обновляем версию
68 cache.incr('data_version', delta=1)
69
70 return JsonResponse({'status': 'success'})
Кэширование ORM запросов
1from django.core.cache import cache
2from django.db import models
3from django.db.models import Q
4import hashlib
5
6class ArticleManager(models.Manager):
7 def get_cached_articles(self, category=None, limit=10):
8 # Создаем уникальный ключ для кэша
9 cache_key_parts = ['articles', 'list']
10 if category:
11 cache_key_parts.append(f'cat_{category.id}')
12 cache_key_parts.append(f'limit_{limit}')
13
14 cache_key = '_'.join(cache_key_parts)
15
16 # Пытаемся получить из кэша
17 articles = cache.get(cache_key)
18 if articles is None:
19 # Получаем из БД
20 queryset = self.filter(is_published=True)
21 if category:
22 queryset = queryset.filter(category=category)
23
24 articles = list(queryset.select_related('author', 'category')[:limit])
25
26 # Кэшируем результат
27 cache.set(cache_key, articles, timeout=1800) # 30 минут
28
29 return articles
30
31 def get_cached_article(self, article_id):
32 cache_key = f'article_detail_{article_id}'
33 article = cache.get(cache_key)
34
35 if article is None:
36 article = self.select_related('author', 'category').get(id=article_id)
37 cache.set(cache_key, article, timeout=3600) # 1 час
38
39 return article
40
41class Article(models.Model):
42 title = models.CharField(max_length=200)
43 content = models.TextField()
44 author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
45 category = models.ForeignKey('Category', on_delete=models.CASCADE)
46 is_published = models.BooleanField(default=False)
47 created_at = models.DateTimeField(auto_now_add=True)
48
49 objects = ArticleManager()
50
51 def save(self, *args, **kwargs):
52 # Инвалидируем кэш при сохранении
53 super().save(*args, **kwargs)
54 self.invalidate_cache()
55
56 def invalidate_cache(self):
57 # Удаляем связанные кэши
58 cache_keys = [
59 f'article_detail_{self.id}',
60 f'articles_cat_{self.category.id}_limit_10',
61 'articles_list_limit_10',
62 ]
63
64 for key in cache_keys:
65 cache.delete(key)
66
67# Использование в views
68def article_list_view(request):
69 category_id = request.GET.get('category')
70 category = None
71
72 if category_id:
73 category = Category.objects.get(id=category_id)
74
75 articles = Article.objects.get_cached_articles(category=category, limit=20)
76 return render(request, 'article_list.html', {'articles': articles, 'category': category})
Кэширование шаблонов
1# В settings.py
2TEMPLATE_LOADERS = (
3 ('django.template.loaders.cached.Loader', (
4 'django.template.loaders.filesystem.Loader',
5 'django.template.loaders.app_directories.Loader',
6 )),
7)
8
9# В шаблонах
10{% load cache %}
11
12<!-- Кэширование блока на 1 час -->
13{% cache 3600 sidebar %}
14 <div class="sidebar">
15 <h3>Популярные статьи</h3>
16 {% for article in popular_articles %}
17 <div class="article-item">
18 <h4>{{ article.title }}</h4>
19 <p>{{ article.excerpt }}</p>
20 </div>
21 {% endfor %}
22 </div>
23{% endcache %}
24
25<!-- Кэширование с переменными -->
26{% cache 1800 user_profile request.user.id %}
27 <div class="user-profile">
28 <h2>{{ user.get_full_name }}</h2>
29 <p>Email: {{ user.email }}</p>
30 <p>Дата регистрации: {{ user.date_joined|date:"d.m.Y" }}</p>
31 </div>
32{% endcache %}
33
34<!-- Кэширование с множественными переменными -->
35{% cache 3600 article_list category.id page_number %}
36 <div class="article-list">
37 {% for article in articles %}
38 <article>
39 <h3>{{ article.title }}</h3>
40 <p>{{ article.excerpt }}</p>
41 </article>
42 {% endfor %}
43 </div>
44{% endcache %}
45
46# Программное кэширование шаблонов
47from django.template.loader import render_to_string
48from django.core.cache import cache
49
50def get_cached_template(template_name, context, cache_key, timeout=3600):
51 # Пытаемся получить из кэша
52 cached_html = cache.get(cache_key)
53
54 if cached_html is None:
55 # Рендерим шаблон
56 cached_html = render_to_string(template_name, context)
57 # Кэшируем результат
58 cache.set(cache_key, cached_html, timeout=timeout)
59
60 return cached_html
61
62# Использование
63def sidebar_view(request):
64 cache_key = f'sidebar_{request.user.id if request.user.is_authenticated else "anonymous"}'
65 sidebar_html = get_cached_template(
66 'sidebar.html',
67 {'user': request.user},
68 cache_key,
69 timeout=1800
70 )
71 return sidebar_html
Кэширование сессий
1# В settings.py
2SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
3SESSION_CACHE_ALIAS = 'session'
4
5# Настройка Redis для сессий
6CACHES = {
7 'default': {
8 'BACKEND': 'django_redis.cache.RedisCache',
9 'LOCATION': 'redis://127.0.0.1:6379/1',
10 },
11 'session': {
12 'BACKEND': 'django_redis.cache.RedisCache',
13 'LOCATION': 'redis://127.0.0.1:6379/2',
14 'OPTIONS': {
15 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
16 },
17 'KEY_PREFIX': 'session',
18 'TIMEOUT': 86400, # 24 часа
19 }
20}
21
22# Работа с сессиями в кэше
23from django.contrib.sessions.backends.cache import SessionStore
24
25def session_management(request):
26 # Получение сессии
27 session = SessionStore(session_key=request.session.session_key)
28
29 # Установка данных в сессию
30 session['user_preferences'] = {
31 'theme': 'dark',
32 'language': 'ru',
33 'notifications': True
34 }
35
36 # Сохранение сессии
37 session.save()
38
39 # Получение данных из сессии
40 preferences = session.get('user_preferences', {})
41
42 return JsonResponse(preferences)
43
44# Кэширование пользовательских данных
45def cache_user_data(request):
46 if request.user.is_authenticated:
47 cache_key = f'user_data_{request.user.id}'
48
49 user_data = cache.get(cache_key)
50 if user_data is None:
51 # Получаем данные пользователя
52 user_data = {
53 'profile': request.user.profile,
54 'permissions': list(request.user.get_all_permissions()),
55 'groups': list(request.user.groups.values_list('name', flat=True))
56 }
57
58 # Кэшируем на 1 час
59 cache.set(cache_key, user_data, timeout=3600)
60
61 return user_data
62
63 return None
Кэширование API ответов
1from django.core.cache import cache
2from django.http import JsonResponse
3from django.views.decorators.cache import cache_page
4from django.views.decorators.http import require_http_methods
5import hashlib
6import json
7
8# Кэширование API endpoint
9@cache_page(60 * 5) # 5 минут
10@require_http_methods(["GET"])
11def cached_api_view(request):
12 data = {
13 'status': 'success',
14 'message': 'This response is cached for 5 minutes',
15 'timestamp': timezone.now().isoformat()
16 }
17 return JsonResponse(data)
18
19# Кэширование с параметрами запроса
20def cached_api_with_params(request):
21 # Создаем ключ кэша на основе параметров
22 params = request.GET.dict()
23 cache_key = f'api_response_{hashlib.md5(json.dumps(params, sort_keys=True).encode()).hexdigest()}'
24
25 response_data = cache.get(cache_key)
26 if response_data is None:
27 # Выполняем запрос к БД или внешнему API
28 response_data = fetch_data_from_database(params)
29
30 # Кэшируем результат
31 cache.set(cache_key, response_data, timeout=1800)
32
33 return JsonResponse(response_data)
34
35# Кэширование с версионированием
36def versioned_api_cache(request):
37 api_version = request.GET.get('version', 'v1')
38 cache_key = f'api_v{api_version}_data'
39
40 data = cache.get(cache_key)
41 if data is None:
42 data = fetch_versioned_data(api_version)
43 cache.set(cache_key, data, timeout=3600)
44
45 return JsonResponse(data)
46
47# Инвалидация кэша API
48def invalidate_api_cache(request):
49 # Удаляем все кэши API
50 cache_keys = cache.keys('api_*')
51 for key in cache_keys:
52 cache.delete(key)
53
54 return JsonResponse({'status': 'cache_cleared'})
55
56# Кэширование с TTL на основе данных
57def smart_cache_api(request):
58 cache_key = 'smart_api_data'
59
60 data = cache.get(cache_key)
61 if data is None:
62 data = fetch_data_with_ttl()
63
64 # Устанавливаем TTL на основе данных
65 ttl = data.get('cache_ttl', 300) # по умолчанию 5 минут
66 cache.set(cache_key, data, timeout=ttl)
67
68 return JsonResponse(data)
Мониторинг и отладка кэша
1from django.core.cache import cache
2from django.core.cache.backends.redis import RedisCache
3import time
4
5# Проверка состояния кэша
6def cache_status():
7 try:
8 # Проверяем подключение к Redis
9 cache.set('test_key', 'test_value', timeout=10)
10 test_value = cache.get('test_key')
11
12 if test_value == 'test_value':
13 return {'status': 'healthy', 'message': 'Cache is working properly'}
14 else:
15 return {'status': 'error', 'message': 'Cache read/write failed'}
16
17 except Exception as e:
18 return {'status': 'error', 'message': str(e)}
19
20# Статистика использования кэша
21def cache_stats():
22 if hasattr(cache, '_cache') and hasattr(cache._cache, 'client'):
23 client = cache._cache.client
24
25 if hasattr(client, 'info'):
26 info = client.info()
27 return {
28 'redis_version': info.get('redis_version'),
29 'connected_clients': info.get('connected_clients'),
30 'used_memory': info.get('used_memory_human'),
31 'total_commands_processed': info.get('total_commands_processed'),
32 }
33
34 return {'status': 'stats_unavailable'}
35
36# Очистка кэша по паттерну
37def clear_cache_pattern(pattern):
38 if hasattr(cache, '_cache') and hasattr(cache._cache, 'client'):
39 client = cache._cache.client
40
41 if hasattr(client, 'scan_iter'):
42 keys_to_delete = []
43 for key in client.scan_iter(match=pattern):
44 keys_to_delete.append(key)
45
46 if keys_to_delete:
47 client.delete(*keys_to_delete)
48
49 return {'deleted_keys': len(keys_to_delete)}
50
51 return {'status': 'clear_failed'}
52
53# Middleware для отслеживания кэша
54class CacheDebugMiddleware:
55 def __init__(self, get_response):
56 self.get_response = get_response
57
58 def __call__(self, request):
59 start_time = time.time()
60
61 # Добавляем информацию о кэше в заголовки
62 response = self.get_response(request)
63
64 if hasattr(cache, '_cache'):
65 cache_backend = cache._cache.__class__.__name__
66 response['X-Cache-Backend'] = cache_backend
67
68 # Добавляем время выполнения
69 execution_time = time.time() - start_time
70 response['X-Execution-Time'] = str(execution_time)
71
72 return response
Лучшие практики кэширования
1. Стратегии кэширования
- Cache-Aside: Приложение проверяет кэш перед обращением к БД
- Write-Through: Данные записываются в кэш и БД одновременно
- Write-Behind: Данные сначала записываются в кэш, затем асинхронно в БД
- Refresh-Ahead: Кэш обновляется до истечения TTL
2. Выбор TTL (Time To Live)
- Статические данные: 24 часа - 7 дней
- Полудинамические данные: 15 минут - 2 часа
- Динамические данные: 1-5 минут
- Критически важные данные: Не кэшировать
3. Ключи кэша
- Используй префиксы для группировки
- Включай версии в ключи для инвалидации
- Избегай слишком длинных ключей
- Используй хеширование для сложных параметров
4. Инвалидация кэша
- Инвалидируй кэш при изменении данных
- Используй версионирование для массовой инвалидации
- Настрой автоматическую очистку устаревших данных
- Мониторь использование памяти кэша
FAQ по кэшированию
Q: Когда использовать кэширование?
A: Для часто запрашиваемых данных, результатов сложных вычислений, статических страниц, API ответов и пользовательских сессий.
Q: Какой бэкенд кэша выбрать?
A: Redis для высоконагруженных приложений, Memcached для простых случаев, LocMemCache для разработки, DatabaseCache для небольших проектов.
Q: Как избежать проблем с устаревшими данными?
A: Используй правильные TTL, инвалидируй кэш при изменениях, применяй версионирование ключей.
Q: Как мониторить производительность кэша?
A: Отслеживай hit/miss ratio, время отклика, использование памяти и количество подключений.
Q: Как кэшировать в продакшене?
A: Используй Redis с кластеризацией, настрой мониторинг, применяй правильные настройки безопасности и резервное копирование.
Примеры реальных сценариев
Кэширование блога
1# Кэширование списка статей
2@cache_page(60 * 30) # 30 минут
3def blog_list_view(request):
4 articles = Article.objects.filter(is_published=True).select_related('author')
5 return render(request, 'blog/list.html', {'articles': articles})
6
7# Кэширование отдельной статьи
8def article_detail_view(request, slug):
9 cache_key = f'article_{slug}'
10 article = cache.get(cache_key)
11
12 if article is None:
13 article = get_object_or_404(Article, slug=slug, is_published=True)
14 cache.set(cache_key, article, timeout=3600)
15
16 return render(request, 'blog/detail.html', {'article': article})
Кэширование e-commerce
1# Кэширование каталога товаров
2def product_catalog_view(request):
3 category_id = request.GET.get('category')
4 page = request.GET.get('page', 1)
5
6 cache_key = f'catalog_cat_{category_id}_page_{page}'
7 products = cache.get(cache_key)
8
9 if products is None:
10 products = Product.objects.filter(category_id=category_id).paginate(page=page)
11 cache.set(cache_key, products, timeout=1800)
12
13 return render(request, 'catalog.html', {'products': products})
Кэширование API
1# Кэширование внешних API
2def external_api_view(request):
3 api_key = request.GET.get('api_key')
4 cache_key = f'external_api_{api_key}'
5
6 response_data = cache.get(cache_key)
7 if response_data is None:
8 response_data = call_external_api(api_key)
9 cache.set(cache_key, response_data, timeout=300)
10
11 return JsonResponse(response_data)