join_algorithm that executes a JOIN, and the block nested loop fallback. The diagrams use max_threads = 2 to keep the illustrations readable.
direct
The direct algorithm performs a lookup in the right-hand side for each row of the left-hand side. The right-hand side must be a storage that supports low-latency key-value lookups. ClickHouse streams the left-hand side in parallel and joins each block by looking up its keys in that storage. There is no build phase, because the data of the right-hand side is already stored in a key-value structure.
The following right-hand sides qualify:
- Dictionaries, except dictionaries with a range key.
- Tables with the
EmbeddedRocksDB,KeeperMap, orRedisengine. MergeTreetables, since 25.12, only whenjoin_algorithmis set todirectas the single value. ClickHouse pushes the JOIN key filter of each left-hand block into a read of the right-hand table. This is efficient when the key can use the primary key index of the right-hand table. Otherwise, ClickHouse scans the right-hand table for each block of the left-hand side.
Join table engine is also read from a prepared in-memory structure without a build phase. This case does not depend on join_algorithm: ClickHouse always uses the hash table that the Join table stores, and EXPLAIN reports it as FilledJoin with Algorithm: HashJoin. In ClickHouse Cloud, the Join table engine is implemented as SharedJoin with a MergeTree backing table.
The direct algorithm is used when all of the following conditions are met:
- The JOIN is an
INNERJOIN withALLstrictness, or aLEFTJOIN withALL,ANY,SEMI, orANTIstrictness. ForMergeTreetables,ANYstrictness is not supported. - The
ONclause contains exactly one equality condition between one column of each side and no other conditions. - The right-hand column is the single-column key of the storage, and its type matches the key type, ignoring
NullableandLowCardinality.
join_algorithm.
Memory and spilling. The query does not build a data structure for the right-hand side. The memory that holds a dictionary is accounted to the dictionary, not to the query, and is reported in the bytes_allocated column of system.dictionaries. The algorithm does not spill to disk.
Limitations. The algorithm supports only a single equality key and does not support RIGHT, FULL, or ASOF JOINs. Filters are not pushed down into the right-hand side.
hash
The hash algorithm reads the complete right-hand side into memory and builds one in-memory hash table from it. It then streams the left-hand side in parallel and joins each row by looking up its key in the hash table. ClickHouse chooses the hash table implementation from the types of the JOIN key columns and the JOIN strictness. The hash table is filled by a single thread.
When it is used. The hash algorithm is the most general implementation. It supports every combination of JOIN type and strictness that ClickHouse implements, with ASOF limited to INNER and LEFT JOINs. It is the only algorithm that supports multiple JOIN keys combined with OR in the ON clause. The auto value supports such JOINs because it executes them with hash. Together with parallel_hash and grace_hash, it also supports conditions that compare columns of both tables in addition to an equality key. With the default join_algorithm, hash is used when parallel_hash does not apply, or when the right-hand side is estimated to have fewer rows than parallel_hash_join_threshold.
Memory and spilling. The hash table and the rows of the right-hand side must fit into memory. Memory use can exceed the uncompressed size of the right-hand side, because the hash table is resized in steps and is not always full. When a spill threshold is set, the JOIN starts in memory and switches to the grace_hash algorithm once the right-hand side exceeds the threshold. See JOIN memory management and spilling.
Limitations. Because the build phase runs in a single thread, it can dominate the query time when the right-hand side is large.
parallel_hash
The parallel_hash algorithm is a variant of hash that builds several hash tables concurrently. It splits the rows of the right-hand side into buckets by applying a hash function to the JOIN key, and fills one hash table per bucket in parallel. The number of hash tables is determined by max_threads. During the probe phase, the same hash function routes each row of the left-hand side to the hash table that can contain its matches.
When it is used. When hash is also listed and an estimate of the right-hand side is available, ClickHouse uses parallel_hash only if the estimate is at least parallel_hash_join_threshold rows. It supports INNER, LEFT, RIGHT, and FULL JOINs with ALL, ANY, SEMI, and ANTI strictness. Since 26.6, it also supports ASOF JOINs. If parallel_hash cannot execute a JOIN, for example because the right-hand side is a dictionary that does not qualify for direct, ClickHouse executes the JOIN with the hash implementation. A JOIN with OR in the ON clause requires hash or auto to be listed in join_algorithm.
Memory and spilling. It usually uses more memory than hash, because it allocates several hash tables. It spills to disk in the same way as hash.
Limitations. It does not support multiple JOIN keys combined with OR or a right-hand side with a special storage.
grace_hash
The grace_hash algorithm is a hash JOIN that does not require the right-hand side to fit into memory. It runs in two phases:
- ClickHouse splits the rows of the right-hand side into buckets by a hash of the JOIN key. It builds an in-memory hash table for the first bucket and writes the other buckets to temporary storage. It splits the left-hand side with the same hash function, joins the rows of the first bucket immediately, and writes the other rows to temporary storage.
- ClickHouse processes the remaining buckets one at a time. For each bucket, it builds a hash table from the right-hand rows and joins the left-hand rows of the same bucket.
grace_hash_join_initial_buckets, rounded up to a power of two. If the in-memory hash table exceeds the spill threshold, ClickHouse doubles the number of buckets and moves the rows that no longer belong to the current bucket. The number of buckets cannot exceed grace_hash_join_max_buckets.
When it is used. The hash and parallel_hash algorithms switch to grace_hash automatically when the right-hand side exceeds the spill threshold. Selecting grace_hash explicitly makes the JOIN partition the right-hand side from the first block, without an in-memory phase. The algorithm supports INNER, LEFT, RIGHT, and FULL JOINs with ALL, ANY, SEMI, and ANTI strictness.
Memory and spilling. Memory use is bounded by the spill threshold. A larger initial number of buckets reduces memory use and increases execution time. The algorithm requires a non-zero spill threshold and configured temporary storage. Without a threshold, ClickHouse skips grace_hash when other algorithms are listed in join_algorithm, and rejects the query when grace_hash is the only value.
Limitations. It does not support ASOF JOINs or multiple JOIN keys combined with OR. Reading and writing temporary files makes it slower than an in-memory hash JOIN.
full_sorting_merge
The full_sorting_merge algorithm is a sort-merge JOIN. ClickHouse sorts both inputs by the JOIN key and identifies matches by an interleaved scan of the two sorted streams. Sorting runs in memory or, for large inputs, as an external sort on disk. If an input is read in the order of the JOIN key, for example because the key is a prefix of the sorting key of a MergeTree table, ClickHouse skips the sort for that input.
When it is used. It is appropriate when one or both tables are physically ordered by the JOIN key, or when the inputs are too large for an in-memory hash table. It supports INNER, LEFT, RIGHT, and FULL JOINs with ALL strictness, INNER, LEFT, and RIGHT JOINs with ANY strictness, and INNER and LEFT JOINs with ASOF strictness.
The merge stage runs in a single stream. Two options parallelize it:
query_plan_join_shard_by_pk_rangessplits a JOIN of twoMergeTreetables into independent merge JOINs over primary-key ranges when the JOIN keys contain a prefix of the primary key of both tables.- The
parallel_full_sorting_mergevalue ofjoin_algorithmshards the inputs by a hash of the JOIN key and runs one merge JOIN per shard. The result is not ordered. The setting reference lists the cases in which a singlefull_sorting_mergeJOIN runs instead.
max_rows_in_set_to_optimize_join setting can filter each input by the set of JOIN keys of the other input before sorting. This filter prevents the read-in-order optimization.
Memory and spilling. Memory use is determined by the sort. When both inputs are read in key order, only a few blocks are in memory at a time. Otherwise, the sort spills to disk according to max_bytes_before_external_sort and max_bytes_ratio_before_external_sort.
Limitations. It does not support SEMI or ANTI strictness, multiple JOIN keys combined with OR, conditions in the ON clause other than equality keys, a right-hand side with a special storage, or USING together with join_use_nulls. Listing it in join_algorithm changes query planning even when another algorithm executes the JOIN. See join_algorithm.
partial_merge
The partial_merge algorithm is a variant of the sort-merge JOIN in which only the right-hand side is fully sorted. ClickHouse splits the right-hand side into blocks of up to partial_merge_join_rows_in_right_blocks rows, sorts each block by the JOIN key, and creates a min-max index for each block. It then sorts each block of the left-hand side in memory and merges it with the right-hand blocks. The min-max index skips right-hand blocks that cannot contain matches.
When it is used. It is designed for the lowest memory use when large tables are joined, at the cost of execution speed. It is most efficient when the physical row order of the left-hand side matches the JOIN key order, because the min-max index then skips most right-hand blocks. It is least efficient when each left-hand block contains a wide range of key values. The auto value of join_algorithm can switch to partial_merge at runtime. The algorithm supports INNER, LEFT, RIGHT, and FULL JOINs with ALL strictness, INNER and LEFT JOINs with ANY strictness, and LEFT JOINs with SEMI strictness.
Memory and spilling. ClickHouse keeps the sorted right-hand blocks in memory until max_rows_in_join or max_bytes_in_join is reached. If neither is set, default_max_bytes_in_join applies. After that, ClickHouse writes the sorted blocks and their indexes to temporary storage. join_on_disk_max_files_to_merge limits the number of files that are merged in parallel.
Limitations. RIGHT and FULL JOINs are supported only with ALL strictness. ANTI and ASOF strictness, multiple JOIN keys combined with OR, and conditions that compare columns of both tables are not supported.
ie_join
The ie_join algorithm implements the sort-based IEJoin algorithm for a JOIN whose ON clause contains two inequality comparisons, <, <=, >, or >=, between expressions of the joined tables. ClickHouse sorts both inputs by the expressions of the two comparisons and finds the matching pairs without comparing every pair of rows. The algorithm is available since 26.8.
When it is used. Its position in join_algorithm sets its priority:
- Listed after other algorithms, it is used only when the
ONclause contains no equality condition between the tables. - Listed first, it is used whenever the
ONclause contains two inequality conditions. Other conditions, including equality conditions, are applied as a filter over the JOIN result or as a residual condition inside the operator.
ON clause contains more than two eligible inequality conditions, ClickHouse chooses the two key conditions by their estimated selectivity from column statistics. Without statistics, it uses the first two in syntax order. The algorithm supports INNER, LEFT, RIGHT, and FULL JOINs with ALL strictness, and LEFT and RIGHT JOINs with SEMI and ANTI strictness.
Memory and spilling. Both inputs are accumulated in memory before joining, and the hard limits apply to both sides together. The algorithm does not spill to disk.
Limitations. The JOIN operator runs in a single thread. Only the sorts of the inputs run in parallel. ANY and ASOF strictness are not supported.
Block nested loop fallback
The block nested loop JOIN executes a JOIN whoseON clause contains no equality condition between the tables and that no other algorithm can execute. ClickHouse materializes the right-hand side and evaluates the ON condition on every pair of a left-hand row and a right-hand row. It is not a value of join_algorithm. It is the last resort of JOIN planning.
When it is used. It applies to an ON clause that is an arbitrary boolean expression over columns of both tables, such as a range check or a function call, when ie_join does not apply. When hash is listed in join_algorithm, an INNER JOIN with ALL strictness is executed as a CROSS JOIN with the condition as a filter instead. The fallback is available since 26.10. The allow_block_nested_loop_join setting controls whether it can be used. When the fallback is not allowed, such a query is rejected with INVALID_JOIN_ON_EXPRESSION. It supports every JOIN type and strictness except ASOF, PASTE, and ANY FULL.
Memory and spilling. Memory is bounded by the materialized right-hand side and does not grow with the size of the result. The right-hand blocks are compressed in memory and written to disk after the spill threshold is reached. The hard limits apply to the right-hand side, including spilled blocks.
Limitations. The cost grows with the product of the row counts of both tables. It is orders of magnitude more expensive than a hash JOIN on the same data. For details on strictness semantics, see JOIN with an arbitrary ON condition.
Supported JOIN types per algorithm
The following table lists the JOIN types that each algorithm supports for each strictness value. It is derived from the support checks in the ClickHouse source code. An empty cell means that the strictness is not supported.
The following rules also apply:
ANY FULLJOINs are rejected before an algorithm is selected, for every algorithm.ASOFJOINs are supported only byhash,parallel_hashsince 26.6, andfull_sorting_merge.parallel_full_sorting_mergeexecutes anASOFJOIN as a singlefull_sorting_mergeJOIN.partial_mergesupportsRIGHTandFULLJOINs only withALLstrictness.- The block nested loop fallback does not support
ASOF,PASTE, orANY FULLJOINs. - Only
hashsupports multiple JOIN keys combined withORin theONclause. Theautovalue executes such JOINs withhash. - Only
hash,parallel_hash, andgrace_hashsupport an equality key combined with a condition that compares columns of both tables. - The
directalgorithm applies only to the right-hand sides listed indirect. ForMergeTreetables, it does not supportANYstrictness. CROSS JOIN, JOINs with a constantONcondition, andPASTE JOINhave dedicated implementations that do not depend onjoin_algorithm.
Choosing a JOIN algorithm
The chart above shows the relative memory use and execution time of six JOIN algorithms. The positions are qualitative, and the actual values depend on the data size, the key types, and the distribution of key values. The chart does not includeie_join, parallel_full_sorting_merge, or the block nested loop fallback.
Optimizing JOIN performance
The best-practices page contains a decision tree for choosing the fastest algorithm for a workload. See Choosing the optimal JOIN algorithm.The join_algorithm setting
The join_algorithm setting accepts a comma-separated list of algorithms. For the default value and all accepted values, see the join_algorithm reference.
ClickHouse tries the listed algorithms in order and selects the first one that supports the JOIN type, the strictness, the ON condition, and the storage of the right-hand side. If no listed algorithm applies, the query fails with an error that lists the enabled algorithms. The following cases do not depend on the order of the list:
- A right-hand side with the
Jointable engine is always joined with the hash table that the table stores. CROSS JOIN, JOINs with a constantONcondition, andPASTE JOINuse dedicated implementations.- An
ONclause without an equality condition between the tables is handled byie_joinor by the block nested loop fallback.
parallel_hash and hash run as a spilling hash JOIN that switches to grace_hash when the right-hand side exceeds the threshold. See spill thresholds.
The auto value
With join_algorithm = 'auto', ClickHouse selects the implementation as follows:
- If a spill threshold is in effect, temporary storage is configured, and
grace_hashsupports the JOIN, ClickHouse uses a hash JOIN that switches tograce_hashwhen the right-hand side exceeds the threshold. - Otherwise, if
partial_mergesupports the JOIN, ClickHouse starts withhashand switches topartial_mergewhen the right-hand side reaches a hard limit. The rows that are already in the hash table are moved to thepartial_mergeJOIN. - Otherwise, ClickHouse uses
hash.
EXPLAIN output, the second case appears as Algorithm: JoinSwitcher.