Skip to main content

datafusion_distributed/distributed_planner/
distributed_config.rs

1use crate::config_extension_ext::set_distributed_option_extension;
2use datafusion::common::{DataFusionError, extensions_options, plan_err};
3use datafusion::config::{ConfigExtension, ConfigOptions};
4use datafusion::execution::TaskContext;
5use datafusion::prelude::SessionConfig;
6use std::sync::Arc;
7
8extensions_options! {
9    /// Configuration for the distributed planner.
10    pub struct DistributedConfig {
11        /// Sets the number of bytes each partitions is expected to scan from parquet files. If
12        /// more partitions than the ones available in one machine would be needed, several machines
13        /// are used, and the scan is distributed.
14        /// Lowering this number will increase parallelism.
15        pub file_scan_config_bytes_per_partition: usize, default = 16 * 1024 * 1024
16        /// Task multiplying factor for when a node declares that it changes the cardinality
17        /// of the data:
18        /// - If a node is increasing the cardinality of the data, this factor will increase.
19        /// - If a node reduces the cardinality of the data, this factor will decrease.
20        /// - In any other situation, this factor is left intact.
21        pub cardinality_task_count_factor: f64, default = cardinality_task_count_factor_default()
22        /// When encountering a UNION operation, isolate its children depending on the task context.
23        /// For example, on a UNION operation with 3 children running in 3 distributed tasks,
24        /// instead of executing the 3 children in each 3 tasks with a DistributedTaskContext of
25        /// 1/3, 2/3, and 3/3 respectively, Execute:
26        /// - The first child in the first task with a DistributedTaskContext of 1/1
27        /// - The second child in the second task with a DistributedTaskContext of 1/1
28        /// - The third child in the third task with a DistributedTaskContext of 1/1
29        pub children_isolator_unions: bool, default = true
30        /// Propagate collected metrics from all nodes in the plan across network boundaries
31        /// so that they can be reconstructed on the head node of the plan.
32        pub collect_metrics: bool, default = true
33        /// Enable broadcast joins for CollectLeft hash joins. When enabled, the build side of
34        /// a CollectLeft join is broadcast to all consumer tasks.
35        pub broadcast_joins: bool, default = true
36        /// The compression used for sending data over the network between workers.
37        /// It can be set to either `zstd`, `lz4` or `none`.
38        pub compression: String, default = "lz4".to_string()
39        /// Overrides `datafusion.execution.batch_size` for worker-executed stages. Because
40        /// `RepartitionExec` reads `session_config().batch_size()` at execute time to size its
41        /// output batches (via its internal `LimitedBatchCoalescer`), this knob lets users tune
42        /// shuffle batch sizes independently of the global `datafusion.execution.batch_size`.
43        ///
44        /// Set to 0 (the default) to apply no override and inherit `datafusion.execution.batch_size`.
45        pub shuffle_batch_size: usize, default = 0
46        /// Maximum tasks that will be assigned per stage during distributed planning.
47        /// If set to 0, this value is the number of workers returned by the provided `WorkerResolver`.
48        /// It defaults to 0.
49        pub max_tasks_per_stage: usize, default = 0
50        /// Enable the PartialReduce optimization, which inserts an extra aggregation pass
51        /// above hash RepartitionExec before network shuffles to reduce shuffle data size.
52        /// Disabled by default because its effectiveness is workload-dependent: it helps when
53        /// aggregation significantly reduces cardinality, but adds overhead when it does not.
54        pub partial_reduce: bool, default = false
55        /// Soft byte budget that each per-worker connection will buffer in memory before pausing
56        /// the gRPC pull from that worker. Per-partition channels are unbounded (to avoid
57        /// head-of-line blocking between sibling partitions), so backpressure is enforced
58        /// globally per [WorkerConnection] using this budget. A single message larger than this
59        /// budget will still be admitted (otherwise we would livelock), so the actual peak per
60        /// connection is `worker_connection_buffer_budget_bytes + max_message_size`.
61        pub worker_connection_buffer_budget_bytes: usize, default = 64 * 1024 * 1024
62        /// Calculates the task count of the different stages at execution time, based on runtime
63        /// information collected by sampling at the head of the stages.
64        ///
65        /// With this option enabled, the shape of the distributed plan is only known after fully
66        /// executing it, as it's dynamically created on the fly during execution.
67        pub dynamic_task_count: bool, default = false
68        /// If `dynamic_task_count` is enabled, this value is the amount of bytes each
69        /// partition is expected to handle. Lower values will result in greater parallelism.
70        pub dynamic_bytes_per_partition: usize, default = 16 * 1024 * 1024
71    }
72}
73
74fn cardinality_task_count_factor_default() -> f64 {
75    if cfg!(test) || cfg!(feature = "integration") {
76        1.5
77    } else {
78        1.0
79    }
80}
81
82impl DistributedConfig {
83    /// Gets the [DistributedConfig] from the [ConfigOptions]'s extensions.
84    pub fn from_config_options(cfg: &ConfigOptions) -> Result<&Self, DataFusionError> {
85        let Some(distributed_cfg) = cfg.extensions.get::<DistributedConfig>() else {
86            return plan_err!("DistributedConfig is not in ConfigOptions.extensions");
87        };
88        Ok(distributed_cfg)
89    }
90    /// Gets the [DistributedConfig] from the [ConfigOptions]'s extensions.
91    pub fn from_config_options_mut(cfg: &mut ConfigOptions) -> Result<&mut Self, DataFusionError> {
92        let Some(distributed_cfg) = cfg.extensions.get_mut::<DistributedConfig>() else {
93            return plan_err!("DistributedConfig is not in ConfigOptions.extensions");
94        };
95        Ok(distributed_cfg)
96    }
97
98    /// Gets the [DistributedConfig] from the [ConfigOptions]'s in the provided [SessionConfig].
99    pub fn from_session_config(session_cfg: &SessionConfig) -> Result<&Self, DataFusionError> {
100        Self::from_config_options(session_cfg.options())
101    }
102
103    /// Gets the [DistributedConfig] from the [ConfigOptions]'s in the provided [TaskContext].
104    pub fn from_task_context(ctx: &Arc<TaskContext>) -> Result<&Self, DataFusionError> {
105        Self::from_session_config(ctx.session_config())
106    }
107
108    /// Ensures that the [DistributedConfig] is present in the [SessionConfig]'s [ConfigOptions].
109    /// If not, it will insert a default [DistributedConfig] into the [SessionConfig]'s [ConfigOptions].
110    pub(crate) fn ensure_in_config(cfg: &mut SessionConfig) {
111        if cfg
112            .options()
113            .extensions
114            .get::<DistributedConfig>()
115            .is_none()
116        {
117            set_distributed_option_extension(cfg, DistributedConfig::default())
118        }
119    }
120}
121
122impl ConfigExtension for DistributedConfig {
123    const PREFIX: &'static str = "distributed";
124}