[Feature] BanyanDB: time-bucket grouping, standalone + distributed
Author: hanahmilyCreated Sep 17, 2026Updated Sep 17, 2026
Labelsfeaturedatabase
Part 2 of 5 of the tag aggregation and time bucketing design, shipped in apache/skywalking-banyandb#1360.
Design references: §5.3 Resolving the bucket width · §7.2 Time-bucket grouping · §10 Compatibility (mixed-version) · §12 Delivery order (stages 2–3)
Depends on: #14088 (API foundation + tag binding).
Scope
Standalone
bucketStart(mirrorsgetWindowStart,sliding_window.go:302); §5.3 width resolution (request → measure interval → reject; deliberately no multiple-of-interval rule, sinceMeasure.intervalis a declared cadence, not an enforced invariant).BatchTimeBucketstreaming operator on the part-scan path: one bucket's groups live at a time, flushed on bucket advance; monotonicity guard (a row whose bucket regresses is a loud error, not a silent duplicate).- Non-streaming map fallback for index-mode measures, chosen on
measureSchema.GetIndexMode()— never a runtime guess — because the index path returns before the ordering guarantee streaming depends on is established. - Conditional D2 reversal:
buildAggOutputLayoutre-emitsRoleTimestamp(set to bucket start) only when a bucket key is present; the existing "aggregation drops the timestamp" assertion stays correct for the unbucketed case. - Analyzer guard rejecting a bucketed request whose resolved ordering isn't time-ascending (protects the streaming precondition).
Distributed
- Node template carries the bucket key; each node floors independently, no coordination needed.
distributedGroupByTagNames/resolveKeyIndicescarry/resolve the bucket as the leading key.- Liaison k-way merge on the bucket (nodes stream bucket-ascending, so the liaison never holds more than one bucket per node stream).
- Mixed-version guard (§10): a bucketed response missing the
RoleTimestampcolumn (an old node that ignored the new proto field) is a hard error, not a silent whole-range collapse.
Out of scope
COUNT_DISTINCT— see #14090.- Run-folding / metadata-only-scan performance work — separate, optional follow-up issue.
Test / Definition of done
bucketStartvs.getWindowStartagreement table, including a boundary timestamp (ts % width == 0) and a negative one.- §5.3 table as literal test cases, including "width not a multiple of the interval → accepted."
plan/build_test.go:221's D2 assertion stays for the unbucketed case, gains a bucketed sibling asserting the timestamp is present and equals the bucket start.- Streaming operator: bucket closure releases live state (not just output), monotonicity guard fires on out-of-order input, first bucket emits before scan end (pipelining),
applyMeasureQueryOrdering'sOrder == nil → orderByTS = truedefault is pinned. - Index-mode: non-chronological input (timestamps arriving A, B, A) still produces one correct result per bucket — proves it took the map, not the streaming path.
- Distributed: node template carries the bucket key; liaison k-way merge correctness over synthetic multi-node frames; mixed-version simulation (a synthetic node response without
RoleTimestamp) triggers the hard error; standalone vs. distributed agreement on the same bucketed fixture.
Source: apache/skywalking