SubhamSinghal opened a new pull request, #25730:
URL: https://github.com/apache/datafusion/pull/25730

   ## Which issue does this PR close?
   
   - Part of #24608. Close #13586.
   
   ## Rationale for this change
   
   `enable_window_topn` rewrites `Filter(rn<=K) → Window(ROW_NUMBER) → Sort` 
into
   `PartitionedTopKExec`. As its own config documentation warns, it helps at 
low partition cardinality
   and regresses at high cardinality. Running the existing ROW_NUMBER sweep in
   `benchmarks/queries/h2o/window.sql` on `J1_1e7_1e7_NA` (10M rows) and 
comparing the flag on vs off
   on the same commit (medians of 7):
   
   | partitions | CPU, ON vs OFF on `main` |
   |---|---|
   | 100 | 5.65× faster |
   | 1,000 | 5.21× faster |
   | 10,000 | 2.28× faster |
   | 100,000 | **2.16× slower** |
   
   The benefit decays with cardinality and inverts around 100K, so the 
optimization becomes a
   pessimization exactly where a per-partition top-K should help most. That is 
why the flag still
   defaults to `false`.
   
   The cause is how retained rows are stored, not the per-partition heaps. For 
every
   (batch, partition) pair, `insert_batch` `take`s a sub-batch of *all* that 
partition's rows in that
   batch — qualifying or not — registers it in a `RecordBatchStore` private to 
that partition, then
   runs `maybe_compact()`. At 100K partitions that is ~100K separate stores, 
one `take` per
   (batch, partition), and a compaction check per (batch, partition) on every 
batch.
   
   ## What changes are included in this PR?
   
   Two coupled changes to the `ROW_NUMBER` arm of `PartitionedTopKExec`. `RANK` 
and `DENSE_RANK` are
   untouched — see the follow-ups below.
   
   1. **One `RecordBatchStore` for the operator** instead of one per partition. 
Each partition holds a
      lean `PartitionHeap` of `PartitionSlot { key: Vec<u8>, batch_id: u32, 
row: u32 }` — the
      row-encoded ORDER BY key inline, output columns by reference, refcounted 
as `DENSE_RANK` already
      does.
   2. **Decide first, gather once.** A single pass over the batch admits rows 
and records only their
      indices; then **one** `take_record_batch` of just the admitted rows 
becomes one store entry. If
      every admission from a batch is superseded within that batch, nothing is 
registered at all.
   
   Sharing the store means `TopKHeap::maybe_compact` can no longer be used on 
this path — it calls
   `store.clear()`, which on a shared store invalidates *other* partitions' 
`batch_id`s, and its guard
   reads a now-global `total_rows`. Since that per-heap compaction was the only 
thing bounding retention
   on `main`, it is replaced by an operator-level one:
   
   3. **`compact_store()`**, run at the end of every `insert_batch`, rewrites 
the store when it holds
      more than `STORE_COMPACTION_RATIO` × the rows the heaps actually point 
at. Without it, retention is
      **unbounded**.
   
   ## What is the testing strategy for this PR?
   
   `datafusion-physical-plan` (1964 tests) and all 512 sqllogictest files pass. 
plus added new UTs
   
   ## Are there any user-facing changes?
   
   No API changes and no change to query results. `enable_window_topn` still 
defaults to `false`.
   
   ## Benchmark details
   
   Queries are the ROW_NUMBER cardinality sweep already in 
`benchmarks/queries/h2o/window.sql`
   (k=2, `ORDER BY v2 DESC`, `WHERE v2 IS NOT NULL`), run against 
`J1_1e7_1e7_NA.parquet`
   
   | partitions | main, ON vs OFF | this branch, ON vs OFF |
   |---|---|---|
   | 100 | 5.65× faster | 5.80× faster |
   | 1,000 | 5.21× faster | 5.90× faster |
   | 10,000 | 2.28× faster | 4.59× faster |
   | 100,000 | **2.16× slower** | **2.01× faster** |
   
   **"Why not just extract `RecordBatchStore` out of `TopKHeap`?"** — measured, 
on a throwaway branch
   with a shared store and `main`'s per-(batch, partition) gather. Sharing the 
store is ~1.2× of the
   win and is *worse* on RSS than `main` (it pins per-(batch, partition) 
sub-batches with nothing
   reclaiming them); decide-then-gather is the remaining ~5.3×, and without it 
the rewrite is still
   slower than the `SortExec` plan it replaces.


-- 
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