Skip to content

compute: Re-encode temporal-bucketing output as the columnar edge - #37787

Merged
antiguru merged 4 commits into
columnar-p9-importsfrom
columnar-ta-temporal-bucket-reencode
Sep 11, 2026
Merged

compute: Re-encode temporal-bucketing output as the columnar edge#37787
antiguru merged 4 commits into
columnar-p9-importsfrom
columnar-ta-temporal-bucket-reencode

Conversation

@antiguru

@antiguru antiguru commented Jul 21, 2026

Copy link
Copy Markdown
Member

Re-encode the temporal-bucketing output back to the columnar edge; the operator itself still works over Vec internally. Removes one mixed-variant edge source ahead of the concat_many teardown.

Columnar dataflow-edge migration. Design doc: doc/developer/design/20260720_columnar_dataflow_edges.md (#37744).

Part of CPU-51.

@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 9978083 to 038ee6e Compare July 22, 2026 08:41
@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

CPU-51

@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 038ee6e to d5f9095 Compare July 22, 2026 16:24
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from d5f9095 to 49c35f9 Compare July 22, 2026 17:51
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch 2 times, most recently from fd0f181 to aab0e0d Compare August 19, 2026 13:17
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from aab0e0d to 8769666 Compare August 20, 2026 08:49
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 8769666 to 13ee1e0 Compare August 20, 2026 09:13
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch 2 times, most recently from 1bfb45e to af1a458 Compare September 10, 2026 11:33
@antiguru
antiguru marked this pull request as ready for review September 10, 2026 11:33
@antiguru
antiguru requested a review from a team as a code owner September 10, 2026 11:33
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch 2 times, most recently from 3609677 to 26f61ed Compare September 10, 2026 12:09
@def-

def- commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- Bucketed TopK input now round-trips Row → Column → Row

src/compute/src/render/top_k.rs:128

The new vec_to_columnar encode at the TopK bucketing site is undone one operator later: map_topk_key's columnar arm calls Columnar::into_owned(row) for every record, whereas the Vec arm it displaces moved the already-owned Row for free. A bucketed TopK therefore pays a full columnar re-encode plus a second per-record Row allocation that the pre-change path did not, on every input record, and nothing downstream consumes the columnar form.

Details

Chain after the change, for the plan shape the file's own mv_topk_firing fixture pins down (TopK::Basic over Get::Collection, whose import producer already emits columnar):

Columnarinto_vec() = ColumnarToVec (one owned Row per record) → bucket → vec_to_columnar (full byte copy of rows, times, diffs) → map_topk_key columnar arm at top_k.rs:647, Columnar::into_owned(row) at top_k.rs:667 (a second owned Row per record).

Before the change: ColumnarToVec → bucket → map_topk_key Vec arm at top_k.rs:637, which is oks.map(|row| (key_row, row)) and reuses the owned Row. So the seam goes from one decode to decode + encode + decode.

map_topk_key's own doc already records that the columnar arm "does not avoid the per-record decode, because there is no columnar batcher to push borrowed rows into here". Unlike the Union site at render.rs:1414 that the commit message motivates, there is no concat_many at TopK, so the encode is not collapsing a mixed-variant concat — it has no beneficiary. (The two context.rs sites each add one encode too, but their consumer, arrange_collection's columnar arm, reads rows borrowed, so there is no second decode there.)

The flag defaults off in production, but misc/python/materialize/mzcompose/__init__.py:129 sets enable_compute_temporal_bucketing = true for every mzcompose environment, so this is the configuration under test.

Suggested fix: leave the TopK site producing CollectionEdge::Vec until build_topk / map_topk_key can consume columnar containers without materializing a Row — the TODO on topk_result_to_columnar already names that work — or, if the uniform-producer property is worth the cost here, say so at the site so the round-trip is not read as an oversight.

Copy link
Copy Markdown
Member Author

The cost is real and the trace is right. This PR adds the vec_to_columnar at top_k.rs:128, and the second decode it runs into, Columnar::into_owned in map_topk_key, comes from #37749. A bucketed TopK does go from one decode to decode, encode, decode, and nothing between the encode and that decode reads the columnar form.

Not taking the first suggestion, though, because leaving the site on CollectionEdge::Vec does not survive the stack. #37797 collapses the edge to a single columnar representation, so the site has to produce columnar there whatever it does here, and the Vec arm of map_topk_key goes away with it. Keeping this one site on Vec for the three intervening PRs would only move the round trip to the PR that deletes the alternative, and buying it back at that point costs a second input representation threaded through build_topk, which is exactly what the collapse removes.

Took the second suggestion instead: doc: record the bucketed TopK container round trip says at the site that the encode buys edge uniformity rather than a local consumer, and points at the push-down.

Worth noting the round trip is already fixed downstack rather than merely tracked. #38371 makes the bucketer columnar-native, so the decode and the encode around it both disappear and the comment there reads "Temporal bucketing is columnar throughout, so the bucketed input edge needs no round-trip". What outlives it is map_topk_key's own decode, which its doc comment already owns and which the TODO on topk_result_to_columnar names as the remaining work.

One correction to the details: enable_compute_temporal_bucketing alone does not reach this code. The site also requires ArrangementStrategy::TemporalBucketing on the TopK, so the mzcompose default exposes it only for plans the optimizer actually buckets, not for every mzcompose TopK.

Posted by Claude Code

@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 71ad028 to 112717a Compare September 10, 2026 18:37
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 112717a to ea3e23e Compare September 11, 2026 18:52
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from ea3e23e to 0ae867d Compare September 11, 2026 20:31
antiguru and others added 4 commits September 11, 2026 23:14
Temporal bucketing (`maybe_apply_temporal_bucketing`) is `Vec`-internal:
it consumes and produces a `StreamVec`. The four sites that drive it
previously decoded the edge with `into_vec` and then re-wrapped the `Vec`
result as `CollectionEdge::Vec`, leaving the last `Vec` producer that fed
a Union's `concat_many`.

Re-encode each bucketed result with `vec_to_columnar` so the output edge
is columnar, matching every other producer after the producer wave. The
bucketer stays on `Vec` internally; only its output boundary changes.
This is a non-consolidating leaf encode, since the bucketer output is not
consolidated and the prior `CollectionEdge::Vec` wrap was non-consolidating.

With every producer now columnar, a consolidating Union that mixes a
bucketed input with a `Direct` input feeds `concat_many` two columnar
edges instead of a mixed pair.

Adds runtime coverage to `temporal_bucketing.slt` under
`enable_compute_temporal_bucketing`: bucketed Reduce, bucketed TopK, and
an `EXCEPT ALL` whose Union carries `[TemporalBucketing, Direct]`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The comments named the migration plan's nodes, which mean nothing to a reader,
and described the union input in terms of a mixed-variant `concat_many` case
rather than what the code does.
A bucketed TopK decodes the input edge for the bucketer, encodes the
bucketed result back, and then has `map_topk_key` decode it again, because
the TopK stages are `Vec`-based. The encode keeps the input edge uniform
with every other producer, which the collection edge requires once it has a
single representation, so the round trip is deliberate rather than an
oversight. Note it at the site and point at the push-down that removes both
halves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four sites carried the same paragraph. Reduce each to the one fact that
drives the decode and encode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@antiguru
antiguru force-pushed the columnar-ta-temporal-bucket-reencode branch from 0ae867d to aad56c2 Compare September 11, 2026 21:15
@def-

def- commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

QA LLM Review

1. MEDIUM -- rt_reduce names the wrong site, leaving both context.rs re-encodes unexercised

test/sqllogictest/temporal_bucketing.slt:312

The rt_reduce fixture is labelled "Hits the arrangement re-encode in context.rs", but a Temporal Filter -> GROUP BY plan buckets at reduce.rs:179, on the keyed ((key, val), T, Diff) stream, which is not a CollectionEdge and is untouched by this PR. Neither context.rs:1156 nor context.rs:1196 is reached by any of the three new runtime fixtures, so the block reads as covering the arrangement seam while asserting nothing about it.

Details

The file's own mv_firing fixture pins the plan for exactly this query shape at line 36: Reduce::Accumulable temporal_bucketing_strategy=TemporalBucketing directly over Get::Collection, no ArrangeBy. render_reduce buckets that keyed stream itself and bypasses ensure_collections, which the file's header comment at line 16 already states.

The other two fixtures are labelled correctly: rt_topk reaches top_k.rs:128, and rt_union reaches render.rs:1416 (its ArrangeBy prints no strategy=, i.e. Direct, because lowering.rs:1228 clears has_future_updates at a consolidating Union).

This matters because context.rs:1196 is the one flipped site whose new variant propagates onward: arrange_collection hands back a Columnar passthrough that becomes the bundle's self.collection at context.rs:1206 for every downstream consumer, where before the flip it was Vec. It is reachable from ordinary SQL, since MirRelationExpr::ArrangeBy over a temporal-filtered input gets strategy_from_future(true) at lowering.rs:1331 — that is what an index on a temporal-filter view builds, and the file already creates v_recent and v_topk_input that way. A dropped record or a mixed-up passthrough variant on that seam leaves the whole new block green.

Fix: repoint rt_reduce's comment at reduce.rs, and add a fixture that actually routes through ensure_collections under the enabled flag, e.g. a CREATE DEFAULT INDEX on a temporal-filtered view over rt_events plus a SELECT from it.

Copy link
Copy Markdown
Member Author

Both halves are right, and I checked them against the code rather than the labels.

rt_reduce does not reach context.rs. render_reduce buckets the keyed ((key, val), T, Diff) stream itself, on key_val_collection.inner, which is not a CollectionEdge and is untouched by this PR. The file's own header comment already states that Reduce builds its arrangement via KeyValPlan and bypasses ensure_collections, so the label contradicts the file three hundred lines above it.

The coverage gap is the more important half, and the arranged-loop site is the one worth a fixture: arrange_collection hands back a Columnar passthrough that becomes the bundle's self.collection for every downstream consumer, where it was Vec before the flip. A dropped record or a swapped passthrough variant there leaves the whole new rt_* block green. Your suggested shape should reach it, since lowering gives ArrangeBy strategy_from_future(input_has_future_updates). The other site needs the raw collection to be formed from an arrangement, so it wants a temporal filter over an indexed input rather than the same fixture.

Taking both as a follow-up rather than amending here. This is the second layer of a ten-PR stack, so a change at this level restacks nine PRs and re-runs their builds, and neither item is a correctness defect in shipped code: one is a wrong comment in a test file, the other is absent coverage rather than failing coverage. The follow-up repoints the comment at reduce.rs and adds fixtures that route through ensure_collections, and I would rather confirm those plan shapes there than land them under a restack.

Posted by Claude Code

@antiguru
antiguru merged commit 73af01e into main Sep 11, 2026
78 checks passed
@antiguru
antiguru deleted the columnar-ta-temporal-bucket-reencode branch September 11, 2026 21:46

Copy link
Copy Markdown
Member Author

Follow-up is up as #38807, stacked on the remaining layers.

It repoints the rt_reduce comment at render_reduce and adds an index on a temporal-filtered view, which reaches the loop that builds the requested arrangements. The plan is pinned alongside the results, because the site is only reached while lowering marks the ArrangeBy with strategy=TemporalBucketing, and without that assertion the fixture could stop covering the site without failing.

The other site I could not reach. It needs the raw collection to be formed from an arrangement in the same ensure_collections call that builds one, and every shape I tried misses: a mz_now() predicate is applied at the top of an MFP chain, so it lifts above a join rather than landing in an ArrangeBy's input MFP, and an index on a temporal-filtered view splits into two dataflow objects whose first half builds no arrangement and therefore takes Direct. That site may be unreachable for bucketing, but I have not shown it, so the PR says only that it stays uncovered.

Posted by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants