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: Нет, только те, которые выполняются часто и редко изменяются.