Django + Redis интеграция
Redis значительно улучшает производительность Django через кэширование, хранение сессий, работу с очередями и многое другое. Это незаменимый инструмент для высоконагруженных приложений.
Установка и настройка
Сначала установи необходимые пакеты:
1pip install django-redis redis celery
Или через poetry:
1poetry add django-redis redis celery
Настройка Redis кэша
Базовая настройка кэша в settings.py:
1CACHES = {
2 'default': {
3 'BACKEND': 'django_redis.cache.RedisCache',
4 'LOCATION': 'redis://127.0.0.1:6379/1',
5 'OPTIONS': {
6 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
7 'CONNECTION_POOL_KWARGS': {
8 'max_connections': 50,
9 'retry_on_timeout': True,
10 },
11 'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
12 'MASTER_CACHE': 'redis://127.0.0.1:6379/1',
13 'SLAVE_CACHE': 'redis://127.0.0.1:6379/2',
14 },
15 'KEY_PREFIX': 'django_cache',
16 'TIMEOUT': 300, # 5 минут по умолчанию
17 },
18 'session': {
19 'BACKEND': 'django_redis.cache.RedisCache',
20 'LOCATION': 'redis://127.0.0.1:6379/2',
21 'OPTIONS': {
22 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
23 },
24 'KEY_PREFIX': 'session',
25 },
26 'long_term': {
27 'BACKEND': 'django_redis.cache.RedisCache',
28 'LOCATION': 'redis://127.0.0.1:6379/3',
29 'OPTIONS': {
30 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
31 },
32 'TIMEOUT': 86400, # 24 часа
33 'KEY_PREFIX': 'long_term',
34 }
35}
Использование кэша в коде
Базовые операции с кэшем:
1from django.core.cache import cache
2from django.core.cache import caches
3
4# Использование default кэша
5cache.set('user:123', {'name': 'John', 'email': 'john@example.com'}, 300)
6user_data = cache.get('user:123')
7
8# Установка значения без TTL
9cache.set('permanent_key', 'value')
10
11# Установка значения с TTL в секундах
12cache.set('temp_key', 'value', 60)
13
14# Проверка существования ключа
15if cache.get('key') is None:
16 cache.set('key', 'value')
17
18# Удаление ключа
19cache.delete('key')
20
21# Увеличение/уменьшение числовых значений
22cache.incr('counter', 1)
23cache.decr('counter', 1)
24
25# Установка множества ключей
26cache.set_many({
27 'key1': 'value1',
28 'key2': 'value2',
29 'key3': 'value3'
30}, 300)
31
32# Получение множества ключей
33values = cache.get_many(['key1', 'key2', 'key3'])
34
35# Очистка всего кэша
36cache.clear()
Кэширование views
Кэширование на уровне views:
1from django.views.decorators.cache import cache_page
2from django.utils.decorators import method_decorator
3from django.views.generic import ListView
4
5# Кэширование function-based view
6@cache_page(60 * 15) # 15 минут
7def book_list(request):
8 books = Book.objects.all()
9 return render(request, 'books/list.html', {'books': books})
10
11# Кэширование class-based view
12@method_decorator(cache_page(60 * 15), name='dispatch')
13class BookListView(ListView):
14 model = Book
15 template_name = 'books/list.html'
16
17 def get_context_data(self, **kwargs):
18 context = super().get_context_data(**kwargs)
19 # Кэшируем только queryset, не весь контекст
20 context['categories'] = cache.get('book_categories')
21 if context['categories'] is None:
22 context['categories'] = list(Category.objects.all())
23 cache.set('book_categories', context['categories'], 300)
24 return context
Кэширование шаблонов
Кэширование фрагментов шаблонов:
1{% load cache %}
2
3<!-- Кэшируем блок на 5 минут -->
4{% cache 300 sidebar %}
5 <div class="sidebar">
6 <h3>Популярные книги</h3>
7 {% for book in popular_books %}
8 <div class="book-item">
9 <h4>{{ book.title }}</h4>
10 <p>{{ book.author }}</p>
11 </div>
12 {% endfor %}
13 </div>
14{% endcache %}
15
16<!-- Кэширование с ключом -->
17{% cache 600 user_profile user.id %}
18 <div class="profile">
19 <h2>{{ user.username }}</h2>
20 <p>{{ user.email }}</p>
21 </div>
22{% endcache %}
23
24<!-- Кэширование с множественными ключами -->
25{% cache 300 book_detail book.id book.updated_at %}
26 <div class="book-detail">
27 <h1>{{ book.title }}</h1>
28 <p>{{ book.description }}</p>
29 </div>
30{% endcache %}
Кэширование сессий
Настройка Redis для хранения сессий:
1# settings.py
2SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
3SESSION_CACHE_ALIAS = 'session'
4SESSION_COOKIE_AGE = 1209600 # 2 недели
5SESSION_SAVE_EVERY_REQUEST = True
6SESSION_EXPIRE_AT_BROWSER_CLOSE = False
7
8# Использование сессий в коде
9def set_user_preferences(request):
10 request.session['theme'] = 'dark'
11 request.session['language'] = 'ru'
12 request.session['notifications'] = True
13
14 # Установка TTL для конкретной сессии
15 request.session.set_expiry(3600) # 1 час
16
17def get_user_preferences(request):
18 theme = request.session.get('theme', 'light')
19 language = request.session.get('language', 'en')
20 return {'theme': theme, 'language': language}
Кэширование ORM запросов
Кэширование результатов запросов к базе данных:
1from django.core.cache import cache
2from django.db.models import Q
3
4class BookService:
5 @staticmethod
6 def get_popular_books():
7 cache_key = 'popular_books'
8 books = cache.get(cache_key)
9
10 if books is None:
11 books = list(Book.objects.filter(
12 rating__gte=4.5
13 ).select_related('author').prefetch_related('reviews')[:10])
14
15 # Кэшируем на 30 минут
16 cache.set(cache_key, books, 1800)
17
18 return books
19
20 @staticmethod
21 def get_books_by_category(category):
22 cache_key = f'books_category_{category}'
23 books = cache.get(cache_key)
24
25 if books is None:
26 books = list(Book.objects.filter(
27 category__name=category
28 ).select_related('author')[:20])
29
30 cache.set(cache_key, books, 3600) # 1 час
31
32 return books
33
34 @staticmethod
35 def invalidate_category_cache(category):
36 cache_key = f'books_category_{category}'
37 cache.delete(cache_key)
38
39 # Также инвалидируем общий кэш популярных книг
40 cache.delete('popular_books')
Кэширование с сигналами
Автоматическая инвалидация кэша при изменении данных:
1from django.db.models.signals import post_save, post_delete
2from django.dispatch import receiver
3from django.core.cache import cache
4
5@receiver(post_save, sender=Book)
6def invalidate_book_cache(sender, instance, **kwargs):
7 # Инвалидируем кэш конкретной книги
8 cache.delete(f'book_detail_{instance.id}')
9
10 # Инвалидируем кэш категории
11 if instance.category:
12 cache.delete(f'books_category_{instance.category.name}')
13
14 # Инвалидируем общий кэш
15 cache.delete('popular_books')
16 cache.delete('all_books')
17
18@receiver(post_delete, sender=Book)
19def invalidate_book_cache_on_delete(sender, instance, **kwargs):
20 # Инвалидируем кэш при удалении
21 cache.delete(f'book_detail_{instance.id}')
22
23 if instance.category:
24 cache.delete(f'books_category_{instance.category.name}')
25
26 cache.delete('popular_books')
27 cache.delete('all_books')
Redis как Celery broker
Настройка Redis для Celery:
1# settings.py
2CELERY_BROKER_URL = 'redis://127.0.0.1:6379/4'
3CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/5'
4CELERY_ACCEPT_CONTENT = ['json']
5CELERY_TASK_SERIALIZER = 'json'
6CELERY_RESULT_SERIALIZER = 'json'
7CELERY_TIMEZONE = 'UTC'
8
9# celery.py
10from celery import Celery
11import os
12
13os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
14
15app = Celery('project')
16app.config_from_object('django.conf:settings', namespace='CELERY')
17app.autodiscover_tasks()
18
19# tasks.py
20from celery import shared_task
21from django.core.cache import cache
22
23@shared_task
24def update_book_statistics():
25 # Обновляем статистику книг
26 stats = Book.objects.aggregate(
27 total=Count('id'),
28 avg_rating=Avg('rating')
29 )
30
31 # Кэшируем результат
32 cache.set('book_statistics', stats, 3600)
33 return stats
34
35@shared_task
36def cleanup_expired_cache():
37 # Очищаем устаревшие ключи
38 # Это можно делать через Redis TTL автоматически
39 pass
Продвинутые техники кэширования
Шаблоны для сложных сценариев:
1from django.core.cache import cache
2from functools import wraps
3import hashlib
4import json
5
6def cache_with_key_generator(key_func, timeout=300):
7 def decorator(func):
8 @wraps(func)
9 def wrapper(*args, **kwargs):
10 # Генерируем ключ кэша
11 cache_key = key_func(*args, **kwargs)
12 result = cache.get(cache_key)
13
14 if result is None:
15 result = func(*args, **kwargs)
16 cache.set(cache_key, result, timeout)
17
18 return result
19 return wrapper
20 return decorator
21
22def generate_cache_key(prefix, *args, **kwargs):
23 """Генерирует уникальный ключ кэша"""
24 key_parts = [prefix] + list(args)
25
26 if kwargs:
27 # Сортируем kwargs для консистентности
28 sorted_kwargs = sorted(kwargs.items())
29 key_parts.extend(sorted_kwargs)
30
31 key_string = json.dumps(key_parts, sort_keys=True)
32 return hashlib.md5(key_string.encode()).hexdigest()
33
34# Использование декоратора
35@cache_with_key_generator(
36 lambda self, category: f'books_category_{category}',
37 timeout=1800
38)
39def get_books_by_category(self, category):
40 return list(Book.objects.filter(category__name=category))
41
42# Кэширование с условной логикой
43class SmartCache:
44 @staticmethod
45 def get_or_set(key, getter_func, timeout=300, condition=None):
46 result = cache.get(key)
47
48 if result is None or (condition and not condition(result)):
49 result = getter_func()
50 cache.set(key, result, timeout)
51
52 return result
53
54 @staticmethod
55 def get_or_set_many(keys_dict, timeout=300):
56 """Получает или устанавливает множество ключей"""
57 results = cache.get_many(keys_dict.keys())
58
59 missing_keys = set(keys_dict.keys()) - set(results.keys())
60
61 if missing_keys:
62 for key in missing_keys:
63 results[key] = keys_dict[key]()
64
65 cache.set_many(results, timeout)
66
67 return results
Мониторинг и отладка Redis
Инструменты для мониторинга производительности:
1import redis
2from django.core.cache import cache
3from django.conf import settings
4
5class RedisMonitor:
6 def __init__(self):
7 self.redis_client = redis.Redis.from_url(
8 settings.CACHES['default']['LOCATION']
9 )
10
11 def get_cache_stats(self):
12 """Получает статистику кэша"""
13 info = self.redis_client.info()
14 return {
15 'used_memory': info['used_memory_human'],
16 'connected_clients': info['connected_clients'],
17 'total_commands_processed': info['total_commands_processed'],
18 'keyspace_hits': info['keyspace_hits'],
19 'keyspace_misses': info['keyspace_misses'],
20 }
21
22 def get_cache_hit_rate(self):
23 """Вычисляет hit rate кэша"""
24 info = self.redis_client.info()
25 hits = info['keyspace_hits']
26 misses = info['keyspace_misses']
27 total = hits + misses
28
29 if total == 0:
30 return 0
31
32 return (hits / total) * 100
33
34 def get_keys_by_pattern(self, pattern):
35 """Получает ключи по паттерну"""
36 return self.redis_client.keys(pattern)
37
38 def get_key_info(self, key):
39 """Получает информацию о ключе"""
40 ttl = self.redis_client.ttl(key)
41 size = self.redis_client.memory_usage(key)
42 return {
43 'ttl': ttl,
44 'size': size,
45 'type': self.redis_client.type(key).decode()
46 }
47
48# Middleware для логирования кэш операций
49import time
50import logging
51from django.utils.deprecation import MiddlewareMixin
52
53logger = logging.getLogger('django.cache')
54
55class CacheLoggingMiddleware(MiddlewareMixin):
56 def process_request(self, request):
57 request.cache_start_time = time.time()
58
59 def process_response(self, request, response):
60 if hasattr(request, 'cache_start_time'):
61 duration = time.time() - request.cache_start_time
62
63 # Логируем медленные запросы
64 if duration > 0.1: # 100ms
65 logger.info(
66 f'Медленный запрос: {request.path} - {duration:.3f}с'
67 )
68
69 return response
Настройка Redis в production
Конфигурация для продакшена:
1# settings/production.py
2CACHES = {
3 'default': {
4 'BACKEND': 'django_redis.cache.RedisCache',
5 'LOCATION': [
6 'redis://redis-master:6379/1',
7 'redis://redis-slave:6379/1',
8 ],
9 'OPTIONS': {
10 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
11 'CONNECTION_POOL_KWARGS': {
12 'max_connections': 100,
13 'retry_on_timeout': True,
14 'socket_connect_timeout': 5,
15 'socket_timeout': 5,
16 },
17 'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
18 'MASTER_CACHE': 'redis://redis-master:6379/1',
19 'SLAVE_CACHE': 'redis://redis-slave:6379/1',
20 'REDIS_CLIENT_KWARGS': {
21 'health_check_interval': 30,
22 'socket_keepalive': True,
23 }
24 },
25 'KEY_PREFIX': 'prod_cache',
26 'TIMEOUT': 300,
27 'VERSION': 1,
28 }
29}
30
31# Redis Sentinel для высокой доступности
32CACHES = {
33 'default': {
34 'BACKEND': 'django_redis.cache.RedisCache',
35 'LOCATION': [
36 'sentinel://sentinel1:26379/0',
37 'sentinel://sentinel2:26379/0',
38 'sentinel://sentinel3:26379/0',
39 ],
40 'OPTIONS': {
41 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
42 'SENTINEL_SERVICE_NAME': 'mymaster',
43 'SENTINEL_KWARGS': {
44 'password': 'sentinel_password',
45 },
46 },
47 }
48}
Лучшие практики
- Используй осмысленные ключи кэша с префиксами
- Устанавливай разумные TTL для разных типов данных
- Инвалидируй кэш при изменении данных
- Мониторь hit rate и производительность кэша
- Используй разные базы Redis для разных целей
- Настраивай connection pool для production
- Логируй медленные операции кэша
- Используй Redis Sentinel для высокой доступности
- Регулярно очищай устаревшие ключи
- Тестируй кэш на реальных данных
FAQ
Q: Как настроить Redis для сессий?
A: Установи SESSION_ENGINE = 'django.contrib.sessions.backends.cache' и SESSION_CACHE_ALIAS = 'default'.
Q: Какой TTL использовать для кэша?
A: Зависит от данных: часто изменяемые - короткий TTL, стабильные - длинный TTL.
Q: Как инвалидировать кэш при изменении данных?
A: Используй Django сигналы для автоматической инвалидации или ручное удаление ключей.
Q: Можно ли использовать Redis для хранения файлов?
A: Да, но лучше для небольших файлов. Для больших используй S3 или локальное хранилище.
Q: Как мониторить производительность Redis?
A: Используй Redis INFO команду, django-redis мониторинг и логирование.
Q: Нужно ли кэшировать все запросы?
A: Нет, только те, которые выполняются часто и редко изменяются.