redis/redis-py · error · DataError

AGGREGATION requires exactly one aggregation spec per key (e

Error message

AGGREGATION requires exactly one aggregation spec per key (expected {numkeys}, got {len(specs)}); a spec may list multiple comma-separated aggregators.

What it means

Raised by _append_n_aggregation for TS.NRANGE/TS.NREVRANGE when the number of aggregation specs does not equal the number of queried keys. Each key needs exactly one spec token (which may itself list multiple comma-separated aggregators, e.g. 'avg,max'). A bare string is the spec for a single-key query.

Source

Thrown at redis/commands/timeseries/commands.py:2062

        bucket_size_msec: int | None,
        numkeys: int,
    ):
        """Append the AGGREGATION clause for TS.NRANGE / TS.NREVRANGE.

        These commands take exactly one aggregation spec token per queried key;
        a single aggregator is never broadcast across keys. A spec token may hold
        several comma-separated aggregators, so the wire form is e.g.
        ``AGGREGATION avg,max sum 1000`` for two keys -- key 0 aggregated by both
        ``avg`` and ``max``, key 1 by ``sum``. Pass one spec string per key, each
        optionally comma-joined (``["avg,max", "sum"]``); a bare string is the
        spec for a single-key query. (Matches RedisTimeSeries PR #2079, which
        replaced the earlier single comma-joined / broadcast token.)
        """
        if aggregators is None:
            return
        specs = [aggregators] if isinstance(aggregators, str) else list(aggregators)
        if len(specs) != numkeys:
            raise DataError(
                "AGGREGATION requires exactly one aggregation spec per key "
                f"(expected {numkeys}, got {len(specs)}); a spec may list "
                "multiple comma-separated aggregators."
            )
        params.extend(["AGGREGATION", *specs, bucket_size_msec])

    @staticmethod
    def _append_chunk_size(params: list[EncodableT], chunk_size: int | None):
        """Append CHUNK_SIZE property to params."""
        if chunk_size is not None:
            params.extend(["CHUNK_SIZE", chunk_size])

    @staticmethod
    def _append_duplicate_policy(
        params: list[EncodableT], duplicate_policy: str | None
    ):
        """Append DUPLICATE_POLICY property to params."""
        if duplicate_policy is not None:

View on GitHub (pinned to da03cdc7e8)

Solutions

  1. Provide exactly one spec per key: for keys=[a,b] use aggregators=['avg','sum'] or aggregators=['avg,max','sum'].
  2. For a single key, pass a bare string: aggregators='avg'.
  3. When unsure, build the list dynamically: aggregators=['avg'] * len(keys).

Example fix

// before
client.ts().nrange(['a','b'], '-', '+', aggregators=['avg'])
// after
client.ts().nrange(['a','b'], '-', '+', aggregators=['avg','sum'])
Defensive patterns

Strategy: validation

Validate before calling

def normalize_n_agg(keys, aggregators):
    if aggregators is None:
        return None
    if isinstance(aggregators, str):
        return aggregators if len(keys) == 1 else [aggregators] * len(keys)
    specs = list(aggregators)
    if len(specs) != len(keys):
        raise ValueError(f"Expected {len(keys)} specs, got {len(specs)}")
    return specs

Type guard

def n_agg_matches(keys, aggregators) -> bool:
    if aggregators is None:
        return True
    if isinstance(aggregators, str):
        return len(keys) == 1
    return len(list(aggregators)) == len(keys)

Try / catch

try:
    client.ts().nrange(keys, '-', '+', aggregators=agg)
except Exception as e:
    if 'exactly one aggregation spec' in str(e):
        client.ts().nrange(keys, '-', '+', aggregators=[agg] * len(keys) if isinstance(agg, str) else agg)
    else:
        raise

Prevention

When it happens

Trigger: client.ts().nrange(['a','b'], '-', '+', aggregators=['avg']) (1 spec, 2 keys); or nrange(['a'], '-', '+', aggregators=['avg','sum']) (2 specs, 1 key). Correct multi-key form: aggregators=['avg,max','sum'] for keys=[a,b].

Common situations: Assuming one aggregator broadcasts across all keys; passing a flat list of aggregators instead of one spec per key; migrating from the old comma-joined broadcast syntax.

Related errors


AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04). Data as JSON: /data/errors/cc7b0aa25a99ec39.json. Report an issue: GitHub.