- Таблицы изменяются редко или допустимо пакетное обновление.
- Связи не являются многие-ко-многим и не имеют чрезмерно высокой мощности.
- В запросах будет использоваться только ограниченное подмножество столбцов, то есть некоторые столбцы можно исключить из денормализации.
- У вас есть возможность перенести обработку из ClickHouse во внешние системы, такие как Flink, где можно выполнять обогащение или уплощение в реальном времени.
Когда требуются JOIN
- Избегайте декартовых произведений: если значение в левой части соответствует нескольким значениям в правой, JOIN вернет несколько строк — так называемое декартово произведение. Если в вашем сценарии не нужны все совпадения из правой части и достаточно любого одного, можно использовать JOIN
ANY(например,LEFT ANY JOIN). Они работают быстрее и потребляют меньше памяти, чем обычные JOIN. - Уменьшайте размер таблиц, участвующих в JOIN: время выполнения и потребление памяти у JOIN растут пропорционально размерам левой и правой таблиц. Чтобы уменьшить объем данных, обрабатываемых в JOIN, добавьте дополнительные условия фильтрации в секции
WHEREилиJOIN ONзапроса. ClickHouse проталкивает условия фильтрации как можно глубже в план запроса, обычно до JOIN. Если фильтры по какой-либо причине не проталкиваются автоматически, перепишите одну из сторон JOIN как подзапрос, чтобы принудительно применить pushdown. - Используйте direct JOIN через словари, если это уместно: стандартные JOIN в ClickHouse выполняются в две фазы: на фазе build правая часть проходится для построения хеш-таблицы, затем на фазе probe левая часть проходится для поиска подходящих строк через lookups в хеш-таблице. Если правая часть — это словарь или другой движок таблицы с характеристиками ключ-значение (например, EmbeddedRocksDB или движок таблицы Join), ClickHouse может использовать алгоритм JOIN “direct”, который фактически устраняет необходимость строить хеш-таблицу и ускоряет обработку запросов. Это работает для JOIN
INNERиLEFT OUTERи предпочтительно для аналитических рабочих нагрузок в реальном времени. - Используйте сортировку таблиц для JOIN: каждая таблица в ClickHouse сортируется по столбцам primary key. Эту сортировку можно использовать с помощью так называемых алгоритмов sort-merge JOIN, таких как
full_sorting_mergeиpartial_merge. В отличие от стандартных алгоритмов JOIN на основе хеш-таблиц (см. ниже:parallel_hash,hash,grace_hash), алгоритмы sort-merge JOIN сначала сортируют, а затем объединяют обе таблицы. Если запрос выполняет JOIN обеих таблиц по соответствующим столбцам их primary key, sort-merge использует оптимизацию, которая пропускает этап сортировки, экономя время обработки и снижая накладные расходы. - Избегайте JOIN со spilling на диск: промежуточные состояния JOIN (например, хеш-таблицы) могут стать настолько большими, что перестанут помещаться в оперативную память. В такой ситуации ClickHouse по умолчанию вернет ошибку нехватки памяти. Некоторые алгоритмы JOIN (см. ниже), например
grace_hash,partial_mergeиfull_sorting_merge, могут выгружать промежуточные состояния на диск и продолжать выполнение запроса. Тем не менее эти алгоритмы JOIN следует использовать с осторожностью, поскольку доступ к диску может значительно замедлить обработку JOIN. Вместо этого мы рекомендуем оптимизировать JOIN-запрос другими способами, чтобы уменьшить размер промежуточных состояний. - Значения по умолчанию как маркеры отсутствия совпадения во внешних JOIN: левый/правый/full outer join включают все значения из левой/правой/обеих таблиц. Если для некоторого значения в другой таблице не найден партнер JOIN, ClickHouse заменяет его специальным маркером. Стандарт SQL требует, чтобы базы данных использовали в качестве такого маркера NULL. В ClickHouse для этого требуется обернуть результирующий столбец в Nullable, что создает дополнительную нагрузку на память и производительность. В качестве альтернативы можно задать параметр
join_use_nulls = 0и использовать значение по умолчанию типа данных результирующего столбца в качестве маркера.
Используйте словари с осторожностьюПри использовании словарей для JOIN в ClickHouse важно понимать, что словари по своей природе не допускают дублирования ключей. При загрузке данных повторяющиеся ключи незаметно дедуплицируются: для каждого ключа сохраняется только последнее загруженное значение. Поэтому словари хорошо подходят для связей «один к одному» и «многие к одному», где нужно только самое актуальное или авторитетное значение. Однако использование словаря для связей «один ко многим» или «многие ко многим» (например, при соединении ролей с актёрами, когда у одного актёра может быть несколько ролей) приведёт к незаметной потере данных, поскольку все совпадающие строки, кроме одной, будут отброшены. В результате словари не подходят для сценариев, где требуется полная реляционная целостность при наличии нескольких совпадений. Подробнее о том, когда словари полезны (и когда нет), см. в разделе Лучшие практики работы со словарями.
Выбор правильного алгоритма JOIN
- Parallel Hash JOIN (default): Быстрый для небольших и средних правых таблиц, помещающихся в память.
- Direct JOIN: Идеален при использовании словарей (или других движков таблиц с характеристиками ключ-значение) с
INNERилиLEFT ANY JOIN— самый быстрый метод для точечных обращений, поскольку не требует построения хеш-таблицы. - Full Sorting Merge JOIN: Эффективен, когда обе таблицы отсортированы по ключу JOIN.
- Partial Merge JOIN: Минимизирует потребление памяти, но работает медленнее — лучше всего подходит для JOIN больших таблиц при ограниченном объеме памяти.
- Grace Hash JOIN: Гибкий алгоритм с настраиваемым потреблением памяти, хорошо подходит для больших датасетов с регулируемыми характеристиками производительности.
Каждый алгоритм поддерживает разные типы JOIN. Полный список поддерживаемых типов JOIN для каждого алгоритма можно найти здесь.
join_algorithm = 'auto' (значение по умолчанию), либо явно задать его в зависимости от вашей рабочей нагрузки. Если вам нужно выбрать алгоритм JOIN для оптимизации производительности или затрат памяти, мы рекомендуем это руководство.
Для оптимальной производительности:
- Сводите количество JOIN к минимуму в рабочих нагрузках с высокими требованиями к производительности.
- Избегайте более 3–4 JOIN в одном запросе.
- Проводите бенчмарк разных алгоритмов на реальных данных — производительность зависит от распределения ключей JOIN и объема данных.