← Назад к вопросам
Как обойти кеширование?
2.0 Middle🔥 111 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как обойти кеширование
Кеширование — критический компонент современных приложений, но иногда нужно обойти кеш для получения свежих данных, отладки или специальных операций. Разберёмся, какие техники использовать в разных контекстах.
1. Проблема и контексты кеширования
Кеш существует на нескольких уровнях:
Браузер (HTTP кеш) → CDN → Сервер (Redis/Memcached) → БД
Нужно понимать, на каком уровне находится кеш, чтобы правильно его обойти.
2. Обход HTTP кеша браузера
На стороне клиента (JavaScript):
// Способ 1: Cache busting через параметр
fetch('/api/data?t=' + Date.now())
.then(r => r.json())
.then(data => console.log(data));
// Способ 2: Header для отключения кеша
fetch('/api/data', {
headers: {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
}
})
// Способ 3: Версионирование URL
fetch('/api/data?v=2.1.0')
// Способ 4: POST вместо GET (POST обычно не кешируется)
fetch('/api/data', { method: 'POST' })
На стороне сервера (Python/Flask/Django):
from flask import make_response
from datetime import datetime
@app.route('/api/data')
def get_data():
response = make_response({'data': 'fresh data'})
# Способ 1: Явно отключить кеш
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
# Способ 2: Добавить Etag для валидации
response.headers['ETag'] = f'"{hash(str(response.data))}' # Уникальный идентификатор
# Способ 3: Last-Modified для условного запроса
response.headers['Last-Modified'] = datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')
return response
3. Обход кеша на уровне приложения (Redis/Memcached)
Отключить кеш для конкретного запроса:
from functools import wraps
import redis
cache = redis.Redis(host='localhost', port=6379, db=0)
def cached(timeout=300):
"""Декоратор для кеширования"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Проверить, нужно ли пропустить кеш
skip_cache = kwargs.pop('skip_cache', False)
cache_key = f"{f.__name__}:{args}:{kwargs}"
if not skip_cache:
cached_value = cache.get(cache_key)
if cached_value:
return json.loads(cached_value)
# Вычислить значение
result = f(*args, **kwargs)
# Сохранить в кеш
if not skip_cache:
cache.setex(cache_key, timeout, json.dumps(result))
return result
return decorated_function
return decorator
@cached(timeout=300)
def get_user_data(user_id):
return {'id': user_id, 'name': 'John'}
# Использование
data = get_user_data(123) # Из кеша (или вычислить и кешировать)
fresh_data = get_user_data(123, skip_cache=True) # Всегда свежие данные
Очистить весь кеш:
import redis
cache = redis.Redis(host='localhost', port=6379, db=0)
# Очистить конкретный ключ
cache.delete('user_data:123')
# Очистить по паттерну
for key in cache.scan_iter('user_data:*'):
cache.delete(key)
# Очистить всю базу
cache.flushdb()
# Очистить всё (опасно!)
# cache.flushall()
4. Обход кеша в Django ORM
Отключить QuerySet кеш:
from django.db import connection
from django.views.decorators.cache import cache_page
from django.core.cache import cache
# Способ 1: Явно отключить кеш вьюхи
@cache_page(0) # Не кешировать
def user_list(request):
users = User.objects.all()
return render(request, 'users.html', {'users': users})
# Способ 2: Очистить конкретный ключ
def update_user(request, user_id):
user = User.objects.get(id=user_id)
user.name = request.POST['name']
user.save()
# Очистить кеш
cache.delete(f'user:{user_id}')
return redirect('user_detail', user_id=user_id)
# Способ 3: Использовать select_related для избежания N+1 проблемы
# вместо кеширования
users = User.objects.select_related('profile').all()
# Способ 4: Принудительная переvalуация QuerySet
users = list(User.objects.all()) # Список вместо QuerySet
users = User.objects.all()[:10] # Только первые 10
Отключить кеш на уровне модели:
class User(models.Model):
name = models.CharField(max_length=100)
# Отключить кеш при сохранении
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
# Очистить связанный кеш
from django.core.cache import cache
cache.delete_many([
'all_users',
f'user:{self.id}',
f'user:email:{self.email}',
])
5. Обход CDN кеша
На стороне клиента:
import requests
# Способ 1: Cache busting в URL
response = requests.get('https://example.com/image.jpg?t=123456')
# Способ 2: Header для отключения кеша
headers = {
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Expires': '0',
}
response = requests.get('https://example.com/api/data', headers=headers)
# Способ 3: ETag валидация
if_none_match = requests.head('https://example.com/api').headers.get('ETag')
headers = {'If-None-Match': if_none_match}
response = requests.get('https://example.com/api', headers=headers)
В FastAPI:
from fastapi import FastAPI, Response
from datetime import datetime, timedelta
app = FastAPI()
@app.get('/api/data')
async def get_data(skip_cache: bool = False):
if skip_cache:
# Отключить кеш на CDN и браузере
headers = {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
}
else:
# Кешировать на 1 час
headers = {
'Cache-Control': 'public, max-age=3600',
'ETag': f'"{hash(str(response))}' # Кешировать по ETag
}
return Response(
content={'data': 'value'},
headers=headers,
media_type='application/json'
)
6. Кеш на уровне Python (functools.lru_cache)
from functools import lru_cache
import time
@lru_cache(maxsize=128)
def expensive_calculation(n):
time.sleep(2) # Имитация дорогой операции
return n ** 2
# Использование
print(expensive_calculation(5)) # 2 секунды
print(expensive_calculation(5)) # Мгновенно (из кеша)
# Очистить кеш
expensive_calculation.cache_clear()
print(expensive_calculation(5)) # 2 секунды (кеш очищен)
# Информация о кеше
print(expensive_calculation.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
7. Обход кеша в запросах к внешним API
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
# Способ 1: VCR для тестирования (запись/воспроизведение HTTP)
import vcr
@vcr.use_cassette('tests/fixtures/api_response.yaml')
def test_api_call():
response = requests.get('https://api.example.com/data')
return response.json()
# Способ 2: Session с контролем кеша
class NoCacheSession(requests.Session):
def request(self, method, url, **kwargs):
kwargs['headers'] = kwargs.get('headers', {})
kwargs['headers']['Cache-Control'] = 'no-cache'
return super().request(method, url, **kwargs)
session = NoCacheSession()
response = session.get('https://api.example.com/data') # Без кеша
8. Вспомогательные техники
Версионирование ресурсов:
# Файлы с версией в имени
# static/js/app.v1.2.3.js
# Когда обновляешь: static/js/app.v1.2.4.js
# В HTML:
<script src="/static/js/app.v1.2.3.js"></script>
# Django helper
from django.templatetags.static import static
# static('js/app.js?v=1.2.3')
Условные запросы:
import requests
# Получить ETag
response = requests.head('https://api.example.com/data')
etag = response.headers.get('ETag')
# Отправить условный запрос
headers = {'If-None-Match': etag}
response = requests.get('https://api.example.com/data', headers=headers)
if response.status_code == 304: # Not Modified
print("Кеш всё ещё актуален")
else:
print("Получены новые данные")
Ключевые моменты
- HTTP заголовки — управлять кешем браузера и CDN
- Cache busting — добавить параметр к URL (t=timestamp или v=version)
- Отключить кеш явно —
Cache-Control: no-cache, no-store, must-revalidate - ETag/If-None-Match — валидировать кеш без загрузки полного ресурса
- Redis FLUSHDB — очистить весь кеш приложения
- Функцион декораторы — контролировать кеш на уровне Python
- skip_cache параметр — давать опцию обойти кеш при необходимости
- CDN контроль — понимать кеширование на разных уровнях
Правильное управление кешем — баланс между производительностью и актуальностью данных.