Skip to content

Parallel hash join threshold#76185

Merged
nickitat merged 33 commits intomasterfrom
parallel_hash_join_threshold
Apr 25, 2025
Merged

Parallel hash join threshold#76185
nickitat merged 33 commits intomasterfrom
parallel_hash_join_threshold

Conversation

@nickitat
Copy link
Copy Markdown
Member

@nickitat nickitat commented Feb 14, 2025

Changelog category (leave one):

  • Performance Improvement

Related: #75261

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Introduced threshold (regulated by setting parallel_hash_join_threshold) to fall back to the hash algorithm when the size of the right table is below the threshold.


The new logic uses the right table size estimation to guess if the parallel_hash will be worth it. It is done here:

auto lhs_estimation = estimateReadRowsCount(*node.children[0]);
auto rhs_estimation = estimateReadRowsCount(*node.children[1]);
/// Consider estimations from hash table sizes cache too
if (const auto & hash_table_key_hashes = join_step->getHashTableKeyHashes();
hash_table_key_hashes && optimization_settings.collect_hash_table_stats_during_joins)
{
StatsCollectingParams params{
/*key_=*/0,
/*enable=*/true,
optimization_settings.max_entries_for_hash_table_stats,
optimization_settings.max_size_to_preallocate_for_joins};
if (auto hint = getHashTablesStatistics<HashJoinEntry>().getSizeHint(params.setKey(hash_table_key_hashes->key_hash_left)))
lhs_estimation = std::min<size_t>(lhs_estimation.value_or(std::numeric_limits<size_t>::max()), hint->source_rows);
if (auto hint = getHashTablesStatistics<HashJoinEntry>().getSizeHint(params.setKey(hash_table_key_hashes->key_hash_right)))
rhs_estimation = std::min<size_t>(rhs_estimation.value_or(std::numeric_limits<size_t>::max()), hint->source_rows);
}

To make it happen, we now collect another data point in HashTablesStatistics - source_rows, meaning exactly the amount of rows in the right-hand side (not the amount of distinct join key values):

struct HashJoinEntry
{
bool shouldBeUpdated(const HashJoinEntry & new_entry) const { return new_entry.ht_size < ht_size / 2 || ht_size < new_entry.ht_size; }
std::string dump() const { return fmt::format("ht_size={}", ht_size); }
size_t ht_size; // the size of the shared hash table
size_t source_rows; // the number of rows in the source table
};

It is needed to be able to differentiate between these two cases:

-- Check estimations obtained from the cache
-- Right table is big, regardless of cardinality of join key, we should use ConcurrentHashJoin
select * from lhs t0 join (select a % 10000 as a from rhs) t1 on t0.a = t1.a settings query_plan_join_swap_table = false format Null;
select trimBoth(explain)
from (
explain actions=1 select * from lhs t0 join (select a % 10000 as a from rhs) t1 on t0.a = t1.a settings query_plan_join_swap_table = false
)
where explain ilike '%Algorithm%';
-- Right table is big, but only a small fraction of rows reaches the join - use HashJoin
select * from lhs t0 join rhs t1 on t0.a = t1.a where t1.a < 10000 settings query_plan_join_swap_table = false format Null;
select trimBoth(explain)
from (
explain actions=1 select * from lhs t0 join rhs t1 on t0.a = t1.a where t1.a < 10000 settings query_plan_join_swap_table = false
)
where explain ilike '%Algorithm%';

@clickhouse-gh
Copy link
Copy Markdown
Contributor

clickhouse-gh bot commented Feb 14, 2025

Workflow [PR], commit [c194ed0]

@clickhouse-gh clickhouse-gh bot added the pr-performance Pull request with some performance improvements label Feb 14, 2025
right_header,
planner_context->getQueryContext(),
std::move(hash_table_stat_cache_key),
/*rhs_estimation=*/{});
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this means it will not take any effect if took this branch

if (!settings[Setting::query_plan_use_new_logical_join_step] || settings[Setting::allow_experimental_parallel_reading_from_replicas])
return buildQueryPlanForJoinNodeLegacy(
join_table_expression, std::move(left_join_tree_query_plan), std::move(right_join_tree_query_plan), outer_scope_columns, planner_context, select_query_info);

@nickitat
Copy link
Copy Markdown
Member Author

Integration tests (tsan, 3/6) - #75982
Integration tests (tsan, 6/6) - #76446

@qoega
Copy link
Copy Markdown
Member

qoega commented Mar 14, 2025

Do you plan to return to this PR?

@nickitat
Copy link
Copy Markdown
Member Author

Do you plan to return to this PR?

Yes, it awaited #76613

@nickitat nickitat force-pushed the parallel_hash_join_threshold branch from f0de853 to 0fc678a Compare March 26, 2025 21:33
@nickitat nickitat enabled auto-merge April 25, 2025 19:24
@nickitat nickitat added this pull request to the merge queue Apr 25, 2025
Merged via the queue into master with commit a93baca Apr 25, 2025
116 of 121 checks passed
@nickitat nickitat deleted the parallel_hash_join_threshold branch April 25, 2025 22:05
@robot-ch-test-poll4 robot-ch-test-poll4 added the pr-synced-to-cloud The PR is synced to the cloud repo label Apr 25, 2025
nickitat added a commit that referenced this pull request Apr 28, 2025
Cherry pick #76185 to 25.3: Parallel hash join threshold
@robot-ch-test-poll4 robot-ch-test-poll4 added the pr-backports-created-cloud deprecated label, NOOP label Apr 28, 2025
nickitat added a commit that referenced this pull request Apr 28, 2025
Cherry pick #76185 to 25.4: Parallel hash join threshold
@robot-ch-test-poll robot-ch-test-poll added the pr-backports-created Backport PRs are successfully created, it won't be processed by CI script anymore label Apr 28, 2025
github-merge-queue bot pushed a commit that referenced this pull request Apr 29, 2025
robot-ch-test-poll2 added a commit that referenced this pull request Apr 29, 2025
nickitat added a commit that referenced this pull request Apr 29, 2025
nickitat added a commit that referenced this pull request Apr 30, 2025
nickitat added a commit that referenced this pull request Apr 30, 2025
nickitat added a commit that referenced this pull request Apr 30, 2025
Backport #76185 to 25.4: Parallel hash join threshold
nickitat added a commit that referenced this pull request Apr 30, 2025
Backport #76185 to 25.3: Parallel hash join threshold
@robot-clickhouse robot-clickhouse added the pr-must-backport-synced The `*-must-backport` labels are synced into the cloud Sync PR label Jul 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-backports-created Backport PRs are successfully created, it won't be processed by CI script anymore pr-backports-created-cloud deprecated label, NOOP pr-must-backport-synced The `*-must-backport` labels are synced into the cloud Sync PR pr-performance Pull request with some performance improvements pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants