Skip to main content
Цель этого раздела — на примере распространённых сценариев показать, как использовать различные методы повышения производительности и оптимизации, такие как анализатор, профилирование запросов или избегайте столбцов с типом Nullable, чтобы повысить производительность запросов к ClickHouse.

Как понять производительность запросов

Об оптимизации производительности лучше всего задумываться на этапе проектирования схемы данных, ещё до первой загрузки данных в ClickHouse.  Но будем честны: сложно заранее предсказать, насколько вырастет объём данных и какие запросы будут выполняться.  Если у вас уже есть развертывание с несколькими запросами, которые вы хотите оптимизировать, первый шаг — понять, как они выполняются и почему одни из них отрабатывают за несколько миллисекунд, а другие требуют больше времени. ClickHouse предоставляет широкий набор инструментов, которые помогают понять, как именно выполняется запрос и какие ресурсы при этом потребляются.  В этом разделе мы рассмотрим эти инструменты и способы работы с ними. 

Общие соображения

Чтобы понять производительность запросов, давайте посмотрим, что происходит в ClickHouse при выполнении запроса.  Следующий раздел намеренно упрощён и местами опускает некоторые детали; цель не в том, чтобы утопить вас в подробностях, а в том, чтобы познакомить с базовыми понятиями. Подробнее см. анализатор запросов Если смотреть на это в самом общем виде, то при выполнении запроса в ClickHouse происходит следующее: 
  • Разбор и анализ запроса
Запрос разбирается и анализируется, после чего создаётся общий план выполнения запроса. 
  • Оптимизация запроса
План выполнения запроса оптимизируется, лишние данные отсекаются, а на основе плана запроса строится конвейер выполнения запроса. 
  • Выполнение конвейера запроса
Данные считываются и обрабатываются параллельно. На этом этапе ClickHouse фактически выполняет операции запроса, такие как фильтрация, агрегации и сортировка. 
  • Финальная обработка
Результаты объединяются, сортируются и форматируются в окончательный результат перед отправкой клиенту. На практике выполняется множество оптимизаций, и в этом руководстве мы поговорим о них немного подробнее. Но уже этих основных понятий достаточно, чтобы понять, что происходит «под капотом», когда ClickHouse выполняет запрос.  Имея это общее представление, давайте рассмотрим, какие инструменты предоставляет ClickHouse и как их можно использовать для отслеживания метрик, влияющих на производительность запросов. 

Набор данных

Мы используем реальный пример, чтобы показать, как подходим к оптимизации производительности запросов.  Возьмём датасет NYC Taxi, содержащий данные о поездках на такси в Нью-Йорке. Сначала загрузим датасет NYC Taxi без какой-либо оптимизации. Ниже приведена команда для создания таблицы и вставки данных из S3 бакета. Обратите внимание, что здесь мы намеренно определяем схему автоматически по данным, то есть без оптимизации.
Давайте посмотрим на схему таблицы, автоматически определённую на основе данных.

Найдите медленные запросы

Журнал запросов

По умолчанию ClickHouse собирает и записывает информацию о каждом выполненном запросе в журнал запросов. Эти данные хранятся в таблице system.query_log Для каждого выполненного запроса ClickHouse записывает такую статистику, как время выполнения запроса, количество прочитанных строк и использование ресурсов, например CPU, использование памяти или обращения к файловому кэшу.  Поэтому журнал запросов — хорошее место, чтобы начать исследование медленных запросов. Вы можете легко выявить запросы, выполнение которых занимает много времени, и просмотреть информацию об использовании ресурсов для каждого из них.  Давайте найдём пять самых долгих запросов в нашем наборе данных NYC taxi.
Поле query_duration_ms показывает, сколько времени заняло выполнение конкретного запроса. Из результатов журнала запросов видно, что выполнение первого запроса занимает 2967 мс, и это можно улучшить.  Возможно, вам также стоит определить, какие запросы создают наибольшую нагрузку на систему, посмотрев, какой запрос потребляет больше всего памяти или CPU. 
Давайте выделим найденные нами долгие запросы и повторно выполним их несколько раз, чтобы оценить время ответа.  На этом этапе крайне важно отключить файловый кэш, установив значение параметра enable_filesystem_cache в 0, чтобы повысить воспроизводимость результатов.
Сведем результаты в таблицу для удобства чтения. Давайте чуть лучше разберемся, что именно делают эти запросы. 
  • Запрос 1 вычисляет распределение расстояний для поездок со средней скоростью более 30 миль в час.
  • Запрос 2 определяет количество поездок и их среднюю стоимость по неделям. 
  • Запрос 3 вычисляет среднюю длительность каждой поездки в датасете.
Ни один из этих запросов не выполняет ничего особенно сложного, за исключением первого, который при каждом выполнении вычисляет время поездки на лету. Однако каждый из этих запросов выполняется дольше секунды, что по меркам ClickHouse очень долго. Мы также можем отметить использование памяти этими запросами: примерно 400 МБ на запрос — это довольно много. Кроме того, каждый запрос, по-видимому, читает одно и то же количество строк (то есть 329.04 миллионов). Давайте быстро проверим, сколько строк в этой таблице.
Таблица содержит 329,04 миллиона строк, поэтому каждый запрос полностью сканирует таблицу.

Оператор EXPLAIN

Теперь, когда у нас есть несколько долгих запросов, давайте разберёмся, как они выполняются. Для этого в ClickHouse есть оператор EXPLAIN. Это очень полезный инструмент, который даёт подробное представление обо всех этапах выполнения запроса, не выполняя сам запрос. Хотя для специалиста, не знакомого с ClickHouse, это может выглядеть довольно сложно, этот инструмент остаётся незаменимым для понимания того, как выполняется ваш запрос. В документации есть подробное руководство о том, что такое оператор EXPLAIN и как использовать его для анализа выполнения запросов. Не будем повторять его содержимое, а сосредоточимся на нескольких командах, которые помогут выявить узкие места в производительности выполнения запросов. Explain indexes = 1 Начнём с EXPLAIN indexes = 1, чтобы посмотреть план запроса. План запроса — это дерево, показывающее, как будет выполняться запрос. В нём видно, в каком порядке будут выполняться части запроса. План запроса, возвращаемый оператором EXPLAIN, читается снизу вверх. Попробуем воспользоваться первым из наших длительных запросов.
Вывод довольно прост. Запрос начинается с чтения данных из таблицы nyc_taxi.trips_small_inferred. Затем применяется предложение WHERE, чтобы отфильтровать строки по вычисленным значениям. Отфильтрованные данные подготавливаются для агрегации, после чего вычисляются квантили. Наконец, результат сортируется и выводится. Здесь видно, что первичные ключи не используются, что вполне логично, поскольку при создании таблицы мы их не задали. В результате ClickHouse выполняет полное сканирование таблицы для этого запроса. Explain Pipeline (конвейер выполнения запроса) EXPLAIN Pipeline показывает конкретную стратегию выполнения запроса. Здесь можно увидеть, как ClickHouse в действительности выполнил общий план запроса, рассмотренный ранее.
Здесь можно обратить внимание на количество потоков, задействованных для выполнения запроса: 59 потоков, что свидетельствует о высокой степени параллелизации. Это ускоряет выполнение запроса, которое на менее мощной машине заняло бы больше времени. Количество потоков, работающих параллельно, объясняет высокий объём памяти, потребляемой запросом. В идеале все медленные запросы следует анализировать одинаково, чтобы выявлять излишне сложные планы выполнения запросов и понимать, сколько строк считывает каждый запрос и какие ресурсы при этом потребляются.

Методология

Выявить проблемные запросы в продакшн-развертывании может быть непросто, поскольку в любой момент времени в вашем развертывании ClickHouse, скорее всего, выполняется большое количество запросов.  Если вы знаете, у какого пользователя, в какой базе данных или в каких таблицах возникают проблемы, можно использовать поля user, tables или databases из system.query_logs, чтобы сузить поиск.  Когда вы определите запросы, которые хотите оптимизировать, можно приступать к работе. На этом этапе разработчики часто совершают типичную ошибку: одновременно меняют несколько параметров, проводят разрозненные эксперименты и в итоге получают смешанные результаты, а главное — не понимают, что именно ускорило запрос.  Оптимизация запросов требует системного подхода. Я не говорю о продвинутом бенчмаркинге, но даже простой процесс, позволяющий понять, как ваши изменения влияют на производительность запросов, может дать очень многое.  Начните с выявления медленных запросов в журнале запросов, затем по отдельности изучите возможные улучшения. При тестировании запроса обязательно отключайте файловый кэш. 
ClickHouse использует кэширование для ускорения выполнения запросов на разных этапах. Это полезно для производительности, но при устранении неполадок оно может скрыть потенциальные узкие места IO или неудачную схему таблицы. Поэтому во время тестирования я рекомендую отключать файловый кэш. В продакшне он должен быть включен.
После того как вы определили возможные оптимизации, рекомендуется внедрять их по одной, чтобы лучше отслеживать, как они влияют на производительность. Ниже приведена схема, описывающая общий подход. Наконец, будьте внимательны к выбросам: нередко запрос выполняется медленно либо потому, что пользователь запустил разовый ресурсоемкий запрос, либо потому, что система по другой причине находилась под нагрузкой. Можно выполнить группировку по полю normalized_query_hash, чтобы выявить ресурсоемкие запросы, которые выполняются регулярно. Скорее всего, именно их и стоит исследовать.

Базовая оптимизация

Теперь, когда у нас есть основа для тестирования, можно приступать к оптимизации. Начать лучше всего с того, как хранятся данные. Как и в любой базе данных, чем меньше данных мы читаем, тем быстрее выполняется запрос.  В зависимости от того, как вы организовали приём данных, вы могли воспользоваться возможностями ClickHouse, чтобы определить схему таблицы на основе принимаемых данных. Это очень удобно на старте, но если вы хотите оптимизировать производительность запросов, схему данных нужно пересмотреть так, чтобы она лучше соответствовала вашему сценарию использования.

Nullable

Как описано в документации с рекомендациями, по возможности избегайте столбцов с типом Nullable. Их часто хочется использовать, поскольку они делают механизм ингестии данных более гибким, но это негативно сказывается на производительности, так как каждый раз приходится обрабатывать дополнительный столбец. SQL-запрос, подсчитывающий строки со значением NULL, позволяет легко определить, каким столбцам в ваших таблицах действительно нужен тип Nullable.
У нас есть только два столбца со значениями NULL: mta_tax и payment_type. Остальные поля не должны иметь тип Nullable.

Низкая мощность

Простая оптимизация для строковых типов — эффективнее использовать тип данных LowCardinality. Как описано в документации по низкой мощности, ClickHouse применяет к столбцам LowCardinality словарное кодирование, что значительно повышает производительность запросов.  Простое практическое правило, которое помогает определить, какие столбцы хорошо подходят для LowCardinality: любой столбец, содержащий менее 10 000 уникальных значений, — отличный кандидат. Вы можете использовать следующий SQL-запрос, чтобы найти столбцы с небольшим числом уникальных значений.
Благодаря низкой мощности эти четыре столбца — ratecode_id, pickup_location_id, dropoff_location_id и vendor_id — хорошо подходят для типа данных LowCardinality.

Оптимизируйте тип данных

ClickHouse поддерживает множество типов данных. Чтобы повысить производительность и уменьшить объем данных, занимаемых на диске, выбирайте минимально возможный тип данных, подходящий для вашего сценария использования.  Для числовых значений можно проверить минимальное и максимальное значения в наборе данных, чтобы убедиться, что текущая точность соответствует фактическим данным. 
Для дат выбирайте такую точность, которая соответствует вашим данным и лучше всего подходит для запросов, которые вы собираетесь выполнять.

Примените оптимизации

Давайте создадим новую таблицу с оптимизированной схемой и заново загрузим данные.
Мы снова выполняем запросы, используя новую таблицу, чтобы проверить, есть ли улучшения.  Мы видим улучшения как по времени выполнения запросов, так и по использованию памяти. Благодаря оптимизации схемы данных удалось уменьшить общий объём хранимых данных, что привело к снижению потребления памяти и сокращению времени обработки.  Давайте проверим размер таблиц, чтобы увидеть разницу. 
Новая таблица значительно меньше предыдущей. Мы видим, что она занимает примерно на 34% меньше места на диске (7.38 GiB против 4.89 GiB).

Важность первичных ключей

В ClickHouse первичные ключи работают иначе, чем в большинстве традиционных систем управления базами данных. В таких системах первичные ключи обеспечивают уникальность и целостность данных. Любая попытка вставить повторяющиеся значения первичного ключа отклоняется, а для быстрого поиска обычно создаётся индекс на основе B-дерева или хеша.  В ClickHouse назначение первичного ключа иное: он не обеспечивает уникальность и не служит для поддержания целостности данных. Вместо этого он предназначен для оптимизации производительности запросов. Первичный ключ задаёт порядок хранения данных на диске и реализован в виде разреженного индекса, который хранит указатели на первую строку каждой гранулы.
Гранулы в ClickHouse — это наименьшие единицы данных, считываемые во время выполнения запроса. Они содержат до фиксированного числа строк, определяемого параметром index_granularity; значение по умолчанию — 8192 строк. Гранулы хранятся непрерывно и сортируются по первичному ключу. 
Правильный выбор первичных ключей важен для производительности, и на практике одни и те же данные нередко хранят в разных таблицах, используя разные наборы первичных ключей, чтобы ускорить определённый набор запросов.  Другие возможности ClickHouse, такие как Projection или materialized view, позволяют использовать другой набор первичных ключей для тех же данных. Во второй части этой серии блогов мы рассмотрим это подробнее. 

Выберите первичные ключи

Выбор правильного набора первичных ключей — сложная задача, и, чтобы найти наилучшее сочетание, могут потребоваться компромиссы и эксперименты.  Пока мы будем придерживаться следующих простых рекомендаций: 
  • Используйте поля, которые применяются для фильтрации в большинстве запросов
  • Сначала выбирайте столбцы с меньшей мощностью 
  • Рассмотрите возможность включить в первичный ключ компонент времени, поскольку фильтрация по времени в наборах данных с временными метками встречается довольно часто. 
В нашем случае мы будем экспериментировать со следующими первичными ключами: passenger_count, pickup_datetime и dropoff_datetime Мощность passenger_count невелика (24 уникальных значения), и это поле используется в наших медленных запросах. Мы также добавляем поля с временными метками (pickup_datetime и dropoff_datetime), так как по ним часто выполняется фильтрация. Создайте новую таблицу с этими первичными ключами и повторно загрузите данные.
Затем мы повторно запускаем наши запросы. Мы сводим результаты трёх экспериментов, чтобы оценить улучшения по времени выполнения, числу обработанных строк и потреблению памяти. 
Запрос 1
Прогон 1Прогон 2Прогон 3
Время выполнения1.699 sec1.353 sec0.765 sec
Обработано строк329.04 million329.04 million329.04 million
Пиковое потребление памяти440.24 MiB337.12 MiB444.19 MiB
Запрос 2
Прогон 1Прогон 2Прогон 3
Время выполнения1.419 sec1.171 sec0.248 sec
Обработано строк329.04 million329.04 million41.46 million
Пиковое потребление памяти546.75 MiB531.09 MiB173.50 MiB
Запрос 3
Прогон 1Прогон 2Прогон 3
Время выполнения1.414 sec1.188 sec0.431 sec
Обработано строк329.04 million329.04 million276.99 million
Пиковое потребление памяти451.53 MiB265.05 MiB197.38 MiB
Мы видим заметное улучшение как по времени выполнения, так и по потреблению памяти.  Запрос 2 выигрывает от первичного ключа больше всего. Посмотрим, чем получившийся план запроса отличается от предыдущего.
Благодаря первичному ключу было выбрано лишь подмножество гранул таблицы. Уже одно это заметно повышает производительность запроса, поскольку ClickHouse приходится обрабатывать существенно меньше данных.

Следующие шаги

Надеемся, это руководство помогло вам лучше понять, как исследовать медленные запросы в ClickHouse и как ускорить их выполнение. Если вы хотите глубже разобраться в этой теме, рекомендуем прочитать о анализаторе запросов и профилировании, чтобы лучше понять, как именно ClickHouse выполняет запрос. По мере того как вы будете лучше разбираться в особенностях ClickHouse, рекомендуем также прочитать о ключах партиционирования и индексах пропуска данных, чтобы освоить более продвинутые методы ускорения запросов.
Последнее изменение 23 июля 2026 г.