Программа курса

Модуль 01 / Урок 2 из 27

Словарь, ключи и рехешинг

Теория и практика

Все ключи Redis лежат в одном глобальном словаре. Понимание его устройства объясняет, почему Redis не «проседает» ступенькой при росте данных и почему память иногда не освобождается сразу.

Один плоский словарь

В Redis нет баз, схем и таблиц в привычном смысле. Есть одно пространство ключей: словарь (hash table) «строка ключа → объект значения». Поиск по ключу — хеш-функция (SipHash) + индексация в бакете: O(1) в среднем. Даже «числовой» ключ — строка: SET 123 x хранит ключ «123», а не число.

Логическое разделение данных — целиком ваша забота, через соглашение об именах:

# Рекомендуемый формат: объект:идентификатор[:поле]
user:1001:profile
session:abc123
cache:product:sku-777
lock:report-generator
💡 Префиксы — это и «пространство имён», и основа диагностики: по префиксу легко считать статистику ключей через SCAN с MATCH и чистить устаревшие данные одним итератором.

Две хеш-таблицы и прогрессивный рехешинг

Словарь Redis — структура из двух таблиц: активной и «строящейся». При росте количества записей (load factor > 1, либо принудительно при BGSAVE-расширении) Redis начинает рехешинг: аллоцирует новую таблицу вдвое больше и постепенно переносит бакеты. Условный «индекс» переноса называется rehashidx.

  • Каждое добавление/удаление/чтение словаря заодно переносит несколько бакетов;
  • чтение ключа ищет сначала в старой таблице, затем в новой;
  • новые записи всегда идут в новую таблицу;
  • когда старая опустела — она освобождается.

Так миллиард ключей переезжает в новую таблицу незаметно для клиентов — сотни микросекунд за операцию вместо одной секундной паузы. Сжатие (load factor < 0.1) работает так же.

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

Служебный словарь expires

Ключам с TTL соответствует второй словарь «ключ → время истечения». Именно поэтому TTL и истечение почти бесплатны, а EXPIRE на несуществующий ключ просто возвращает 0. Полезные команды вокруг TTL — в модуле 2, здесь важно знать, что TTL — это отдельная запись в отдельной таблице, а не «время внутри значения».

Лимиты и здравый смысл

  • Максимальный размер ключа и строкового значения — 512 МБ. Это анти-паттерн: такие значения медленно ходят по сети, тормозят репликацию и миграции. Практический потолок — десятки килобайт для строки.
  • Ключи бинарно-безопасны: можно даже пустую строку SET "" x, но не надо.
  • Короткие ключи экономят память, но не в ущерб читаемости: u:1001:p хуже user:1001:profile, если вы не гугл.

Логические базы: SELECT 0..15

Standalone Redis поддерживает 16 логических баз (SELECT 3). Это один словарь с префиксной изоляцией, не отдельные области памяти. В Redis Cluster логических баз нет — только DB 0. Поэтому индустрия давно ушла от использования SELECT в пользу префиксов и/или отдельных инстансов: перенос кода в кластер не требует переделки ключей.

Заглянуть внутрь своими руками

DBSIZE                      # число ключей
RANDOMKEY                   # случайный ключ
EXISTS user:1001:profile    # 1/0
TYPE user:1001:profile      # hash/string/...
MEMORY USAGE user:1001:profile   # байт на объект
SCAN 0 MATCH user:* COUNT 1000   # итератор по префиксу (подробно в 1.4)

Практика

  1. Залейте 1 млн ключей (redis-benchmark -n 1000000 -t set -d 8 -q), затем удалите 90% из них скриптом через SCAN+UNLINK и понаблюдайте INFO memory | grep used_memory пару минут.
  2. Сравните: SET "ключ с пробелами" v, SET "" v, бинарный ключ через SET "\x00\x01" v — убедитесь, что всё работает, и подумайте, стоит ли так делать.
  3. Создайте ключ с TTL и найдите его след в обоих словарях через DEBUG SLEEP 0 (не получится — поэтому просто изучите INFO keyspace: колонка «expires» показывает число ключей с TTL).
Мини-квиз

1. Почему Redis не блокируется на рехешинге при росте БД? — Рехешинг прогрессивный: бакеты переносятся по чуть-чуть при каждой операции.

2. Чем SELECT 5 отличается от префикса tenant5:? — Ничем физически: та же память, тот же словарь. Но SELECT недоступен в кластере, а префикс работает везде.

3. Нужен ли «индекс» по ключам, чтобы быстро искать по префиксу? — Словарь индексирует только точные совпадения. Поиск по префиксу — это всегда обход (SCAN с MATCH), т.е. O(N) по всем ключам, поэтому MATCH — фильтр вывода, а не оптимизация.