The fastest way to aggregate a billion rows is to never materialize them as rows. HelixQL pushes filters and partial aggregation down to the chunk scan itself.

A naive query engine scans rows into memory, then filters, then aggregates — three separate passes over data that ballooned before it needed to. HelixQL pushes as much of that work as possible down into the chunk-scanning layer, so filtering and partial aggregation happen while data is still compressed.

Filter pushdown against chunk metadata

Every chunk carries a min/max summary for its value range and its label set reference. A `where value > 500` filter is checked against the chunk's max before a single point is decoded — if the chunk's max is 420, the whole chunk is skipped without decompression. On a representative query, this metadata-only skip eliminates 60-75% of candidate chunks before decode even starts.

Partial aggregation during scan

For `summarize sum()/count()/avg()`, the storage layer doesn't materialize every decoded point into a row buffer — it accumulates the aggregate directly as it decodes each chunk, discarding individual values immediately. This keeps memory flat regardless of how many points are scanned, instead of scaling with row count.

from metrics
| where name == 'http.server.duration' and route == '/checkout'
| where value > 500ms
| summarize count(), avg(value) by bin(timestamp, 1m)

In this query, both `where` clauses and the `summarize` push all the way down to the chunk scan — the query engine above storage never sees an individual data point, only partial per-chunk aggregates that get merged.

What can't be pushed down

Percentile aggregations (`p95`, `p99`) can't be fully pushed down the same way, since a true percentile needs the full distribution, not a partial sum. Instead, storage pushes down a t-digest sketch construction per chunk, which is still far cheaper than materializing raw rows — sketch construction costs about 3x a simple sum/count pass, versus the 15-40x cost of materializing full rows for percentile computation upstream.

Measured impact

On a benchmark query scanning 40,000 chunks with a selective filter, full pushdown (filter + partial aggregation) completes in 210ms and holds under 40MB of working memory. The same query with pushdown disabled — materializing filtered rows before aggregating — takes 2.8s and peaks at 1.1GB of memory.

  • Chunk-level min/max metadata skips 60-75% of candidate chunks before any decompression.
  • Partial aggregation during scan keeps memory flat instead of scaling with matched row count.
  • Percentile aggregations push down as t-digest sketch construction, not full pushdown, but still avoid row materialization.