Ключи, пространства имён и время жизни

Redis — это, по сути, гигантский словарь «ключ — значение». Хорошие имена ключей — половина успеха.

В Redis нет таблиц и схем. Структуру вашим данным придают только имена ключей. Поэтому соглашение об именовании — это не косметика, а архитектура.

На верхнем уровне Redis устроен предельно просто: есть плоское пространство ключей. Каждый ключ — это строка (например, user:42:profile), а его значение — одна из структур данных. Никакой иерархии папок нет. Иллюзию структуры создают сами ключи.

Соглашение об именовании через двоеточие

Сложилась общепринятая практика: разделять части ключа двоеточием :. Это не синтаксис Redis, а просто конвенция, но крайне полезная:

user:42                 # данные пользователя 42
user:42:sessions        # его сессии
order:1001:items        # позиции заказа 1001
cache:weather:moscow     # кэш погоды по городу
ratelimit:api:ip:1.2.3.4 # счётчик лимита по IP

Двоеточия создают логические «пространства имён». По префиксу легко понять, что за данные, и проще искать связанные ключи.

Время жизни ключа: TTL

Главная фишка Redis для кэширования — ключи могут сами удаляться по таймеру. Это называется TTL (Time To Live):

127.0.0.1:6379> SET session:abc "user42"
OK
127.0.0.1:6379> EXPIRE session:abc 60
(integer) 1
127.0.0.1:6379> TTL session:abc
(integer) 58
127.0.0.1:6379> SET token:xyz "..." EX 300
OK
127.0.0.1:6379> PERSIST session:abc
(integer) 1

EXPIRE key 60 — удалить ключ через 60 секунд. TTL key — сколько секунд осталось (-1 = живёт вечно, -2 = ключа уже нет). SET ... EX 300 — задать значение и TTL одной командой. PERSIST — снять таймер, сделать ключ вечным.

   Жизненный цикл ключа с TTL

   SET key val EX 60
        |
        v
   [ключ жив, TTL=60] --tick--> [TTL=59] --...--> [TTL=0]
                                                      |
                                                      v
                                              [ключ удалён]
                                               GET -> (nil)

Как работает под капотом

Redis не сканирует постоянно все ключи в поисках истёкших — это было бы дорого. Применяются две стратегии вместе. Пассивное (ленивое) истечение: когда вы обращаетесь к ключу, Redis проверяет его TTL и, если он истёк, удаляет ключ прямо тогда. Активное истечение: фоновый процесс периодически берёт случайную выборку ключей с TTL и удаляет просроченные. Это компромисс между нагрузкой и быстротой освобождения памяти.

Частые ошибки

  • Перезапись ключа сбрасывает TTL. Если сделать SET key newval без EX, ключ снова станет вечным. Используйте SET key val KEEPTTL, чтобы сохранить таймер.
  • Команда KEYS * на проде. Она блокирует сервер, перебирая все ключи. Вместо неё — SCAN с курсором.
  • Хаотичное именование. Ключи без префиксов превращают БД в свалку, по которой невозможно ориентироваться.

Best practices

  • Используйте префиксы через двоеточие: тип:id:поле.
  • Кэш-ключам почти всегда нужен TTL, чтобы память не переполнялась.
  • Для обхода ключей в проде применяйте SCAN, а не KEYS.

Итог: Redis — плоское пространство ключей, где структуру задают сами имена. Двоеточия создают логические пространства имён. TTL позволяет ключам самоудаляться — это основа кэширования. Истечение работает лениво и активно, экономя ресурсы.

Безопасный обход ключей через SCAN

Раз уж зашла речь про опасность KEYS *, важно освоить правильную замену — SCAN. Она обходит пространство ключей порциями, используя курсор, и не блокирует сервер на всё время обхода:

SCAN 0 MATCH "user:*" COUNT 100
1) "17"                  # следующий курсор (0 = обход завершён)
2) 1) "user:42"
   2) "user:43"
# повторяем SCAN 17 ... пока курсор не вернётся к 0

Курсор — это не «номер страницы», а внутренняя метка состояния обхода. Вы зовёте SCAN снова и снова с полученным курсором, пока он не станет 0. SCAN даёт слабые гарантии (ключ, существовавший всё время обхода, точно встретится; добавленные/удалённые в процессе — как повезёт), но именно это позволяет ему не блокировать сервер.

Аналогичные «курсорные» команды есть для структур: HSCAN по полям хеша, SSCAN по элементам множества, ZSCAN по sorted set. Правило простое: на проде любой полный обход — через *SCAN, никогда через KEYS или SMEMBERS/HGETALL на больших структурах.

Проверьте себя
1. Что вернёт команда TTL, если у ключа нет установленного срока жизни?
A0
B-1
C-2
Dnil
2. Зачем в именах ключей Redis используют двоеточие, например user:42:profile?
AЭто обязательный синтаксис Redis для вложенных данных
BЭто общепринятое соглашение для создания логических пространств имён
CДвоеточие ускоряет поиск ключа
DБез двоеточия Redis отклонит ключ