-
Notifications
You must be signed in to change notification settings - Fork 841
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
POC Memory Tracking of Potentially Shared Buffer #6590
Conversation
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 think this should provide a mechanism for consumers like DF to track memory usage of buffered arrays accurately, and with performance that is no worse than the current get_buffer_memory_size
, which this would likely lead us to deprecate.
@@ -44,6 +44,9 @@ pub struct Bytes { | |||
|
|||
/// how to deallocate this region | |||
deallocation: Deallocation, | |||
|
|||
#[cfg(feature = "pool")] | |||
reservation: std::sync::Mutex<Option<Box<dyn crate::MemoryReservation>>>, |
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 originally used parking_lot::Mutex
but this would have made Buffer not RewindSafe, which would be a breaking change (and given there is a test for this I presume this is important).
|
||
/// A memory reservation within a [`MemoryPool`] that is freed on drop | ||
pub trait MemoryReservation { | ||
fn resize(&mut self, new: usize); |
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 don't actually have a use-case for this atm, but this seemed sensible to add now to avoid it being difficult later
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.
Mutable buffer might need this. It may resize its reservation dynamically
/// Register this [`Bytes`] with the provided [`MemoryPool`] | ||
#[cfg(feature = "pool")] | ||
pub fn claim(&self, pool: &dyn crate::MemoryPool) { | ||
*self.reservation.lock().unwrap() = Some(pool.register(self.capacity())); |
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.
There are two implications of this formulation:
- We always allocate a new memory reservation which could be wasteful
- We allocate from the new pool before freeing the memory from the previous reservation
|
||
/// Register this [`Buffer`] with the provided [`MemoryPool`] | ||
#[cfg(feature = "pool")] | ||
pub fn claim(&self, pool: &dyn crate::MemoryPool) { |
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.
We could plumb this claim API through the various arrays and RecordBatch. Consumers like DF could then claim RecordBatch
they wish to buffer, and know they aren't double-counting.
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.
That is interesting -- so the idea is that this would support cooperatively assigning / tracking usage (rather than relying on some global allocator to do so).
That aligns pretty nicely with the "no overhead unless asked" and "everything being explicit" principles
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 think this API would be easier to use / manage if it also returned the prior reservation, if any
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 think this would make it harder to chain through to Arrays and RecordBatch that may consist of multiple buffers and therefore reservations. It would also mean potentially keeping around "duplicate" reservations for longer than necessary, which would become even more problematic if the same buffer is used multiple times.
What would be the use-case for the returned reservation?
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 was thinking like if only some Arrays in a RecordBatch have a reservation but some didn't and then a system like the DataFusion memory pool added an entirely new reservation for all the arrays wiping out the old reservations
In that case I would want the previous reservations to be unregistered
Although maybe Drop
that did the unregistering would be good enough for that case 🤔
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.
Although maybe Drop that did the unregistering would be good enough for that case 🤔
That's what is intended, and follows RAII best-practices
[lib] | ||
name = "arrow_buffer" | ||
path = "src/lib.rs" | ||
bench = false | ||
|
||
[features] | ||
pool = [] |
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.
As it is very hard to guage what if any performance implications there are of this, I think it is important that it is gated by a feature flag at least initially.
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.
Thank you @tustvold -- this looks very cool
My suggested next steps are twofold:
- Write up some self contained example showing how to use this API in a real world scenario (perhaps @haohuaijin 's The
get_array_memory_size()
get wrong result(with different compression method) after deconde record from ipc format #6363 of tracking memory sent to/from IPC buffers) - Try and plumb it into DataFusion's MemoryPool and see how that looks / if this API is sufficient
cc @waynexia and @jhorstmann in case you are interested
|
||
/// Register this [`Buffer`] with the provided [`MemoryPool`] | ||
#[cfg(feature = "pool")] | ||
pub fn claim(&self, pool: &dyn crate::MemoryPool) { |
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.
That is interesting -- so the idea is that this would support cooperatively assigning / tracking usage (rather than relying on some global allocator to do so).
That aligns pretty nicely with the "no overhead unless asked" and "everything being explicit" principles
|
||
/// Register this [`Buffer`] with the provided [`MemoryPool`] | ||
#[cfg(feature = "pool")] | ||
pub fn claim(&self, pool: &dyn crate::MemoryPool) { |
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 think this API would be easier to use / manage if it also returned the prior reservation, if any
Thanks for bringing this up 👍 I'll take a closer look tomorrow |
This looks fits the requirement of precise memory tracking without touching the unstable rust feature 💯 Adding an explicit API looks fine to me. I'd like to give it a try on mutable buffer either to see if there is any hiding problem |
Implementation for On writing that patch I find This API LGTM in general. |
How does this and tustvold#82 looks to you @tustvold @alamb @jhorstmann? I plan to prepare a formal patch if it looks good. And then try this in DF as @alamb suggested here #6590 (review) |
I think we would need to get this mechanism integrated into As for the This doesn't mean we can't add the |
I have a specific use case (and it's why I started the previous PR) for tracking mutable buffer's usage. There are some relatively long-live mutable buffers in GreptimeDB's write path and I want to track their memory usage. So after finishing the immutable buffer, I'd personally prioritize adding this new API to mutable buffer, but without other internal usages like |
Which issue does this PR close?
Relates to #6439.
Rationale for this change
A POC showing how we could add more accurate memory tracking to arrays without a huge amount of churn
What changes are included in this PR?
This adds two traits
MemoryPool
andMemoryReservation
that work together to allow accurate instrumentation ofBuffer
memory usage.Are there any user-facing changes?