Repository navigation
feat: Add unique id for every memory consumer - #15613
Conversation
alamb
left a comment
There was a problem hiding this comment.
Thank you @EmilyMatt -- this API make a lot of sense to me
I think it would be useful to add tests for this feature, so that we don't break it in the future accidentally
Specifically, I think testing the "memory consumer ids are unique" behavior would be best as that is the goal of the API. SO for example, perhaps write a query that has multiple AggregateExecs and verify that the assigned memory consumer Ids are different
I would also recommend adding ids to the various messages (such as in TrackedConsumerPool) https://github.com/apache/datafusion/blob/main/datafusion/execution/src/memory_pool/pool.rs#L261
So that the id can be correlated in more places
I have modified this PR to also reflect these changes in the TrackConsumer pool, but in order to ensure a valid state this also requires a breaking API change - MemoryConsumer no longer implements Hash and Clone I'd have preferred to deprecate the Clone implementation and only removing it in a future version, along with an explanation, but unfortunately, traits cannot be deprecated using the attribute. |
berkaysynnada
left a comment
There was a problem hiding this comment.
Are we planning to deprecate MemoryConsumer and replace it all with TrackedConsumer ?
I think TrackedConsumer is only relevant for the TrackConsumerPool, it doesn't exist anywhere else, most pools don't track consumers at all, it is up to users to implement a MemoryPool that takes into consideration each Consumer and how much it has requested. |
alamb
left a comment
There was a problem hiding this comment.
Thank you @EmilyMatt -- I think this looks like a very nice improvement to me
FYI @wiedld and @2010YOUY01
I also marked this PR as an API change as MemoryConsumer no longer implements Clone.
| } | ||
| } | ||
|
|
||
| pub fn clone_with_new_id(&self) -> Self { |
There was a problem hiding this comment.
| pub fn clone_with_new_id(&self) -> Self { | |
| /// Return a clone of the MemoryConsumer that has a new | |
| /// unique ID assigned. | |
| pub fn clone_with_new_id(&self) -> Self { |
There was a problem hiding this comment.
No problem, added some more documentation in general
| /// | ||
| /// [proxy]: datafusion_common::utils::proxy | ||
| #[derive(Debug, PartialEq, Eq, Hash, Clone)] | ||
| #[derive(Debug)] |
There was a problem hiding this comment.
Can you also please add a note to this doc string that says each MemoryConsumer gets an automatically assigned globally unique id and that to clone a memory consumer, see [MemoryConsumer::clone_with_new_id]
| tracked_consumer.name.to_owned(), | ||
| tracked_consumer.can_spill, | ||
| ), | ||
| tracked_consumer.reserved.load(Ordering::Acquire), |
There was a problem hiding this comment.
As a follow on / cleanup it would be possible to refactor this code into methods, so like this (and it might make the code easier to read)
| tracked_consumer.reserved.load(Ordering::Acquire), | |
| tracked_consumer.reserved(), |
There was a problem hiding this comment.
The same thing goes for other updates later down
There was a problem hiding this comment.
Sounds good to me, done^
| // using the previously set sizes for other consumers | ||
| let mut r5 = MemoryConsumer::new("r5").register(&pool); | ||
| let expected = "Additional allocation failed with top memory consumers (across reservations) as: r1 consumed 50 bytes, r3 consumed 20 bytes, r2 consumed 15 bytes. Error: Failed to allocate additional 150 bytes for r5 with 0 bytes already allocated for this reservation - 5 bytes remain available for the total pool"; | ||
| let expected = format!("Additional allocation failed with top memory consumers (across reservations) as: r1#{}(can spill: false) consumed 50 bytes, r3#{}(can spill: false) consumed 20 bytes, r2#{}(can spill: false) consumed 15 bytes. Error: Failed to allocate additional 150 bytes for r5 with 0 bytes already allocated for this reservation - 5 bytes remain available for the total pool", r1.consumer().id(), r3.consumer().id(), r2.consumer().id()); |
|
@alamb Thanks for taking the time to look at this. I've addressed the relevant points and added some general documentation, I've also modified the reserved field to an AtomicUsize(instead of AtomicU64), as that feels more inline with the memory pool API, which uses usize everywhere, I hope that's not overtly obtrusive. |
|
This is great -- thanks again for the work and contribution @EmilyMatt |
* feat: Add unique id for every memory consumer * imports * feat: Make MemoryConsumer non-clonable, update tracked pool to use ids * Add hash impl and fmt * use a single cfg instead wrapping the if * Address review, add documentation
Which issue does this PR close?
Rationale for this change
This makes an important change to the MemoryConsumer API, letting any memory pool identify which consumer is requesting the memory, and therefor separate consumers that might have the same name (for example, two AggregateExec operators in the same Partition)
This also modifies the TrackConsumer pool to adhere to these changes.
What changes are included in this PR?
Nothing except the addition of a unique identifier in the consumer, and an id() function to retrieve it.
Are these changes tested?
All of the TrackedConsumer pool tests now verify this, as they have all been updated to accomodate the fact that the pool uses the IDs to track (the inner pool is unchanged in its behaviour atm)
This ensures the validity of the logic
Are there any user-facing changes?
Yes, MemoryConsumer is no longer clone-able, it is now moved into the register() function and thats it, to avoid an invalid state where a MemoryConsumer can be registered more than once.
The TrackedConsumerMemoryPool will now store Consumers based on their IDs, instead of names, so will be able to track multiple consumers with the same name, based on their ID, and the error messages are updated accordingly.