Skip to content

Commit 1d003f1

Browse files
committed
Drop velocity-based estimation in favor of driver path completion estimation
1 parent 0f5cf3b commit 1d003f1

13 files changed

Lines changed: 411 additions & 186 deletions

File tree

benchmarks/cdk/bin/datafusion-bench.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ async function main() {
2525
.option('--repartition-file-min-size <number>', 'repartition_file_min_size DF option', '10485760' /* upstream default */)
2626
.option('--target-partitions <number>', 'target_partitions DF option', '8')
2727
.option('--dynamic <boolean>', 'Use the dynamic task count assigner', 'false')
28-
.option('--bytes-per-partition-per-second <number>', 'Target throughput in bytes per partition per second for the dynamic task count allocator', `${16 * 1024 * 1024}`)
28+
.option('--dynamic-bytes-per-partition <number>', 'Target throughput in bytes per partition per second for the dynamic task count allocator', `${16 * 1024 * 1024}`)
2929
.option('--queries <string>', 'Specific queries to run', undefined)
3030
.option('--debug <boolean>', 'Print the generated plans to stdout')
3131
.option('--warmup <boolean>', 'Perform a warmup query before the benchmarks', 'true')
@@ -49,7 +49,7 @@ async function main() {
4949
const broadcastJoins = options.broadcastJoins === 'true' || options.broadcastJoins === 1
5050
const partialReduce = options.partialReduce === 'true' || options.partialReduce === 1
5151
const dynamicTaskCount = options.dynamic === 'true' || options.dynamic === 1
52-
const bytesPerPartitionPerSecond = parseInt(options.bytesPerPartitionPerSecond)
52+
const dynamicBytesPerPartition = parseInt(options.dynamicBytesPerPartition)
5353
const debug = options.debug === true || options.debug === 'true' || options.debug === 1
5454
const warmup = options.warmup === true || options.warmup === 'true' || options.warmup === 1
5555

@@ -64,7 +64,7 @@ async function main() {
6464
broadcastJoins,
6565
partialReduce,
6666
dynamicTaskCount,
67-
bytesPerPartitionPerSecond,
67+
dynamicBytesPerPartition,
6868
maxTasksPerStage,
6969
repartitionFileMinSize,
7070
targetPartitions
@@ -107,7 +107,7 @@ class DataFusionRunner implements BenchmarkRunner {
107107
broadcastJoins: boolean;
108108
partialReduce: boolean;
109109
dynamicTaskCount: boolean;
110-
bytesPerPartitionPerSecond: number;
110+
dynamicBytesPerPartition: number;
111111
maxTasksPerStage: number;
112112
repartitionFileMinSize: number;
113113
targetPartitions: number;
@@ -195,7 +195,7 @@ class DataFusionRunner implements BenchmarkRunner {
195195
SET distributed.broadcast_joins=${this.options.broadcastJoins};
196196
SET distributed.partial_reduce=${this.options.partialReduce};
197197
SET distributed.dynamic_task_count=${this.options.dynamicTaskCount};
198-
SET distributed.bytes_per_partition_per_second=${this.options.bytesPerPartitionPerSecond};
198+
SET distributed.dynamic_bytes_per_partition=${this.options.dynamicBytesPerPartition};
199199
SET distributed.max_tasks_per_stage=${this.options.maxTasksPerStage};
200200
SET datafusion.optimizer.repartition_file_min_size=${this.options.repartitionFileMinSize};
201201
SET datafusion.execution.target_partitions=${this.options.targetPartitions};

src/common/recursion.rs

Lines changed: 215 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{DistributedTaskContext, NetworkBoundaryExt};
33
use datafusion::common::Result;
44
use datafusion::common::tree_node::{Transformed, TreeNode, TreeNodeIterator, TreeNodeRecursion};
55
use datafusion::physical_plan::ExecutionPlan;
6+
use datafusion::physical_plan::joins::{CrossJoinExec, HashJoinExec, NestedLoopJoinExec};
67
use std::cell::RefCell;
78
use std::sync::Arc;
89

@@ -26,6 +27,36 @@ pub(crate) trait TreeNodeExt {
2627
f: F,
2728
) -> Result<TreeNodeRecursion>;
2829

30+
/// Applies `f` top-down (pre-order) to the nodes on the plan's *driver path*: the operators
31+
/// whose ongoing data production feeds, batch for batch, into the root's output. The
32+
/// [`TreeNodeRecursion`] returned by `f` steers it as in [`TreeNode::apply`].
33+
///
34+
/// It differs from a full traversal only at the pipeline-breaking joins ([`HashJoinExec`],
35+
/// [`NestedLoopJoinExec`], [`CrossJoinExec`]): it follows the probe side (`right`) and skips the
36+
/// build side (`left`), since a join fully materializes its build side before emitting any
37+
/// output row, so build-side rows are setup work rather than output progress. Every other
38+
/// operator is transparent.
39+
///
40+
/// Used to estimate a running stage's completion: the total rows to pull come from the leaves
41+
/// on the driver path (see `estimated_driver_path_leaf_rows`).
42+
///
43+
/// ```text
44+
/// ┌────────────┐
45+
/// │ HashJoin │ visited
46+
/// └─────┬──────┘
47+
/// build │ probe
48+
/// (left) │ │ (right)
49+
/// ┌────────┘ └────────┐
50+
/// ▼ ▼
51+
/// ┌────────┐ ┌────────┐
52+
/// │ Scan B │ SKIPPED │ Scan P │ visited
53+
/// └────────┘ └────────┘
54+
/// ```
55+
fn apply_driver_path<F: FnMut(&Self) -> Result<TreeNodeRecursion>>(
56+
&self,
57+
f: F,
58+
) -> Result<TreeNodeRecursion>;
59+
2960
/// Recursively rewrite the tree using `f` in a top-down (pre-order) fashion, propagating
3061
/// the appropriate [DistributedTaskContext] based on the presence of nodes that can isolate
3162
/// tasks, like [ChildrenIsolatorUnionExec].
@@ -104,6 +135,32 @@ impl TreeNodeExt for Arc<dyn ExecutionPlan> {
104135
recurse(self, ctx, &mut f)
105136
}
106137

138+
fn apply_driver_path<F: FnMut(&Self) -> Result<TreeNodeRecursion>>(
139+
&self,
140+
mut f: F,
141+
) -> Result<TreeNodeRecursion> {
142+
fn recurse<F: FnMut(&Arc<dyn ExecutionPlan>) -> Result<TreeNodeRecursion>>(
143+
plan: &Arc<dyn ExecutionPlan>,
144+
f: &mut F,
145+
) -> Result<TreeNodeRecursion> {
146+
f(plan)?.visit_children(|| {
147+
if let Some(hash_join) = plan.downcast_ref::<HashJoinExec>() {
148+
recurse(hash_join.right(), f)
149+
} else if let Some(nested_loop_join) = plan.downcast_ref::<NestedLoopJoinExec>() {
150+
recurse(nested_loop_join.right(), f)
151+
} else if let Some(cross_join) = plan.downcast_ref::<CrossJoinExec>() {
152+
recurse(cross_join.right(), f)
153+
} else {
154+
plan.children()
155+
.into_iter()
156+
.apply_until_stop(|child| recurse(child, f))
157+
}
158+
})
159+
}
160+
161+
recurse(self, &mut f)
162+
}
163+
107164
fn transform_down_with_dt_ctx<
108165
F: FnMut(Self, DistributedTaskContext) -> Result<Transformed<Self>>,
109166
>(
@@ -213,9 +270,13 @@ mod tests {
213270
use crate::execution_plans::ChildWeight;
214271
use crate::stage::RemoteStage;
215272
use crate::{NetworkCoalesceExec, Stage};
216-
use datafusion::arrow::datatypes::Schema;
273+
use datafusion::arrow::datatypes::{DataType, Field, Schema};
274+
use datafusion::common::{JoinType, NullEquality};
275+
use datafusion::physical_expr::PhysicalExpr;
276+
use datafusion::physical_expr::expressions::Column;
217277
use datafusion::physical_plan::coalesce_partitions::CoalescePartitionsExec;
218278
use datafusion::physical_plan::empty::EmptyExec;
279+
use datafusion::physical_plan::joins::PartitionMode;
219280
use datafusion::physical_plan::union::UnionExec;
220281
use insta::assert_snapshot;
221282
// ── apply_with_dt_ctx ────────────────────────────────────────────────────────
@@ -348,6 +409,81 @@ mod tests {
348409
");
349410
}
350411

412+
// ── apply_driver_path ────────────────────────────────────────────────────
413+
414+
#[test]
415+
fn driver_path_leaf() {
416+
let plan = leaf();
417+
assert_snapshot!(trace_driver_path(&plan), @"Leaf");
418+
}
419+
420+
#[test]
421+
fn driver_path_top_down_order() {
422+
let plan = union(vec![single(leaf()), leaf()]);
423+
assert_snapshot!(trace_driver_path(&plan), @r"
424+
Union
425+
Single
426+
Leaf
427+
Leaf
428+
");
429+
}
430+
431+
#[test]
432+
fn driver_path_jump_skips_subtree() {
433+
let child = single(leaf());
434+
let plan = single(Arc::clone(&child));
435+
assert_snapshot!(
436+
trace_driver_path_with(&plan, |p| {
437+
if Arc::ptr_eq(p, &child) { TreeNodeRecursion::Jump } else { TreeNodeRecursion::Continue }
438+
}),
439+
@r"
440+
Single
441+
Single [->jump]
442+
");
443+
}
444+
445+
#[test]
446+
fn driver_path_hash_join_uses_probe_side() {
447+
let plan = hash_join(
448+
single(leaf_i32("l")),
449+
union(vec![leaf_i32("r"), leaf_i32("r")]),
450+
);
451+
assert_snapshot!(trace_driver_path(&plan), @r"
452+
HashJoin
453+
Union
454+
Leaf
455+
Leaf
456+
");
457+
}
458+
459+
#[test]
460+
fn driver_path_nested_loop_join_uses_probe_side() {
461+
let plan = nested_loop_join(
462+
single(leaf_i32("l")),
463+
union(vec![leaf_i32("r"), leaf_i32("r")]),
464+
);
465+
assert_snapshot!(trace_driver_path(&plan), @r"
466+
NestedLoopJoin
467+
Union
468+
Leaf
469+
Leaf
470+
");
471+
}
472+
473+
#[test]
474+
fn driver_path_cross_join_uses_probe_side() {
475+
let plan = cross_join(
476+
single(leaf_i32("l")),
477+
union(vec![leaf_i32("r"), leaf_i32("r")]),
478+
);
479+
assert_snapshot!(trace_driver_path(&plan), @r"
480+
CrossJoin
481+
Union
482+
Leaf
483+
Leaf
484+
");
485+
}
486+
351487
// ── transform_down_with_dt_ctx ────────────────────────────────────────────
352488

353489
#[test]
@@ -566,6 +702,14 @@ mod tests {
566702
Arc::new(EmptyExec::new(Arc::new(Schema::empty())))
567703
}
568704

705+
fn leaf_i32(name: &str) -> Arc<dyn ExecutionPlan> {
706+
Arc::new(EmptyExec::new(Arc::new(Schema::new(vec![Field::new(
707+
name,
708+
DataType::Int32,
709+
true,
710+
)]))))
711+
}
712+
569713
fn single(child: Arc<dyn ExecutionPlan>) -> Arc<dyn ExecutionPlan> {
570714
Arc::new(CoalescePartitionsExec::new(child))
571715
}
@@ -581,6 +725,47 @@ mod tests {
581725
Arc::new(NetworkCoalesceExec::try_new(input, producer_tasks, 1).unwrap())
582726
}
583727

728+
fn hash_join(
729+
left: Arc<dyn ExecutionPlan>,
730+
right: Arc<dyn ExecutionPlan>,
731+
) -> Arc<dyn ExecutionPlan> {
732+
Arc::new(
733+
HashJoinExec::try_new(
734+
left,
735+
right,
736+
join_on(),
737+
None,
738+
&JoinType::Inner,
739+
None,
740+
PartitionMode::CollectLeft,
741+
NullEquality::NullEqualsNothing,
742+
false,
743+
)
744+
.unwrap(),
745+
)
746+
}
747+
748+
fn nested_loop_join(
749+
left: Arc<dyn ExecutionPlan>,
750+
right: Arc<dyn ExecutionPlan>,
751+
) -> Arc<dyn ExecutionPlan> {
752+
Arc::new(NestedLoopJoinExec::try_new(left, right, None, &JoinType::Inner, None).unwrap())
753+
}
754+
755+
fn cross_join(
756+
left: Arc<dyn ExecutionPlan>,
757+
right: Arc<dyn ExecutionPlan>,
758+
) -> Arc<dyn ExecutionPlan> {
759+
Arc::new(CrossJoinExec::new(left, right))
760+
}
761+
762+
fn join_on() -> Vec<(Arc<dyn PhysicalExpr>, Arc<dyn PhysicalExpr>)> {
763+
vec![(
764+
Arc::new(Column::new("l", 0)) as Arc<dyn PhysicalExpr>,
765+
Arc::new(Column::new("r", 0)) as Arc<dyn PhysicalExpr>,
766+
)]
767+
}
768+
584769
fn remote_network_boundary() -> Arc<dyn ExecutionPlan> {
585770
network_boundary(leaf(), 1)
586771
.as_network_boundary()
@@ -630,6 +815,12 @@ mod tests {
630815
"CIU"
631816
} else if p.is::<NetworkCoalesceExec>() {
632817
"Network"
818+
} else if p.is::<HashJoinExec>() {
819+
"HashJoin"
820+
} else if p.is::<NestedLoopJoinExec>() {
821+
"NestedLoopJoin"
822+
} else if p.is::<CrossJoinExec>() {
823+
"CrossJoin"
633824
} else {
634825
"?"
635826
}
@@ -664,6 +855,29 @@ mod tests {
664855
lines.join("\n")
665856
}
666857

858+
fn trace_driver_path(root: &Arc<dyn ExecutionPlan>) -> String {
859+
trace_driver_path_with(root, |_| TreeNodeRecursion::Continue)
860+
}
861+
862+
fn trace_driver_path_with<F: FnMut(&Arc<dyn ExecutionPlan>) -> TreeNodeRecursion>(
863+
root: &Arc<dyn ExecutionPlan>,
864+
mut decide: F,
865+
) -> String {
866+
let mut lines = vec![];
867+
root.apply_driver_path(|p| {
868+
let rec = decide(p);
869+
let suffix = match rec {
870+
TreeNodeRecursion::Continue => "",
871+
TreeNodeRecursion::Jump => " [->jump]",
872+
TreeNodeRecursion::Stop => " [->stop]",
873+
};
874+
lines.push(format!("{}{suffix}", plan_label(p)));
875+
Ok(rec)
876+
})
877+
.unwrap();
878+
lines.join("\n")
879+
}
880+
667881
fn trace_dt_ctx_down(root: Arc<dyn ExecutionPlan>, dt_ctx: DistributedTaskContext) -> String {
668882
trace_dt_ctx_down_with(root, dt_ctx, |_| TreeNodeRecursion::Continue)
669883
}

0 commit comments

Comments
 (0)