Skip to content

Commit 8a2d618

Browse files
authored
Fix array_agg memory over use (apache#16346)
* Fix array_agg memory over accounting * Add comment
1 parent 06ccae2 commit 8a2d618

2 files changed

Lines changed: 84 additions & 5 deletions

File tree

datafusion/common/src/scalar/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3525,6 +3525,12 @@ impl ScalarValue {
35253525
}
35263526
}
35273527
}
3528+
3529+
/// Compacts ([ScalarValue::compact]) the current [ScalarValue] and returns it.
3530+
pub fn compacted(mut self) -> Self {
3531+
self.compact();
3532+
self
3533+
}
35283534
}
35293535

35303536
/// Compacts the data of an `ArrayData` into a new `ArrayData`.

datafusion/functions-aggregate/src/array_agg.rs

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ use std::mem::{size_of, size_of_val};
2323
use std::sync::Arc;
2424

2525
use arrow::array::{
26-
new_empty_array, Array, ArrayRef, AsArray, BooleanArray, ListArray, StructArray,
26+
make_array, new_empty_array, Array, ArrayRef, AsArray, BooleanArray, ListArray,
27+
StructArray,
2728
};
2829
use arrow::compute::{filter, SortOptions};
2930
use arrow::datatypes::{DataType, Field, FieldRef, Fields};
3031

3132
use datafusion_common::cast::as_list_array;
33+
use datafusion_common::scalar::copy_array_data;
3234
use datafusion_common::utils::{get_row_at_idx, SingleRowListArrayBuilder};
3335
use datafusion_common::{exec_err, internal_err, Result, ScalarValue};
3436
use datafusion_expr::function::{AccumulatorArgs, StateFieldsArgs};
@@ -313,7 +315,11 @@ impl Accumulator for ArrayAggAccumulator {
313315
};
314316

315317
if !val.is_empty() {
316-
self.values.push(val);
318+
// The ArrayRef might be holding a reference to its original input buffer, so
319+
// storing it here directly copied/compacted avoids over accounting memory
320+
// not used here.
321+
self.values
322+
.push(make_array(copy_array_data(&val.to_data())));
317323
}
318324

319325
Ok(())
@@ -423,7 +429,8 @@ impl Accumulator for DistinctArrayAggAccumulator {
423429
if nulls.is_none_or(|nulls| nulls.null_count() < val.len()) {
424430
for i in 0..val.len() {
425431
if nulls.is_none_or(|nulls| nulls.is_valid(i)) {
426-
self.values.insert(ScalarValue::try_from_array(val, i)?);
432+
self.values
433+
.insert(ScalarValue::try_from_array(val, i)?.compacted());
427434
}
428435
}
429436
}
@@ -577,8 +584,14 @@ impl Accumulator for OrderSensitiveArrayAggAccumulator {
577584
if nulls.is_none_or(|nulls| nulls.null_count() < val.len()) {
578585
for i in 0..val.len() {
579586
if nulls.is_none_or(|nulls| nulls.is_valid(i)) {
580-
self.values.push(ScalarValue::try_from_array(val, i)?);
581-
self.ordering_values.push(get_row_at_idx(ord, i)?)
587+
self.values
588+
.push(ScalarValue::try_from_array(val, i)?.compacted());
589+
self.ordering_values.push(
590+
get_row_at_idx(ord, i)?
591+
.into_iter()
592+
.map(|v| v.compacted())
593+
.collect(),
594+
)
582595
}
583596
}
584597
}
@@ -714,6 +727,7 @@ impl Accumulator for OrderSensitiveArrayAggAccumulator {
714727
#[cfg(test)]
715728
mod tests {
716729
use super::*;
730+
use arrow::array::{ListBuilder, StringBuilder};
717731
use arrow::datatypes::{FieldRef, Schema};
718732
use datafusion_common::cast::as_generic_string_array;
719733
use datafusion_common::internal_err;
@@ -980,6 +994,56 @@ mod tests {
980994
Ok(())
981995
}
982996

997+
#[test]
998+
fn does_not_over_account_memory() -> Result<()> {
999+
let (mut acc1, mut acc2) = ArrayAggAccumulatorBuilder::string().build_two()?;
1000+
1001+
acc1.update_batch(&[data(["a", "c", "b"])])?;
1002+
acc2.update_batch(&[data(["b", "c", "a"])])?;
1003+
acc1 = merge(acc1, acc2)?;
1004+
1005+
// without compaction, the size is 2652.
1006+
assert_eq!(acc1.size(), 732);
1007+
1008+
Ok(())
1009+
}
1010+
#[test]
1011+
fn does_not_over_account_memory_distinct() -> Result<()> {
1012+
let (mut acc1, mut acc2) = ArrayAggAccumulatorBuilder::string()
1013+
.distinct()
1014+
.build_two()?;
1015+
1016+
acc1.update_batch(&[string_list_data([
1017+
vec!["a", "b", "c"],
1018+
vec!["d", "e", "f"],
1019+
])])?;
1020+
acc2.update_batch(&[string_list_data([vec!["e", "f", "g"]])])?;
1021+
acc1 = merge(acc1, acc2)?;
1022+
1023+
// without compaction, the size is 16660
1024+
assert_eq!(acc1.size(), 1660);
1025+
1026+
Ok(())
1027+
}
1028+
1029+
#[test]
1030+
fn does_not_over_account_memory_ordered() -> Result<()> {
1031+
let mut acc = ArrayAggAccumulatorBuilder::string()
1032+
.order_by_col("col", SortOptions::new(false, false))
1033+
.build()?;
1034+
1035+
acc.update_batch(&[string_list_data([
1036+
vec!["a", "b", "c"],
1037+
vec!["c", "d", "e"],
1038+
vec!["b", "c", "d"],
1039+
])])?;
1040+
1041+
// without compaction, the size is 17112
1042+
assert_eq!(acc.size(), 2112);
1043+
1044+
Ok(())
1045+
}
1046+
9831047
struct ArrayAggAccumulatorBuilder {
9841048
return_field: FieldRef,
9851049
distinct: bool,
@@ -1059,6 +1123,15 @@ mod tests {
10591123
.collect()
10601124
}
10611125

1126+
fn string_list_data<'a>(data: impl IntoIterator<Item = Vec<&'a str>>) -> ArrayRef {
1127+
let mut builder = ListBuilder::new(StringBuilder::new());
1128+
for string_list in data.into_iter() {
1129+
builder.append_value(string_list.iter().map(Some).collect::<Vec<_>>());
1130+
}
1131+
1132+
Arc::new(builder.finish())
1133+
}
1134+
10621135
fn data<T, const N: usize>(list: [T; N]) -> ArrayRef
10631136
where
10641137
ScalarValue: From<T>,

0 commit comments

Comments
 (0)