go-redis/redis · error
redis: Watch requires all keys to be in the same slot
Error message
redis: Watch requires all keys to be in the same slot
What it means
Returned by ClusterClient.Watch when the watched keys do not all hash to the same cluster slot. Redis Cluster cannot run a MULTI/EXEC transaction (WATCH) across multiple slots, so go-redis rejects this client-side before contacting any node. Each key is hashed with hashtag.Slot; if any differ from the first key's slot, the error is returned immediately.
Source
Thrown at osscluster.go:38
"github.com/redis/go-redis/v9/auth"
"github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/hashtag"
"github.com/redis/go-redis/v9/internal/otel"
"github.com/redis/go-redis/v9/internal/pool"
"github.com/redis/go-redis/v9/internal/proto"
"github.com/redis/go-redis/v9/internal/routing"
"github.com/redis/go-redis/v9/maintnotifications"
"github.com/redis/go-redis/v9/push"
)
const (
minLatencyMeasurementInterval = 10 * time.Second
)
var (
errClusterNoNodes = errors.New("redis: cluster has no nodes")
errNoWatchKeys = errors.New("redis: Watch requires at least one key")
errWatchCrosslot = errors.New("redis: Watch requires all keys to be in the same slot")
)
// ClusterOptions are used to configure a cluster client and should be
// passed to NewClusterClient.
type ClusterOptions struct {
// A seed list of host:port addresses of cluster nodes.
Addrs []string
// ClientName will execute the `CLIENT SETNAME ClientName` command for each conn.
ClientName string
// NewClient creates a cluster node client with provided name and options.
// If NewClient is set by the user, the user is responsible for handling maintnotifications upgrades and push notifications.
NewClient func(opt *Options) *Client
// The maximum number of retries before giving up. Command is retried
// on network errors and MOVED/ASK redirects.View on GitHub (pinned to 36d97525cd)
Solutions
- Wrap all watched keys in a Redis hash tag so they share a slot, e.g. "{user}:1" and "{user}:2" — the substring inside {} determines the slot.
- If the keys are genuinely unrelated and must be on different slots, restructure to avoid multi-key transactions; use per-key independent operations or Lua scripts scoped to one key.
- Fall back to a non-cluster Client (single endpoint) or a Ring if you truly need cross-key WATCH semantics, though this sacrifices cluster scaling.
Example fix
// before
err := clusterClient.Watch(ctx, func(tx *redis.Tx) error {
return tx.Set(ctx, "user:2", newVal, 0).Err()
}, "user:1", "user:2")
// returns: redis: Watch requires all keys to be in the same slot
// after — hash tag forces both keys into one slot
err := clusterClient.Watch(ctx, func(tx *redis.Tx) error {
return tx.Set(ctx, "{user}:2", newVal, 0).Err()
}, "{user}:1", "{user}:2") Defensive patterns
Strategy: validation
Validate before calling
import "github.com/redis/go-redis/v9/internal/hashtag"
func safeClusterWatch(ctx context.Context, c *redis.ClusterClient, fn func(*redis.Tx) error, keys ...string) error {
if len(keys) == 0 {
return errors.New("watch requires at least one key")
}
slot := hashtag.Slot(keys[0])
for _, k := range keys[1:] {
if hashtag.Slot(k) != slot {
return fmt.Errorf("keys %q and %q are in different slots; use a hash tag like {tag}:key", keys[0], k)
}
}
return c.Watch(ctx, fn, keys...)
} Prevention
- Always use Redis hash tags ({tag}) for keys involved in the same multi-key transaction or WATCH.
- Validate slot equality client-side with hashtag.Slot before calling ClusterClient.Watch.
- Centralize key construction in helpers that enforce a shared hash tag for related keys.
When it happens
Trigger: Calling ClusterClient.Watch(ctx, fn, key1, key2, ...) where two or more keys map to different hash slots, e.g. Watch(ctx, fn, "user:1", "user:2"). Without hash tags these land on different slots. Only ClusterClient enforces this; single-node Client.Watch has no slot concept.
Common situations: Migrating from standalone Redis to Redis Cluster without realizing WATCH needs same-slot keys. Using natural key names ("user:1", "user:2") that scatter across slots. Attempting multi-key optimistic locking patterns (CAS) that worked on standalone Redis.
Related errors
- redis: NewFailoverClusterClient nil options
- redis: NewClusterClient nil options
- redis: connection pool exhausted
- redis: connection pool timeout
- redis: no valid results to aggregate for max operation
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/b715375ccd9cc625.json.
Report an issue: GitHub.