-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Address memory over-accounting in array_agg #16816
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
base: main
Are you sure you want to change the base?
Address memory over-accounting in array_agg #16816
Conversation
…asuring array_agg accumulator size
c90401b
to
ac407a1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the approach. It's a case where the under accounting seems more manageable than a vast over accounting, and performance should be better than copying too.
@@ -1008,8 +1002,7 @@ mod tests { | |||
acc2.update_batch(&[data(["b", "c", "a"])])?; | |||
acc1 = merge(acc1, acc2)?; | |||
|
|||
// without compaction, the size is 2652. | |||
assert_eq!(acc1.size(), 732); | |||
assert_eq!(acc1.size(), 266); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! To convince myself this approach was still accurate enough, I was thinking we should compare this size to the raw data size, which I got from:
let data1 = data(["a", "c", "b"]);
let data2 = data(["b", "c", "a"]);
println!("Size of data: {} {}", data1.get_array_memory_size(), data2.get_array_memory_size());
I see that the data size is 1208 for each. But it's related to excessive capacity of the Array, which I would hope does not happen too much for larger arrays (the ones that actually matter for memory accounting). I would also hope unused memory pages are not effectively allocated by the kernel anyway, so not taking memory in practice (unsure about that in this case).
If we update the data
helper to use shrink_to_fit
:
fn data<T, const N: usize>(list: [T; N]) -> ArrayRef
where
ScalarValue: From<T>,
{
let values: Vec<_> = list.into_iter().map(ScalarValue::from).collect();
let mut array = ScalarValue::iter_to_array(values).expect("Cannot convert to array");
array.shrink_to_fit();
array
}
Then we get 139 byte per array, which is 278 total, so we are under-accounting by only 12 bytes in practice. Which sounds good.
// not used here. | ||
self.values | ||
.push(make_array(copy_array_data(&val.to_data()))); | ||
self.values.push(val) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
love this one, as the call looks much cheaper now
Which issue does this PR close?
Rationale for this change
Follow up on:
array_agg
function #16519The
ArrayAggAccumulator
, unlikeDistinctArrayAggAccumulator
orOrderSensitiveArrayAggAccumulator
or many other accumulators, accumulates the values by directly referencing to the sourceArrayRef
s rather than making aScalarValue
out of it. This is good for performance as we can afford to just keep references to the original buffers, but it has the drawback that it makes complicated measuring the memory consumed by theArrayAggAccumulator
.When calling
ArrayRef::get_array_memory_size()
we get the memory occupied by the whole underlaying buffer, but it's not technically true thatArrayAggAccumulator
is occupying all that space.I found this other
ArrayData::get_slice_memory_size()
method with the following docs:Which leads me to think that if we are accumulating
ArrayRef
s, rather than compacting them by copying their data, it might be better to just not do it and report the consumed memory by callingget_slice_memory_size()
. I think it's still not technically true thatArrayAggAccumulator
is occupying that space, as it's memory not owned byArrayAggAccumulator
, it's just referenced by it but owned by someone else, but it's the closest thing to reality that I was able to come up with.What changes are included in this PR?
Stops copying ArrayRef data in
ArrayAggAccumulator
and starts measuring its occupied size withget_slice_memory_size()
Are these changes tested?
yes, by existing tests
Are there any user-facing changes?
People should stop seeing ResourceExhausted errors in aggregations that imply
ArrayAgg