Skip to content

Commit a7fe44c

Browse files
committed
Optional serialization/deserialization for WorkerChannel
1 parent 9524a5a commit a7fe44c

16 files changed

Lines changed: 280 additions & 209 deletions

src/common/maybe_encoded.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
use crate::DistributedCodec;
2+
use datafusion::arrow::datatypes::SchemaRef;
3+
use datafusion::common::{Result, internal_err};
4+
use datafusion::execution::TaskContext;
5+
use datafusion::physical_expr::Partitioning;
6+
use datafusion::physical_plan::ExecutionPlan;
7+
use datafusion_proto::physical_plan::from_proto::parse_protobuf_partitioning;
8+
use datafusion_proto::physical_plan::to_proto::serialize_partitioning;
9+
use datafusion_proto::physical_plan::{
10+
AsExecutionPlan, DefaultPhysicalProtoConverter, PhysicalPlanDecodeContext,
11+
};
12+
use datafusion_proto::protobuf;
13+
use datafusion_proto::protobuf::proto_error;
14+
use prost::Message;
15+
use std::sync::Arc;
16+
17+
/// A value that a transport may either leave encoded or materialize in memory.
18+
#[derive(Clone)]
19+
pub enum MaybeEncoded<T> {
20+
Encoded(Vec<u8>),
21+
Decoded(T),
22+
}
23+
24+
impl<T> MaybeEncoded<T> {
25+
pub fn decode_with(self, decode: impl FnOnce(Vec<u8>) -> Result<T>) -> Result<T> {
26+
match self {
27+
Self::Encoded(encoded) => decode(encoded),
28+
Self::Decoded(decoded) => Ok(decoded),
29+
}
30+
}
31+
32+
pub fn try_decoded(self) -> Result<T> {
33+
match self {
34+
Self::Encoded(_) => {
35+
internal_err!("Expected {} to be decoded", std::any::type_name::<T>())
36+
}
37+
Self::Decoded(decoded) => Ok(decoded),
38+
}
39+
}
40+
}
41+
42+
impl MaybeEncoded<Arc<dyn ExecutionPlan>> {
43+
pub fn encode(self, ctx: &Arc<TaskContext>) -> Result<Vec<u8>> {
44+
match self {
45+
Self::Encoded(encoded) => Ok(encoded),
46+
Self::Decoded(plan) => {
47+
let codec = DistributedCodec::new_combined_with_user(ctx.session_config());
48+
Ok(
49+
protobuf::PhysicalPlanNode::try_from_physical_plan(plan, &codec)?
50+
.encode_to_vec(),
51+
)
52+
}
53+
}
54+
}
55+
56+
pub fn decode(self, task_ctx: &TaskContext) -> Result<Arc<dyn ExecutionPlan>> {
57+
self.decode_with(|encoded| {
58+
let codec = DistributedCodec::new_combined_with_user(task_ctx.session_config());
59+
let proto_node = protobuf::PhysicalPlanNode::try_decode(encoded.as_ref())?;
60+
proto_node.try_into_physical_plan(task_ctx, &codec)
61+
})
62+
}
63+
}
64+
65+
impl MaybeEncoded<Partitioning> {
66+
pub fn encode(self, ctx: &Arc<TaskContext>) -> Result<Vec<u8>> {
67+
match self {
68+
Self::Encoded(encoded) => Ok(encoded),
69+
Self::Decoded(partitioning) => {
70+
let codec = DistributedCodec::new_combined_with_user(ctx.session_config());
71+
Ok(serialize_partitioning(
72+
&partitioning,
73+
&codec,
74+
// I think nobody cares about this being the default PhysicalProtoConverter.
75+
// If someone does, please open an issue.
76+
&DefaultPhysicalProtoConverter {},
77+
)?
78+
.encode_to_vec())
79+
}
80+
}
81+
}
82+
83+
pub fn decode(self, schema: SchemaRef, task_ctx: &TaskContext) -> Result<Partitioning> {
84+
self.decode_with(|encoded| {
85+
let proto_partitioning = protobuf::Partitioning::decode(encoded.as_slice())
86+
.map_err(|err| proto_error(err.to_string()))?;
87+
let codec = DistributedCodec::new_combined_with_user(task_ctx.session_config());
88+
let decode_ctx = PhysicalPlanDecodeContext::new(task_ctx, &codec);
89+
parse_protobuf_partitioning(
90+
Some(&proto_partitioning),
91+
&decode_ctx,
92+
&schema,
93+
// I think nobody cares about this being the default PhysicalProtoConverter.
94+
// If someone does, please open an issue.
95+
&DefaultPhysicalProtoConverter {},
96+
)?
97+
.ok_or_else(|| proto_error("Could not parse partitioning"))
98+
})
99+
}
100+
}

src/common/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod children_helpers;
2+
mod maybe_encoded;
23
mod once_lock;
34
mod recursion;
45
mod task_context_helpers;
@@ -7,6 +8,7 @@ mod uuid;
78
mod vec;
89

910
pub(crate) use children_helpers::require_one_child;
11+
pub use maybe_encoded::MaybeEncoded;
1012
pub(crate) use once_lock::OnceLockResult;
1113
pub(crate) use recursion::TreeNodeExt;
1214
pub(crate) use task_context_helpers::task_ctx_with_extension;

src/coordinator/query_coordinator.rs

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ use crate::work_unit_feed::WorkUnitFeedRegistry;
1010
use crate::work_unit_feed::{build_work_unit_batch_msg, set_work_unit_send_time};
1111
use crate::worker::LocalWorkerContext;
1212
use crate::{
13-
BytesCounterMetric, BytesMetricExt, CoordinatorToWorkerMsg,
14-
DISTRIBUTED_DATAFUSION_TASK_ID_LABEL, DistributedCodec, DistributedTaskContext,
15-
DistributedWorkUnitFeedContext, LoadInfo, NetworkBoundaryExt, SetPlanRequest, Stage,
16-
TaskEstimator, TaskKey, TaskRoutingContext, WorkUnitFeedDeclaration, WorkerToCoordinatorMsg,
17-
get_distributed_channel_resolver, get_distributed_worker_resolver,
13+
CoordinatorToWorkerMsg, DISTRIBUTED_DATAFUSION_TASK_ID_LABEL, DistributedTaskContext,
14+
DistributedWorkUnitFeedContext, LoadInfo, MaybeEncoded, NetworkBoundaryExt, SetPlanRequest,
15+
Stage, TaskEstimator, TaskKey, TaskRoutingContext, WorkUnitFeedDeclaration,
16+
WorkerToCoordinatorMsg, get_distributed_channel_resolver, get_distributed_worker_resolver,
1817
};
1918
use datafusion::common::DataFusionError;
2019
use datafusion::common::instant::Instant;
@@ -25,10 +24,7 @@ use datafusion::execution::TaskContext;
2524
use datafusion::physical_expr_common::metrics::{ExecutionPlanMetricsSet, Label, MetricBuilder};
2625
use datafusion::physical_plan::ExecutionPlan;
2726
use datafusion::prelude::SessionConfig;
28-
use datafusion_proto::physical_plan::AsExecutionPlan;
29-
use datafusion_proto::protobuf::PhysicalPlanNode;
3027
use futures::{Stream, StreamExt, TryStreamExt};
31-
use prost::Message;
3228
use rand::Rng;
3329
use std::ops::DerefMut;
3430
use std::sync::{Arc, Mutex};
@@ -49,6 +45,7 @@ const WORK_UNIT_FEED_CHUNK_SIZE: usize = 256;
4945
/// [StageCoordinator] scoped to each individual stage.
5046
pub(super) struct QueryCoordinator {
5147
task_ctx: Arc<TaskContext>,
48+
metrics: ExecutionPlanMetricsSet,
5249
coordinator_to_worker_metrics: CoordinatorToWorkerMetrics,
5350
metrics_store: Option<Arc<MetricsStore>>,
5451
end_stream_notifier: Arc<Notify>,
@@ -64,6 +61,7 @@ impl QueryCoordinator {
6461
) -> Self {
6562
Self {
6663
task_ctx,
64+
metrics: metrics_set.clone(),
6765
metrics_store,
6866
coordinator_to_worker_metrics: CoordinatorToWorkerMetrics::new(metrics_set),
6967
end_stream_notifier: Arc::new(Notify::new()),
@@ -80,6 +78,7 @@ impl QueryCoordinator {
8078
stage_id: stage.num,
8179
task_count: stage.tasks,
8280
task_ctx: &self.task_ctx,
81+
metrics_set: &self.metrics,
8382
metrics: &self.coordinator_to_worker_metrics,
8483
metrics_store: &self.metrics_store,
8584
end_stream_notifier: &self.end_stream_notifier,
@@ -124,6 +123,7 @@ pub(super) struct StageCoordinator<'a> {
124123
stage_id: usize,
125124
task_count: usize,
126125
task_ctx: &'a Arc<TaskContext>,
126+
metrics_set: &'a ExecutionPlanMetricsSet,
127127
metrics: &'a CoordinatorToWorkerMetrics,
128128
metrics_store: &'a Option<Arc<MetricsStore>>,
129129
end_stream_notifier: &'a Arc<Notify>,
@@ -143,28 +143,23 @@ impl<'a> StageCoordinator<'a> {
143143
UnboundedReceiver<WorkerToCoordinatorMsg>,
144144
)> {
145145
let session_config = self.task_ctx.session_config();
146-
let codec = DistributedCodec::new_combined_with_user(session_config);
147146

148147
let (specialized, work_unit_feed_declarations) = self.task_specialized_plan(task_i)?;
149148

150-
let plan_proto =
151-
PhysicalPlanNode::try_from_physical_plan(specialized, &codec)?.encode_to_vec();
152-
let plan_size = plan_proto.len();
153-
154149
let task_key = TaskKey {
155150
query_id: self.query_id,
156151
stage_id: self.stage_id,
157152
task_number: task_i,
158153
};
159154

160-
let msg = CoordinatorToWorkerMsg::SetPlanRequest(SetPlanRequest {
155+
let set_plan_request = SetPlanRequest {
161156
task_key,
162157
task_count: self.task_count,
163-
plan_proto,
158+
plan: MaybeEncoded::Decoded(specialized),
164159
work_unit_feed_declarations,
165160
target_worker_url: url.clone(),
166161
query_start_time_ns: self.metrics.instantiation_time,
167-
});
162+
};
168163

169164
let (coordinator_to_worker_tx, coordinator_to_worker_rx) =
170165
tokio::sync::mpsc::unbounded_channel();
@@ -176,8 +171,7 @@ impl<'a> StageCoordinator<'a> {
176171
let mut headers = get_config_extension_propagation_headers(session_config)?;
177172
headers.extend(get_passthrough_headers(session_config));
178173

179-
let coordinator_to_worker_stream = futures::stream::once(async { msg })
180-
.chain(UnboundedReceiverStream::new(coordinator_to_worker_rx))
174+
let coordinator_to_worker_stream = UnboundedReceiverStream::new(coordinator_to_worker_rx)
181175
.map(set_work_unit_send_time)
182176
// Keep the request side of the channel open until the query ends: this tail emits
183177
// no messages and only completes, once the `Notify` fires. Workers interpret this
@@ -193,15 +187,22 @@ impl<'a> StageCoordinator<'a> {
193187
.boxed();
194188

195189
let metrics = self.metrics.clone();
190+
let metrics_set = self.metrics_set.clone();
191+
let task_ctx = Arc::clone(self.task_ctx);
196192

197193
self.join_set.lock().unwrap().spawn(async move {
198194
let start = Instant::now();
199195
let mut client = channel_resolver.get_worker_client_for_url(&url).await?;
200196
let mut worker_to_coordinator_stream = client
201-
.coordinator_channel(headers, coordinator_to_worker_stream)
197+
.coordinator_channel(
198+
headers,
199+
set_plan_request,
200+
coordinator_to_worker_stream,
201+
&task_ctx,
202+
metrics_set,
203+
)
202204
.await?;
203205
metrics.plan_send_latency.record(&start);
204-
metrics.plan_bytes_sent.add_bytes(plan_size);
205206
while let Some(msg) = worker_to_coordinator_stream.try_next().await? {
206207
if worker_to_coordinator_tx.send(msg).is_err() {
207208
break; // receiver dropped
@@ -454,7 +455,6 @@ impl Drop for NotifyGuard {
454455
/// Metrics that measure network details about communications between [DistributedExec] and a worker.
455456
#[derive(Clone)]
456457
pub(super) struct CoordinatorToWorkerMetrics {
457-
pub(super) plan_bytes_sent: BytesCounterMetric,
458458
pub(super) plan_send_latency: Arc<LatencyMetric>,
459459
pub(super) instantiation_time: usize,
460460
}
@@ -469,10 +469,6 @@ fn with_task_id_label(builder: MetricBuilder) -> MetricBuilder {
469469
impl CoordinatorToWorkerMetrics {
470470
pub(super) fn new(metrics: &ExecutionPlanMetricsSet) -> Self {
471471
Self {
472-
// Metric that measures to total sum of bytes worth of subplans sent.
473-
plan_bytes_sent: MetricBuilder::new(metrics)
474-
.with_label(Label::new(DISTRIBUTED_DATAFUSION_TASK_ID_LABEL, "0"))
475-
.bytes_counter("plan_bytes_sent"),
476472
// Latency statistics about the network calls issued to the workers for feeding subplans.
477473
plan_send_latency: Arc::new(LatencyMetric::new(
478474
"plan_send_latency",
@@ -502,8 +498,7 @@ mod tests {
502498
/// use-after-free that only reproduced in optimized abort builds. Passing
503499
/// the builder as a named `fn` ([`with_task_id_label`]) sidesteps it.
504500
///
505-
/// `CoordinatorToWorkerMetrics::new` registers three labeled metrics —
506-
/// `plan_bytes_sent` and the latency `_max`/`_avg` pair — each of which
501+
/// `CoordinatorToWorkerMetrics::new` registers the latency `_max`/`_avg` pair, each of which
507502
/// must own a distinct heap buffer holding exactly its single `task_id`
508503
/// label. The miscompile is observable as the `_avg` buffer aliasing the
509504
/// `_max` buffer with length 2.
@@ -530,9 +525,9 @@ mod tests {
530525

531526
assert_eq!(
532527
labeled.len(),
533-
3,
534-
"iteration {iteration}: expected 3 labeled metrics \
535-
(plan_bytes_sent, plan_send_latency_max, plan_send_latency_avg), \
528+
2,
529+
"iteration {iteration}: expected 2 labeled metrics \
530+
(plan_send_latency_max, plan_send_latency_avg), \
536531
got {labeled:x?}"
537532
);
538533

src/distributed_planner/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ pub use distributed_config::DistributedConfig;
1414
pub(crate) use inject_network_boundaries::{
1515
InjectNetworkBoundaryContext, NetworkBoundaryBuilderResult, inject_network_boundaries,
1616
};
17-
pub(crate) use network_boundary::ProducerHead;
18-
pub use network_boundary::{NetworkBoundary, NetworkBoundaryExt};
17+
pub use network_boundary::{NetworkBoundary, NetworkBoundaryExt, ProducerHead};
1918
pub use session_state_builder_ext::SessionStateBuilderExt;
2019
pub(crate) use statistics::calculate_cost;
2120
pub(crate) use task_estimator::{CombinedTaskEstimator, set_distributed_task_estimator};

0 commit comments

Comments
 (0)