NoahKusaba opened a new issue, #2854:
URL: https://github.com/apache/datafusion-ballista/issues/2854
**Describe the bug**
With the adaptive planner (the default), a `UNION ALL` whose branches each
end in a node that merges its input into one partition, such as the `SortExec`
an `ORDER BY` puts on each branch, returns some rows several times when the
union's stage is split between tasks.
When a stage is split, each task gets the plan restricted to its own
partitions, and a branch of a `UnionExec` the task doesn't own is restricted to
no partitions. A branch that merges its input into one partition still reports
one partition afterwards, and the nodes below the merge read their input whole.
The executor runs every partition of the task's plan, so each task runs that
branch in full, and its rows come back once per task.
The static planner already handles this: `can_stay_inline`
(`ballista/scheduler/src/planner.rs`) gives such a branch a stage of its own,
so it reaches the union as a shuffle reader that restricts to nothing cleanly
(the fix for #2184). The adaptive planner has no equivalent.
Whether a stage is split depends on free task slots, so this shows on a busy
cluster, or always with `ballista.scheduler.max_partitions_per_task = 1`.
**To Reproduce**
Standalone Ballista, a one-file Parquet table `p` with rows `(1, 'a'), (2,
'b')`, and `ballista.scheduler.max_partitions_per_task = 1`:
| Query | Rows | Expected |
|---|---|---|
| `SELECT id, name FROM p UNION ALL SELECT id, name FROM p` | 4 | 4 |
| `SELECT id, name FROM p UNION ALL SELECT id, name FROM p ORDER BY id` | 8
| 4 |
Other branches that merge their input into one partition do the same. Each
was checked against DataFusion on the adaptive planner with
`max_partitions_per_task = 1`:
| Branch shape | Example | Result |
|---|---|---|
| aggregate without `GROUP BY` | `SELECT count(*) FROM test WHERE id > 100
UNION ALL SELECT count(*) FROM test` | 3 rows, should be 2 |
| `LIMIT` (`CoalescePartitionsExec`) | `SELECT count(*) FROM (SELECT o_id
FROM (SELECT o_id FROM orders LIMIT 10) UNION ALL SELECT c_id FROM customers)`
| over 110, should be 110 |
| `ORDER BY … LIMIT` (`SortPreservingMergeExec`) | `SELECT * FROM (SELECT
o_id FROM orders ORDER BY o_id LIMIT 3) UNION ALL SELECT c_id FROM customers
WHERE c_id < 2` | 7 rows, should be 5 |
(`test` is `alltypes_plain.parquet`; `orders` is 1000 rows over four CSV
files and `customers` 100 rows in one.)
The same happens with an Iceberg table (andygrove's repro on #2638).
**Expected behavior**
4 rows, as without the split.
**Additional context**
- #2852 (open) makes the scheduler refuse such a task ("cannot restrict a
`UnionExec` branch to partitions []: its `SortExec` keeps a partition count of
1…"), so the query fails instead of returning wrong rows. This issue is for
making it succeed. Until then, `ballista.planner.adaptive.enabled = false`
avoids it.
- The scheduler can't fix it per task: replacing the whole branch with an
empty node changes the task plan's node count, which `update_task_metrics`
rejects, and emptying only the branch's leaves still leaves a global
aggregate's `count = 0` row.
- Proposed fix: have the adaptive planner give a union branch that can't be
restricted to no partitions a stage of its own, as the static planner does, by
sharing `can_stay_inline`'s test (a trial restriction to `[]`, plus the
broadcast check) with `DistributedExchangeRule`
(`ballista/scheduler/src/state/aqe/optimizer_rule/distributed_exchange.rs`).
- Related: #2184 (the static planner's fix), #2679 / #2721 (leaves the
scheduler can't restrict).
--
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]