Skip to content

refactor: SessionConfig.extensions instead ConfigOptions.extensions - #550

Merged
gabotechs merged 17 commits into
datafusion-contrib:mainfrom
Rich-T-kid:rich-T-kid/use-SessionCOnfig
Jul 15, 2026
Merged

refactor: SessionConfig.extensions instead ConfigOptions.extensions#550
gabotechs merged 17 commits into
datafusion-contrib:mainfrom
Rich-T-kid:rich-T-kid/use-SessionCOnfig

Conversation

@Rich-T-kid

@Rich-T-kid Rich-T-kid commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

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_registry no longer live in ConfigOptions.extensions. They are stored directly in SessionConfig.extensions via set_extension/get_extension, which removes all the hollow ConfigField + Debug stub impls these types required.

The main structural change is inject_network_boundaries taking &SessionConfig instead of &ConfigOptions so it can reach the AnyMap. All other changes are mechanical callsite updates.

@Rich-T-kid Rich-T-kid left a comment

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.

left some comments for reviewers on some of my rational in case it helps 🚀

Comment thread src/distributed_planner/distributed_query_planner.rs Outdated
Comment thread src/distributed_planner/task_estimator.rs Outdated
Comment thread src/test_utils/work_unit_file_scan.rs Outdated
Comment thread src/distributed_ext.rs Outdated
@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/use-SessionCOnfig branch from d393923 to 302551b Compare July 12, 2026 21:46
@gabotechs

Copy link
Copy Markdown
Collaborator

🤔 I think you might have misunderstood the issue description in #538.

The scope is not to move DistributedConfig to SessionConfig.extensions, the idea is to just move those fields that were transported as __private_* fields in DistributedConfig as normal SessionConfig.extensions.

Citing the issue description

it should be very easy to transport these structs as normal SessionConfig.extensions objects, instead of hacking them into ConfigOptions.

Here, structs mean CombinedTaskEstimator, ChannelResolverExtension, WorkerResolverExtension and WorkUnitFeedRegistry

@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/use-SessionCOnfig branch from e066089 to aa5231b Compare July 13, 2026 13:11
@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/use-SessionCOnfig branch from 56b6672 to 93990e6 Compare July 13, 2026 14:12
Comment thread src/distributed_planner/task_estimator.rs
Comment thread src/distributed_planner/inject_network_boundaries.rs Outdated
Comment thread src/distributed_planner/inject_network_boundaries.rs Outdated
Comment thread src/distributed_planner/distributed_query_planner.rs Outdated

@gabotechs gabotechs left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice blood red PR!

Comment thread src/distributed_planner/distributed_config.rs
Comment thread src/distributed_planner/distributed_config.rs
Comment thread src/distributed_planner/inject_network_boundaries.rs Outdated
@Rich-T-kid

Rich-T-kid commented Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Any chance to leave this one untouched? or is this change needed for this PR?

@gabotechs

from_config_options_mut() took &mut ConfigOptions, which has no access to the SessionConfig-level propagation prefix registry. So any DistributedConfig field set before the worker resolver, things like compression, collect_metrics, broadcast_joins, was never propagated to workers because the prefix was only registered inside set_distributed_worker_resolver(). Changing the signature to &mut SessionConfig lets the method register the prefix and insert DistributedConfig in one place, making both orderings safe.
I added two regression test to catch this behavior in the future.
distributed_config_header_propagation_scalar_before_resolver() and distributed_config_header_propagation_scalar_after_resolver() in worker_resolver.rs.

Comment thread examples/custom_worker_url_routing.rs Outdated
Comment on lines -124 to 114
/// 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())
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This change also seems unrelated to this PR, ca we keep the from_config_options_mut method?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

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.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@Rich-T-kid
Rich-T-kid force-pushed the rich-T-kid/use-SessionCOnfig branch from 15a9f98 to efb8121 Compare July 13, 2026 17:46
Comment thread examples/custom_worker_url_routing.rs Outdated
Comment on lines +222 to +224
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()?;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔 Damn... this is actually how people are expected to access their worker resolver, and the API is taking a hit on usability now...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

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.

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(),
        ))
    }

Comment on lines -124 to 114
/// 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())
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/distributed_planner/distributed_query_planner.rs Outdated
Comment thread src/distributed_planner/distributed_config.rs Outdated
Comment thread src/worker_resolver.rs Outdated
Comment thread src/distributed_ext.rs Outdated
use crate::{
ChannelResolver, DistributedConfig, TaskEstimator, WorkUnitFeed, WorkUnitFeedProvider,
WorkerResolver,
WorkerResolver, get_distributed_worker_resolver as get_worker_resolver,

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.

renamed this because otherwise
impl DistributedExt for SessionConfig :: get_distributed_worker_resolver() would called get_distributed_worker_resolver() which may be confusing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread examples/custom_worker_url_routing.rs Outdated
Comment thread src/distributed_planner/distributed_config.rs
Comment thread src/test_utils/routing.rs Outdated
Comment thread src/distributed_ext.rs Outdated
/// ```
fn with_distributed_worker_resolver<T: WorkerResolver + 'static>(self, resolver: T) -> Self;

fn get_distributed_worker_resolver(&self) -> Result<Arc<dyn WorkerResolver>, DataFusionError>;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/distributed_ext.rs Outdated
use crate::{
ChannelResolver, DistributedConfig, TaskEstimator, WorkUnitFeed, WorkUnitFeedProvider,
WorkerResolver,
WorkerResolver, get_distributed_worker_resolver as get_worker_resolver,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread src/distributed_ext.rs
Comment on lines +866 to +871
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(),
))
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤔 damn, it's true, there's no way of accessing the worker resolver here...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

And omit the DistributedGetterExt impl for SessionStateBuilder

@gabotechs

Copy link
Copy Markdown
Collaborator

cc @timsaucer, this PR removes that hack we were doing for adding extensions as ConfigOptions. Does this help with the datafusion-python integration?

@Rich-T-kid
Rich-T-kid requested a review from gabotechs July 15, 2026 17:42
@Rich-T-kid

Rich-T-kid commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

I've introduced the changes you mentioned in beda7de

@gabotechs gabotechs left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💯 Nice work @Rich-T-kid!

Comment thread src/distributed_planner/distributed_config.rs
@gabotechs
gabotechs merged commit 5851c72 into datafusion-contrib:main Jul 15, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use SessionConfig.extensions instead ConfigOptions.extensions for carrying structs

2 participants