Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Кэш в компьютерных системах
Определение
Кэш — это промежуточное хранилище данных, которое обеспечивает быстрый доступ к часто используемой информации. Кэш находится между быстрым, но дорогим ресурсом и медленным, но дешёвым хранилищем.
Основная идея кэша:
Медленно: Диск (2-100мс) ← вероятно нужные данные
↑
Быстро: Оперативная память ← кэш
↑
Оченьбыстро: CPU кэш (0.001мс) ← часто используемые данные
Смысл: избежать повторных дорогих операций (запросы к БД, вычисления, сетевые запросы).
Уровни кэширования (в порядке от быстрого к медленному)
1. CPU Кэш (аппаратный)
Внутри процессора:
- L1 кэш: 32 KB, ~4 цикла (очень быстро)
- L2 кэш: 256 KB, ~10 циклов
- L3 кэш: 8 MB, ~40 циклов
- RAM: 8-64 GB, ~200 циклов
На это разработчик не влияет (аппаратный уровень).
2. Браузерный кэш
// localStorage — постоянный кэш (до 5MB)
localStorage.setItem('user', JSON.stringify({id: 1, name: 'Ivan'}));
const user = JSON.parse(localStorage.getItem('user'));
// sessionStorage — временный кэш (чистится при закрытии вкладки)
sessionStorage.setItem('temp', 'data');
// HTTP кэш (браузер автоматически кэширует GET запросы)
// Cache-Control: max-age=3600 (кэшировать 1 час)
3. Серверный кэш (In-Memory)
from functools import lru_cache
from cachetools import TTLCache
# Простой Python кэш
@lru_cache(maxsize=128) # Кэширует последние 128 результатов
def expensive_computation(n):
"""Вычисляет что-то дорогое"""
return sum(range(n))
expensive_computation(1000000) # Медленно первый раз
expensive_computation(1000000) # Быстро со второго раза (из кэша)
# Кэш с временем истечения (TTL)
cache = TTLCache(maxsize=100, ttl=300) # 300 секунд
cache['key'] = 'value'
# value будет удалён через 5 минут
4. Распределённый кэш (Redis)
import redis
# Подключаемся к Redis
cache = redis.Redis(host='localhost', port=6379)
# Сохранить данные на 1 час
cache.setex('user:1', 3600, json.dumps({'id': 1, 'name': 'Ivan'}))
# Получить данные
user_data = cache.get('user:1') # Очень быстро (из памяти)
# Удалить
cache.delete('user:1')
5. HTTP/CDN кэш
from fastapi import FastAPI
from fastapi.responses import Response
app = FastAPI()
@app.get("/data")
async def get_data():
data = {"result": "expensive computation"}
return Response(
content=data,
headers={
"Cache-Control": "public, max-age=3600" # Кэшировать 1 час
}
)
Стратегии кэширования
1. Cache-Aside (Look-Aside)
def get_user(user_id):
"""Проверяем кэш, если нет — берём из БД"""
# 1. Попробуем кэш
cached = cache.get(f'user:{user_id}')
if cached:
return json.loads(cached) # Нашли в кэше!
# 2. Не нашли — берём из БД
user = db.query(User).filter(User.id == user_id).first()
# 3. Сохраняем в кэш
cache.setex(f'user:{user_id}', 3600, json.dumps(user.to_dict()))
return user
2. Write-Through
def update_user(user_id, data):
"""Обновляем везде одновременно"""
# 1. Обновляем в БД
user = db.update(user_id, data)
# 2. Одновременно обновляем кэш
cache.setex(f'user:{user_id}', 3600, json.dumps(user.to_dict()))
return user
3. Write-Behind (Write-Back)
def update_user_fast(user_id, data):
"""Быстрое обновление (сначала кэш, потом БД)"""
# 1. Быстро обновляем кэш
cache.setex(f'user:{user_id}', 3600, json.dumps(data))
# 2. Асинхронно отправляем в БД
background_task.schedule(db.update, user_id, data, delay=5)
return data # Вернули сразу
# Опасно: если система упадёт, данные потеряются!
Проблемы и решения
Проблема 1: Стёртый кэш (Cache Invalidation)
# Когда обновили данные, кэш устарел
def update_user(user_id, name):
db.update_user(user_id, name)
# БД обновлена, но кэш старый!
# Нужно **инвалидировать** кэш
cache.delete(f'user:{user_id}') # Стираем кэш
# Следующий запрос пересчитает кэш из обновленной БД
"Кэш инвалидация — одна из двух сложных проблем в CS" (Phil Karlton)
Проблема 2: Cache Stampede (Thundering Herd)
# Опасно: когда кэш истёк, все запросы идут в БД одновременно
# Результат: перегруз БД
# Решение: вероятностное обновление (probabilistic refresh)
import random
def get_user(user_id):
cached = cache.get(f'user:{user_id}')
if cached:
# Случайный шанс пересчитать (1%)
if random.random() < 0.01:
# Пересчитываем в фоне (без блокировки)
background_task.schedule(refresh_user, user_id)
return json.loads(cached) # Вернули старые данные
# Нет в кэше — вычисляем
user = db.query(User).filter(User.id == user_id).first()
cache.setex(f'user:{user_id}', 3600, json.dumps(user.to_dict()))
return user
Проблема 3: Cache Miss
# Если запросили данные, которых нет в кэше и нет в БД
# Надо избежать повторных запросов
def get_product(product_id):
# Кэширование даже отсутствия данных
cached = cache.get(f'product:{product_id}')
if cached == 'NOT_FOUND':
return None # Знаем, что этого нет
if cached:
return json.loads(cached)
# Не нашли в кэше
product = db.query(Product).filter(Product.id == product_id).first()
if not product:
# Кэшируем отсутствие на короткое время
cache.setex(f'product:{product_id}', 300, 'NOT_FOUND') # 5 минут
return None
cache.setex(f'product:{product_id}', 3600, json.dumps(product.to_dict()))
return product
Реальный пример: кэширование список пользователей
from fastapi import FastAPI
import redis
import json
app = FastAPI()
cache = redis.Redis()
@app.get("/users")
async def list_users():
"""Получить список пользователей"""
# 1. Проверяем кэш
cached_users = cache.get('users:list')
if cached_users:
print("Из кэша!")
return json.loads(cached_users)
# 2. Кэша нет — берём из БД (дорого)
print("Из БД...")
users = db.query(User).all() # <- медленный запрос
result = [u.to_dict() for u in users]
# 3. Кэшируем на 1 час
cache.setex('users:list', 3600, json.dumps(result))
return result
@app.post("/users")
async def create_user(user_data):
"""Создать пользователя"""
# Создаём
new_user = db.create_user(user_data)
# Инвалидируем кэш (он устарел)
cache.delete('users:list')
# При следующем GET запросе кэш пересчитается
return new_user
Best Practices
✓ Кэшируй дорогие операции:
- Запросы к БД
- Вычисления
- Сетевые запросы
- Рендеринг HTML
✓ Используй TTL (Time To Live):
cache.setex('key', 3600, value) # Автоматически удалится через час
✓ Инвалидируй кэш при обновлениях:
db.update()
cache.delete('key') # Стираем кэш
✓ Используй распределённый кэш (Redis) для многосерверных систем
✓ Помни о размере кэша:
# Не кэшируй всё подряд! Память конечна
cache = TTLCache(maxsize=1000, ttl=3600) # Макимум 1000 элементов
✓ Мониторь hit rate (% успешных обращений):
hits = cache.hits
misses = cache.misses
ratio = hits / (hits + misses)
print(f"Cache hit ratio: {ratio * 100}%") # Должен быть > 80%
Итог
Кэш — это один из самых важных инструментов оптимизации производительности:
- Снижает нагрузку на БД
- Ускоряет ответы
- Улучшает пользовательский опыт
Но требует осторожности:
- Cache invalidation сложна
- Устаревание данных
- Использование памяти
Правило: Нет ничего свободного в кэше. Выбирай между актуальностью и производительностью.