Кэширование в 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)