[Feature] BanyanDB: local-scan performance follow-up for tag aggregation (optional)
Author: hanahmilyCreated Sep 17, 2026Updated Sep 17, 2026
Labelsfeaturedatabase
Part 5 of 5 of the tag aggregation and time bucketing design, shipped in apache/skywalking-banyandb#1360.
Design references: §7.7 The local scan: entity and indexed tags never touch the data file · §12 Delivery order (stages 8a–9, both marked optional in the design)
Depends on: #14089 (time-bucket grouping) and #14090 (COUNT_DISTINCT) — this optimizes both. Not required for either to ship; the design explicitly defers this and asks that the payoff be measured before it's built.
Scope
Phase A — run-folding (design's "intermediate step," no new execution path)
- Fold each maximal
(series, bucket)span once instead of per row, using a plan-time flag marking which key columns are index-resolved — derived fromsearchSeriesList's existing three-way projection split, never inferred at runtime by sampling column values. - Applies whenever an index-resolved tag is a group key:
COUNT_DISTINCTcollapsesn − 1redundant set inserts per span into one;COUNTbecomescount += spanLength.
Phase B — metadata-only scan (after Phase A lands)
- Bypass block decode entirely when every tag the query touches (group keys, aggregation target, any tag a residual predicate touches) resolves from the series index — gated by the range-containment guard (a block whose
[min,max]isn't fully inside the query range must still readtimestamps.bin) and the no-residual-predicate guard. index_modemeasures are the structural first case: they hold no part-resident tags at all, so an index-mode aggregation is index-only by construction.
Out of scope
- Anything that changes correctness or the query surface — this issue is pure performance, gated by differential tests against the existing (already-correct) path.
Test / Definition of done
- Phase A: differential test against the unoptimized per-row path on the same fixture (identical results, fewer group-map probes); a magnitude assertion (an 8192-row block at hour buckets collapses to ~137 spans, not 8192 key computations).
- Phase B: differential test against the decoding path across both index-mode and part-backed fixtures (identical results); the range-containment fixture — a block with rows at 09:00 and 09:59 (one hourly bucket) queried over 09:30–09:31 must yield no bucket row, while the same block fully inside the query range takes the shortcut.
Source: apache/skywalking