Когда запросы в ClickHouse выполняются медленно, важно определить узкие места и применить оптимизации. Вот пошаговое руководство по устранению медленных запросов.
1. Выявление медленных запросов с использованием системных таблиц
ClickHouse записывает детали выполнения запросов в таблицу system.query_log
, что помогает отслеживать медленные запросы.
✅ Шаг 1: Найдите медленные запросы
SELECT query, query_duration_ms, read_rows, read_bytes
FROM system.query_log
WHERE type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 10;
📌 Ключевые столбцы для проверки:
query_duration_ms
: Время выполнения запроса.read_rows
: Количество строк, которые были отсканированы.read_bytes
: Количество байт, считанных с диска.
🔹 Цель: Определить запросы, которые сканируют слишком много строк или читают слишком много данных.
2. Анализ плана выполнения запроса
ClickHouse предоставляет команду EXPLAIN для просмотра, как выполняется запрос.
✅ Шаг 2: Используйте EXPLAIN для проверки плана выполнения запроса
EXPLAIN PLAN FOR
SELECT SUM(revenue) FROM sales WHERE event_time >= '2024-01-01';
📌 Что искать в выводе:
- Полное сканирование таблицы? (ReadFromMergeTree без индекса)
- Неэффективная фильтрация? (отсутствует правильный ORDER BY)
- Сканируется слишком много строк? (Проверьте
read_rows
в журнале запросов)
🔹 Цель: Снизить количество ненужных сканирований строк путем оптимизации индексов и фильтрации.
3. Оптимизация проектирования таблицы (ORDER BY, PARTITION BY)
Если EXPLAIN показывает полное сканирование таблицы, схема таблицы может быть неоптимизированной.
✅ Шаг 3: Убедитесь в эффективности ORDER BY
ClickHouse использует разреженные индексы вместо традиционных B-деревьев. ORDER BY
должен соответствовать шаблону запросов.
❌ Плохой пример (Неэффективно для запросов по времени)
CREATE TABLE sales (
id UInt32,
event_time DateTime,
revenue Float64
) ENGINE = MergeTree()
ORDER BY id; -- ❌ Неэффективно для запросов по event_time
✅ Хороший пример (Оптимизировано для запросов по дате)
CREATE TABLE sales (
id UInt32,
event_time DateTime,
revenue Float64
) ENGINE = MergeTree()
ORDER BY (event_time, id); -- ✅ Ускоряет фильтрацию по event_time
📌 Исправление:
- Выбирайте
ORDER BY
в зависимости от того, какие поля часто используются в WHERE. - Если запросы фильтруют по дате, используйте
ORDER BY (event_time, id)
. - Если запросы фильтруют по пользователю, используйте
ORDER BY (user_id, event_time)
.
🔹 Влияние: Ускоряет запросы по дате в 10-100 раз.
4. Использование партиционирования для уменьшения объема сканируемых данных
Если запросы сканируют слишком много данных, партиционирование помогает пропустить ненужные разделы.
✅ Шаг 4: Используйте PARTITION BY
, чтобы избежать полного сканирования таблицы
CREATE TABLE sales (
id UInt32,
event_time DateTime,
revenue Float64
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(event_time) -- ✅ Партиционирование по месяцам
ORDER BY (event_time, id);
📌 Исправление:
- Если запросы фильтруют по
event_time
, используйте партиционирование по месяцам (toYYYYMM(event_time)
). - Если фильтрация по
user_id
, используйте хешированное партиционирование.
🔹 Влияние: Уменьшает время запроса в 100 раз.
5. Оптимизация JOIN-ов
ClickHouse загружает всю правую таблицу в память при выполнении JOIN-а, что может замедлить запросы.
✅ Шаг 5: Используйте словари вместо JOIN-ов
Вместо:
SELECT logs.*, users.name
FROM logs
JOIN users ON logs.user_id = users.id;
Используйте словарь:
CREATE DICTIONARY user_dict
(
id UInt32,
name String
)
PRIMARY KEY id
SOURCE(CLICKHOUSE(TABLE 'users'))
LIFETIME(600);
Затем выполните запрос:
SELECT logs.*, dictGet('user_dict', 'name', logs.user_id) FROM logs;
🔹 Влияние: выполняются в 10-100 раз быстрее.
6. Проверка фрагментации MergeTree (OPTIMIZE TABLE)
ClickHouse хранит данные в частях. Если фрагментов слишком много, чтение становится медленным.
✅ Шаг 6: Оптимизируйте слияние таблиц
OPTIMIZE TABLE sales FINAL;
📌 Исправление:
- Регулярно выполняйте
OPTIMIZE TABLE
, чтобы уменьшить количество фрагментов. - Используйте большие пакеты вставок вместо множества мелких вставок.
🔹 Влияние: Ускоряет запросы за счет уменьшения количества фрагментов.
7. Снижение нагрузки на запросы (GROUP BY, DISTINCT, Агрегации)
Дорогие операции GROUP BY
или DISTINCT
потребляют много памяти.
✅ Шаг 7: Используйте AggregatingMergeTree
для предварительных вычислений
Вместо:
SELECT event_time, count(*) FROM sales GROUP BY event_time;
Создайте материализованное представление для предварительно вычисленных результатов:
CREATE MATERIALIZED VIEW sales_mv
ENGINE = AggregatingMergeTree()
ORDER BY event_time
AS SELECT event_time, count(*) AS event_count FROM sales GROUP BY event_time;
Затем запрос:
SELECT * FROM sales_mv;
🔹 Влияние: Ускоряет запросы с GROUP BY
в 10-100 раз.
8. Оптимизация выполнения запросов с использованием max_threads
По умолчанию ClickHouse использует все доступные ядра процессора, что может перегрузить систему.
✅ Шаг 8: Отрегулируйте max_threads
для запросов с высокой конкурентностью
SET max_threads = 16;
SET max_memory_usage = 10GB;
📌 Исправление: Уменьшите max_threads
на перегруженных кластерах, чтобы избежать перегрузки процессора.
🔹 Влияние: Предотвращает ухудшение производительности при высокой конкурентности.
9. Мониторинг производительности запросов (system.metrics)
ClickHouse предоставляет данные о производительности в реальном времени.
✅ Шаг 9: Проверьте метрики производительности в реальном времени
SELECT metric, value FROM system.metrics WHERE metric IN ('Query', 'ReadRows', 'MemoryUsage');
📌 Исправление:
- Если
ReadRows
слишком высок, оптимизируйте индексы. - Если
MemoryUsage
слишком высок, уменьшите конкурентность запросов.
🔹 Влияние: Выявляет узкие места ресурсов в реальном времени.
10. Проверка использования диска (system.parts)
Если запросы медленные из-за чтения с диска, проверьте активные части данных.
✅ Шаг 10: Проанализируйте использование диска и выполняющиеся запросы
SELECT table, sum(bytes_on_disk) / 1024 / 1024 AS size_MB FROM system.parts GROUP BY table ORDER BY size_MB DESC;
📌 Исправление:
- Оптимизируйте сжатие для больших таблиц (CODEC(ZSTD)).
- Используйте быстрые SSD-диски для ClickHouse.
🔹 Влияние: Снижает узкие места ввода-вывода для больших наборов данных.
🚀 Итоговое резюме: Устранение узких мест производительности запросов
Проблема | Исправление | Влияние |
---|---|---|
Полное сканирование таблицы | Оптимизировать ORDER BY и PARTITION BY | 🔥 Запросы в 10-100 раз быстрее |
Медленные JOIN-ы | Использовать словари | 🔥 Поиск в 100 раз быстрее |
Слишком много мелких частей | Выполнить OPTIMIZE TABLE FINAL; | 🔥 Меньше накладных расходов на слияние |
Высокое использование памяти (GROUP BY) | Использовать материализованные представления | 🔥 Агрегации в 10-100 раз быстрее |
Перегрузка процессора | Установить max_threads = 16; | 🔥 Более стабильная производительность |
Медленная работа с диском | Использовать SSD-диски, сжатие | 🔥 Снижение задержки чтения |
🚀 Заключение:
- Оптимизация схемы данных — ключ к производительности ClickHouse.
- Избегайте сканирования лишних строк с помощью
ORDER BY
,PARTITION BY
и эффективных запросов. - Мониторьте метрики в реальном времени, чтобы выявлять узкие места.