zhuqi-lucas opened a new issue, #24771:
URL: https://github.com/apache/datafusion/issues/24771

   ### Is your feature request related to a problem or challenge?
   
   `OptimizeAggregateOrder` (`physical-optimizer/src/update_aggr_exprs.rs`) 
detects when the input ordering already satisfies a `first_value`/`last_value` 
`ORDER BY` requirement and calls `with_beneficial_ordering(true)`, which sets 
`is_input_pre_ordered` on the UDAF.
   
   The per-group `Accumulator` path honors it: 
`FirstValueAccumulator`/`LastValueAccumulator` skip all comparisons and just 
take the first/last (non-null) row 
(`functions-aggregate/src/first_last.rs:906`).
   
   The `GroupsAccumulator` path does not: `create_groups_accumulator` never 
threads the flag through, and `FirstLastGroupsAccumulator` has no pre-ordered 
branch at all. So as soon as the query has a `GROUP BY`, the full machinery 
runs on every batch even when the input is perfectly sorted:
   
   - build a `LexicographicalComparator` over the ordering columns,
   - run the per-row tournament in `get_filtered_extreme_of_each_group`,
   - materialize the winning row's ordering key into `ScalarValue`s 
(`extract_row_at_idx_to_buf`),
   - cross-batch `compare_rows` against the stored per-group orderings.
   
   None of that is needed when the input is pre-ordered: for `last_value` the 
last seen row per group always wins, so `update_batch` degenerates to an 
unconditional overwrite per `(group, row)` — no comparator, no `orderings` 
boxing.
   
   **Motivating workload**: a materialized aggregation over an options NBBO 
table (~1.5B rows/day, ~1.8M groups/day) whose file sort order is exactly 
`(ticker, block_timestamp, sequence_number)`:
   
   ```sql
   SELECT date, ticker,
     LAST_VALUE(block_timestamp ORDER BY block_timestamp, sequence_number),
     LAST_VALUE(bid_price       ORDER BY block_timestamp, sequence_number),
     LAST_VALUE(ask_price       ORDER BY block_timestamp, sequence_number)
   FROM nbbo_quotes
   GROUP BY date, ticker
   ```
   
   The group keys are a prefix of the sort order, and within each group the 
rows are already sorted by the aggregate's requirement. A CPU profile of this 
query shows ~20% of on-CPU time inside `FirstLastGroupsAccumulator` (dominated 
by `get_filtered_extreme_of_each_group`), all of it avoidable comparisons.
   
   ### Describe the solution you'd like
   
   Mirror the existing single-group fast path in the grouped one:
   
   1. Pass `is_input_pre_ordered` through `create_groups_accumulator` into 
`FirstLastGroupsAccumulator`.
   2. When set, replace the tournament in `update_batch` with a single pass 
over `group_indices` that records the first (for `first_value`) or last (for 
`last_value`) qualifying row index per group in the batch, then updates state 
unconditionally (later batches always win for `last_value`; for `first_value`, 
only unset groups are written). The `orderings` vector and comparator are not 
needed in this mode.
   
   The optimizer-side detection already exists; this is only about consuming 
the flag symmetrically.
   
   ### Describe alternatives you've considered
   
   - Leaving it to the planner to rewrite `last_value` into something cheaper: 
there is nothing cheaper to rewrite into today; the accumulator is the right 
place.
   - #23682 (coalescing same-`ORDER BY` first/last into one struct accumulator) 
helps the unsorted case and is complementary; it does not remove the per-row 
comparisons when the input is already sorted.
   
   I can work on this.
   


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