Describe the bug
When SortExec runs in TopK mode (ORDER BY ... LIMIT k on unsorted input) and the k result rows span more than one output batch, its output_batches metric reports 1 regardless of how many batches the operator actually emits.
For example, a top-25 query at datafusion.execution.batch_size = 10 emits three batches ([10, 10, 5] rows) but reports output_batches=1.
To Reproduce
The following standalone test (e.g. dropped into datafusion/core/tests/) fails on main:
use std::sync::Arc;
use datafusion::common::Result;
use datafusion::physical_plan::metrics::MetricValue;
use datafusion::physical_plan::sorts::sort::SortExec;
use datafusion::physical_plan::{ExecutionPlan, collect};
use datafusion::prelude::*;
/// The `output_batches` metric of a TopK sort should equal the number of
/// batches the operator emits to its consumer.
#[tokio::test]
async fn topk_output_batches_metric_matches_emitted_batches() -> Result<()> {
// A top-25 over 100 unsorted rows, at batch_size 10, so the TopK result
// must be emitted as multiple batches.
let config = SessionConfig::new()
.with_batch_size(10)
.with_target_partitions(1);
let ctx = SessionContext::new_with_config(config);
ctx.sql("CREATE TABLE t AS SELECT value FROM range(0, 100)")
.await?
.collect()
.await?;
let df = ctx
.sql("SELECT value FROM t ORDER BY value DESC LIMIT 25")
.await?;
let plan = df.create_physical_plan().await?;
let batches = collect(Arc::clone(&plan), ctx.task_ctx()).await?;
let emitted_sizes: Vec<usize> = batches.iter().map(|b| b.num_rows()).collect();
let sort = find_sort(&plan).expect("plan should contain SortExec");
let metrics = sort.metrics().expect("SortExec should have metrics");
let output_batches = metrics
.sum(|m| matches!(m.value(), MetricValue::OutputBatches(_)))
.expect("output_batches metric should be present")
.as_usize();
// The 25 result rows arrive as three batches of at most batch_size rows
assert_eq!(emitted_sizes, vec![10, 10, 5]);
// ... so the metric should report three output batches
assert_eq!(
output_batches,
emitted_sizes.len(),
"output_batches metric disagrees with the number of emitted batches"
);
Ok(())
}
fn find_sort(plan: &Arc<dyn ExecutionPlan>) -> Option<Arc<dyn ExecutionPlan>> {
if plan.downcast_ref::<SortExec>().is_some() {
return Some(Arc::clone(plan));
}
plan.children().into_iter().find_map(find_sort)
}
Output:
assertion `left == right` failed: output_batches metric disagrees with the number of emitted batches
left: 1
right: 3
Expected behavior
output_batches matches the number of batches the operator emits to its consumer (3 in the reproducer above).
Additional context
Only the TopK path is affected. SortExec without a fetch, and SortExec with a fetch over already-sorted input (the LimitStream path), report output_batches correctly.
Describe the bug
When
SortExecruns in TopK mode (ORDER BY ... LIMIT kon unsorted input) and the k result rows span more than one output batch, itsoutput_batchesmetric reports1regardless of how many batches the operator actually emits.For example, a top-25 query at
datafusion.execution.batch_size = 10emits three batches ([10, 10, 5]rows) but reportsoutput_batches=1.To Reproduce
The following standalone test (e.g. dropped into
datafusion/core/tests/) fails onmain:Output:
Expected behavior
output_batchesmatches the number of batches the operator emits to its consumer (3in the reproducer above).Additional context
Only the TopK path is affected.
SortExecwithout afetch, andSortExecwith afetchover already-sorted input (theLimitStreampath), reportoutput_batchescorrectly.