🤖 from Claude
Motivation
The #280 reducer-coverage discussion surfaced an asymmetry: spill applies to all reducers (single-block is byte-identical to pooled, so even non-mergeable reducers work; only multi-block spill is mergeable-only), whereas merge-mode streaming is mergeable-only up front — validate_streaming rejects expressions, non-len scalars, and non-tdigest ragged before any reads. Today the only wired merge laws are len/count (summation) and the two tdigest reducers (#279).
This issue is to decide how (and how far) to generalize merge-mode toward the full reducer surface, so a config isn't silently forced onto spill/pooled just because it uses a common reduction.
The math — three tiers of reducer
- Trivially mergeable (associative), not yet wired. These have exact incremental laws and just need wiring:
min, max (elementwise), sum, count
mean (as running sum + count)
var / std (via streaming moments — Welford M2 / Chan parallel-variance combine)
- bounding box / extent, any linear combination of the above
- Approximately mergeable. Exact form is non-mergeable but a mergeable sketch exists:
- Genuinely non-mergeable. No incremental law over flushes:
- arbitrary
expression: fields whose intermediate isn't a function of mergeable aggregates (e.g. np.median(h) directly, order-dependent ops)
- histograms with data-dependent bins
Options
- (a) Wire the tier-1 associative laws (min/max/sum/mean/moments) into the streaming/spill fold. Biggest coverage win for least risk — these are exact. Mirrors how
len and tdigest are already wired.
- (b) Policy for tier-3 (non-mergeable) fields. Choose one:
- reject (status quo) — clear but forces manual backend choice.
- auto-fallback — a config with any non-mergeable field auto-routes to spill (single-block exact) or pooled, instead of erroring. Best "just works" UX; needs care that the fallback's memory is acceptable.
- partial-stream — fold the mergeable fields, buffer only the non-mergeable fields' raw obs. Hybrid; the non-mergeable fields lose the memory bound, so probably only sensible when they're few/small.
- (c) Expression decomposition (stretch). Detect when an
expression: is a pure function of mergeable aggregates (e.g. mean = sum/count) and stream it by folding the parts. Powerful but fiddly; likely a later phase.
Open questions
- Is the goal true parity with spill (accept every config, fall back as needed), or just widening the mergeable set (tier 1) and keeping an explicit reject for tier 3?
- For tier-3 fallback — auto-route to spill, auto-route to pooled, or keep rejecting with a clearer "use mode: spill / drop streaming" message?
- Should the associative laws live in one place shared by merge-mode and spill (they both fold), so a reducer's merge law is declared once? (Today
len and tdigest are special-cased in both StreamingAggregator and SpillAggregator.)
Related: #279 (tdigest merge laws, the current template for wiring one), #280 (parallel spill reducer — Flavor A is reducer-agnostic, so it composes with whatever laws this issue adds). Labeled discuss — no code until the policy (esp. the tier-3 fallback) is settled.
🤖 from Claude
Motivation
The #280 reducer-coverage discussion surfaced an asymmetry: spill applies to all reducers (single-block is byte-identical to pooled, so even non-mergeable reducers work; only multi-block spill is mergeable-only), whereas merge-mode streaming is mergeable-only up front —
validate_streamingrejects expressions, non-lenscalars, and non-tdigest ragged before any reads. Today the only wired merge laws arelen/count(summation) and the two tdigest reducers (#279).This issue is to decide how (and how far) to generalize merge-mode toward the full reducer surface, so a config isn't silently forced onto spill/pooled just because it uses a common reduction.
The math — three tiers of reducer
min,max(elementwise),sum,countmean(as runningsum+count)var/std(via streaming moments — Welford M2 / Chan parallel-variance combine)expression:fields whose intermediate isn't a function of mergeable aggregates (e.g.np.median(h)directly, order-dependent ops)Options
lenand tdigest are already wired.expression:is a pure function of mergeable aggregates (e.g.mean = sum/count) and stream it by folding the parts. Powerful but fiddly; likely a later phase.Open questions
lenand tdigest are special-cased in bothStreamingAggregatorandSpillAggregator.)Related: #279 (tdigest merge laws, the current template for wiring one), #280 (parallel spill reducer — Flavor A is reducer-agnostic, so it composes with whatever laws this issue adds). Labeled
discuss— no code until the policy (esp. the tier-3 fallback) is settled.