andygrove opened a new issue, #6857:
URL: https://github.com/apache/datafusion-comet/issues/6857

   ### Describe the bug
   
   `count(DISTINCT x)` next to another aggregate comes back too large when the 
native aggregate that deduplicates the distinct keys runs short of memory. 
Spark plans `SELECT g, sum(v), count(DISTINCT d) FROM t GROUP BY g` as four 
aggregates. The second one is a `PartialMerge` keyed by `(g, d)`. It has to 
leave exactly one row per `(g, d)` pair, because the stage above it counts 
those rows.
   
   Comet runs a `PartialMerge` as a DataFusion `AggregateMode::Partial` 
aggregate (`native/core/src/execution/planner.rs`, the 
`ProtoAggregateMode::PartialMerge => DFAggregateMode::Partial` arm, with 
`MergeAsPartial` wrappers). In `Partial` mode, DataFusion's 
`GroupedHashAggregateStream` handles memory pressure with 
`OutOfMemoryMode::EmitEarly`: it emits the groups it holds and starts again. 
That's fine for a real partial aggregate, but here the same `(g, d)` key leaves 
the stage more than once, and the distinct count includes every copy.
   
   Nothing fails. `sum(v)` is still right, because partial sums add up, but 
`count(DISTINCT d)` is wrong.
   
   ### Steps to reproduce
   
   On `main` at dc880d6d26 with Spark 4.1.3, in a `CometTestBase` suite (2g 
off-heap):
   
   ```scala
   spark
     .range(0, 400000, 1, 1)
     .selectExpr("id % 100 AS g", "id % 200000 AS d", "id AS v")
     .write
     .parquet(path)
   ```
   
   Then, with these settings:
   
   ```
   spark.sql.adaptive.enabled=false
   spark.sql.shuffle.partitions=1
   spark.comet.batchSize=1024
   spark.comet.exec.memoryPool.fraction=0.002
   spark.comet.exec.respectDataFusionConfigs=true
   spark.comet.datafusion.execution.sort_spill_reservation_bytes=65536
   ```
   
   ```sql
   SELECT g, sum(v), count(DISTINCT d) FROM t GROUP BY g
   ```
   
   Spark returns `count(DISTINCT d) = 2000` for every group. Comet returns 4000 
for all 100 groups, and every `sum(v)` matches.
   
   The metrics show where the extra rows come from. The `PartialMerge` 
aggregate keyed by `(g, d)` reports `output_rows` = 400,000, twice the 200,000 
distinct pairs. No aggregate reports a spill. With the default pool, the same 
stage reports 200,000 rows and the answer is right.
   
   ### Expected behavior
   
   The same counts as Spark. Under memory pressure, a `PartialMerge` that 
deduplicates keys should spill or fail. It should never emit the same key twice.
   
   ### Additional context
   
   - It isn't specific to `sum`. Since #4727, `collect_list` in a 
distinct-combined plan runs natively too. Under the same settings, `SELECT _1, 
collect_list(_3), count(DISTINCT _2) FROM t GROUP BY _1`, with a 32,768-row 
table where `_1 = i % 4096`, `_2 = i % 8192` and `_3` is a string of about 260 
characters, returns 4 or 5 instead of 2. On 1.1.0 that plan fell back to Spark 
(`hasNativeArrayBufferAgg`), so the `collect_list` case is new on `main`.
   - The `sum` case goes through the native `PartialMerge` support from #4003, 
and `branch-1.0` and `branch-1.1` plan it the same way, so 1.0.x and 1.1.0 are 
probably affected too. I haven't run it on those branches.
   - `CometBaseAggregate.doConvert` already serializes an empty `PartialMerge` 
(no aggregate expressions) as native `Final`, because it "must fully 
deduplicate its keys". A `PartialMerge` with aggregate expressions needs the 
same guarantee, but it runs as native `Partial`.
   - I found this while reviewing #5903. That PR doesn't change it.
   


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