alamb opened a new issue, #24468:
URL: https://github.com/apache/datafusion/issues/24468

   ### 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`. The 
`output_rows` metric is correct.
   
   ### To Reproduce
   
   The following standalone test (e.g. dropped into `datafusion/core/tests/`) 
fails on `main`:
   
   ```rust
   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
   ```
   
   The first assertion passes, i.e. the operator really does emit three 
batches; only the metric disagrees.
   
   ### 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.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to