Этот вопрос проверяет понимание принципов кэширования: какие кэши бывают, как выбрать ключи, TTL и стратегию обновления, чтобы снизить нагрузку на базу данных и не сломать корректность данных.
Кэширование обычно организуют по схеме cache-aside: сначала проверяют кэш, и только при промахе идут в базу данных, сохраняя результат в кэш. Часто используют внешние in-memory хранилища вроде Redis, а также локальный кэш в процессе приложения для самых горячих данных.
Ключи кэша должны однозначно соответствовать запросу (например, user:{id}), а для большинства записей нужно задавать TTL, чтобы данные не устаревали.
Инвалидация кэша делается при изменении данных (удаление/обновление ключа) или через короткий TTL. Важно помнить, что кэш — это ускоритель чтения, и при проектировании нужно балансировать между свежестью данных и производительностью.
Кэширование позволяет существенно снизить количество обращений к базе, но требует продуманной схемы ключей, TTL и инвалидации.
Основные варианты:
Локальный кэш в процессе приложения
словари, functools.lru_cache, in-memory структуры;
очень быстрый доступ, но кэш не разделяется между инстансами.
Внешний кэш (обычно Redis / Memcached)
кэш общий для всех инстансов;
поддерживает TTL, эвикцию, разные типы структур.
Кэш на уровне БД / прокси
например, PgBouncer с prepared statements, query cache в некоторых системах;
меньше контроля, но иногда можно получить “бесплатное” ускорение.
Чаще всего в микросервисах используют схему: локальный кэш для супер-горячих данных + Redis как общий кэш.
Определение:Cache-aside (lazy loading) — паттерн, при котором приложение сначала пробует получить данные из кэша, и только при промахе обращается к БД, затем кладёт результат в кэш.
Алгоритм:
get(key) из кэша.
Если есть — вернуть.
Если нет — запросить из БД, сохранить в кэш с TTL, вернуть.
Пример (Python + Redis, упрощённо):
import json
import redis
r = redis.Redis(host="localhost", port=6379, db=0)
def get_user(user_id):
key = f"user:{user_id}"
cached = r.get(key)
if cached:
return json.loads(cached)
user = load_user_from_db(user_id) # запрос к БД
if user:
r.setex(key, 60, json.dumps(user)) # TTL = 60 секунд
return user
Другие паттерны (кратко):
write-through — при записи в БД сразу пишем в кэш.
write-back — пишем в кэш, а в БД синхронизируем асинхронно (сложно, используется реже).
read-through — логика чтения из БД спрятана внутри кэша (библиотека/прослойка).
Что важно при проектировании:
Ключ должен:
однозначно соответствовать запросу (user:123, product:42, orders:user:123:page:1);
быть достаточно коротким и понятным для отладки.
TTL:
для редко меняющихся справочников (страны, тарифы) можно задавать большой TTL;
для часто меняющихся данных — короткий TTL или явная инвалидация при обновлении.
Пример выбора:
профиль пользователя: user:{id}, TTL 1–5 минут;
конфигурация: config:{name}, TTL 1 час или без TTL, но с ручной инвалидацией.
Это самая сложная часть кэширования.
Подходы:
Инвалидация при записи:
после успешного UPDATE/INSERT/DELETE в БД:
удалить ключ (DEL key),
либо обновить значение в кэше.
TTL:
позволяет “самообновляться” кэшу со временем;
уменьшает риск сильно устаревших данных, но не даёт строгой согласованности.
Для критичных данных часто комбинируют:
краткий TTL (например, 30–60 секунд),
плюс явная инвалидация при изменении, если это возможно.
Слишком большой TTL → пользователи видят старые данные.
Кэширование "тяжёлых" запросов без продуманной инвалидации → неочевидные баги.
Отсутствие лимитов по памяти → Redis или локальный кэш разрастается до упора.
Нет метрик кэша:
не видим hit rate,
не понимаем, помогает кэш или только усложняет архитектуру.
Рекомендуется:
мониторить:
cache_hits,
cache_misses,
размер кэша,
время ответа Redis.
постепенно добавлять кэш на самые “дорогие” запросы.
Хорошее кэширование обычно строится на паттерне cache-aside с внешним кэшем (Redis) и продуманными:
ключами,
TTL,
стратегией инвалидации,
метриками.
Кэш имеет смысл применять для частых и тяжёлых запросов к БД, где допустима небольшая задержка обновления данных.