Зачем нужно кэширование?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужно кэширование
Кэширование — это техника хранения часто используемых данных в быстром доступе с целью снизить время отклика и нагрузку на систему. Это одна из самых важных оптимизаций в разработке.
Основная идея
Вместо повторных дорогостоящих операций (запросы к БД, HTTP запросы, вычисления), сохраняем результат и переиспользуем его.
Основные причины использования кэширования
1. Снижение времени отклика (Latency)
Кэш в памяти работает в миллиарды раз быстрее чем диск:
import time
def get_user_from_db(user_id):
time.sleep(0.1) # Имитация запроса к БД
return {'id': user_id, 'name': 'John'}
start = time.time()
for i in range(100):
user = get_user_from_db(1) # 100 * 100ms = 10 сек
end = time.time()
print(f'Без кэша: {end - start:.2f}с') # 10 сек
# С кэшем
cache = {}
def get_user_cached(user_id):
if user_id in cache:
return cache[user_id]
time.sleep(0.1)
user = {'id': user_id, 'name': 'John'}
cache[user_id] = user
return user
start = time.time()
for i in range(100):
user = get_user_cached(1)
end = time.time()
print(f'С кэшем: {end - start:.2f}с') # 0.1 сек
2. Снижение нагрузки на базу данных
Без кэша каждый запрос бьёт по БД. С кэшем большинство запросов обслуживаются из памяти:
Топ популярные товары в интернет-магазине:
80% запросов касаются 20% товаров (Парето)
Кэшируем топ 20% товаров в Redis
80% запросов обслуживаются из Redis
БД получает только 20% нагрузки
Сервер может обслужить в 5 раз больше пользователей
3. Масштабируемость
servers = 100
requests_per_server = 1000
cache_hit_rate = 0.95 # 95% попадают в кэш
requests_to_db = servers * requests_per_server * (1 - cache_hit_rate)
print(f'Запросов к БД: {requests_to_db:.0f} из {servers * requests_per_server}')
# 5000 из 100000 (95% экономия)
Типы кэширования
1. Кэш в памяти приложения (In-Memory Cache)
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # Быстро, результаты кэшируются
2. Распределённый кэш (Redis, Memcached)
import redis
rc = redis.Redis(host='localhost', port=6379)
def get_user_redis(user_id):
key = f'user:{user_id}'
cached = rc.get(key)
if cached:
return json.loads(cached)
user = db.query_user(user_id)
rc.setex(key, 3600, json.dumps(user))
return user
3. HTTP кэш (браузер, CDN)
from fastapi import FastAPI
from fastapi.responses import Response
app = FastAPI()
@app.get('/api/products/{product_id}')
def get_product(product_id: int):
product = db.get_product(product_id)
return Response(
content=json.dumps(product),
headers={'Cache-Control': 'public, max-age=3600'}
)
Проблемы кэширования
1. Инвалидация кэша (Cache Invalidation)
Когда данные обновляются, кэш может содержать старую информацию:
rc.set('user:1:posts', '[post1, post2]')
# Пользователь создаёт новый пост
db.create_post(user_id=1, title='New Post')
# Кэш содержит старые посты!
print(rc.get('user:1:posts')) # [post1, post2]
# Решение: инвалидировать кэш
def create_post(user_id, title):
post = db.create_post(user_id, title)
rc.delete(f'user:{user_id}:posts')
return post
2. Холодный кэш при запуске
def warmup_cache():
popular_products = db.get_top_products(limit=1000)
for product in popular_products:
rc.set(f'product:{product.id}', json.dumps(product))
warmup_cache()
Стратегии кэширования
Cache-Aside (Lazy Loading)
def get_data(key):
if key in cache:
return cache[key]
data = db.get(key)
cache[key] = data
return data
Write-Through
def write_data(key, value):
cache[key] = value
db.set(key, value)
Write-Behind (Write-Back)
def write_data(key, value):
cache[key] = value
async_queue.put((key, value)) # Асинхронно в БД
Практический пример
from functools import wraps
import json
import redis
rc = redis.Redis()
def cached(ttl=3600):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
key = f'{func.__name__}:{args}:{kwargs}'
cached_result = rc.get(key)
if cached_result:
return json.loads(cached_result)
result = func(*args, **kwargs)
rc.setex(key, ttl, json.dumps(result))
return result
return wrapper
return decorator
@cached(ttl=3600)
def expensive_operation(user_id):
return db.query_user(user_id)
# Первый вызов: медленный (БД) - 100ms
user = expensive_operation(1)
# Второй вызов: быстрый (кэш) - 1ms
user = expensive_operation(1)
В заключение: кэширование критически важный инструмент для оптимизации производительности. Используй его везде где есть дорогостоящие операции, но помни о проблемах инвалидации и консистентности данных.