← Назад к вопросам

Что происходит по истечению 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')

Важные моменты

  1. Lazy deletion — ключ удаляется только при обращении
  2. Active expiration — Redis периодически чистит БД в фоне
  3. Утечки памяти — возможны, если ключи редко читаются
  4. LRU/LFU — использует для автоматической чистки при переполнении памяти
  5. TTL проверка — используй ttl() и pttl() для контроля

Разработчик должен помнить: TTL в Redis — это гарантия удаления, но не гарантия времени удаления. Для критичных систем рекомендуется явное управление.