refactor: SessionConfig.extensions instead ConfigOptions.extensions - #550
Conversation
Rich-T-kid
left a comment
There was a problem hiding this comment.
left some comments for reviewers on some of my rational in case it helps 🚀
d393923 to
302551b
Compare
|
🤔 I think you might have misunderstood the issue description in #538. The scope is not to move Citing the issue description
Here, |
e066089 to
aa5231b
Compare
56b6672 to
93990e6
Compare
|
| /// Gets the [DistributedConfig] from the [ConfigOptions]'s extensions. | ||
| pub fn from_config_options_mut(cfg: &mut ConfigOptions) -> Result<&mut Self, DataFusionError> { | ||
| let Some(distributed_cfg) = cfg.extensions.get_mut::<DistributedConfig>() else { | ||
| return plan_err!("DistributedConfig is not in ConfigOptions.extensions"); | ||
| }; | ||
| Ok(distributed_cfg) | ||
| /// Gets the [DistributedConfig] from the [SessionConfig], inserting a default if not present. | ||
| /// Always registers the `"distributed"` propagation prefix so coordinator-side settings are | ||
| /// included in gRPC headers sent to workers, regardless of call ordering. | ||
| pub fn from_session_config_mut(cfg: &mut SessionConfig) -> Result<&mut Self, DataFusionError> { | ||
| if cfg | ||
| .options() | ||
| .extensions | ||
| .get::<DistributedConfig>() | ||
| .is_none() | ||
| { | ||
| set_distributed_option_extension(cfg, DistributedConfig::default()); | ||
| } else { | ||
| register_config_extension_prefix(cfg, DistributedConfig::PREFIX); | ||
| } | ||
| Ok(cfg | ||
| .options_mut() | ||
| .extensions | ||
| .get_mut::<DistributedConfig>() | ||
| .unwrap()) | ||
| } |
There was a problem hiding this comment.
This change also seems unrelated to this PR, ca we keep the from_config_options_mut method?
There was a problem hiding this comment.
Also, that way of injecting DistributedConfig in the config.extensions... looks a bit too LLMy, it's overall pretty convoluted and unnecessary.
For ensuring that the DistributedConfig is always present in the SessionConfig, rather than doing on each from_session_config_mut, I'd instead just put in in SessionStateBuilderExt, because that's the only mandatory place people should go enable distributed-datafusion:
impl SessionStateBuilderExt for SessionStateBuilder {
fn with_distributed_planner(mut self) -> Self {
let config = self.config().get_or_insert_default().options_mut();
config
.optimizer
.enable_physical_uncorrelated_scalar_subquery = false;
if config.extensions.get::<DistributedConfig>().is_none() {
config.extensions.insert(DistributedConfig::default());
}
let prev = std::mem::take(self.query_planner());
self.with_query_planner(Arc::new(DistributedQueryPlanner { prev }))
}
}But not sure if this is necessary.
There was a problem hiding this comment.
Reverted this change. There were some test breaking due to this but I resolved them by adding
.with_distributed_option_extension(DistributedConfig::default()) to the sessionState builder
There was a problem hiding this comment.
I'm afraid we cannot do that, because that means that in the same way this is blowing up in our tests here, it will blow up in other people's codebase, se we need to find an alternative.
15a9f98 to
efb8121
Compare
| let d_cfg = DistributedConfig::from_task_context(&ctx.task_ctx)?; | ||
| let available_urls = d_cfg.worker_resolver().get_urls()?; | ||
| let available_urls = | ||
| get_distributed_worker_resolver(ctx.task_ctx.session_config())?.get_urls()?; |
There was a problem hiding this comment.
🤔 Damn... this is actually how people are expected to access their worker resolver, and the API is taking a hit on usability now...
There was a problem hiding this comment.
What comes to mind is, in the same way that we have the DistributedExt::with_distributed_worker_resolver and DistributedExt::set_distributed_worker_resolver, we can have the DistributedExt::get_distributed_worker_resolver for retrieving the worker resolver.
How does that sound?
There was a problem hiding this comment.
This look's good but it requires adding this stub for impl DistributedExt for SessionStateBuilder
fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError> {
Err(DataFusionError::Execution(
"get_distributed_worker_resolver is not available on SessionStateBuilder; use SessionState or SessionConfig".to_string(),
))
}
| /// Gets the [DistributedConfig] from the [ConfigOptions]'s extensions. | ||
| pub fn from_config_options_mut(cfg: &mut ConfigOptions) -> Result<&mut Self, DataFusionError> { | ||
| let Some(distributed_cfg) = cfg.extensions.get_mut::<DistributedConfig>() else { | ||
| return plan_err!("DistributedConfig is not in ConfigOptions.extensions"); | ||
| }; | ||
| Ok(distributed_cfg) | ||
| /// Gets the [DistributedConfig] from the [SessionConfig], inserting a default if not present. | ||
| /// Always registers the `"distributed"` propagation prefix so coordinator-side settings are | ||
| /// included in gRPC headers sent to workers, regardless of call ordering. | ||
| pub fn from_session_config_mut(cfg: &mut SessionConfig) -> Result<&mut Self, DataFusionError> { | ||
| if cfg | ||
| .options() | ||
| .extensions | ||
| .get::<DistributedConfig>() | ||
| .is_none() | ||
| { | ||
| set_distributed_option_extension(cfg, DistributedConfig::default()); | ||
| } else { | ||
| register_config_extension_prefix(cfg, DistributedConfig::PREFIX); | ||
| } | ||
| Ok(cfg | ||
| .options_mut() | ||
| .extensions | ||
| .get_mut::<DistributedConfig>() | ||
| .unwrap()) | ||
| } |
There was a problem hiding this comment.
I'm afraid we cannot do that, because that means that in the same way this is blowing up in our tests here, it will blow up in other people's codebase, se we need to find an alternative.
| use crate::{ | ||
| ChannelResolver, DistributedConfig, TaskEstimator, WorkUnitFeed, WorkUnitFeedProvider, | ||
| WorkerResolver, | ||
| WorkerResolver, get_distributed_worker_resolver as get_worker_resolver, |
There was a problem hiding this comment.
renamed this because otherwise
impl DistributedExt for SessionConfig :: get_distributed_worker_resolver() would called get_distributed_worker_resolver() which may be confusing.
There was a problem hiding this comment.
Why would it be confusing? this is in the same situation as set_distributed_task_estimator, set_distributed_work_unit_feed, set_distributed_worker_resolver, set_distributed_channel_resolver, which have the same names than their counterpart methods in DistributedExt.
For consistency with the other functions, I'd just import this one without aliasing.
| /// ``` | ||
| fn with_distributed_worker_resolver<T: WorkerResolver + 'static>(self, resolver: T) -> Self; | ||
|
|
||
| fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError>; |
There was a problem hiding this comment.
We are missing a doc comment here. I'd explain what it does and one example of where is typically used.
For example, it can be typically used inside a TaskEstimator::route_tasks implementation for retrieving the available URLs at any given moment.
| use crate::{ | ||
| ChannelResolver, DistributedConfig, TaskEstimator, WorkUnitFeed, WorkUnitFeedProvider, | ||
| WorkerResolver, | ||
| WorkerResolver, get_distributed_worker_resolver as get_worker_resolver, |
There was a problem hiding this comment.
Why would it be confusing? this is in the same situation as set_distributed_task_estimator, set_distributed_work_unit_feed, set_distributed_worker_resolver, set_distributed_channel_resolver, which have the same names than their counterpart methods in DistributedExt.
For consistency with the other functions, I'd just import this one without aliasing.
| fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError> { | ||
| Err(DataFusionError::Execution( | ||
| "get_distributed_worker_resolver is not available on SessionStateBuilder; use SessionState or SessionConfig".to_string(), | ||
| )) | ||
| } | ||
|
|
There was a problem hiding this comment.
🤔 damn, it's true, there's no way of accessing the worker resolver here...
There was a problem hiding this comment.
Then, I think it's probably worth it to create another extension trait for getters. I bet that we are going to be in this same situation more times in the future, so it might be better to tackle this now.
Here's an idea:
pub trait DistributedGetterExt: Sized {
/// TODO: nice description
fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError>;
}
impl DistributedGetterExt for SessionConfig {
fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError> {
get_distributed_worker_resolver(self)
}
}
impl DistributedGetterExt for SessionState {
delegate! {
to self.config() {
fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError>;
}
}
}
impl DistributedGetterExt for SessionContext {
delegate! {
to self.state_ref().read().config() {
fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError>;
}
}
}And expose it in lib.rs like this, along with the other DistributedExt:
pub use distributed_ext::{DistributedExt, DistributedGetterExt};There was a problem hiding this comment.
And omit the DistributedGetterExt impl for SessionStateBuilder
|
cc @timsaucer, this PR removes that hack we were doing for adding extensions as ConfigOptions. Does this help with the datafusion-python integration? |
|
I've introduced the changes you mentioned in beda7de |
gabotechs
left a comment
There was a problem hiding this comment.
💯 Nice work @Rich-T-kid!
closes #538
What Changed
Move 4 non-serializable DistributedConfig fields to SessionConfig.extensions
__private_task_estimator,__private_channel_resolver,__private_worker_resolver, and__private_work_unit_feed_registryno longer live inConfigOptions.extensions. They are stored directly inSessionConfig.extensionsvia set_extension/get_extension, which removes all the hollow ConfigField + Debug stub impls these types required.The main structural change is
inject_network_boundariestaking &SessionConfig instead of &ConfigOptions so it can reach the AnyMap. All other changes are mechanical callsite updates.