Redis Cache Stampede: Как предотвратить с помощью блокировок и джиттера TTL
Узнайте, как внезапный всплеск запросов к истекшему ключу кэша может перегрузить вашу базу данных и как остановить это с помощью мьютекс-блокировок, вероятностной ранней перекомпьютации и джиттера TTL в Redis.
Redis Cache Stampede: Как предотвратить с помощью блокировок и джиттера TTL
Cache stampede (или cache thundering herd) — это ситуация, когда множество запросов одновременно пытаются обновить истекший ключ кэша, вызывая лавину обращений к базе данных. Это может привести к значительной нагрузке и даже к отказу системы.
Проблема
Представьте, что у вас есть кэшированный результат сложного запроса к базе данных. Когда срок действия кэша истекает, первый запрос, который не находит данные, начинает пересчитывать результат. Однако если в этот момент приходит много других запросов, они все видят, что кэш пуст, и тоже начинают пересчет. В результате база данных получает шквал одинаковых запросов.
Решения
Мьютекс-блокировки (Mutex Locks)
- Используйте команду
SET NX(Set if Not eXists) для установки блокировки на ключ. Только один процесс сможет установить блокировку и начать пересчет. Остальные ждут или используют устаревшие данные. - Пример на Python с использованием Redis:
import redis import time r = redis.Redis() lock_key = "lock:mykey" cache_key = "mykey" ttl = 60 def get_data(): # Попытка получить данные из кэша data = r.get(cache_key) if data is not None: return data # Попытка получить блокировку lock = r.set(lock_key, "locked", nx=True, ex=10) if lock: # Пересчет данных (имитация долгой операции) time.sleep(2) new_data = "expensive result" r.setex(cache_key, ttl, new_data) r.delete(lock_key) return new_data else: # Ожидание или использование устаревших данных time.sleep(0.1) return get_data() # рекурсивная попытка
- Используйте команду
Вероятностная ранняя перекомпьютация (Probabilistic Early Recomputation)
- Вместо того чтобы ждать истечения TTL, вы можете начать пересчет заранее с некоторой вероятностью. Например, если TTL составляет 60 секунд, то при каждом запросе, когда до истечения остается менее 10 секунд, с вероятностью 50% запускается фоновый пересчет.
- Это снижает вероятность одновременного истечения многих ключей.
Джиттер TTL (TTL Jitter)
- Добавьте случайное смещение к TTL каждого ключа, чтобы они истекали не одновременно. Например, вместо фиксированного TTL в 60 секунд используйте значение от 55 до 65 секунд.
- Пример:
import random base_ttl = 60 jitter = random.uniform(-5, 5) ttl = base_ttl + jitter r.setex(cache_key, ttl, data)
Заключение
Cache stampede — серьезная проблема, но ее можно эффективно предотвратить с помощью комбинации блокировок, вероятностной ранней перекомпьютации и джиттера TTL. Выбор метода зависит от ваших требований к согласованности данных и допустимой нагрузке на базу данных.