redis/redis-rb · error · Redis::Distributed::CannotDistribute
JSON_MSET cannot be used in Redis::Distributed because the k
Error message
JSON_MSET cannot be used in Redis::Distributed because the keys involved need to be on the same server or because we cannot guarantee that the operation will be atomic.
What it means
Redis::Distributed shards keys across N standalone servers via a client-side hash ring. JSON.MSET is a single atomic (transactional) command that sets several JSON documents at once, and the server can only keep that guarantee when every key is on one node. Since the ring may place the triplet keys on different nodes, the client raises CannotDistribute up front rather than issue a partially-applied multi-document write. The raise is unconditional — even when all keys happen to hash to the same node.
Source
Thrown at lib/redis/distributed.rb:371
# Get the value of a key and sets its time to live based on options.
def getex(key, **options)
node_for(key).getex(key, **options)
end
# Set the JSON value at a path in the document stored under a key.
def json_set(key, path, value, **options)
node_for(key).json_set(key, path, value, **options)
end
# Get the JSON value(s) at one or more paths in the document stored under a key.
def json_get(key, *paths, **options)
node_for(key).json_get(key, *paths, **options)
end
# Set one or more JSON values. The keys may live on different nodes and the operation must be
# atomic, so it cannot be distributed.
def json_mset(*)
raise CannotDistribute, :json_mset
end
# Get the values at a path from several keys. Keys are grouped by node, queried per node, and
# reassembled in the original key order.
def json_mget(*keys, path, **options)
keys.flatten!(1)
values = keys.group_by { |key| node_for(key) }.each_with_object({}) do |(node, subkeys), acc|
node.json_mget(*subkeys, path, **options).each_with_index do |value, i|
acc[subkeys[i]] = value
end
end
keys.map { |key| values[key] }
end
# Delete the JSON value(s) at a path in the document stored under a key.
def json_del(key, path = nil)
node_for(key).json_del(key, path)
endView on GitHub (pinned to 2ba9010b91)
Solutions
- Write each document individually: dist.json_set(key, "$", value) per triplet — correct across shards, but loses multi-document atomicity
- If the keys can share a hash tag, run the command on that one node: dist.node_for("{tag}doc1").json_mset("{tag}doc1", "$", v1, "{tag}doc2", "$", v2)
- Wrap the writes in your own cross-node compensation (write, verify, roll back) only if you truly need all-or-nothing behavior
- If atomic multi-key JSON writes are a hard requirement, switch topology to Redis::Cluster and use hash tags for co-location
Example fix
# before
dist.json_mset("doc1", "$", { "a" => 1 }, "doc2", "$", { "b" => 2 })
# => Redis::Distributed::CannotDistribute
# after (per-key writes)
dist.json_set("doc1", "$", { "a" => 1 })
dist.json_set("doc2", "$", { "b" => 2 })
# after (shared hash tag, atomic on one node)
dist.node_for("{user1}doc1").json_mset("{user1}doc1", "$", { "a" => 1 }, "{user1}doc2", "$", { "b" => 2 }) Defensive patterns
Strategy: fallback
Validate before calling
# Branch on client class before issuing a multi-document JSON write
def safe_json_mset(client, *triplets, raw: false)
if client.is_a?(Redis::Distributed)
triplets.each_slice(3) { |key, path, value| client.json_set(key, path, value, raw: raw) }
else
client.json_mset(*triplets, raw: raw)
end
end Type guard
def distributed_client?(client) client.is_a?(Redis::Distributed) end
Try / catch
begin
dist.json_mset(*triplets)
rescue Redis::Distributed::CannotDistribute => e
logger.warn("#{e.message}; falling back to per-key json_set")
triplets.each_slice(3) { |key, path, value| dist.json_set(key, path, value) }
end Prevention
- Audit RedisJSON usage for cross-key commands (JSON.MSET) before moving to client-side sharding
- Wrap document-batch writes in one helper so the per-key fallback is centralized
- If several documents belong to one aggregate, give them a shared {tag} prefix so per-node atomic operations stay possible
When it happens
Trigger: Calling dist.json_mset("doc1", "$", v1, "doc2", "$", v2) (flat key/path/value triplets) on a Redis::Distributed client. Hit when JSON-bulk-loading or document-sync code written for a standalone Redis (RedisJSON / Redis 8.0+) runs against a Distributed topology.
Common situations: Migrating an app that uses RedisJSON documents from standalone Redis to client-side sharding; shared model/persistence layers that accept either Redis or Redis::Distributed; batch document upserts that used JSON.MSET for atomicity; upgrading a gem that newly delegates to json_mset internally.
Related errors
- MAPPED_MSETNX cannot be used in Redis::Distributed because t
- PIPELINED cannot be used in Redis::Distributed because the k
- Can't unsubscribe if not subscribed.
- UNWATCH cannot be used in Redis::Distributed because the key
- wrong number of arguments (expected key/path/value triplets)
AI-assisted analysis of redis/redis-rb@2ba9010b91 (2026-08-23).
Data as JSON: /api/errors/145a2fd315710383.
Report an issue: GitHub.