Skip to main content

datafusion_distributed/events/
desired_task_count.rs

1use super::TaskCountAnnotation::{Desired, Maximum};
2use super::common::EventHandlerChain;
3use async_trait::async_trait;
4use datafusion::common::Result;
5use datafusion::execution::config::SessionConfig;
6use datafusion::physical_plan::ExecutionPlan;
7use std::sync::Arc;
8
9/// Annotation attached to a single [ExecutionPlan] that determines how many distributed tasks
10/// it should run on.
11#[derive(Debug, Clone, Copy)]
12pub enum TaskCountAnnotation {
13    /// The desired number of distributed tasks for this node. The final task count for the
14    /// annotated node might not be exactly this number, it is more like a hint, so depending
15    /// on the desired task count of adjacent nodes, the final task count might change.
16    Desired(usize),
17    /// Sets a maximum number of distributed tasks for this node. Typically used with the inner
18    /// value of 1, stating that this node cannot be executed in a distributed fashion.
19    Maximum(usize),
20}
21
22/// Information supplied when the planner asks a handler for a node's desired task count.
23///
24/// Handlers may return a [`DesiredTaskCountEventResponse`] for any execution-plan node. The
25/// planner reconciles responses from the nodes in the same stage into its final task count.
26#[derive(Clone, Copy)]
27pub struct DesiredTaskCountEvent<'a> {
28    /// The execution-plan node being evaluated.
29    pub plan: &'a Arc<dyn ExecutionPlan>,
30    /// The session configuration that registered the handlers and holds query options.
31    pub session_config: &'a SessionConfig,
32}
33
34/// Result of running a [TaskEstimator] on a leaf node. It tells the distributed planner hints
35/// about how many tasks should be used in [Stage]s that contain leaf nodes.
36pub struct DesiredTaskCountEventResponse {
37    /// The number of tasks that should be used in the [Stage] containing the leaf node.
38    ///
39    /// Even if implementations get to decide this number, there are situations where it can
40    /// get overridden:
41    /// - If a [Stage] contains multiple leaf nodes, the one that declares the biggest
42    ///   task_count wins.
43    /// - If there are less available workers than this number, the number of available workers
44    ///   is chosen.
45    pub task_count: TaskCountAnnotation,
46}
47
48impl DesiredTaskCountEventResponse {
49    /// Tells the distributed planner that the evaluated stage can have **at maximum** the provided
50    /// number of tasks, setting a hard upper limit.
51    ///
52    /// Returning `DesiredTaskCountEventResponse::maximum(1)` tells the distributed planner that the
53    /// evaluated stage cannot be distributed.
54    ///
55    /// Even if a `DesiredTaskCountEventResponse::maximum(N)` is provided, any other node in the
56    /// same stage providing a value of `DesiredTaskCountEventResponse::maximum(M)` where `M` < `N`
57    /// will have preference.
58    pub fn maximum(value: usize) -> Self {
59        DesiredTaskCountEventResponse {
60            task_count: Maximum(value),
61        }
62    }
63
64    /// Tells the distributed planner that the evaluated can **optimally** have the provided
65    /// number of tasks, setting a soft task count hint that can be overridden by others.
66    ///
67    /// The provided `DesiredTaskCountEventResponse::desired(N)` can be overridden by:
68    /// - Other nodes providing a `DesiredTaskCountEventResponse::desired(M)` where `M` > `N`.
69    /// - Any other node providing a `DesiredTaskCountEventResponse::maximum(M)` where `M` can be
70    ///   anything.
71    pub fn desired(value: usize) -> Self {
72        DesiredTaskCountEventResponse {
73            task_count: Desired(value),
74        }
75    }
76}
77
78#[async_trait]
79pub trait DesiredTaskCountHandler: Send + Sync + 'static {
80    /// Function applied to each node that returns a [DesiredTaskCountEventResponse] hinting how
81    /// many tasks should be used in the [Stage] containing that node, or an error if the hint
82    /// cannot be determined.
83    ///
84    /// Handlers are asynchronous and may await metadata or external services. Handler functions
85    /// return a [`DesiredTaskCountFuture`] so their futures can borrow from the event.
86    ///
87    /// All the [TaskEstimator] registered in the session will be applied to the node
88    /// until one returns an estimation.
89    ///
90    ///
91    /// If no estimation is returned from any of the registered [TaskEstimator]s, then:
92    /// - If the node is a leaf node,`Maximum(1)` is assumed, hinting the distributed planner
93    ///   that the leaf node cannot be distributed across tasks.
94    /// - If the node is a normal node in the plan, then the maximum task count from its children
95    ///   is inherited.
96    async fn handle(
97        &self,
98        ev: DesiredTaskCountEvent<'_>,
99    ) -> Option<Result<DesiredTaskCountEventResponse>>;
100}
101
102impl From<TaskCountAnnotation> for usize {
103    fn from(annotation: TaskCountAnnotation) -> Self {
104        annotation.as_usize()
105    }
106}
107
108impl TaskCountAnnotation {
109    pub fn as_usize(&self) -> usize {
110        match self {
111            Desired(desired) => *desired,
112            Maximum(maximum) => *maximum,
113        }
114    }
115
116    pub(crate) fn limit(self, limit: usize) -> Self {
117        match self {
118            Desired(desired) => Desired(desired.min(limit)),
119            Maximum(maximum) => Maximum(maximum.min(limit)),
120        }
121    }
122
123    pub(crate) fn merge(self, other: TaskCountAnnotation) -> Self {
124        match (self, other) {
125            (Desired(a), Desired(b)) => Desired(std::cmp::max(a, b)),
126            (Desired(_), Maximum(b)) => Maximum(b),
127            (Maximum(a), Desired(_)) => Maximum(a),
128            (Maximum(a), Maximum(b)) => Maximum(std::cmp::min(a, b)),
129        }
130    }
131}
132
133#[async_trait]
134impl<F> DesiredTaskCountHandler for F
135where
136    F: Send + Sync + 'static,
137    F: for<'a> Fn(DesiredTaskCountEvent<'a>) -> Option<Result<DesiredTaskCountEventResponse>>,
138{
139    async fn handle(
140        &self,
141        ev: DesiredTaskCountEvent<'_>,
142    ) -> Option<Result<DesiredTaskCountEventResponse>> {
143        self(ev)
144    }
145}
146
147#[async_trait]
148impl DesiredTaskCountHandler for usize {
149    async fn handle(
150        &self,
151        ev: DesiredTaskCountEvent<'_>,
152    ) -> Option<Result<DesiredTaskCountEventResponse>> {
153        ev.plan
154            .children()
155            .is_empty()
156            .then(|| Ok(DesiredTaskCountEventResponse::desired(*self)))
157    }
158}
159
160#[async_trait]
161impl DesiredTaskCountHandler for Arc<dyn DesiredTaskCountHandler> {
162    async fn handle(
163        &self,
164        ev: DesiredTaskCountEvent<'_>,
165    ) -> Option<Result<DesiredTaskCountEventResponse>> {
166        self.as_ref().handle(ev).await
167    }
168}
169
170pub(crate) type DesiredTaskCountHandlers = EventHandlerChain<dyn DesiredTaskCountHandler>;
171
172impl DesiredTaskCountHandlers {
173    pub(crate) async fn handle(
174        ev: DesiredTaskCountEvent<'_>,
175    ) -> Option<Result<DesiredTaskCountEventResponse>> {
176        let handlers = ev
177            .session_config
178            .get_extension::<DesiredTaskCountHandlers>()?;
179        for handler in handlers.iter() {
180            if let Some(response) = handler.handle(ev).await {
181                return Some(response);
182            }
183        }
184        None
185    }
186}