redis/go-redis · error
redis: Watch requires all keys to be in the same shard
Error message
redis: Watch requires all keys to be in the same shard
What it means
A Redis transaction (WATCH/MULTI/EXEC) can only run against a single server, but consistent hashing mapped the provided keys onto two or more different Ring shards. Ring.Watch detects the mismatch and returns this error instead of sending a broken transaction.
Source
Thrown at ring.go:1012
for _, key := range keys {
if key != "" {
shard, err := c.sharding.GetByKey(key)
if err != nil {
return err
}
shards = append(shards, shard)
}
}
if len(shards) == 0 {
return fmt.Errorf("redis: Watch requires at least one shard")
}
if len(shards) > 1 {
for _, shard := range shards[1:] {
if shard.Client != shards[0].Client {
err := fmt.Errorf("redis: Watch requires all keys to be in the same shard")
return err
}
}
}
return shards[0].Client.Watch(ctx, fn, keys...)
}
// Close closes the ring client, releasing any open resources.
//
// It is rare to Close a Ring, as the Ring is meant to be long-lived
// and shared between many goroutines.
func (c *Ring) Close() error {
c.heartbeatCancelFn()
return c.sharding.Close()
}
View on GitHub (pinned to c5cad058c7)
Solutions
- Make related keys share a hash tag so they hash to the same shard: use "user1:{id}:a" and "user1:{id}:b" instead of plain key names
- Watch only keys known to be co-located; drop unneeded keys from the Watch list
- Split the logic into separate transactions per shard, or use Redis Cluster/standalone with a Lua script for atomicity across keys
Example fix
// before
ring.Watch(ctx, fn, "user:1:profile", "user:2:profile") // different shards
// after
ring.Watch(ctx, fn, "user:{1}:profile", "user:{1}:prefs") // same hash tag {1} Defensive patterns
Strategy: validation
Validate before calling
func sameShard(ring *redis.Ring, keys ...string) bool {
shardFor := ring.(interface{ ShardByKey(key string) (string, error) })
s, err := shardFor.ShardByKey(keys[0])
if err != nil { return false }
for _, k := range keys[1:] {
s2, err := shardFor.ShardByKey(k)
if err != nil || s2 != s { return false }
}
return true
} Try / catch
if err := ring.Watch(ctx, fn, keys...); err != nil {
var crossShard bool = strings.Contains(err.Error(), "same shard")
if crossShard {
// fall back to per-shard transactions or a Lua script
return executePerShard(ctx, keys, fn)
}
return err
} Prevention
- Design key names with shared hash tags for co-located data: {tag}
- Never mix unrelated keys in a single Watch call
- Document sharding policy so hash tags are used consistently
When it happens
Trigger: ringClient.Watch(ctx, fn, keyA, keyB) where keyA hashes to shard X and keyB to shard Y — different shard.Client pointers at ring.go:1012.
Common situations: Watching a key plus a lock key whose hash tags differ; sharded data layout that keeps related keys on different shards; changing weights or shard list so previously co-located keys split.
Related errors
- redis: Watch requires at least one key
- redis: Watch requires at least one shard
- failed to create operation duration histogram: %w
- failed to create connection count metric: %w
- failed to create connection create time histogram: %w
AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01).
Data as JSON: /api/errors/b9c33a40db236606.
Report an issue: GitHub.