go-redis/redis · error

redis: Watch requires at least one shard

Error message

redis: Watch requires at least one shard

What it means

Returned by Ring.Watch when keys were supplied but none of them resolved to a shard. Watch filters out empty-string keys before resolving shards; if every key is "" the shards slice stays empty and the transaction cannot proceed because there is no shard to lock.

Source

Thrown at ring.go:982

	if len(keys) == 0 {
		return fmt.Errorf("redis: Watch requires at least one key")
	}

	var shards []*ringShard

	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.

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Sanitize the keys slice: drop empty strings and verify at least one non-empty key remains before calling Watch.
  2. Fix the upstream producer of the keys so it never yields empty values.
  3. Add a unit test asserting keys are non-empty at the Watch call site.

Example fix

// before
keys := []string{"", "", ""}
err := ring.Watch(ctx, fn, keys...)

// after
var nonEmpty []string
for _, k := range keys {
    if k != "" {
        nonEmpty = append(nonEmpty, k)
    }
}
if len(nonEmpty) == 0 {
    return errors.New("no valid keys")
}
err := ring.Watch(ctx, fn, nonEmpty...)
Defensive patterns

Strategy: validation

Validate before calling

var nonEmpty []string
for _, k := range keys {
    if k != "" {
        nonEmpty = append(nonEmpty, k)
    }
}
if len(nonEmpty) == 0 {
    return errors.New("no non-empty keys to watch")
}
return ring.Watch(ctx, fn, nonEmpty...)

Try / catch

if err := ring.Watch(ctx, fn, keys...); err != nil {
    if strings.Contains(err.Error(), "requires at least one shard") {
        // all keys were empty; sanitize upstream
    }
}

Prevention

When it happens

Trigger: Calling ring.Watch(ctx, fn, keys...) where every element of keys is an empty string. The loop appends a shard only for non-empty keys, so an all-empty slice yields zero shards.

Common situations: Keys slice populated from a struct/JSON that had missing fields; a map iteration producing zero-length values; a trim/normalization step that reduced all keys to ""; accidental placeholder keys.

Related errors


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