ClickHouse 25.6 и параллелизм JOIN: что изменилось в планировщике и почему стало быстрее
После обновления аналитических кластеров до ClickHouse 25.6 запросы с широкими JOIN ускорились на 25-40%. Разбираем настройки планировщика и как читать новый EXPLAIN.
ClickHouse 25.6 - улучшение параллелизма при JOIN и новые оптимизации планировщика - октябрь 2025
В октябре обновили несколько аналитических кластеров с 25 LTS до 25.6, и первое, что заметили на следующий день в мониторинге - характерный провал в P95 latency на тяжёлых запросах. Провал в хорошем смысле: запросы стали укладываться быстрее. На кластерах с широкими JOIN это было видно невооружённым глазом.
Что изменилось в 25.6 в части JOIN
Если коротко - команда ClickHouse переработала параллелизацию hash join. До 25.6 при выполнении hash join несколько потоков строили хеш-таблицу правой части по одной: каждый поток брал свой диапазон строк, но фаза финализации таблицы была по сути однопоточной. Это ограничение проявлялось именно на широких JOIN-ах: большая правая часть, много уникальных ключей, сборка хеш-таблицы становилась узким местом.
В 25.6 фаза build хеш-таблицы распараллелена по-настоящему: несколько потоков строят независимые партиции, потом объединяют. Это классический partitioned hash join, который давно есть в аналитических СУБД, но в ClickHouse реализация была другой исторически - оптимизированной под другой профиль нагрузки.
Второе изменение - планировщик запросов получил несколько новых эвристик для выбора стратегии JOIN. Раньше ClickHouse довольно агрессивно переходил к parallel_hash join только при определённых условиях по размеру; теперь эвристика учитывает ещё cardinality оценку правой части и наличие фильтров.
Как мы это увидели в EXPLAIN
На 25 LTS типичный EXPLAIN PIPELINE для тяжёлого JOIN выглядел примерно так: после сканирования правой части появлялась воронка - CreatingSets на одном потоке, потом распараллеливание для probe-фазы. На 25.6 в pipeline появился JoinBuildConcurrently - отдельный узел, который явно показывает параллельную сборку.
Смотреть стоит через EXPLAIN PIPELINE graph=1, compact=0 - вывод стал детальнее, и теперь можно увидеть число потоков на каждом этапе. Если в вашем плане по-прежнему видите однопоточный CreatingSets там, где вы ожидаете параллельную сборку - скорее всего, план выбрал другую стратегию, и нужно разобраться почему.
Настройки, которые стоит проверить
После обновления мы прошлись по нескольким параметрам. Часть из них существовала и раньше, но их влияние теперь другое.
max_threads - базовый параметр, ничего нового, но именно он определяет число партиций в новом параллельном build. Если у вас он выставлен в половину от физических ядер «чтобы не нагружать», на JOIN-нагрузке стоит пересмотреть.
join_algorithm - теперь значение parallel_hash работает шире, чем раньше. Мы раньше не выставляли его явно, полагаясь на автовыбор. После 25.6 в ряде случаев явный parallel_hash дал ещё несколько процентов сверх того, что даёт автовыбор - планировщик консервативен в оценках, и если вы знаете профиль запроса, явная настройка оправдана.
max_bytes_in_join - если правая часть JOIN вылезает за этот лимит, ClickHouse переключается на другую стратегию (обычно к grace_hash или auto). В 25.6 это поведение не изменилось, но с новым параллельным build одни и те же данные умещаются в memory-бюджет эффективнее, потому что партиции строятся компактнее. Несколько наших случаев, где раньше запрос уходил в grace_hash, теперь умещаются в memory без ухудшения производительности.
prefer_localhost_replica и настройки шардирования мы не трогали - это уже другая история, не про планировщик.
Что конкретно у нас изменилось
На одном кластере - витрина событий, аналитика по пользователям, несколько JOIN на справочники размером от сотен тысяч до десятков миллионов строк. Самые тяжёлые запросы давали P95 в районе 8-12 секунд на 25 LTS. После обновления до 25.6 и после ручной установки join_algorithm = parallel_hash на уровне профиля - P95 упал до 5-7 секунд на большинстве запросов. Есть несколько запросов, где улучшение заметнее, есть один, где почти не изменилось - там правая часть JOIN маленькая и параллелизация не даёт эффекта.
На другом кластере - более разнородная нагрузка, там цифры скромнее, но тоже положительные. В целом «25-40% на широких JOIN» - это честное ощущение по нескольким неделям мониторинга после обновления, не cherry-picked замер.
Что стоит сделать при обновлении
Первое: прогоните EXPLAIN PIPELINE на ваших самых тяжёлых JOIN-запросах до и после обновления. Разница в структуре pipeline сразу покажет, применяется ли новая параллелизация или нет.
Второе: проверьте system.query_log после обновления - там есть ProfileEvents с детализацией по JOIN. В 25.6 появились новые счётчики, связанные с параллельным build, которых не было раньше. Это удобная точка для сравнения.
Третье: если у вас настроен join_algorithm = auto и запросы не стали заметно быстрее - попробуйте явно выставить parallel_hash для тяжёлых запросов или профиля аналитических пользователей. Автовыбор планировщика бывает осторожным.
Четвёртое: обновление само по себе прошло штатно, rolling upgrade по одной реплике, ClickHouse 25.6 совместим с 25 LTS по репликации в пределах разумного окна.
Мы продолжаем наблюдать за поведением кластеров под реальной нагрузкой - есть несколько паттернов запросов, где хотим понять границы новых эвристик. Если занимаетесь аналитикой на ClickHouse и хотите разобраться с оптимизацией JOIN-тяжёлых запросов, это часть работы в рамках DWH/BI-проектов.