ClickHouse / ClickHouse/ClickHouse
Automatic parallel replicas: some plan shapes do not hash-match with parallel_replicas_plan_based, so no statistics are collected
- Dominant language
- C++
- Stars
- 49.9k
- Forks
- 9k
- Avg merge
- 21h 32m
- Merged PRs (30d)
- 515
Description
https://github.com/ClickHouse/ClickHouse/pull/115788 taught automatic parallel replicas to recognize the plan-based reading step, which is what lets it collect statistics at all when `parallel_replicas_plan_based` is on. Some shapes still do not match, so no statistics are collected and the optimization never enables parallel replicas for them.
The matching itself is in `findCorrespondingNodeInSingleNodePlan` (`src/Processors/QueryPlan/Optimizations/considerEnablingParallelReplicas.cpp`): the node that would sit at the top of the replicas' plan is looked up in the single-node plan by the bottom-up hash from `calculateHashTableCacheKeys`. When the two plan builds differ by even one pass-through step the hashes differ and the lookup logs `Cannot find step with matching hash in single-node plan`.
Known cases, each of which had to pin `parallel_replicas_plan_based = 0`:
- **`04341_autopr_mode1_apply_join`** - with join runtime filters on, the plan-based local branch carries an extra identity `Expression` between the `Aggregating` and the `Join`, so the hashes diverge. `INNER` and `RIGHT` report `ParallelReplicasUsedCount = 0`; `LEFT` (no runtime filter) still applies. With `enable_join_runtime_filters = 0` it matches and applies.
- **`03634_autopr_input_bytes_estimation_compact`** and **`04838_autopr_input_bytes_estimation_compact_codec`** - plain reads, where the fragment is the reading step itself, so `considerEnablingParallelReplicas` bails with "The matched node is the reading step itself, cannot estimate the amount of data sent to the initiator". The query-based implementation lands on the `Expression` above the read instead, because `findTopNodeOfReplicasPlan` descends one level past a top `Expression`/`Filter` for every `Union` child - a descent meant for the remote branch.
A related detail worth deciding on: a step whose own hash contribution is 0 (a rename-only `Expression`) is documented as "transparent", but `calculateHashTableCacheKeys` still re-hashes its child through it, so an extra transparent step shifts every ancestor hash. Making it truly transparent would fix the join case, but it also makes such a step hash-identical to its child, so the "first node with a matching hash" scan over an `unordered_map` in `findCorrespondingNodeInSingleNodePlan` would need to become deterministic at the same time.
Going the other way, plan-based already matches a shape the query-based implementation does not: `03707_autopr_unsupported_cases` lists `unsupported_aggregation_over_aggregation` as unmatchable, and under plan-based it matches and collects. That case is pinned to the query-based implementation so the test keeps documenting the query-based limits.
Related: https://github.com/ClickHouse/ClickHouse/pull/115788
Related: https://github.com/ClickHouse/ClickHouse/pull/112351
Contributor guide
Research direction
Start in src/Processors/QueryPlan/Optimizations/considerEnablingParallelReplicas.cpp, especially findCorrespondingNodeInSingleNodePlan and the hash keys from calculateHashTableCacheKeys. Run the named 04341_autopr_mode1_apply_join, 03634_autopr_input_bytes_estimation_compact, and 04838_autopr_input_bytes_estimation_compact_codec tests to reproduce the unmatched shapes. Compare the plan-based and query-based behavior, then make the matching deterministic and verify that statistics are collected without the pinned settings.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- databases, distributed-systems, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100