Repository navigation
fix(query): a multi-key select with a distinct count beside an expression aggregate - #760
singaraiona wants to merge 2 commits into
Conversation
…sion aggregate (#756) A select grouped by two or more keys failed with `name` when it held a `(count (distinct x))` beside an aggregate over an expression, such as `(sum (if (== st 'bad) 1 0))`. The distinct count is not a DAG aggregate, so with more than one key the select fell to the eval-level grouping, which evaluated an aggregate's argument with no column in scope — and which is serial, 8x slower than the parallel path for the shapes that did not fail. The eval-level grouping now evaluates an aggregate's argument over the selected rows with the columns bound, as a row-wise projection (group_key_eval), since the eval-level `if` is scalar. A plain multi-key by: stays on the DAG path when the outputs the DAG cannot serve are distinct counts and literal broadcasts: the post-group scatter maps rows to groups through the composite key (rgid_build_multikey, a parallel tuple probe that applies the selection), and the existing per-group kernels serve the distinct counts. Two shapes the issue's workarounds exposed go the same way. An aggregate over an `if` dropped the group onto the legacy engine, 6x slower under a where: selection: the v2 expression bridge now materializes an input the element-wise compiler declines through the DAG executor. A distinct count alone took the two-pass rewrite — group by (keys, value), then count — which is a radix grouping of every tuple when that composite is not dense (1.8 GB and 3x the time at 10 M rows with 45 groups of 3 M values); the rewrite is now gated on a sampled estimate of the group and value counts (agg_group_card_estimate) and the select otherwise groups by the keys and counts over the slices. Closes #756. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Reviewed 2d624e2. The fix works for #756. The wins hold, and everything else matched dev and independent oracles, except a wrong result with -0.0 keys. Ours vs dev, 10M rows,
Correctness: composite scatter results matched dev's eval-level grouping, an encoded single-key oracle, and a
BUG: F64/F32 keys holding -0.0 lose rows on the composite path. The grouping canonicalises -0.0 to +0.0 when it reads a key (group.c:62-89, #407), but
The gate (
Single-key Other gate notes:
Behaviour changes users will see:
Tests. I reverted each piece in turn:
|
A
selectgrouped by two or more keys failed withnamewhen it held a(count (distinct x))beside an aggregate over an expression, such as(sum (if (== st 'bad) 1 0)). Every narrower form worked.Fixes #756.
The distinct count is not a DAG aggregate, so with more than one key the select fell to the eval-level composite grouping. That path evaluated a streaming aggregate's argument with plain
ray_evaland no column in scope, so only a bare column name worked. It is also serial, which is the 8x slowdown the issue measured on the inner-select workaround.Three changes, each closing one of the issue's observations:
group_key_eval(fix(query): group by a computed key beside distinct aggregates and row expressions #708's helper). A plain scoped eval would still have been wrong: the eval-leveliftests whole-vector truthiness and returns a scalar.by:stays on the parallel DAG path when the only outputs the DAG cannot serve are distinct counts and literal broadcasts. The post-group scatter maps rows to groups through the composite key:rgid_build_multikeyhashes the result's key tuples and probes every row in parallel, applying thewhere:selection as the single-key probe does. The existing per-group kernels then serve the distinct counts, including the global-hash kernel past 50 K groups. STR, GUID, LIST and mapcommon keys keep their previous routes; a row expression beside a composite key keeps the eval-level grouping.ifdropped the whole group onto the legacy engine, 6x slower under awhere:selection (109 ms against 14 ms without one): the v2 expression bridge inexec_group_v2_exprsnow materializes an input the element-wise compiler declines through the DAG executor, with the selection cleared around the call so the vector comes back at full length. A distinct count alone took the two-pass rewrite, grouping by (keys, value) and then counting per key. When that composite is not dense it is a radix grouping that materializes and orders every tuple: 1.8 GB and 3x the time of the slice dedup at 10 M rows with 45 groups of 3 M values. The rewrite is now gated oncd_two_pass_preferred: it keeps the rewrite when the (keys, value) space packs within the row count, when the groups outnumber 256 K, or when they are fewer than the workers; otherwise the select groups by the keys and counts over the slices. The estimate comes fromagg_group_card_estimate, a strided sample of up to 65,536 rows: span for integer keys, distinct codes for SYM keys (codes of a shared domain interleave, so a span would overstate a 5-value key by orders of magnitude), a fraction of a millisecond at any size.Validation:
test/rfl/regress/issue_756.rflcompares every shape against the two-select-and-join oracle: both aggregate orders, an aggregate over arithmetic,where:on whole keys and on part of every morsel, three keys with an integer key, literal broadcasts,desc:/take:, the by-dict form, a distinct count alone with and withoutwhere:, the eval-level grouping forced by a row expression, and anifaggregate underwhere:for one and two keys.where:prefilter shape (desc: s take: 5), two distinct counts beside a sum, a distinct over an expression, and a 210 K-group integer composite on the global-hash kernel, all equal to the join oracle.Benchmark: the issue's table, 10 M rows, release build, 7 cores, median of repeats, milliseconds. "dev" is
24c14e85.nameerrorwhere: (!= k1 'a)nameerrorleft-join(sum id)where:The distinct-count gate across group and value cardinalities, 10 M rows, the two-pass rewrite on dev against this PR's choice:
{a: k1 b: k2}, 45 × 3 MPeak memory for the issue's distinct count alone falls from +1.8 GB (the (keys, value) radix grouping) to +78 MB. The single-key fused kernel (
ray_cd_fused) is untouched.