Решение проблем ClickHouse
Частые ошибки при работе с ClickHouse — симптомы, причины и пошаговые решения.
Область
Категория
Симптомы
- INSERT завершается ошибкой 'Too many parts' при превышении порога parts_to_delay_insert (обычно 300-500 активных частей)
- Записи в таблицу замедляются или блокируются — ClickHouse искусственно задерживает INSERT, ожидая слияний
- Постоянная активность фоновых слияний в system.merges, но счётчик частей не уменьшается
- Метрика system.metrics.BackgroundMergesAndMutationsPoolTask близка к максимуму пула слияний
Причина
Слишком частые INSERT-операции без батчинга или async_insert создают новый кусок данных при каждой вставке. ClickHouse не успевает сливать куски быстрее, чем они создаются, и число активных кусков превышает порог parts_to_delay_insert.
Решение
- Включите async_insert=1 на уровне пользователя или сессии: INSERT с накоплением в буфер и автоматическим батчингом сокращает число кусков в десятки раз
- Увеличьте min_insert_block_size_rows (до 1 000 000+) и min_insert_block_size_bytes (до 256 МБ) для формирования более крупных кусков при каждой вставке
- Проверьте parts_to_delay_insert и parts_to_throw_insert в config.xml — при необходимости временно увеличьте порог, но устраняйте причину, а не симптом
- Консолидируйте клиентские INSERT в батчи по 10 000 — 100 000 строк на уровне приложения, избегая одиночных INSERT per event
Связанные уроки:
Симптомы
- Запрос прерывается с кодом ошибки 241 и сообщением о превышении max_memory_usage
- Запрос работает на малых данных, но падает при полном датасете или сложных GROUP BY/JOIN
- В system.query_log видно поле memory_usage, близкое к лимиту, непосредственно перед исключением
- Повторное выполнение того же запроса с LIMIT даёт корректный результат — подтверждает нехватку памяти
Причина
Запрос превысил лимит max_memory_usage (по умолчанию 10 ГБ или настройка сервера). Чаще всего причина — агрегация или сортировка большого числа уникальных ключей, которые целиком помещаются в хеш-таблицу в оперативной памяти.
Решение
- Включите внешнюю агрегацию: SET max_bytes_before_external_group_by = 8000000000 — ClickHouse будет сбрасывать промежуточные результаты на диск при нехватке памяти
- Включите внешнюю сортировку: SET max_bytes_before_external_sort = 8000000000 для ORDER BY на больших данных
- Увеличьте max_memory_usage для конкретного пользователя или запроса, если сервер располагает достаточной RAM
- Перепишите запрос с использованием approx_count_distinct() или quantileTDigest() вместо точных агрегатов там, где допускается приближённый результат
Связанные уроки:
Симптомы
- INSERT с insert_quorum > 1 завершается ошибкой — недостаточно живых реплик для подтверждения записи
- Запись полностью блокируется, пока не восстановится нужное число реплик
- system.replicas показывает, что часть реплик offline или значительно отстают
- Ошибка возникает при плановом обслуживании, если отключено более одной реплики одновременно
Причина
Для завершения кворумной вставки (insert_quorum) требуется подтверждение от заданного числа реплик, но часть реплик недоступна (упала, перезапускается, имеет сетевые проблемы). ClickHouse не завершает INSERT, пока не достигнут кворум.
Решение
- Убедитесь, что не более N-quorum реплик недоступны одновременно — при обслуживании отключайте серверы поочерёдно
- Проверьте состояние реплик в system.replicas — поля is_readonly, is_leader, queue_size помогут найти проблемную реплику
- Временно уменьшите insert_quorum до 1 при экстренной необходимости продолжить вставку (с пониманием рисков согласованности)
- Настройте insert_quorum_timeout достаточно большим, чтобы дать медленной реплике время подтвердить запись
Связанные уроки:
Симптомы
- Сервер не может запустить реплику таблицы после перезапуска — выдаёт REPLICA_IS_ALREADY_ACTIVE
- В system.replicas видна реплика в состоянии active, хотя сервер только что поднялся
- Устаревшая сессия Keeper удерживает ephemeral-узел реплики даже после остановки ClickHouse
- Ручное обращение к system.replicas или DETACH/ATTACH TABLE временно решает проблему
Причина
Сессия ClickHouse Keeper (или ZooKeeper) не была корректно закрыта при предыдущей остановке сервера — ephemeral-узел /replicas/{replica}/is_active ещё существует в Keeper. При новом старте ClickHouse видит, что узел занят, и отказывается его захватить.
Решение
- Подождите истечения session_timeout_ms сессии Keeper (по умолчанию 10 000 мс) — узел удалится автоматически, и реплика активируется
- Убедитесь, что ClickHouse корректно останавливается командой systemctl stop — не kill -9, который оставляет сессии открытыми
- Проверьте логи Keeper на предмет split-brain или потери кворума, которые могут задерживать удаление ephemeral-узлов
- В крайнем случае выполните SYSTEM RESTART REPLICA table_name для принудительного сброса состояния реплики
Связанные уроки:
Симптомы
- INSERT или слияние прерывается с NOT_ENOUGH_SPACE — диск или том хранилища заполнен
- TTL MOVE не переносит старые данные на холодный уровень хотя настроен
- df -h показывает 95%+ утилизацию диска с данными ClickHouse
- Новые части не создаются, background merges останавливаются
Причина
Диск исчерпал свободное место — ClickHouse не может записать новые куски или временные файлы слияний. TTL MOVE мог не сработать из-за неправильно сконфигурированной storage policy: нет маршрута перемещения, недоступен S3 endpoint или превышен размер буферной очереди.
Решение
- Немедленно освободите место: удалите ненужные таблицы (DROP TABLE), используйте TRUNCATE или удалите старые партиции через ALTER TABLE ... DROP PARTITION
- Проверьте storage policy в config.xml или storage_policies.xml — убедитесь, что volume cold правильно указывает на другой диск или S3
- Запустите TTL вручную: ALTER TABLE t MATERIALIZE TTL для немедленного применения правил TTL и перемещения старых данных
- Настройте мониторинг disk_usage_bytes в системных таблицах или через Prometheus endpoint (порт 9363) с алертом при 80%
Связанные уроки:
Симптомы
- SELECT из таблицы завершается ошибкой контрольной суммы или ошибкой чтения inode для конкретного куска
- DESCRIBE TABLE и DDL-операции работают, но чтение данных из проблемного куска невозможно
- В логах ClickHouse видны сообщения 'Renaming broken part' — сервер перемещает повреждённый кусок в detached/
- fsck или smartctl сообщают о ошибках на диске, либо куски находятся на диске с ненулевым reallocated_sectors
Причина
Файлы куска данных повреждены на диске: аппаратный сбой (битый сектор, плохой RAID), аварийное отключение питания без flush на диск, или повреждение файловой системы. ClickHouse хранит контрольные суммы для каждого куска и при несоответствии считает кусок неисправным.
Решение
- Проверьте директорию detached/ для таблицы — ClickHouse автоматически изолирует повреждённые куски туда
- Если есть реплики, выполните SYSTEM SYNC REPLICA table_name — реплика скачает недостающий кусок с другого сервера
- При отсутствии реплик: выполните SELECT из backup или восстановите из снапшота, затем вставьте данные вручную; потеря данных в повреждённом куске неизбежна
- Проверьте состояние дисков с помощью smartctl -a /dev/sdX и при необходимости замените диск до создания новых реплик
Связанные уроки:
Симптомы
- Запросы с dictGet() завершаются ошибкой — словарь не был загружен или не может обновиться
- system.dictionaries показывает is_loaded=0 и status='FAILED' с текстом ошибки подключения к источнику
- ClickHouse периодически пытается перезагрузить словарь, но каждая попытка завершается неудачей
- Ошибка возникла после изменения хоста/порта источника или ротации credentials
Причина
Источник данных словаря (MySQL, PostgreSQL, HTTP, ClickHouse remote table) недоступен: изменился хост, порт или учётные данные, источник перезапускается, или сетевые правила блокируют соединение из ClickHouse.
Решение
- Проверьте доступность источника напрямую с сервера ClickHouse: curl, telnet или clickhouse-client к удалённому ClickHouse
- Обновите настройки словаря в XML или DDL (CREATE DICTIONARY) и выполните SYSTEM RELOAD DICTIONARY dict_name
- Используйте Named Collections для хранения credentials словарей отдельно от DDL — упрощает ротацию учётных данных без изменения схемы
- Настройте lifetime min/max в словаре — при ошибке загрузки ClickHouse продолжает использовать кешированные данные до истечения max lifetime
Связанные уроки:
Симптомы
- INSERT в реплицированную таблицу завершается ошибкой 'Table is in readonly mode'
- DDL-операции и мутации тоже отклоняются — таблица принимает только SELECT
- system.replicas показывает is_readonly=1 для данной реплики
- Проблема возникла после сетевого сбоя, перезапуска Keeper или длительного простоя сервера
Причина
Реплика потеряла соединение с ClickHouse Keeper или сессия истекла. Без доступа к Keeper таблица не может координировать репликацию и переходит в режим readonly, чтобы не допустить несогласованных данных.
Решение
- Проверьте доступность Keeper с сервера ClickHouse: clickhouse-keeper-client -h keeper_host -p 9181 или проверьте логи на connectivity errors
- Убедитесь, что сессионный таймаут Keeper достаточно велик для реальных задержек сети: увеличьте zookeeper_session_timeout_ms в config.xml
- Дождитесь автоматического восстановления после нормализации соединения — реплика выйдет из readonly автоматически
- Если readonly сохраняется после восстановления Keeper — выполните SYSTEM RESTART REPLICA table_name
Связанные уроки:
Симптомы
- Запрос прерывается с превышением max_rows_to_read до получения результата
- В system.query_log видно read_rows близкое к лимиту в момент прерывания запроса
- Запрос работает для небольших таблиц, но падает на production датасете
- Ошибка является преднамеренной — в production включён query governance для защиты кластера от тяжёлых scan
Причина
Настройка max_rows_to_read ограничивает число строк, которые запрос может прочитать с диска — механизм защиты production кластеров от несанкционированных full-scan запросов. Запрос читает больше строк, чем разрешено профилем пользователя или сессионной настройкой.
Решение
- Добавьте фильтр по первичному ключу WHERE в запрос — ClickHouse оптимизирует чтение через гранулы и читает только нужные части
- Используйте skip-индексы или проекции для сокращения объёма читаемых данных без изменения max_rows_to_read
- Если запрос действительно требует чтения большого объёма данных — увеличьте лимит для конкретного пользователя или используйте SETTINGS max_rows_to_read=0 для разового обхода
- Настройте system.query_log для мониторинга тяжёлых запросов и выявления кандидатов на оптимизацию
Связанные уроки:
Симптомы
- INSERT в базовую таблицу зависает или завершается ошибкой циклической зависимости
- Создание нового материализованного представления завершается успешно, но вставки в зависимые таблицы перестают работать
- Ошибка сложно воспроизводима — проявляется только при вставке в таблицу, находящуюся в начале цепочки
- system.tables показывает несколько MV с взаимно зависимыми target-таблицами
Причина
Создана цепочка материализованных представлений, где MV A пишет в таблицу B, из которой MV C читает и пишет обратно в таблицу A. ClickHouse выполняет MV-триггеры синхронно в рамках INSERT, что приводит к циклическому вызову и deadlock или Stack Overflow.
Решение
- Спроектируйте граф MV как DAG (направленный ациклический граф) без обратных рёбер — каждая целевая таблица должна быть только листом, не источником в той же цепочке
- Используйте промежуточную таблицу-буфер с другим именем для разрыва цикла
- Выполните DETACH TABLE / DROP VIEW для проблемного MV и перепроектируйте цепочку
- Документируйте зависимости всех MV в команде — циклические зависимости легче предотвратить на этапе проектирования
Связанные уроки:
Симптомы
- Запрос с -State или -Merge комбинатором падает с ошибкой 'Unknown aggregate function'
- Таблица AggregatingMergeTree создана, но SELECT с finalizeAggregation() возвращает неверные результаты или ошибку
- Ошибка возникает при смешении -State и немодифицированных агрегатных функций в одном запросе
- INSERT SELECT из другой таблицы в AggregatingMergeTree падает из-за несоответствия типов колонок
Причина
AggregatingMergeTree требует, чтобы в колонках хранились промежуточные состояния агрегатных функций типа AggregateFunction(func, ...). При вставке необходимо использовать func-State(), при чтении — func-Merge(). Нарушение этого контракта или опечатка в имени функции приводит к ошибке.
Решение
- При INSERT в AggregatingMergeTree используйте INSERT SELECT ... funcState() as col вместо func() — это записывает промежуточное состояние, а не итоговое значение
- При SELECT используйте funcMerge(col) для финального слияния состояний из хранилища: SELECT funcMerge(col) FROM table GROUP BY key
- Проверьте тип колонки через DESCRIBE TABLE — колонка с AggregatingMergeTree должна иметь тип AggregateFunction(func, arg_types...)
- Используйте Materialized View на базовую таблицу для автоматического заполнения AggregatingMergeTree без ручного управления -State
Симптомы
- JOIN-запрос с большой правой таблицей падает с ошибкой о превышении лимита памяти для хеш-таблицы
- Запрос работает в тестовой среде с малым датасетом, но падает на production
- Отчёт EXPLAIN показывает алгоритм hash join, хотя правая таблица содержит миллионы строк
- Увеличение max_memory_usage помогает временно, но не решает проблему масштабируемости
Причина
ClickHouse по умолчанию использует hash join, загружая всю правую таблицу в оперативную память. При большом размере правой таблицы хеш-таблица не помещается в max_bytes_in_join и запрос падает.
Решение
- Переключитесь на grace_hash join: SET join_algorithm = 'grace_hash' — алгоритм сбрасывает часть хеш-таблицы на диск при нехватке памяти
- Попробуйте partial_merge join для случаев, когда обе таблицы частично отсортированы по ключу JOIN
- Переосмыслите запрос: уменьшите правую таблицу через подзапрос с фильтрами до JOIN, а не после
- Используйте словари (dictGet) вместо JOIN для справочных таблиц — словари загружаются в память один раз и не участвуют в hash join
Связанные уроки:
Симптомы
- system.replicas показывает большой absolute_delay (сотни секунд) для одной или нескольких реплик
- SELECT на отставшей реплике возвращает устаревшие данные по сравнению с лидером
- Очередь репликации system.replication_queue содержит тысячи ожидающих операций
- Слияния на отставшей реплике занимают значительно больше времени, чем на лидере
Причина
Реплика не успевает применять записи из лога репликации. Причины: перегруженный диск на реплике, недостаточное число background потоков репликации, или лидер выполнил большой batch слияний, которые реплика должна воспроизвести.
Решение
- Увеличьте число потоков репликации: background_fetches_pool_size = 8 и background_replication_threads = 16 в config.xml
- Проверьте дисковый I/O на отставшей реплике — iostat -x 1 покажет утилизацию; проблема часто в конкурирующих слияниях
- Выполните SYSTEM SYNC REPLICA ON CLUSTER для принудительной синхронизации всех реплик кластера
- Мониторьте system.replicas.absolute_delay в Prometheus/Grafana с алертом при lag > 60 секунд
Связанные уроки:
Симптомы
- INSERT-запросы выполняются секунды вместо миллисекунд, throughput загрузки ниже ожидаемого
- Профайлер INSERT в system.query_log показывает высокое время записи на диск (write_bytes велики, но медленно)
- Клиентское приложение вставляет строки по одной (INSERT VALUES per event) в цикле
- Число активных частей растёт быстро — слияния не успевают, появляется Too many parts
Причина
Синхронная вставка без батчинга: каждый INSERT создаёт новый кусок на диске с fsync. ClickHouse оптимизирован для вставки крупных батчей (от 10 000 строк), а не для высокочастотных мелких вставок — это фундаментальная разница между OLTP и OLAP паттернами.
Решение
- Включите async_insert=1: ClickHouse накапливает небольшие вставки в буфер и сбрасывает их единым куском, что сокращает число частей в 10-100 раз
- На уровне приложения агрегируйте события в батчи по 10 000-100 000 строк перед отправкой INSERT — это наиболее эффективный паттерн
- Используйте формат RowBinary или Native вместо JSONEachRow для снижения накладных расходов парсинга при высокой частоте вставок
- Настройте async_insert_max_data_size и async_insert_busy_timeout_ms для оптимального баланса задержки и throughput
Связанные уроки:
Симптомы
- OPTIMIZE TABLE ... FINAL выполняется часами и блокирует другие DDL-операции
- Запрос занимает всю доступную I/O пропускную способность диска, ухудшая производительность других запросов
- Для большой таблицы (сотни GB или TB) OPTIMIZE требует слияния тысяч кусков в один
- Мутация (ALTER TABLE ... UPDATE/DELETE) не завершается, так как ожидает OPTIMIZE
Причина
OPTIMIZE FINAL принудительно сливает все куски таблицы в один, включая уже слитые куски. Для больших таблиц это создаёт огромную нагрузку: читаются и переписываются сотни гигабайт данных. Фоновые слияния ClickHouse были разработаны как инкрементальный процесс, а OPTIMIZE FINAL — экстренный инструмент, не для регулярного использования.
Решение
- Не используйте OPTIMIZE FINAL для регулярной работы — доверяйте фоновым слияниям ClickHouse; вмешивайтесь только для тестирования дедупликации или конкретных задач
- Для дедупликации в ReplacingMergeTree лучше использовать SELECT с FINAL modifier вместо принудительного OPTIMIZE
- Если OPTIMIZE необходим — выполняйте по партициям: ALTER TABLE t OPTIMIZE PARTITION 'part_key', это снижает нагрузку
- Ограничьте ресурсы OPTIMIZE через SETTINGS max_threads=2 для минимизации влияния на производительность кластера
Связанные уроки:
Симптомы
- Запросы к S3-бакету через s3() функцию или S3 storage policy завершаются ошибкой 403 Access Denied
- Создание таблицы с S3 движком успешно, но SELECT возвращает ошибку при первом обращении к данным
- Ошибка появилась после ротации AWS credentials или смены IAM роли
- Локальный clickhouse-client с теми же credentials работает, но контейнер Docker — нет
Причина
ClickHouse не может аутентифицироваться в S3: неправильные access_key_id/secret_access_key, credentials устарели, IAM роль не имеет прав на бакет, или named collection содержит старые учётные данные. Частая причина в Docker/Kubernetes: instance metadata endpoint недоступен для получения ролевых credentials.
Решение
- Используйте Named Collections для управления S3 credentials: CREATE NAMED COLLECTION s3_creds AS access_key_id='...', secret_access_key='...' — упрощает ротацию без изменения DDL
- Проверьте IAM policy: бакет должен разрешать s3:GetObject, s3:PutObject, s3:ListBucket для ClickHouse principal
- Никогда не встраивайте credentials в DDL таблиц напрямую — используйте named collections или environment variables через config.xml
- В Docker убедитесь, что контейнер имеет доступ к instance metadata: curl http://169.254.169.254/latest/meta-data/ из контейнера
Связанные уроки:
Симптомы
- После DETACH и ATTACH партиции данные в таблице не появляются или партиция не найдена
- ATTACH возвращает ошибку 'No such partition' или 'Partition not found in detached directory'
- При DETACH использовалось выражение, не совпадающее с реальным partition key таблицы
- В директории detached/ есть куски, но они имеют другое имя, чем ожидает ClickHouse
Причина
DETACH PARTITION требует точного значения выражения partition key, которое совпадает с реальными partition ID кусков. Если в PARTITION BY используется toYYYYMM(date), нужно указывать числовое значение 202401, а не строку '2024-01'. Несоответствие приводит к тому, что партиция детачится, но ATTACH не может её найти.
Решение
- Проверьте реальные partition IDs через: SELECT DISTINCT partition, partition_id FROM system.parts WHERE table = 'your_table' — используйте именно эти значения в DETACH/ATTACH
- При работе с toYYYYMM() используйте числовое значение: ALTER TABLE t DETACH PARTITION 202401 (не '2024-01')
- Для восстановления данных из detached/: скопируйте куски в правильную директорию и выполните ATTACH PART 'part_name'
- Документируйте partition expression таблицы — при несовпадении партиций выполняйте DESCRIBE TABLE для проверки
Связанные уроки:
Симптомы
- Все INSERT в реплицированные таблицы завершаются ошибкой — кластер перешёл в режим readonly
- Логи Keeper показывают 'quorum not reached' или 'leader election failed'
- Два из трёх (или три из пяти) узлов Keeper недоступны одновременно
- clickhouse-keeper-client не может подключиться ни к одному из узлов Keeper
Причина
Raft кворум Keeper потерян: недостаточно живых узлов (нужно > N/2) для избрания лидера и обработки записей в Raft log. Без работающего Keeper ClickHouse не может координировать репликацию и блокирует все записи для предотвращения split-brain.
Решение
- Восстановите кворум, вернув упавшие узлы Keeper в строй — при наличии трёх узлов достаточно поднять один из двух упавших
- Для экстренного одноузлового восстановления (не рекомендуется в production): установите force_recovery=true в keeper_config.xml и перезапустите один узел
- После восстановления кворума проверьте целостность данных через system.replicas и system.replication_queue
- Для предотвращения: разместите Keeper-узлы в разных availability zones, никогда не проводите обслуживание более одного узла одновременно
Связанные уроки:
Симптомы
- ALTER TABLE ADD COLUMN выполнился успешно на одном узле, но SELECT с новой колонкой падает на других
- Реплика показывает, что колонка существует в структуре таблицы, но данные в ней NULL или колонка отсутствует
- DDL-операция выполнена без ON CLUSTER — изменение применено только на одном шарде или реплике
- Материализованная или ALIAS колонка дала разные результаты на разных репликах кластера
Причина
DDL-операция ALTER TABLE выполнена без ON CLUSTER и не реплицировалась на все узлы. В ClickHouse каждый узел применяет DDL независимо — для синхронного изменения на всём кластере обязательно использование ON CLUSTER с именем кластера из config.xml.
Решение
- Используйте ON CLUSTER для всех DDL-операций в распределённых окружениях: ALTER TABLE t ON CLUSTER '{cluster}' ADD COLUMN col_name Type
- Проверьте актуальное состояние схемы на всех шардах: SELECT host, name, type FROM clusterAllReplicas('cluster', system.columns) WHERE table = 'table_name'
- Для исправления расхождения: выполните ALTER TABLE ADD COLUMN на отставших узлах вручную или через ON CLUSTER
- Настройте distributed_ddl_task_timeout на достаточно большое значение, чтобы DDL дождался ответа от всех реплик
Связанные уроки:
Симптомы
- INSERT с данными JSON завершается ошибкой парсинга даже для корректного JSON
- Ошибка типа 'Cannot parse JSON: unexpected token [' указывает на JSON-массив вместо объектов построчно
- Клиент отправляет [{...},{...}] (массив объектов), а ClickHouse ожидает newline-delimited JSON
- Вставка работает для одной строки, но падает для батча из нескольких строк
Причина
ClickHouse не поддерживает формат 'JSON array of objects' напрямую — для построчной вставки используется JSONEachRow, где каждый JSON-объект на отдельной строке. Формат JSON в ClickHouse предназначен для вывода (SELECT), а не для INPUT массивов объектов.
Решение
- Используйте FORMAT JSONEachRow для вставки построчного JSON: каждая строка — отдельный JSON-объект без запятых между строками
- Для Python-клиентов: передавайте данные через clickhouse-driver с format='JSONEachRow' или используйте clickhouse-connect
- Если источник отдаёт JSON-массив: трансформируйте на стороне клиента — разбейте массив на отдельные строки перед INSERT
- Проверьте доступные форматы для INSERT: SELECT name FROM system.formats WHERE is_input = 1 — JSONEachRow, JSONCompactEachRow, CSV, Parquet, Native
Связанные уроки:
Симптомы
- ALTER TABLE ... UPDATE или DELETE выдан, но после длительного ожидания system.mutations показывает is_done=0
- Поле parts_to_do не уменьшается — мутация не прогрессирует
- Фоновые слияния остановились или значительно замедлились из-за конкуренции с мутацией
- INSERT в ту же таблицу замедлился или заблокировался
Причина
Мутация в ClickHouse реализована как фоновый процесс перезаписи кусков — каждый кусок данных должен быть переписан с применением UPDATE/DELETE. При большом числе кусков или высокой нагрузке записи мутация конкурирует с фоновыми слияниями за ресурсы I/O и может зависнуть.
Решение
- Проверьте статус в system.mutations: SELECT * FROM system.mutations WHERE is_done = 0 — поле latest_fail_reason покажет причину зависания
- Убедитесь, что background pool не занят: увеличьте background_pool_size в config.xml для большего параллелизма мутаций
- Рассмотрите использование lightweight DELETE (ClickHouse 23.3+) вместо ALTER TABLE DELETE — lightweight DELETE помечает строки без перезаписи кусков
- Для отмены застрявшей мутации: KILL MUTATION WHERE mutation_id = 'id' — будут отменены все незавершённые части
Связанные уроки:
Симптомы
- Длинный аналитический запрос прерывается с ошибкой превышения max_execution_time
- В system.query_log видно query_duration_ms, равное max_execution_time * 1000
- Проблема только на production данных — на тестовом подмножестве запрос завершается нормально
- Таймаут срабатывает нерегулярно в зависимости от нагрузки кластера
Причина
Запрос выполняется дольше лимита max_execution_time (по умолчанию 0 = без лимита, но часто устанавливается администратором в 30-300 секунд для production защиты). Причины: неоптимальный план запроса, отсутствие skip-индексов, большой full-scan или тяжёлый JOIN.
Решение
- Проанализируйте план запроса через EXPLAIN PIPELINE SELECT ... — найдите узкие места: большие ReadBuffer, медленные Join или Sort шаги
- Добавьте фильтр по первичному ключу WHERE date BETWEEN ... для ограничения читаемых гранул через sparse index
- Используйте skip-индексы (bloom_filter, set) на часто фильтруемых колонках не в первичном ключе
- Если запрос обоснованно требует времени — временно увеличьте лимит через SETTINGS max_execution_time=600 в сессии
Связанные уроки:
Симптомы
- Длинный SELECT с большим результатом обрывается на стороне клиента с 'Broken pipe' или 'Connection reset'
- В логах сервера видно 'max_concurrent_queries exceeded' для нового клиента в момент разрыва
- Проблема воспроизводится при выполнении нескольких тяжёлых запросов одновременно
- HTTP-клиент получает пустой response или обрезанный результат без сообщения об ошибке
Причина
Клиент отключился в процессе передачи большого результата (network timeout на клиентской стороне или сам закрыл соединение), либо сервер достиг лимита max_concurrent_queries и отклонил новый запрос. ClickHouse по умолчанию не переотправляет результаты после разрыва соединения.
Решение
- Увеличьте send_timeout и receive_timeout в настройках HTTP-клиента для длинных запросов: они должны превышать максимальное время выполнения запроса
- Для выгрузки больших результатов используйте INSERT INTO ... SELECT вместо SELECT с передачей данных клиенту
- Проверьте max_concurrent_queries в config.xml: при необходимости увеличьте или настройте priority-очереди через workload
- Используйте clickhouse-client вместо HTTP API для длинных запросов — TCP-соединение более устойчиво к таймаутам
Связанные уроки:
Симптомы
- INSERT по HTTP завершается ошибкой сервера об усечённом теле запроса
- Часть данных вставлена, часть потеряна — ClickHouse не гарантирует атомарность HTTP INSERT при разрыве
- Ошибка появляется при вставке больших файлов (>1 ГБ) через HTTP с клиентским таймаутом
- system.query_log показывает read_bytes меньше реального размера данных для упавшего INSERT
Причина
HTTP-соединение было разорвано до передачи всего тела INSERT-запроса: клиентский таймаут, проблема сети или прокси (nginx/HAProxy) закрыл соединение. ClickHouse не может восстановить незавершённый HTTP-запрос.
Решение
- Разбейте большой INSERT на меньшие батчи (не более 100-500 МБ каждый) для надёжной передачи по HTTP
- Увеличьте таймауты на всех уровнях: клиент, прокси (proxy_read_timeout в nginx), и ClickHouse (http_receive_timeout)
- Используйте clickhouse-client с Native protocol вместо HTTP для больших вставок — TCP с keepalive более устойчив к разрывам
- Рассмотрите использование async_insert=1 с буферизацией на стороне ClickHouse — при разрыве буфер не теряется немедленно
Связанные уроки:
Симптомы
- Новые запросы отклоняются с ошибкой о превышении серверного лимита памяти, хотя max_memory_usage на запрос не достигнут
- free -h показывает минимальный free+buff/cache на сервере — ОС находится под давлением памяти
- Несколько параллельных запросов суммарно превышают RAM сервера
- OOM killer ОС убивает ClickHouse процесс при пиковой нагрузке
Причина
max_server_memory_usage_to_ram_ratio (по умолчанию 0.9) задаёт абсолютный лимит памяти всего сервера ClickHouse, в отличие от max_memory_usage, который ограничивает один запрос. При высокой конкурентности сумма памяти всех запросов превышает серверный порог.
Решение
- Настройте max_server_memory_usage_to_ram_ratio = 0.8 в config.xml для резервирования 20% RAM для ОС и системных процессов
- Установите max_concurrent_queries для ограничения числа одновременных тяжёлых запросов
- Используйте workload management (ClickHouse 24.6+) для приоритизации запросов и ограничения памяти per-workload
- Мониторьте MemoryTracking и MemoryAllocated в system.metrics для раннего обнаружения давления памяти до достижения лимита