Skip to content

fix: Incorrect memory accounting in array_agg function #16519

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

sfluor
Copy link
Contributor

@sfluor sfluor commented Jun 23, 2025

Which issue does this PR close?

See this issue: #16517

What changes are included in this PR?

Fixes the over-accounting in array_agg functions

Are these changes tested?

Added a test that shows the problem.

Are there any user-facing changes?

Cloning the data might lead to slightly higher "real" memory usage.

@github-actions github-actions bot added the functions Changes to functions implementation label Jun 23, 2025
@sfluor sfluor force-pushed the sami/fix-overaccounting-of-memory-in-array-agg branch from d4a69ce to 1da3e04 Compare June 23, 2025 16:17
@sfluor sfluor marked this pull request as ready for review June 24, 2025 08:01
Comment on lines +344 to 349
// The ArrayRef might be holding a reference to its original input buffer, so
// storing it here directly copied/compacted avoids over accounting memory
// not used here.
self.values
.push(make_array(copy_array_data(&values.to_data())));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 I'm not sure if this will solve the issue. Keep in mind that the merge_batch method argument receives the states of other accumulators, which already hold "compacted" data, so I'd expect this compaction here to be unnecessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add a special case to copy_array_data to avoid copying the data when it already is only a single row / has no offset 🤔

Right now it seems to copy the data unconditionally which is a non trivial overhead on each row 🤔

pub fn copy_array_data(src_data: &ArrayData) -> ArrayData {
let mut copy = MutableArrayData::new(vec![&src_data], true, src_data.len());
copy.extend(0, 0, src_data.len());
copy.freeze()

Perhaps we can do that as a follow of PR

Comment on lines +1022 to +1025
acc1.merge_batch(&[Arc::new(a1.slice(0, 1))])?;
acc2.merge_batch(&[Arc::new(a2.slice(0, 1))])?;

acc1 = merge(acc1, acc2)?;
Copy link
Contributor

@gabotechs gabotechs Jun 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merge_batch functions do not receive arbitrary data, it receives the results of calling state() in other accumulators. A fairer test would be to do something like:

Suggested change
acc1.merge_batch(&[Arc::new(a1.slice(0, 1))])?;
acc2.merge_batch(&[Arc::new(a2.slice(0, 1))])?;
acc1 = merge(acc1, acc2)?;
acc1.update_batch(&[Arc::new(a1.slice(0, 1))])?;
acc2.update_batch(&[Arc::new(a2.slice(0, 1))])?;
acc1 = merge(acc1, acc2)?;

With this, you would notice that the test result is the same regardless of the changes in this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gabotechs
Copy link
Contributor

EDIT: I'm seeing that there's cases where the merge_batch method is used for something other than merging states:

let res = match mode {
AggregateMode::Partial
| AggregateMode::Single
| AggregateMode::SinglePartitioned => accum.update_batch(&values),
AggregateMode::Final | AggregateMode::FinalPartitioned => {
accum.merge_batch(&values)
}
};

Which means that probably we do want compaction to happen also in the merge_batch function. I think this is good to go then 👍

Copy link
Contributor

@gabotechs gabotechs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good for a review cc @alamb

@alamb
Copy link
Contributor

alamb commented Jun 25, 2025

Thanks for the code and review @gabotechs and @sfluor

alamb
alamb previously approved these changes Jun 25, 2025
@alamb
Copy link
Contributor

alamb commented Jun 25, 2025

There appears to be an array_agg benchmark -- I will run that on this PR to see what it shows

@alamb

This comment was marked as outdated.

1 similar comment
@alamb

This comment was marked as outdated.

@alamb

This comment was marked as outdated.

@alamb
Copy link
Contributor

alamb commented Jun 25, 2025

🤖 ./gh_compare_branch_bench.sh Benchmark Script Running
Linux aal-dev 6.11.0-1015-gcp #15~24.04.1-Ubuntu SMP Thu Apr 24 20:41:05 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
Comparing sami/fix-overaccounting-of-memory-in-array-agg (1da3e04) to b6c8cc5 diff
BENCH_NAME=array_agg
BENCH_COMMAND=cargo bench --bench array_agg
BENCH_FILTER=
BENCH_BRANCH_NAME=sami_fix-overaccounting-of-memory-in-array-agg
Results will be posted here when complete

@alamb
Copy link
Contributor

alamb commented Jun 25, 2025

🤖: Benchmark completed

Details

group                                                                              main                                   sami_fix-overaccounting-of-memory-in-array-agg
-----                                                                              ----                                   ----------------------------------------------
array_agg i64 merge_batch 30% nulls, 0% of nulls point to a zero length array      1.00    547.4±7.12µs        ? ?/sec    9.08      5.0±0.03ms        ? ?/sec
array_agg i64 merge_batch 30% nulls, 100% of nulls point to a zero length array    1.00      6.1±0.03µs        ? ?/sec    7.26     44.5±0.85µs        ? ?/sec
array_agg i64 merge_batch 30% nulls, 50% of nulls point to a zero length array     1.00    547.7±1.36µs        ? ?/sec    9.28      5.1±0.02ms        ? ?/sec
array_agg i64 merge_batch 30% nulls, 90% of nulls point to a zero length array     1.00    548.2±0.87µs        ? ?/sec    9.38      5.1±0.02ms        ? ?/sec
array_agg i64 merge_batch 30% nulls, 99% of nulls point to a zero length array     1.00    561.4±4.24µs        ? ?/sec    9.12      5.1±0.06ms        ? ?/sec
array_agg i64 merge_batch 70% nulls, 0% of nulls point to a zero length array      1.00    243.2±1.64µs        ? ?/sec    8.13  1977.2±10.35µs        ? ?/sec
array_agg i64 merge_batch 70% nulls, 100% of nulls point to a zero length array    1.00      5.9±0.02µs        ? ?/sec    3.96     23.2±0.43µs        ? ?/sec
array_agg i64 merge_batch 70% nulls, 50% of nulls point to a zero length array     1.00    242.1±0.26µs        ? ?/sec    8.39      2.0±0.02ms        ? ?/sec
array_agg i64 merge_batch 70% nulls, 90% of nulls point to a zero length array     1.00    243.5±0.44µs        ? ?/sec    8.08  1968.8±15.78µs        ? ?/sec
array_agg i64 merge_batch 70% nulls, 99% of nulls point to a zero length array     1.00    243.0±0.62µs        ? ?/sec    8.26      2.0±0.01ms        ? ?/sec
array_agg i64 merge_batch all nulls, 100% of nulls point to a zero length array    1.00     86.9±0.11ns        ? ?/sec    1.01     87.6±0.14ns        ? ?/sec
array_agg i64 merge_batch all nulls, 90% of nulls point to a zero length array     1.00     86.9±0.09ns        ? ?/sec    1.01     87.6±0.15ns        ? ?/sec
array_agg i64 merge_batch no nulls                                                 1.00    100.8±0.12ns        ? ?/sec    544.14    54.8±3.04µs        ? ?/sec

@alamb alamb dismissed their stale review June 26, 2025 17:57

Bnchmarks show significant regression

@alamb
Copy link
Contributor

alamb commented Jun 26, 2025

🤔 the benchmarks show a significant regression in performance (10x in some cases)

I think we need to resolve that prior to merging this in

We have some documentation on how to profile here: https://datafusion.apache.org/library-user-guide/profiling.html

The benchmarks can be run locally like this

cargo bench --bench array_agg

I think it might be worth explroing: #16519 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
functions Changes to functions implementation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Incorrect memory accounting in array_agg function
3 participants