go-redis/redis · error

redis: empty timeseries aggregator spec at index %d

Error message

redis: empty timeseries aggregator spec at index %d

What it means

Returned by buildNRangeAggregationArgs when one of the per-key aggregator specs in the Aggregators [][]Aggregator slice is an empty slice (len == 0). Each key that is present in the aggregators list must declare at least one aggregator.

Source

Thrown at timeseries_commands.go:1382

	}
	return &TSNRangePivotRowSliceCmd{
		baseCmd: cmd.cloneBaseCmd(),
		val:     val,
	}
}

// buildNRangeAggregationArgs validates and returns one aggregator spec string per key for
// TS.NRANGE / TS.NREVRANGE. The number of specs must equal the number of keys. Each spec
// lists one or more aggregators for its key and is emitted as a single comma-joined wire
// token; specs for different keys are separate wire tokens.
func buildNRangeAggregationArgs(keys []string, aggregators [][]Aggregator) ([]string, error) {
	if len(aggregators) != len(keys) {
		return nil, fmt.Errorf("redis: TS.NRANGE/TS.NREVRANGE requires exactly %d aggregator spec(s), got %d", len(keys), len(aggregators))
	}
	parts := make([]string, len(aggregators))
	for i, spec := range aggregators {
		if len(spec) == 0 {
			return nil, fmt.Errorf("redis: empty timeseries aggregator spec at index %d", i)
		}
		names := make([]string, len(spec))
		for j, agg := range spec {
			if agg == Invalid {
				return nil, fmt.Errorf("redis: invalid timeseries aggregator at index %d[%d]: Invalid (%d)", i, j, agg)
			}
			s := agg.String()
			if s == "" {
				return nil, fmt.Errorf("redis: invalid timeseries aggregator at index %d[%d]: %d", i, j, agg)
			}
			names[j] = s
		}
		parts[i] = strings.Join(names, ",")
	}
	return parts, nil
}

// appendNRangeOptions appends optional TS.NRANGE / TS.NREVRANGE arguments to args.

View on GitHub (pinned to 36d97525cd)

Solutions

  1. Ensure every Aggregators[i] contains at least one valid aggregator.
  2. Build specs with append so empty entries are never created: for _, k := range keys { specs = append(specs, []redis.Aggregator{redis.Avg}) }.
  3. Validate each inner slice is non-empty before the call.

Example fix

// before
opts := & redis.TSNRangeOptions{
    Aggregators: [][]redis.Aggregator{{redis.Avg}, {}},
}

// after
opts := &redis.TSNRangeOptions{
    Aggregators: [][]redis.Aggregator{{redis.Avg}, {redis.Sum}},
}
Defensive patterns

Strategy: validation

Validate before calling

func validateNRangeSpecs(aggs [][]redis.Aggregator) error {
    for i, spec := range aggs {
        if len(spec) == 0 {
            return fmt.Errorf("empty aggregator spec at index %d", i)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: Calling TSNRangeWithArgs/TSNRevRangeWithArgs where Aggregators[i] is []Aggregator{} for some key i (allocated but never populated).

Common situations: Initialising the outer slice to the right length with make but forgetting to fill an inner element, or conditionally appending specs where one branch appends nothing.

Related errors


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