Ключи, пространства имён и время жизни
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 на больших структурах.