Redis Cluster: шардирование
Репликация спасает от сбоев, но весь датасет всё равно живёт в памяти одного мастера. Когда данные перестают помещаться или поток записи упирается в один узел, нужен Redis Cluster — данные шардируются по нескольким мастерам.
Redis Cluster — встроенный режим горизонтального масштабирования: ключи распределяются между несколькими узлами-мастерами. Каждый мастер хранит лишь свою долю данных, а у каждого мастера могут быть свои реплики для отказоустойчивости.
Зачем это на практике
У одиночного Redis (даже с репликами) есть жёсткий потолок: весь набор данных обязан помещаться в память одного мастера, и все записи идут в один процесс. Реплики лишь копируют данные — они не увеличивают ни общий объём, ни пропускную способность записи. Redis Cluster снимает оба ограничения: добавляя узлы, вы увеличиваете и суммарную память, и суммарный throughput. Терабайт данных можно разложить по десяти мастерам, и каждый отвечает только за свою часть.
При этом кластер не отменяет репликацию, а дополняет её: типичная боевая топология — несколько мастеров, и у каждого хотя бы одна реплика. Тогда кластер переживает падение отдельного мастера (его реплика автоматически повышается) и при этом масштабируется данными.
16384 хэш-слота
В основе шардирования лежит фиксированное число хэш-слотов: ровно 16384. Это не узлы, а логические «корзины», по которым раскладываются ключи. Все 16384 слота поделены между мастерами кластера: например, при трёх мастерах узел A держит слоты 0–5460, узел B — 5461–10922, узел C — 10923–16383.
Куда попадёт конкретный ключ, определяет простая формула:
slot = CRC16(key) mod 16384
Берётся CRC16 от имени ключа, остаток от деления на 16384 даёт номер слота, а номер слота однозначно указывает на узел, который этим слотом владеет. Почему слоты, а не «ключ → узел» напрямую? Потому что при добавлении или удалении узла перемещаются слоты целиком (и ключи внутри них), а формула вычисления слота не меняется. Это и есть способ перебалансировки: часть слотов переезжает на новый узел, остальное остаётся на месте — никакого глобального перехеширования всех ключей.
Как команда попадает на нужный узел
Клиент может подключиться к любому узлу кластера. Если ключ из команды принадлежит слоту, которым владеет другой узел, тот, к кому обратились, отвечает не данными, а перенаправлением MOVED с адресом правильного узла:
redis-cli -c -p 7000 SET user:42 "Аня"
# узел видит, что слот ключа принадлежит другому узлу:
-> Redirected to slot [4063] located at 127.0.0.1:7001
OK
Флаг -c включает в redis-cli кластерный режим — клиент сам следует перенаправлению. Так же устроены и нормальные клиентские библиотеки: при старте они скачивают карту «слот → узел» (командой CLUSTER SLOTS / CLUSTER SHARDS), кэшируют её и обращаются сразу к правильному узлу. MOVED прилетает лишь тогда, когда карта устарела (например, слот переехал) — клиент обновляет карту и повторяет запрос. Отдельный ответ ASK используется временно, пока слот в процессе миграции: часть ключей уже на новом узле, часть ещё на старом.
Hash tags: класть ключи в один слот
Поскольку ключи разбросаны по слотам, два связанных ключа почти наверняка окажутся на разных узлах. Чтобы заставить набор ключей попасть в один слот, используют hash tag: если в имени ключа есть подстрока в фигурных скобках {...}, слот считается только по содержимому скобок, а не по всему ключу.
# без тега ключи уедут в разные слоты:
user:42:profile -> slot A
user:42:sessions -> slot B
# с hash tag {42} оба ключа считаются по "42" -> один и тот же слот:
user:{42}:profile
user:{42}:sessions
Это нужно ровно тогда, когда над несколькими ключами выполняется одна команда (см. ниже). Но не злоупотребляйте: если все ключи загнать под один тег, они соберутся в одном слоте на одном узле — и шардирование перестанет работать, появится «горячий» узел.
Ограничения мультиключевых операций
Главное практическое следствие шардирования: команда, затрагивающая несколько ключей (MGET, MSET, SINTERSTORE, SUNION, транзакция MULTI/EXEC, Lua-скрипт), работает только если все её ключи лежат в одном слоте. Иначе кластер вернёт ошибку CROSSSLOT:
# ключи в разных слотах -> ошибка:
redis-cli -c MGET user:1 user:2
(error) CROSSSLOT Keys in request don't hash to the same slot
# тот же запрос с hash tag -> все ключи в одном слоте, работает:
redis-cli -c MGET user:{shard1}:1 user:{shard1}:2
Поэтому проектируя данные под кластер, заранее группируйте связанные ключи через hash tag, если над ними нужны атомарные мультиключевые операции. То, что на одиночном Redis «просто работало», в кластере требует осознанного именования ключей.
Как это работает под капотом
Узлы кластера общаются между собой по отдельному cluster bus (обычно порт данных + 10000) по бинарному gossip-протоколу: обмениваются информацией о том, кто жив, кто какие слоты держит и кто чья реплика. Полную карту слотов знает каждый узел, поэтому он и может ответить MOVED с верным адресом. Отказоустойчивость встроена: если мастер пропадает и это подтверждает большинство мастеров, одна из его реплик повышается в мастер и забирает его слоты — отдельный Sentinel кластеру не нужен, эта логика уже внутри.
Есть тонкость доступности: чтобы кластер обслуживал запросы, по умолчанию все 16384 слота должны быть кому-то назначены. Если узел вместе со всеми своими репликами потерян и его слоты осиротели, кластер по умолчанию (cluster-require-full-coverage yes) перестаёт отвечать на запросы к недостающим слотам, защищая от выдачи неполных данных. Это поведение настраивается, но смысл по умолчанию — «лучше честно отказать, чем притвориться, что данных нет».
Частые ошибки
- Ждать, что мультиключевые команды работают как на одиночном Redis. В кластере
MGET/MSET/транзакции по ключам из разных слотов падают сCROSSSLOT. Группируйте ключи hash tag'ом. - Загнать все ключи под один общий hash tag. Тогда всё садится в один слот на один узел — шардирование исчезает, появляется горячий узел. Тег нужен только для реально связанных групп.
- Не использовать кластерный клиент. Обычный клиент без поддержки кластера получит
MOVEDи, не умея его обработать, будет возвращать ошибки. Включайте кластерный режим (вredis-cli— флаг-c; в библиотеке — cluster-клиент). - Считать число слотов настраиваемым. 16384 — фиксированная величина протокола; меняется не число слотов, а их распределение между узлами.
- Запускать кластер из двух мастеров без реплик. Для надёжного определения отказа и failover нужно большинство мастеров (рекомендуется минимум три мастера), а реплики дают собственно отказоустойчивость.
Итоги
- Redis Cluster шардирует данные по нескольким мастерам, увеличивая и память, и пропускную способность записи; у мастеров есть реплики для HA.
- Пространство ключей делится на 16384 хэш-слота; слот ключа =
CRC16(key) mod 16384, а слоты распределены между узлами. - Клиент кэширует карту «слот → узел»; при устаревшей карте узел отвечает
MOVED(или временноASKпри миграции слота). - Hash tag
{...}кладёт группу ключей в один слот, чтобы над ними работали мультиключевые команды. - Мультиключевые операции (MGET/MSET/транзакции/Lua) требуют, чтобы все ключи были в одном слоте, иначе — ошибка
CROSSSLOT.