← Назад к вопросам
Что происходит по истечению TTL в Redis?
1.2 Junior🔥 201 комментариев
#Базы данных (NoSQL)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
TTL в Redis: что происходит по истечению
TTL (Time To Live) — это срок жизни ключа в Redis. По истечению TTL ключ автоматически удаляется. Но важно понимать, КАК именно это происходит, потому что в Redis используется "ленивое" удаление.
Два механизма удаления
1. Lazy Deletion (Ленивое удаление)
Kогда ты пытаешься получить ключ с истекшим TTL, Redis проверяет его время жизни и удаляет, если оно истекло:
import redis
import time
client = redis.Redis(host='localhost', port=6379)
# Устанавливаем значение с TTL 5 секунд
client.setex('session:123', 5, 'user_data')
print(client.get('session:123')) # b'user_data' — есть
time.sleep(6) # Ждем истечения TTL
print(client.get('session:123')) # None — ключ удален при попытке чтения
Проблема: если никто не пытается получить ключ, он остается в памяти!
# Ключ может остаться в памяти, если его никто не читает
client.setex('abandoned:key', 5, 'data')
# ... проходит 10 секунд, никто не читает
# Ключ все еще занимает память, несмотря на истекший TTL
2. Active Expiration (Активное удаление)
Redis периодически сканирует БД в фоне и удаляет истекшие ключи:
# Это происходит автоматически в фоне Redis
# Процесс:
# 1. Берет случайный набор ключей (20 элементов по умолчанию)
# 2. Проверяет их TTL
# 3. Удаляет истекшие ключи
# 4. Если удалено >25%, повторяет на другом наборе
# 5. Если удалено <25%, переходит к следующему набору БД
Как это работает на практике
Кейс 1: Активная работа (сессии пользователей)
# Сессия пользователя
user_session = {
'user_id': 123,
'login_time': time.time(),
'ip': '192.168.1.1'
}
client.setex('session:user123', 3600, json.dumps(user_session)) # 1 час
# При каждом запросе пользователя:
for request in incoming_requests:
session = client.get(f'session:{request.user_id}') # Lazy deletion
if session:
# Обновляем TTL, чтобы сессия "жила"
client.expire(f'session:{request.user_id}', 3600)
else:
# Сессия истекла, требуется переaвторизация
return redirect_to_login()
Кейс 2: Кеш с низким трафиком (может остаться в памяти)
# Редко используемые данные
client.setex('cache:expensive_query', 3600, heavy_result)
# Если запрос идет редко, ключ может остаться в памяти
# даже после истечения TTL
# Решение: явное удаление или использование LRU политики
Производительность и утечки памяти
Проблема: ключи остаются в памяти
# Плохо: огромное количество ключей с TTL, но мало читается
for i in range(1000000):
# Логирует события с TTL 24 часа
client.setex(f'event:log:{i}', 86400, f'event_data_{i}')
# Если эти события читают редко, они займут память
Решение: использовать LRU политику
# В redis.conf
maxmemory 100mb
maxmemory-policy allkeys-lru # Удалять наименее используемые ключи
# Или в Python:
# Следить за размером Redis через INFO
info = client.info('memory')
print(info['used_memory_human']) # Текущий размер
print(info['used_memory_peak_human']) # Пик памяти
Явное управление TTL
# Проверить оставшееся время
remaining_ttl = client.ttl('session:123') # Секунды
if remaining_ttl == -1:
print("Ключ существует, но TTL не установлен")
elif remaining_ttl == -2:
print("Ключ не существует или уже удален")
else:
print(f"До истечения {remaining_ttl} секунд")
# Обновить TTL
client.expire('session:123', 3600)
# Удалить TTL (ключ будет жить вечно)
client.persist('session:123')
# Установить TTL при создании
client.setex('key', 3600, 'value')
# Или с точностью до миллисекунд
client.psetex('key', 3600000, 'value')
Практический пример: очередь задач с таймаутом
class TaskQueue:
def __init__(self, redis_client, timeout=300):
self.client = redis_client
self.timeout = timeout
def add_task(self, task_id, task_data):
# Сохраняем задачу с TTL
# Если воркер не обработал за timeout, задача удалится автоматически
self.client.setex(
f'task:{task_id}',
self.timeout,
json.dumps(task_data)
)
self.client.lpush('task_queue', task_id)
def get_task(self):
task_id = self.client.rpop('task_queue')
if task_id:
task_data = self.client.get(f'task:{task_id}')
if task_data: # Может быть None, если TTL истек
return json.loads(task_data)
return None
def task_completed(self, task_id):
self.client.delete(f'task:{task_id}')
# Использование
queue = TaskQueue(redis_client)
queue.add_task('task_1', {'action': 'send_email'})
task = queue.get_task() # Получаем и обрабатываем
if task:
process(task)
queue.task_completed('task_1')
Важные моменты
- Lazy deletion — ключ удаляется только при обращении
- Active expiration — Redis периодически чистит БД в фоне
- Утечки памяти — возможны, если ключи редко читаются
- LRU/LFU — использует для автоматической чистки при переполнении памяти
- TTL проверка — используй
ttl()иpttl()для контроля
Разработчик должен помнить: TTL в Redis — это гарантия удаления, но не гарантия времени удаления. Для критичных систем рекомендуется явное управление.