Как понять производительность запросов
Общие соображения
- Разбор и анализ запроса
- Оптимизация запроса
- Выполнение конвейера запроса
- Финальная обработка
Набор данных
Найдите медленные запросы
Журнал запросов
system.query_log.
Для каждого выполненного запроса ClickHouse записывает такую статистику, как время выполнения запроса, количество прочитанных строк и использование ресурсов, например CPU, использование памяти или обращения к файловому кэшу.
Поэтому журнал запросов — хорошее место, чтобы начать исследование медленных запросов. Вы можете легко выявить запросы, выполнение которых занимает много времени, и просмотреть информацию об использовании ресурсов для каждого из них.
Давайте найдём пять самых долгих запросов в нашем наборе данных NYC taxi.
query_duration_ms показывает, сколько времени заняло выполнение конкретного запроса. Из результатов журнала запросов видно, что выполнение первого запроса занимает 2967 мс, и это можно улучшить.
Возможно, вам также стоит определить, какие запросы создают наибольшую нагрузку на систему, посмотрев, какой запрос потребляет больше всего памяти или CPU.
enable_filesystem_cache в 0, чтобы повысить воспроизводимость результатов.
Давайте чуть лучше разберемся, что именно делают эти запросы.
- Запрос 1 вычисляет распределение расстояний для поездок со средней скоростью более 30 миль в час.
- Запрос 2 определяет количество поездок и их среднюю стоимость по неделям.
- Запрос 3 вычисляет среднюю длительность каждой поездки в датасете.
Оператор EXPLAIN
nyc_taxi.trips_small_inferred. Затем применяется предложение WHERE, чтобы отфильтровать строки по вычисленным значениям. Отфильтрованные данные подготавливаются для агрегации, после чего вычисляются квантили. Наконец, результат сортируется и выводится.
Здесь видно, что первичные ключи не используются, что вполне логично, поскольку при создании таблицы мы их не задали. В результате ClickHouse выполняет полное сканирование таблицы для этого запроса.
Explain Pipeline (конвейер выполнения запроса)
EXPLAIN Pipeline показывает конкретную стратегию выполнения запроса. Здесь можно увидеть, как ClickHouse в действительности выполнил общий план запроса, рассмотренный ранее.
Методология
user, tables или databases из system.query_logs, чтобы сузить поиск.
Когда вы определите запросы, которые хотите оптимизировать, можно приступать к работе. На этом этапе разработчики часто совершают типичную ошибку: одновременно меняют несколько параметров, проводят разрозненные эксперименты и в итоге получают смешанные результаты, а главное — не понимают, что именно ускорило запрос.
Оптимизация запросов требует системного подхода. Я не говорю о продвинутом бенчмаркинге, но даже простой процесс, позволяющий понять, как ваши изменения влияют на производительность запросов, может дать очень многое.
Начните с выявления медленных запросов в журнале запросов, затем по отдельности изучите возможные улучшения. При тестировании запроса обязательно отключайте файловый кэш.
ClickHouse использует кэширование для ускорения выполнения запросов на разных этапах. Это полезно для производительности, но при устранении неполадок оно может скрыть потенциальные узкие места IO или неудачную схему таблицы. Поэтому во время тестирования я рекомендую отключать файловый кэш. В продакшне он должен быть включен.После того как вы определили возможные оптимизации, рекомендуется внедрять их по одной, чтобы лучше отслеживать, как они влияют на производительность. Ниже приведена схема, описывающая общий подход. Наконец, будьте внимательны к выбросам: нередко запрос выполняется медленно либо потому, что пользователь запустил разовый ресурсоемкий запрос, либо потому, что система по другой причине находилась под нагрузкой. Можно выполнить группировку по полю normalized_query_hash, чтобы выявить ресурсоемкие запросы, которые выполняются регулярно. Скорее всего, именно их и стоит исследовать.
Базовая оптимизация
Nullable
mta_tax и payment_type. Остальные поля не должны иметь тип Nullable.
Низкая мощность
ratecode_id, pickup_location_id, dropoff_location_id и vendor_id — хорошо подходят для типа данных LowCardinality.
Оптимизируйте тип данных
Примените оптимизации
Мы видим улучшения как по времени выполнения запросов, так и по использованию памяти. Благодаря оптимизации схемы данных удалось уменьшить общий объём хранимых данных, что привело к снижению потребления памяти и сокращению времени обработки.
Давайте проверим размер таблиц, чтобы увидеть разницу.
Важность первичных ключей
Гранулы в 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 sec | 1.353 sec | 0.765 sec |
| Обработано строк | 329.04 million | 329.04 million | 329.04 million |
| Пиковое потребление памяти | 440.24 MiB | 337.12 MiB | 444.19 MiB |
| Запрос 2 | |||
|---|---|---|---|
| Прогон 1 | Прогон 2 | Прогон 3 | |
| Время выполнения | 1.419 sec | 1.171 sec | 0.248 sec |
| Обработано строк | 329.04 million | 329.04 million | 41.46 million |
| Пиковое потребление памяти | 546.75 MiB | 531.09 MiB | 173.50 MiB |
| Запрос 3 | |||
|---|---|---|---|
| Прогон 1 | Прогон 2 | Прогон 3 | |
| Время выполнения | 1.414 sec | 1.188 sec | 0.431 sec |
| Обработано строк | 329.04 million | 329.04 million | 276.99 million |
| Пиковое потребление памяти | 451.53 MiB | 265.05 MiB | 197.38 MiB |