← Назад к вопросам

Что такое кэш?

2.0 Middle🔥 131 комментариев
#Другое

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кэш в компьютерных системах

Определение

Кэш — это промежуточное хранилище данных, которое обеспечивает быстрый доступ к часто используемой информации. Кэш находится между быстрым, но дорогим ресурсом и медленным, но дешёвым хранилищем.

Основная идея кэша:

Медленно:  Диск (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 сложна
  • Устаревание данных
  • Использование памяти

Правило: Нет ничего свободного в кэше. Выбирай между актуальностью и производительностью.

Что такое кэш? | PrepBro