{"id":"e7fa732bc26b2a33","repo":"go-redis/redis","slug":"redis-watch-requires-all-keys-to-be-in-the-same-s-e7fa73","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":988,"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":970,"sourceCodeEnd":1006,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/ring.go#L970-L1006","documentation":"Returned by Ring.Watch when the supplied keys hash to more than one distinct shard. Ring uses consistent hashing across independent Redis nodes; a MULTI/EXEC transaction can only lock keys that live on the same node, so cross-shard key sets are rejected.","triggerScenarios":"Calling ring.Watch(ctx, fn, keys...) with two or more keys whose hash slots route to different ring shards (different Ring client instances / different Redis nodes).","commonSituations":"Multi-key transaction on a Ring without co-locating keys via hash tags; refactoring a single-node client to a Ring without adjusting transactional access patterns; assuming keys on the same prefix hash together (they do not, unless wrapped in {tag}).","solutions":["Co-locate transactional keys on the same shard using a Redis hash tag, e.g. {user1}:profile and {user1}:cart, so they route together.","If co-location is not possible, split the transaction into per-shard transactions or use a single-shard client instead of a Ring.","Review the Ring's shard count and hashing to understand which keys collide."],"exampleFix":"// before\nkeys := []string{\"user:1:profile\", \"user:1:cart\"} // may hash to different shards\nerr := ring.Watch(ctx, fn, keys...)\n\n// after\nkeys := []string{\"{user:1}:profile\", \"{user:1}:cart\"} // same hash tag -> same shard\nerr := ring.Watch(ctx, fn, keys...)","handlingStrategy":"validation","validationCode":"// Ensure all transactional keys share a hash tag before Watch.\nif len(keys) > 1 {\n    tag := internal.Hashtag(keys[0])\n    for _, k := range keys[1:] {\n        if internal.Hashtag(k) != tag || tag == \"\" {\n            return errors.New(\"keys must share a {hashtag} to be co-located on a Ring shard\")\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := ring.Watch(ctx, fn, keys...); err != nil {\n    if strings.Contains(err.Error(), \"same shard\") {\n        // re-issue keys with a shared {tag} or split into per-shard transactions\n    }\n}","preventionTips":["Design multi-key access around a shared {entity-id} hash tag.","Document which key patterns are transactional so future authors keep the tag.","Prefer a single-node Client if you need unconstrained multi-key transactions."],"tags":["ring","transaction","sharding","hashtag"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}