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

Кеш в памяти процесса даёт минимальную задержку, но разваливается при двух воркерах, нескольких инстансах и каждом перезапуске. Redis медленнее локального словаря из-за сети, зато становится общей точкой хранения. Выбор между ними определяет не только скорость ответа, но и то, увидят ли пользователи одинаковые данные.
Главное различие: где живёт значение
Локальный кеш хранится в адресном пространстве приложения. В Python это может быть словарь, объект с TTL или кеш декоратора. Каждый процесс получает собственную копию: Gunicorn с четырьмя воркерами создаёт четыре независимых кеша, а два контейнера приложения — ещё два.
Redis работает как отдельный сервер. Приложение отправляет ему команды по сети, а разные процессы и инстансы обращаются к одному набору ключей. Это решает проблему рассинхронизации, но добавляет зависимость от доступности Redis, сетевой задержки и лимита соединений.
TTL — time to live, время жизни записи. После его окончания значение считается просроченным и должно быть загружено заново. TTL не заменяет продуманную инвалидацию: если данные изменились раньше, старый кеш может оставаться видимым до истечения срока.
Если кеш должен быть одинаковым для двух процессов, локальная память уже не является общей инфраструктурой.
Когда достаточно кеша в памяти
Локальный вариант подходит, когда значение недорого пересчитать, потеря кеша безопасна, а небольшая разница между воркерами не влияет на результат. Типичные примеры — конфигурация, список редко меняющихся категорий, разобранный шаблон, результат вычисления для конкретного процесса.
Его сильная сторона — отсутствие сетевого перехода. Нет отдельного сервиса, авторизации, мониторинга и проблем с подключением. При рестарте приложение просто начинает заполнять кеш заново.
Цена простоты проявляется в распределённой системе:
- изменение записи в базе не очищает копии в других процессах;
- каждый инстанс тратит память на одинаковые значения;
- после деплоя все процессы могут одновременно начать тяжёлые запросы к базе;
- кеш нельзя использовать как надёжное хранилище: процесс может завершиться в любой момент.
Минимальный локальный кеш на Python
Ниже — фрагмент для Python 3.12. Он показывает TTL и блокировку доступа к словарю. Блокировка защищает структуру данных внутри одного процесса, но не синхронизирует другие процессы.
import time
from threading import Lock
cache = {}
cache_lock = Lock()
def get_cached(key, loader, ttl_seconds=30):
now = time.monotonic()
with cache_lock:
item = cache.get(key)
if item is not None and item[0] > now:
return item[1]
value = loader()
with cache_lock:
cache[key] = (time.monotonic() + ttl_seconds, value)
return value
Такой код не защищает от ситуации, когда десять запросов одновременно не находят ключ и все запускают loader. Для дорогой загрузки понадобится отдельная дедупликация вычислений или другой механизм блокировки. Это уже отдельная задача, а не повод превращать локальный кеш в подобие Redis.
Когда нужен Redis
Redis оправдан, если результат должен быть общим для нескольких процессов, кеш переживает короткий перезапуск приложения или в нём появляются счётчики, очереди и другие структуры. Для обычного кеширования достаточно рассматривать Redis 7.x как удалённое key-value-хранилище, не используя его как замену основной базе.
Сценарии, где общий кеш особенно полезен:
- несколько экземпляров API должны видеть один и тот же результат;
- нужно ограничивать частоту запросов по пользователю или IP;
- дорогой результат строится один раз и читается многими воркерами;
- сессии должны сохраняться при переключении запроса между инстансами;
- нужно публиковать сигнал об изменении кеша через общий инфраструктурный слой.
Redis не делает данные автоматически надёжными. При настройке только как кеш потеря содержимого после сбоя может быть нормальной. Если же приложение рассчитывает на сохранность записей, нужно отдельно проверить политику персистентности, репликацию, восстановление и поведение при нехватке памяти.
Пример чтения и записи через redis-py
Фрагмент рассчитан на redis-py 5.x и Redis 7.x. Значение сериализуется в JSON, а параметр ex задаёт TTL в секундах.
import json
import redis
redis_client = redis.Redis(
host='localhost',
port=6379,
decode_responses=True,
socket_timeout=0.2,
)
def get_cached(key, loader, ttl_seconds=30):
raw_value = redis_client.get(key)
if raw_value is not None:
return json.loads(raw_value)
value = loader()
redis_client.set(key, json.dumps(value), ex=ttl_seconds)
return value
Короткий таймаут здесь важнее удобства: зависший Redis не должен удерживать HTTP-запрос бесконечно. В рабочем сервисе обработайте ошибку соединения и решите заранее, можно ли в таком случае отдать свежие данные напрямую, использовать устаревшее значение или вернуть ошибку.
Сравнение подходов
| Критерий | Кеш в памяти процесса | Redis |
|---|---|---|
| Скорость | Самый короткий путь: доступ к памяти процесса | Добавляется сетевой запрос и сериализация |
| Общие данные | Нет между процессами и инстансами | Да, если они подключены к одному Redis |
| Перезапуск приложения | Все записи теряются | Зависит от настройки персистентности и режима отказа |
| Эксплуатационная цена | Почти нет отдельной инфраструктуры | Нужны лимиты, мониторинг, резервный сценарий и управление доступом |
| Риск рассинхронизации | Высокий при нескольких воркерах | Ниже, но инвалидацию всё равно проектирует приложение |
| Подходящий объём | Ограничен памятью конкретного процесса | Ограничен памятью Redis и его политикой вытеснения |
Цена Redis, которую часто не считают
Один вызов Redis — это не только операция GET. В запрос входят сериализация, передача по сети, ожидание ответа и обработка ошибки. Если endpoint делает десятки мелких обращений к Redis, суммарная задержка может оказаться выше, чем повторное вычисление простого значения.
Есть и операционная цена. Нужно следить за использованием памяти, количеством соединений, частотой промахов кеша, задержкой команд и ошибками подключения. Для Redis-кластера добавляются распределение ключей и особенности отказа отдельных узлов. Точную стоимость нельзя вывести из одной цифры запросов в секунду: она зависит от размера значений, паттерна чтения, сети и режима отказоустойчивости.
Как выбирать на практике
- Определите допустимую потерю. Если после рестарта можно пересчитать значение, локальный кеш остаётся кандидатом. Если потеря ломает пользовательский сценарий, не храните такие данные только в памяти процесса.
- Проверьте топологию. Один процесс и один инстанс допускают локальный кеш. Несколько воркеров, контейнеров или серверов требуют общего слоя либо явного согласия на рассинхронизацию.
- Посчитайте размер. Для локального варианта учитывайте память каждого процесса отдельно. Для Redis — общий объём, накладные расходы структур и резерв под пики.
- Задайте поведение при недоступности. Опишите, что происходит при таймауте Redis: запрос идёт в базу, отдаётся устаревший результат или операция запрещается.
- Измерьте до и после. Снимите p50, p95 и p99 задержки, долю попаданий, число обращений к источнику и ошибки. Тестируйте на объёме данных и параллельности, близких к рабочим; универсальных цифр скорости для этих подходов нет.
Рабочий компромисс: два уровня
Иногда используют локальный кеш перед Redis: сначала проверяют память процесса, затем общий кеш, затем базу. Это снижает число сетевых обращений, но усложняет инвалидацию. После изменения данных локальные копии могут остаться устаревшими, поэтому нужны короткий локальный TTL, версия ключа или сигнал об очистке.
Такую схему не стоит включать автоматически. Если данные меняются часто, выигрыш от локального уровня может быть меньше цены рассинхронизации. Если значения большие, локальная копия ещё и увеличивает расход памяти каждого воркера.
Что ломается первым
Локальный кеш
- «На одном сервере всё работает, после масштабирования данные разные». У каждого процесса своя копия. Уменьшите роль локального кеша или вынесите общий слой в Redis.
- Всплеск запросов после деплоя. Все пустые кеши одновременно обращаются к базе. Нужны прогрев, ограничение параллельной загрузки или постепенное включение новых инстансов.
- Процесс съедает память. Ограничьте число ключей и размер значений, а не только TTL. Истёкшие записи не всегда освобождают память ровно в момент окончания срока.
Redis
- «Не удаётся подключиться к Redis». Проверьте DNS, порт, сетевые правила, лимит соединений и таймауты. Приложение должно иметь заранее определённый запасной путь.
- «Кеш есть, но база всё равно перегружена». Проверьте ключи, TTL, сериализацию и долю промахов. Разные форматы ключей для одного ресурса часто превращают кеш в набор непопаданий.
- Значения исчезают раньше TTL. Это может быть вытеснение при нехватке памяти, очистка при деплое или политика эксплуатации. Смотрите метрики Redis, а не только код приложения.
Что делать дальше
Начните с матрицы ключей: источник данных, допустимая устарелость, TTL, размер значения, владелец инвалидации и действие при промахе. Для каждого сценария измерьте долю попаданий и p95 задержки отдельно для локального и Redis-варианта. В проде сначала поставьте мониторинг памяти, ошибок, таймаутов и промахов, а уже затем добавляйте второй уровень кеша или усложняйте отказоустойчивость.
Сноска. Инвалидация — удаление или пометка устаревшей записи после изменения исходных данных. TTL решает только вопрос автоматического старения и не сообщает кешу, что запись уже изменилась.
Тест и проверочная: Быстрые ответы с объяснением, почему остальные варианты неверны
Спросить за 10 ₽Источники: Redis Documentation: caching and expiration, Python Documentation: threading and time
Частые вопросы
Что выбрать для приложения с несколькими воркерами?
Redis всегда быстрее кеша в памяти?
Что произойдёт при падении Redis?
Можно ли хранить в кеше данные, которые нельзя потерять?
Почему после установки Redis база всё равно получает много запросов?
Что делать, если у меня другая версия redis-py или Redis?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.