← Назад к вопросам
Как кэширование ускоряет код?
2.0 Middle🔥 181 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как кэширование ускоряет код
Кэширование — это сохранение результатов дорогостоящих операций для их повторного использования. Это один из самых эффективных способов оптимизации производительности.
1. Принцип работы кэширования
Кэширование избегает повторных вычислений:
# БЕЗ кэширования — выполняется каждый раз
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
import time
start = time.time()
result = fibonacci(40) # Очень медленно (~40 сек)
print(f"Время: {time.time() - start:.2f}s") # 40+ секунд
# С кэшированием — вычисляется один раз
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci_cached(n):
if n <= 1:
return n
return fibonacci_cached(n-1) + fibonacci_cached(n-2)
start = time.time()
result = fibonacci_cached(40) # Мгновенно
print(f"Время: {time.time() - start:.2f}s") # <0.001 сек
Улучшение на миллионы раз! Вместо 10 миллиардов рекурсивных вызовов — только 41.
2. Виды кэширования
2.1 Функциональное кэширование (memoization)
Сохранение результатов функции по входным параметрам:
from functools import lru_cache
@lru_cache(maxsize=128) # Максимум 128 результатов
def expensive_calculation(x, y):
"""Дорогостоящая операция"""
import time
time.sleep(1) # Эмуляция медленного кода
return x ** y
# Первый вызов — медленно (1 сек)
result1 = expensive_calculation(2, 10) # 1024
# Второй вызов с теми же аргументами — мгновенно
result2 = expensive_calculation(2, 10) # Из кэша
print(expensive_calculation.cache_info())
# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
2.2 Кэширование запросов к БД
from functools import lru_cache
import time
from datetime import datetime, timedelta
class UserRepository:
def __init__(self):
self._cache = {}
self._cache_time = {}
def get_user_by_id(self, user_id: int):
"""Кэш с истечением"""
now = datetime.now()
# Проверить, есть ли в кэше и не истёк ли
if user_id in self._cache:
if now - self._cache_time[user_id] < timedelta(minutes=5):
print(f"Из кэша: {user_id}")
return self._cache[user_id]
# Если кэша нет — запрос в БД
print(f"Из БД: {user_id}")
time.sleep(0.5) # Эмуляция запроса
user = {"id": user_id, "name": f"User {user_id}"}
self._cache[user_id] = user
self._cache_time[user_id] = now
return user
repo = UserRepository()
# Первый вызов — из БД (500ms)
user1 = repo.get_user_by_id(1)
# Второй вызов — из кэша (мгновенно)
user2 = repo.get_user_by_id(1)
3. Кэширование HTTP запросов
import requests
from functools import lru_cache
import time
@lru_cache(maxsize=32)
def fetch_github_user(username: str):
"""Кэширование API ответов"""
print(f"Запрос к API: {username}")
time.sleep(1) # Эмуляция сетевой задержки
response = requests.get(f"https://api.github.com/users/{username}")
return response.json()
# Первый вызов — реальный запрос (1 сек)
data1 = fetch_github_user("torvalds")
# Второй вызов — из кэша (мгновенно)
data2 = fetch_github_user("torvalds")
print(data1 == data2) # True
4. Кэш с использованием Redis (для распределённых систем)
import redis
import json
from typing import Any
class RedisCache:
def __init__(self, host="localhost", port=6379):
self.redis_client = redis.Redis(host=host, port=port, decode_responses=True)
def get(self, key: str) -> Any:
"""Получить значение из кэша"""
value = self.redis_client.get(key)
if value:
return json.loads(value)
return None
def set(self, key: str, value: Any, ttl: int = 3600):
"""Сохранить в кэш с TTL (время жизни)"""
self.redis_client.setex(
key,
ttl, # Секунды
json.dumps(value)
)
# Использование
cache = RedisCache()
# Первый вызов — вычисляем и кэшируем
key = "user:123"
user_data = cache.get(key)
if not user_data:
print("Запрос в БД")
user_data = {"id": 123, "name": "John"}
cache.set(key, user_data, ttl=300) # Кэш на 5 минут
# Второй вызов — из кэша
user_data = cache.get(key) # Мгновенно
5. Кэширование на разных уровнях
# Level 1: Локальная память (в процессе)
from functools import lru_cache
@lru_cache(maxsize=100)
def get_config(key: str):
# Для часто читаемых настроек
pass
# Level 2: Redis (между микросервисами)
redis_cache = RedisCache()
# Level 3: CDN кэш (для статического контента)
# Настраивается на уровне HTTP заголовков
# Пример полного стека кэширования
def get_user(user_id: int):
# Уровень 1: Локальная память
cached = get_user.cache.get(user_id)
if cached:
return cached
# Уровень 2: Redis
key = f"user:{user_id}"
cached = redis_cache.get(key)
if cached:
get_user.cache[user_id] = cached
return cached
# Уровень 3: БД
print(f"Запрос в БД: {user_id}")
user = db.session.query(User).get(user_id)
# Сохраняем на оба уровня кэша
get_user.cache[user_id] = user
redis_cache.set(key, user.to_dict(), ttl=300)
return user
get_user.cache = {} # Локальный кэш
6. Кэширование в FastAPI
from fastapi import FastAPI, HTTPException
from functools import lru_cache
from datetime import datetime, timedelta
app = FastAPI()
@lru_cache(maxsize=32)
def get_expensive_data(limit: int):
"""Кэшируется как на уровне Python, так и HTTP"""
import time
time.sleep(2)
return [{"id": i, "data": f"item {i}"} for i in range(limit)]
@app.get("/data")
async def get_data(limit: int = 10):
"""HTTP кэширование"""
return {
"data": get_expensive_data(limit),
"cache_info": get_expensive_data.cache_info()._asdict()
}
# Клиент автоматически кэширует благодаря HTTP заголовкам
# Cache-Control: max-age=300
7. Проблемы с кэшированием
# ПРОБЛЕМА 1: Невалидный кэш (stale cache)
# Решение: TTL (время жизни кэша)
redis_cache.set(key, value, ttl=300) # Автоматический expiry
# ПРОБЛЕМА 2: Слишком большой кэш (memory leak)
# Решение: maxsize
@lru_cache(maxsize=128) # Макс 128 записей
def my_function(x):
pass
# ПРОБЛЕМА 3: Кэш затирается (cache invalidation)
# Решение: инвалидировать при изменении данных
def update_user(user_id: int, data):
db.update_user(user_id, data)
# Инвалидировать кэш
redis_cache.delete(f"user:{user_id}")
get_user.cache_clear() # Для lru_cache
Реальное сравнение производительности
import timeit
import time
def slow_function(n):
time.sleep(0.1)
return n * 2
from functools import lru_cache
@lru_cache(maxsize=128)
def cached_function(n):
time.sleep(0.1)
return n * 2
# БЕЗ кэша — 10 вызовов
start = time.time()
for i in range(10):
slow_function(5)
print(f"Без кэша: {time.time() - start:.2f}s") # 1.0 сек
# С кэшем — 10 вызовов
start = time.time()
for i in range(10):
cached_function(5)
print(f"С кэшем: {time.time() - start:.2f}s") # 0.1 сек (только первый)
print(f"Ускорение: 10x") # Минимум!
Лучшие практики
- Кэшируй то, что часто читается: конфиги, результаты API, данные БД
- Не кэшируй часто меняющиеся данные: балансы счётов, статусы заказов
- Установи TTL: автоматическое истечение кэша
- Инвалидируй при обновлении: очисти кэш при изменении данных
- Монитори попадания кэша: отслеживай эффективность
Итог
Кэширование ускоряет код на порядки, сохраняя результаты дорогостоящих операций. От простого до распределённых кэшей Redis — выбирай инструмент по задаче. Но помни: проблема кэширования — его инвалидация.