go-redis/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

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.

Source

Thrown at ring.go:988

	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 36d97525cd)

Solutions

  1. Co-locate transactional keys on the same shard using a Redis hash tag, e.g. {user1}:profile and {user1}:cart, so they route together.
  2. If co-location is not possible, split the transaction into per-shard transactions or use a single-shard client instead of a Ring.
  3. Review the Ring's shard count and hashing to understand which keys collide.

Example fix

// before
keys := []string{"user:1:profile", "user:1:cart"} // may hash to different shards
err := ring.Watch(ctx, fn, keys...)

// after
keys := []string{"{user:1}:profile", "{user:1}:cart"} // same hash tag -> same shard
err := ring.Watch(ctx, fn, keys...)
Defensive patterns

Strategy: validation

Validate before calling

// Ensure all transactional keys share a hash tag before Watch.
if len(keys) > 1 {
    tag := internal.Hashtag(keys[0])
    for _, k := range keys[1:] {
        if internal.Hashtag(k) != tag || tag == "" {
            return errors.New("keys must share a {hashtag} to be co-located on a Ring shard")
        }
    }
}

Try / catch

if err := ring.Watch(ctx, fn, keys...); err != nil {
    if strings.Contains(err.Error(), "same shard") {
        // re-issue keys with a shared {tag} or split into per-shard transactions
    }
}

Prevention

When it happens

Trigger: 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).

Common situations: 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}).

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/e7fa732bc26b2a33.json. Report an issue: GitHub.