Skip to content

feat: Add unique id for every memory consumer - #15613

Merged
alamb merged 6 commits into
apache:mainfrom
EmilyMatt:unique_consumer_id
Apr 11, 2025
Merged

alamb merged 6 commits into
apache:mainfrom
EmilyMatt:unique_consumer_id

Conversation

@EmilyMatt

@EmilyMatt EmilyMatt commented Apr 7, 2025 •

Copy link
Copy Markdown
Contributor

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.

@github-actions github-actions Bot added the execution Related to the execution crate label Apr 7, 2025

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@alamb alamb mentioned this pull request Apr 7, 2025
12 tasks
@EmilyMatt

Copy link
Copy Markdown
Contributor Author

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
This is in order to ensure a user does not call clone() on a consumer, and then register the cloned one, while still maintaining an owned MemoryConsumer, which can be called with register() again, despite having the same id as an already existing MemoryConsumer in the pool.

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.
This means this PR needs the api change label, but I can't manually set it^

@alamb alamb added the api change Changes the API exposed to users of the crate label Apr 7, 2025
Comment thread datafusion/execution/src/memory_pool/mod.rs

@berkaysynnada berkaysynnada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we planning to deprecate MemoryConsumer and replace it all with TrackedConsumer ?

@EmilyMatt

Copy link
Copy Markdown
Contributor Author

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.
This API is only meant to help users do that, as the current API does not really allow doing that with certainty(there can be surrounding hacks to achieve that functionality, but they far from perfect without some unique identifier)

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
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 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No problem, added some more documentation in general

///
/// [proxy]: datafusion_common::utils::proxy
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
#[derive(Debug)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed^

Comment thread datafusion/execution/src/memory_pool/pool.rs
tracked_consumer.name.to_owned(),
tracked_consumer.can_spill,
),
tracked_consumer.reserved.load(Ordering::Acquire),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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)

Suggested change
tracked_consumer.reserved.load(Ordering::Acquire),
tracked_consumer.reserved(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The same thing goes for other updates later down

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is a nice change

@EmilyMatt

Copy link
Copy Markdown
Contributor Author

@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.

@alamb
alamb merged commit 263222e into apache:main Apr 11, 2025
@alamb

alamb commented Apr 11, 2025

Copy link
Copy Markdown
Contributor

This is great -- thanks again for the work and contribution @EmilyMatt

nirnayroy pushed a commit to nirnayroy/datafusion that referenced this pull request May 2, 2025
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api change Changes the API exposed to users of the crate execution Related to the execution crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unique identifier for MemoryConsumer

3 participants