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

Зачем нужно кэширование?

2.0 Middle🔥 231 комментариев
#REST API и HTTP#Архитектура и паттерны

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

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

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

Зачем нужно кэширование

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

Основная идея

Вместо повторных дорогостоящих операций (запросы к БД, 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)

В заключение: кэширование критически важный инструмент для оптимизации производительности. Используй его везде где есть дорогостоящие операции, но помни о проблемах инвалидации и консистентности данных.