Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
68c6fd8
feat: add data‑free SQL harness for array_agg_distinct benchmark
kosiew Sep 9, 2026
713afe2
chore: adjust benchmark scope from 1M to 100K groups
kosiew Sep 9, 2026
e015a1a
bench: scale array_agg distinct groups
kosiew Sep 11, 2026
93f2dae
empty commit
kosiew Sep 9, 2026
06bbae4
feat: add optional AggregateMetric API with lazy internal timers and …
kosiew Sep 8, 2026
238ab26
feat(metrics): safe refactors to avoid redundant allocations and impr…
kosiew Sep 8, 2026
0338b63
fix: assert both internal_distinct timers are >0 in test
kosiew Sep 8, 2026
1a27e68
docs: update metrics.md with identity/cardinality, accumulator timer,…
kosiew Sep 8, 2026
e3fce28
fix(DistinctArrayAggAccumulator): correct `size()` calculation
kosiew Sep 8, 2026
12ec1e1
feat(aggregates): add 2‑partition execution test and fix array_agg di…
kosiew Sep 8, 2026
3f9c375
feat(metrics): add lock‑free OnceLock fast path for AggregateSubMetrics
kosiew Sep 9, 2026
7444e9a
fix: restore Time::add min‑1ns behavior and remove exact‑duration API
kosiew Sep 9, 2026
139b807
fix(time): corrected split to preserve exact duration adds and avoid …
kosiew Sep 9, 2026
16d93d9
feat(metrics): require RefUnwindSafe for AggregateMetric and add comp…
kosiew Sep 9, 2026
7e1ac88
test: add legacy grouped `array_agg(DISTINCT)` metric test
kosiew Sep 9, 2026
31cfdc7
fix(array_agg): skip internal DISTINCT timing for small batches and i…
kosiew Sep 9, 2026
5947693
feat(datafusion): add grouped update metric and update_batch_grouped …
kosiew Sep 9, 2026
030cf81
fix(groups_accumulator): improve Instant usage and fix unnecessary Op…
kosiew Sep 10, 2026
1e91ca3
refactor: simplify distinct_metric cloning in DistinctArrayAggAccumul…
kosiew Sep 10, 2026
c797453
feat(metrics): add array_agg(DISTINCT) merge, convert_to_state, and m…
kosiew Sep 11, 2026
9d82f56
feat: cache aggregate metric handles for grouped accumulators
kosiew Sep 11, 2026
30f0ce4
feat(groups-accumulator-adapter): move slice/filter prep outside timi…
kosiew Sep 11, 2026
620e660
feat: convert_to_state uses bounded 64‑row prep chunks with timer‑wra…
kosiew Sep 11, 2026
68945a9
refactor: simplify metric lookup and consolidate implementation block…
kosiew Sep 11, 2026
fd8c4f9
empty commit2-before merge main
kosiew Sep 11, 2026
c7fd9f6
Merge branch 'main' into aggmetrics-06-23570
kosiew Sep 11, 2026
eab374e
fix(metrics): create execution-owned aggregate_sub_metrics helper and…
kosiew Sep 11, 2026
2c2bae2
docs: clarify metrics documentation about internal submetrics
kosiew Sep 11, 2026
e6e4a85
perf(aggregate): move size accounting out of timer and improve error …
kosiew Sep 11, 2026
e7d2d7e
test: add regression test for groups_accumulator
kosiew Sep 11, 2026
3b9fed4
Merge branch 'main' into aggmetrics-06-23570
kosiew Sep 11, 2026
2b8c4cf
fix: clear successful groups before propagating later-group error and…
kosiew Sep 11, 2026
3e08f38
fix: update array_agg_distinct benchmark group size from 100K to 1000K
kosiew Sep 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions benchmarks/bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ nlj: Benchmark for simple nested loop joins, testing various
hj: Benchmark for simple hash joins, testing various join scenarios
smj: Benchmark for simple sort merge joins, testing various join scenarios
dict: Benchmark for dictionary-encoded group-by scenarios
array_agg_distinct: 1000K-group, two-row-per-group array_agg(DISTINCT) benchmark
compile_profile: Compile and execute TPC-H across selected Cargo profiles, reporting timing and binary size


Expand Down Expand Up @@ -651,6 +652,9 @@ main() {
dict)
run_dict
;;
array_agg_distinct)
run_array_agg_distinct
;;
compile_profile)
run_compile_profile "${PROFILE_ARGS[@]}"
;;
Expand Down Expand Up @@ -1665,6 +1669,14 @@ run_dict() {
debug_run $CARGO_COMMAND --bin dfbench -- dict --iterations 5 -o "${RESULTS_FILE}" ${QUERY_ARG} ${LATENCY_ARG}
}

# Runs the data-free high-cardinality array_agg(DISTINCT) SQL benchmark.
run_array_agg_distinct() {
echo "Running array_agg_distinct benchmark..."
debug_run env BENCH_NAME=array_agg_distinct \
${QUERY:+BENCH_QUERY="${QUERY}"} \
bash -c "$SQL_CARGO_COMMAND"
}


compare_benchmarks() {
BASE_RESULTS_DIR="${SCRIPT_DIR}/results"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
description = "High-cardinality array_agg(DISTINCT) SQL benchmarks"

query_pattern = "q{QUERY_ID_PADDED}.benchmark"

[[examples]]
command = "cargo run --release --bin benchmark_runner -- array_agg_distinct"
description = "Run the high-cardinality array_agg(DISTINCT) benchmark."

[[examples]]
command = "cargo run --release --bin benchmark_runner -- array_agg_distinct --query 1 --iterations 5 --output /tmp/array_agg_distinct.json"
description = "Run five iterations and write comparable JSON results."
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name Q01
group array_agg_distinct

expect_plan AggregateExec

run
-- 1M groups, 2 rows/group, and 2 distinct values/group. `range` is end-exclusive.
-- This is data-free so comparisons isolate grouped array_agg(DISTINCT) execution.
SELECT value / 2 AS k, array_agg(DISTINCT value % 2) AS distinct_values
FROM range(2000000)
GROUP BY value / 2;
53 changes: 53 additions & 0 deletions datafusion/expr-common/src/accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,26 @@
use arrow::array::ArrayRef;
use datafusion_common::{Result, ScalarValue, internal_err};
use std::fmt::Debug;
use std::sync::Arc;
use std::time::Duration;

/// A metric owned by one aggregate implementation.
///
/// Aggregate implementations use this interface for optional internal
/// subphases. The execution engine owns metric registration and aggregation.
pub trait AggregateMetric: Debug + Send + Sync + std::panic::RefUnwindSafe {
/// Adds elapsed time to this metric.
fn add_duration(&self, duration: Duration);
}

/// Factory for optional metrics owned by one aggregate expression.
///
/// `subphase` must be a stable static identifier. An implementation may request
/// no metrics. The execution engine assigns the aggregate expression identity.
pub trait AggregateMetrics: Debug + Send + Sync {
/// Returns the metric for an aggregate-owned internal subphase.
fn metric(&self, subphase: &'static str) -> Arc<dyn AggregateMetric>;
}

/// Tracks an aggregate function's state.
///
Expand Down Expand Up @@ -49,6 +69,12 @@ use std::fmt::Debug;
/// [`merge_batch`]: Self::merge_batch
/// [window function]: https://en.wikipedia.org/wiki/Window_function_(SQL)
pub trait Accumulator: Send + Sync + Debug + std::any::Any {
/// Supplies optional metrics owned by this aggregate expression.
///
/// The default preserves compatibility for accumulators without internal
/// submetrics.
fn set_metrics(&mut self, _metrics: Arc<dyn AggregateMetrics>) {}

/// Updates the accumulator's state from its input.
///
/// `values` contains the arguments to this aggregate function.
Expand All @@ -58,6 +84,33 @@ pub trait Accumulator: Send + Sync + Debug + std::any::Any {
/// running sum.
fn update_batch(&mut self, values: &[ArrayRef]) -> Result<()>;

/// Returns an optional metric timed once per grouped adapter input batch.
///
/// A grouped accumulator adapter uses this for aggregate-owned work it
/// dispatches to one accumulator per group. The default preserves the
/// usual per-accumulator update path.
fn grouped_update_batch_metric(&self) -> Option<Arc<dyn AggregateMetric>> {
None
}

/// Updates state when called by a grouped accumulator adapter.
///
/// The default delegates to [`Self::update_batch`]. Implementations that
/// return a [`Self::grouped_update_batch_metric`] can avoid timing every
/// per-group call; the adapter records one interval for the full batch.
fn update_batch_grouped(&mut self, values: &[ArrayRef]) -> Result<()> {
self.update_batch(values)
}

/// Merges state when called by a grouped accumulator adapter.
///
/// The default delegates to [`Self::merge_batch`]. Implementations that
/// return a [`Self::grouped_update_batch_metric`] can avoid timing every
/// per-group merge; the adapter records one interval for the full batch.
fn merge_batch_grouped(&mut self, states: &[ArrayRef]) -> Result<()> {
self.merge_batch(states)
}

/// Returns the final aggregate value.
///
/// For example, the `SUM` accumulator maintains a running sum,
Expand Down
9 changes: 9 additions & 0 deletions datafusion/expr-common/src/groups_accumulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

use arrow::array::{ArrayRef, BooleanArray};
use datafusion_common::{Result, exec_err, not_impl_err, utils::split_vec_min_alloc};
use std::sync::Arc;

use crate::accumulator::AggregateMetrics;

/// Describes how many rows should be emitted during grouping.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down Expand Up @@ -188,6 +191,12 @@ impl<'a> GroupSelection<'a> {
/// [`Accumulator`]: crate::accumulator::Accumulator
/// [Aggregating Millions of Groups Fast blog]: https://arrow.apache.org/blog/2023/08/05/datafusion_fast_grouping/
pub trait GroupsAccumulator: Send + std::any::Any {
/// Supplies optional metrics owned by this aggregate expression.
///
/// The default preserves compatibility for accumulators without internal
/// submetrics.
fn set_metrics(&mut self, _metrics: Arc<dyn AggregateMetrics>) {}

/// Updates the accumulator's state from its arguments, encoded as
/// a vector of [`ArrayRef`]s.
///
Expand Down
4 changes: 3 additions & 1 deletion datafusion/expr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ pub use datafusion_doc::{
DocSection, Documentation, DocumentationBuilder, aggregate_doc_sections,
scalar_doc_sections, window_doc_sections,
};
pub use datafusion_expr_common::accumulator::Accumulator;
pub use datafusion_expr_common::accumulator::{
Accumulator, AggregateMetric, AggregateMetrics,
};
pub use datafusion_expr_common::columnar_value::ColumnarValue;
pub use datafusion_expr_common::groups_accumulator::{
EmitTo, GroupSelection, GroupsAccumulator,
Expand Down
Loading