{"record":{"id":"b9c33a40db236606","repo":"redis/go-redis","slug":"redis-watch-requires-all-keys-to-be-in-the-same-s","errorCode":null,"errorMessage":"redis: Watch requires all keys to be in the same shard","messagePattern":"redis: Watch requires all keys to be in the same shard","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ring.go","lineNumber":1012,"sourceCode":"\tfor _, key := range keys {\n\t\tif key != \"\" {\n\t\t\tshard, err := c.sharding.GetByKey(key)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tshards = append(shards, shard)\n\t\t}\n\t}\n\n\tif len(shards) == 0 {\n\t\treturn fmt.Errorf(\"redis: Watch requires at least one shard\")\n\t}\n\n\tif len(shards) > 1 {\n\t\tfor _, shard := range shards[1:] {\n\t\t\tif shard.Client != shards[0].Client {\n\t\t\t\terr := fmt.Errorf(\"redis: Watch requires all keys to be in the same shard\")\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn shards[0].Client.Watch(ctx, fn, keys...)\n}\n\n// Close closes the ring client, releasing any open resources.\n//\n// It is rare to Close a Ring, as the Ring is meant to be long-lived\n// and shared between many goroutines.\nfunc (c *Ring) Close() error {\n\tc.heartbeatCancelFn()\n\n\treturn c.sharding.Close()\n}\n","sourceCodeStart":994,"sourceCodeEnd":1030,"githubUrl":"https://github.com/redis/go-redis/blob/c5cad058c72f58370553b48566302303cf8a2e89/ring.go#L994-L1030","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nring.Watch(ctx, fn, \"user:1:profile\", \"user:2:profile\") // different shards\n// after\nring.Watch(ctx, fn, \"user:{1}:profile\", \"user:{1}:prefs\") // same hash tag {1}","handlingStrategy":"validation","validationCode":"func sameShard(ring *redis.Ring, keys ...string) bool {\n    shardFor := ring.(interface{ ShardByKey(key string) (string, error) })\n    s, err := shardFor.ShardByKey(keys[0])\n    if err != nil { return false }\n    for _, k := range keys[1:] {\n        s2, err := shardFor.ShardByKey(k)\n        if err != nil || s2 != s { return false }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if err := ring.Watch(ctx, fn, keys...); err != nil {\n    var crossShard bool = strings.Contains(err.Error(), \"same shard\")\n    if crossShard {\n        // fall back to per-shard transactions or a Lua script\n        return executePerShard(ctx, keys, fn)\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","ring","transactions","sharding"],"backgroundTag":"cross-slot-keys","analyzedSha":"c5cad058c72f58370553b48566302303cf8a2e89","analyzedAt":"2026-09-01T06:50:53.388Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}