skip to content

What does wrapping part of a Redis key in curly braces, as in user:{1000}:profile, do to where the key is placed in a Redis Cluster, and when would you deliberately use that?

level: middleimportance: must knowfreq 55%

answer

  1. first { ... first following } , non-empty
  2. hash only the inside, ignore the rest of the key
  3. {} empty tag = hash the whole key
  4. tag the narrowest identity, slot is indivisible
  5. CLUSTER KEYSLOT to prove co-location

basics

~20 s

Curly braces form a hash tag: Redis hashes only the substring inside the first {...} instead of the whole key. Keys sharing a tag share a slot and therefore a node, which is how you co-locate keys that must be read or written together.

solid answer

~50 s

The braces make a **hash tag**. When a key contains `{`, and somewhere after it a `}` with at least one byte between them, Redis computes the slot from that inner substring only: `CRC16("1000") mod 16384` for `user:{1000}:profile`. Every key carrying the same tag lands in the same slot and thus on the same master. That is the only supported way to force co-location. You use it when an access path genuinely needs several keys on one node - a Lua script that reads and writes a group of keys, a transaction, a `SINTERSTORE` across sets belonging to one entity. The rules matter: only the *first* `{` and the *first* `}` after it count, an empty `{}` is ignored (the whole key is hashed), and a `}` before any `{` is ignored. Choose the tag to be the narrowest identifier that the operation truly needs, because everything sharing a tag is stuck on one node.

code

text · 10 lines
text
> CLUSTER KEYSLOT "user:{1000}:profile"   # hashes 1000
(integer) 5288
> CLUSTER KEYSLOT "cart:{1000}"            # hashes 1000 -> same slot
(integer) 5288
> CLUSTER KEYSLOT "foo{}{bar}"             # empty first tag -> whole key
(integer) 13298
> CLUSTER KEYSLOT "foo{{bar}}"             # hashes the string {bar
(integer) 12574
> CLUSTER KEYSLOT "foo}{bar}"              # leading } ignored -> hashes bar
(integer) 5061

go deeper

for a junior

Know that braces mark a hash tag, that only the braced part is hashed, and that shared tags mean shared node.

for a middle

State the parsing rules precisely including the empty-tag and nested-brace cases, and name concrete uses such as scripts, transactions and MGET.

for a senior

Lead with the design rule - tag the narrowest identity - and explain slot indivisibility as the reason over-broad tags are unfixable without a data rewrite.

for a principal

Treat tags as a schema-level commitment: they encode a partitioning contract with no enforcement, so they need documented conventions, skew monitoring, and review when new key families are added.

## What a hash tag is By default Redis Cluster derives a key's hash slot from the entire key name: `CRC16(key) mod 16384`. A **hash tag** overrides the input to that function. If the key contains a `{` character, and after it there is a `}` character with at least one byte in between, then Redis hashes only the bytes between them. The rest of the key name has no effect on placement at all. So `user:{1000}:profile`, `user:{1000}:sessions` and `cart:{1000}` all hash the string `1000` and therefore share one slot - and, since a slot lives on exactly one master, one node. ## The exact parsing rules The algorithm is deliberately tiny and worth memorising, because near-miss key names are a classic bug: - Find the first `{`. If there is none, hash the whole key. - From there, find the first `}`. If there is none, hash the whole key. - If that `}` immediately follows the `{` (an empty tag), hash the whole key. - Otherwise hash exactly the bytes strictly between them. Consequences: `foo{}{bar}` hashes the whole key, because the first brace pair is empty. `foo{{bar}}` hashes `{bar` - the first `{` opens, the first following `}` closes, so the inner opening brace is part of the tag. `foo}{bar}` hashes `bar`, since the leading `}` is irrelevant. Only one tag is ever considered; a second pair of braces is just ordinary key bytes. ## Why you would want it Redis Cluster refuses any command whose keys span more than one slot, and a Lua script or a transaction is executed on one node against local data only. Hash tags are the mechanism that makes such operations legal. Typical legitimate uses: - A script that atomically debits one key and credits another for the same account: `{acct:42}:balance`, `{acct:42}:ledger`. - Set algebra over sets that belong to one entity: `SINTERSTORE {u:9}:common {u:9}:following {u:9}:followers`. - Fetching a small, fixed group of fields for one entity in a single round trip with `MGET`. - Any workflow where you must observe several keys with `WATCH` and then update them together. ## Choosing the tag The design rule is: **tag the narrowest identity that the atomic operation actually spans**. A tag of `{user:1000}` places one user's handful of keys together - fine, and the load spreads because there are millions of users. A tag of `{tenant:acme}` or, worse, `{app}` places an unbounded and unevenly sized group on one node. That matters because a slot is indivisible. Rebalancing moves whole slots, so if one slot holds 30 GB because a huge tenant was tagged into it, no amount of resharding will relieve that node; the only fix is to redesign the key names and rewrite the data. The same slot is also a single event-loop's worth of CPU, so a hot tag becomes a hot node while the rest of the cluster idles. A second subtlety is that hash tags create *hidden* coupling. The braces are just characters; nothing in the schema records why they are there. Two years later somebody adds `{tenant:acme}:audit-log` because it looked like the house style, and now a growth-unbounded key is pinned to a tenant's slot. Document the tag convention next to the key-naming convention. ## Verifying co-location `CLUSTER KEYSLOT <key>` returns the slot the server computes, so two calls prove co-location without guessing. Note the quoting: in shells and in `redis-cli`, braces may need quoting to survive expansion. `CLUSTER COUNTKEYSINSLOT <slot>` shows how many keys accumulated there, which is the cheapest early warning that a tag is too broad. On a per-node basis, comparing key counts and memory across masters (`INFO keyspace`, `--memkeys` scans) reveals skew caused by tagging. ## What hash tags do not give you Co-location is placement, not a transaction boundary by itself - it merely makes single-node atomic constructs legal. It does not survive a slot moving to another node in the sense of changing anything for you: the keys move together (they share a slot), which is exactly the point, but during a migration a client may be redirected. And a tag does not make the group cheap: a script touching a thousand co-located keys still occupies one node's single command-execution thread for the whole run.

  • What slot does the key foo{}{bar} map to, and why?
    It maps to the slot of the whole key string `foo{}{bar}`, not of `bar`. The rule looks at the first `{` and the first `}` after it; here they are adjacent, so the tag is empty and Redis falls back to hashing the entire key name. Only the first candidate pair is ever considered.
  • Is there any downside to tagging every key of a tenant with {tenant:id} so all multi-key commands keep working?
    Yes - it pins an unbounded, unevenly sized group of keys to one slot, and a slot cannot be split by resharding. A large tenant then produces a memory-heavy and CPU-hot node that rebalancing cannot fix, and the only remedy is renaming keys and rewriting the data. Tag the narrowest identity an atomic operation genuinely spans.

A hash tag is like writing the postcode in brackets on an envelope: the sorting office reads only the bracketed part, so every envelope with the same bracket lands in the same sorting bin no matter what the rest of the address says.

saying these in an interview costs you the question

  • Thinking braces are decorative or a naming convention with no runtime effect
  • Believing the braces are stripped from the stored key name
  • Assuming every {...} pair in the key is considered, rather than only the first non-empty one
  • Tagging by tenant or environment to make all multi-key commands work, ignoring slot skew
  • Expecting a hash tag to make an operation atomic by itself rather than merely legal on one node

context