Learning Platform
Глоссарий
Troubleshooting

Решение проблем ClickHouse

Частые ошибки при работе с ClickHouse — симптомы, причины и пошаговые решения.

Область

Категория

Показано 25 из 25 ошибок

Симптомы

  • 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.

Решение

  1. Включите async_insert=1 на уровне пользователя или сессии: INSERT с накоплением в буфер и автоматическим батчингом сокращает число кусков в десятки раз
  2. Увеличьте min_insert_block_size_rows (до 1 000 000+) и min_insert_block_size_bytes (до 256 МБ) для формирования более крупных кусков при каждой вставке
  3. Проверьте parts_to_delay_insert и parts_to_throw_insert в config.xml — при необходимости временно увеличьте порог, но устраняйте причину, а не симптом
  4. Консолидируйте клиентские 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 ГБ или настройка сервера). Чаще всего причина — агрегация или сортировка большого числа уникальных ключей, которые целиком помещаются в хеш-таблицу в оперативной памяти.

Решение

  1. Включите внешнюю агрегацию: SET max_bytes_before_external_group_by = 8000000000 — ClickHouse будет сбрасывать промежуточные результаты на диск при нехватке памяти
  2. Включите внешнюю сортировку: SET max_bytes_before_external_sort = 8000000000 для ORDER BY на больших данных
  3. Увеличьте max_memory_usage для конкретного пользователя или запроса, если сервер располагает достаточной RAM
  4. Перепишите запрос с использованием approx_count_distinct() или quantileTDigest() вместо точных агрегатов там, где допускается приближённый результат

Связанные уроки:

Симптомы

  • INSERT с insert_quorum > 1 завершается ошибкой — недостаточно живых реплик для подтверждения записи
  • Запись полностью блокируется, пока не восстановится нужное число реплик
  • system.replicas показывает, что часть реплик offline или значительно отстают
  • Ошибка возникает при плановом обслуживании, если отключено более одной реплики одновременно

Причина

Для завершения кворумной вставки (insert_quorum) требуется подтверждение от заданного числа реплик, но часть реплик недоступна (упала, перезапускается, имеет сетевые проблемы). ClickHouse не завершает INSERT, пока не достигнут кворум.

Решение

  1. Убедитесь, что не более N-quorum реплик недоступны одновременно — при обслуживании отключайте серверы поочерёдно
  2. Проверьте состояние реплик в system.replicas — поля is_readonly, is_leader, queue_size помогут найти проблемную реплику
  3. Временно уменьшите insert_quorum до 1 при экстренной необходимости продолжить вставку (с пониманием рисков согласованности)
  4. Настройте 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 видит, что узел занят, и отказывается его захватить.

Решение

  1. Подождите истечения session_timeout_ms сессии Keeper (по умолчанию 10 000 мс) — узел удалится автоматически, и реплика активируется
  2. Убедитесь, что ClickHouse корректно останавливается командой systemctl stop — не kill -9, который оставляет сессии открытыми
  3. Проверьте логи Keeper на предмет split-brain или потери кворума, которые могут задерживать удаление ephemeral-узлов
  4. В крайнем случае выполните SYSTEM RESTART REPLICA table_name для принудительного сброса состояния реплики

Связанные уроки:

Симптомы

  • INSERT или слияние прерывается с NOT_ENOUGH_SPACE — диск или том хранилища заполнен
  • TTL MOVE не переносит старые данные на холодный уровень хотя настроен
  • df -h показывает 95%+ утилизацию диска с данными ClickHouse
  • Новые части не создаются, background merges останавливаются

Причина

Диск исчерпал свободное место — ClickHouse не может записать новые куски или временные файлы слияний. TTL MOVE мог не сработать из-за неправильно сконфигурированной storage policy: нет маршрута перемещения, недоступен S3 endpoint или превышен размер буферной очереди.

Решение

  1. Немедленно освободите место: удалите ненужные таблицы (DROP TABLE), используйте TRUNCATE или удалите старые партиции через ALTER TABLE ... DROP PARTITION
  2. Проверьте storage policy в config.xml или storage_policies.xml — убедитесь, что volume cold правильно указывает на другой диск или S3
  3. Запустите TTL вручную: ALTER TABLE t MATERIALIZE TTL для немедленного применения правил TTL и перемещения старых данных
  4. Настройте мониторинг disk_usage_bytes в системных таблицах или через Prometheus endpoint (порт 9363) с алертом при 80%

Связанные уроки:

Симптомы

  • SELECT из таблицы завершается ошибкой контрольной суммы или ошибкой чтения inode для конкретного куска
  • DESCRIBE TABLE и DDL-операции работают, но чтение данных из проблемного куска невозможно
  • В логах ClickHouse видны сообщения 'Renaming broken part' — сервер перемещает повреждённый кусок в detached/
  • fsck или smartctl сообщают о ошибках на диске, либо куски находятся на диске с ненулевым reallocated_sectors

Причина

Файлы куска данных повреждены на диске: аппаратный сбой (битый сектор, плохой RAID), аварийное отключение питания без flush на диск, или повреждение файловой системы. ClickHouse хранит контрольные суммы для каждого куска и при несоответствии считает кусок неисправным.

Решение

  1. Проверьте директорию detached/ для таблицы — ClickHouse автоматически изолирует повреждённые куски туда
  2. Если есть реплики, выполните SYSTEM SYNC REPLICA table_name — реплика скачает недостающий кусок с другого сервера
  3. При отсутствии реплик: выполните SELECT из backup или восстановите из снапшота, затем вставьте данные вручную; потеря данных в повреждённом куске неизбежна
  4. Проверьте состояние дисков с помощью smartctl -a /dev/sdX и при необходимости замените диск до создания новых реплик

Связанные уроки:

Симптомы

  • Запросы с dictGet() завершаются ошибкой — словарь не был загружен или не может обновиться
  • system.dictionaries показывает is_loaded=0 и status='FAILED' с текстом ошибки подключения к источнику
  • ClickHouse периодически пытается перезагрузить словарь, но каждая попытка завершается неудачей
  • Ошибка возникла после изменения хоста/порта источника или ротации credentials

Причина

Источник данных словаря (MySQL, PostgreSQL, HTTP, ClickHouse remote table) недоступен: изменился хост, порт или учётные данные, источник перезапускается, или сетевые правила блокируют соединение из ClickHouse.

Решение

  1. Проверьте доступность источника напрямую с сервера ClickHouse: curl, telnet или clickhouse-client к удалённому ClickHouse
  2. Обновите настройки словаря в XML или DDL (CREATE DICTIONARY) и выполните SYSTEM RELOAD DICTIONARY dict_name
  3. Используйте Named Collections для хранения credentials словарей отдельно от DDL — упрощает ротацию учётных данных без изменения схемы
  4. Настройте lifetime min/max в словаре — при ошибке загрузки ClickHouse продолжает использовать кешированные данные до истечения max lifetime

Связанные уроки:

Симптомы

  • INSERT в реплицированную таблицу завершается ошибкой 'Table is in readonly mode'
  • DDL-операции и мутации тоже отклоняются — таблица принимает только SELECT
  • system.replicas показывает is_readonly=1 для данной реплики
  • Проблема возникла после сетевого сбоя, перезапуска Keeper или длительного простоя сервера

Причина

Реплика потеряла соединение с ClickHouse Keeper или сессия истекла. Без доступа к Keeper таблица не может координировать репликацию и переходит в режим readonly, чтобы не допустить несогласованных данных.

Решение

  1. Проверьте доступность Keeper с сервера ClickHouse: clickhouse-keeper-client -h keeper_host -p 9181 или проверьте логи на connectivity errors
  2. Убедитесь, что сессионный таймаут Keeper достаточно велик для реальных задержек сети: увеличьте zookeeper_session_timeout_ms в config.xml
  3. Дождитесь автоматического восстановления после нормализации соединения — реплика выйдет из readonly автоматически
  4. Если 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 запросов. Запрос читает больше строк, чем разрешено профилем пользователя или сессионной настройкой.

Решение

  1. Добавьте фильтр по первичному ключу WHERE в запрос — ClickHouse оптимизирует чтение через гранулы и читает только нужные части
  2. Используйте skip-индексы или проекции для сокращения объёма читаемых данных без изменения max_rows_to_read
  3. Если запрос действительно требует чтения большого объёма данных — увеличьте лимит для конкретного пользователя или используйте SETTINGS max_rows_to_read=0 для разового обхода
  4. Настройте system.query_log для мониторинга тяжёлых запросов и выявления кандидатов на оптимизацию

Симптомы

  • INSERT в базовую таблицу зависает или завершается ошибкой циклической зависимости
  • Создание нового материализованного представления завершается успешно, но вставки в зависимые таблицы перестают работать
  • Ошибка сложно воспроизводима — проявляется только при вставке в таблицу, находящуюся в начале цепочки
  • system.tables показывает несколько MV с взаимно зависимыми target-таблицами

Причина

Создана цепочка материализованных представлений, где MV A пишет в таблицу B, из которой MV C читает и пишет обратно в таблицу A. ClickHouse выполняет MV-триггеры синхронно в рамках INSERT, что приводит к циклическому вызову и deadlock или Stack Overflow.

Решение

  1. Спроектируйте граф MV как DAG (направленный ациклический граф) без обратных рёбер — каждая целевая таблица должна быть только листом, не источником в той же цепочке
  2. Используйте промежуточную таблицу-буфер с другим именем для разрыва цикла
  3. Выполните DETACH TABLE / DROP VIEW для проблемного MV и перепроектируйте цепочку
  4. Документируйте зависимости всех MV в команде — циклические зависимости легче предотвратить на этапе проектирования

Симптомы

  • Запрос с -State или -Merge комбинатором падает с ошибкой 'Unknown aggregate function'
  • Таблица AggregatingMergeTree создана, но SELECT с finalizeAggregation() возвращает неверные результаты или ошибку
  • Ошибка возникает при смешении -State и немодифицированных агрегатных функций в одном запросе
  • INSERT SELECT из другой таблицы в AggregatingMergeTree падает из-за несоответствия типов колонок

Причина

AggregatingMergeTree требует, чтобы в колонках хранились промежуточные состояния агрегатных функций типа AggregateFunction(func, ...). При вставке необходимо использовать func-State(), при чтении — func-Merge(). Нарушение этого контракта или опечатка в имени функции приводит к ошибке.

Решение

  1. При INSERT в AggregatingMergeTree используйте INSERT SELECT ... funcState() as col вместо func() — это записывает промежуточное состояние, а не итоговое значение
  2. При SELECT используйте funcMerge(col) для финального слияния состояний из хранилища: SELECT funcMerge(col) FROM table GROUP BY key
  3. Проверьте тип колонки через DESCRIBE TABLE — колонка с AggregatingMergeTree должна иметь тип AggregateFunction(func, arg_types...)
  4. Используйте Materialized View на базовую таблицу для автоматического заполнения AggregatingMergeTree без ручного управления -State

Связанные уроки:

Симптомы

  • JOIN-запрос с большой правой таблицей падает с ошибкой о превышении лимита памяти для хеш-таблицы
  • Запрос работает в тестовой среде с малым датасетом, но падает на production
  • Отчёт EXPLAIN показывает алгоритм hash join, хотя правая таблица содержит миллионы строк
  • Увеличение max_memory_usage помогает временно, но не решает проблему масштабируемости

Причина

ClickHouse по умолчанию использует hash join, загружая всю правую таблицу в оперативную память. При большом размере правой таблицы хеш-таблица не помещается в max_bytes_in_join и запрос падает.

Решение

  1. Переключитесь на grace_hash join: SET join_algorithm = 'grace_hash' — алгоритм сбрасывает часть хеш-таблицы на диск при нехватке памяти
  2. Попробуйте partial_merge join для случаев, когда обе таблицы частично отсортированы по ключу JOIN
  3. Переосмыслите запрос: уменьшите правую таблицу через подзапрос с фильтрами до JOIN, а не после
  4. Используйте словари (dictGet) вместо JOIN для справочных таблиц — словари загружаются в память один раз и не участвуют в hash join

Связанные уроки:

Симптомы

  • system.replicas показывает большой absolute_delay (сотни секунд) для одной или нескольких реплик
  • SELECT на отставшей реплике возвращает устаревшие данные по сравнению с лидером
  • Очередь репликации system.replication_queue содержит тысячи ожидающих операций
  • Слияния на отставшей реплике занимают значительно больше времени, чем на лидере

Причина

Реплика не успевает применять записи из лога репликации. Причины: перегруженный диск на реплике, недостаточное число background потоков репликации, или лидер выполнил большой batch слияний, которые реплика должна воспроизвести.

Решение

  1. Увеличьте число потоков репликации: background_fetches_pool_size = 8 и background_replication_threads = 16 в config.xml
  2. Проверьте дисковый I/O на отставшей реплике — iostat -x 1 покажет утилизацию; проблема часто в конкурирующих слияниях
  3. Выполните SYSTEM SYNC REPLICA ON CLUSTER для принудительной синхронизации всех реплик кластера
  4. Мониторьте 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 паттернами.

Решение

  1. Включите async_insert=1: ClickHouse накапливает небольшие вставки в буфер и сбрасывает их единым куском, что сокращает число частей в 10-100 раз
  2. На уровне приложения агрегируйте события в батчи по 10 000-100 000 строк перед отправкой INSERT — это наиболее эффективный паттерн
  3. Используйте формат RowBinary или Native вместо JSONEachRow для снижения накладных расходов парсинга при высокой частоте вставок
  4. Настройте 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 — экстренный инструмент, не для регулярного использования.

Решение

  1. Не используйте OPTIMIZE FINAL для регулярной работы — доверяйте фоновым слияниям ClickHouse; вмешивайтесь только для тестирования дедупликации или конкретных задач
  2. Для дедупликации в ReplacingMergeTree лучше использовать SELECT с FINAL modifier вместо принудительного OPTIMIZE
  3. Если OPTIMIZE необходим — выполняйте по партициям: ALTER TABLE t OPTIMIZE PARTITION 'part_key', это снижает нагрузку
  4. Ограничьте ресурсы 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.

Решение

  1. Используйте Named Collections для управления S3 credentials: CREATE NAMED COLLECTION s3_creds AS access_key_id='...', secret_access_key='...' — упрощает ротацию без изменения DDL
  2. Проверьте IAM policy: бакет должен разрешать s3:GetObject, s3:PutObject, s3:ListBucket для ClickHouse principal
  3. Никогда не встраивайте credentials в DDL таблиц напрямую — используйте named collections или environment variables через config.xml
  4. В 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 не может её найти.

Решение

  1. Проверьте реальные partition IDs через: SELECT DISTINCT partition, partition_id FROM system.parts WHERE table = 'your_table' — используйте именно эти значения в DETACH/ATTACH
  2. При работе с toYYYYMM() используйте числовое значение: ALTER TABLE t DETACH PARTITION 202401 (не '2024-01')
  3. Для восстановления данных из detached/: скопируйте куски в правильную директорию и выполните ATTACH PART 'part_name'
  4. Документируйте 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.

Решение

  1. Восстановите кворум, вернув упавшие узлы Keeper в строй — при наличии трёх узлов достаточно поднять один из двух упавших
  2. Для экстренного одноузлового восстановления (не рекомендуется в production): установите force_recovery=true в keeper_config.xml и перезапустите один узел
  3. После восстановления кворума проверьте целостность данных через system.replicas и system.replication_queue
  4. Для предотвращения: разместите Keeper-узлы в разных availability zones, никогда не проводите обслуживание более одного узла одновременно

Связанные уроки:

Симптомы

  • ALTER TABLE ADD COLUMN выполнился успешно на одном узле, но SELECT с новой колонкой падает на других
  • Реплика показывает, что колонка существует в структуре таблицы, но данные в ней NULL или колонка отсутствует
  • DDL-операция выполнена без ON CLUSTER — изменение применено только на одном шарде или реплике
  • Материализованная или ALIAS колонка дала разные результаты на разных репликах кластера

Причина

DDL-операция ALTER TABLE выполнена без ON CLUSTER и не реплицировалась на все узлы. В ClickHouse каждый узел применяет DDL независимо — для синхронного изменения на всём кластере обязательно использование ON CLUSTER с именем кластера из config.xml.

Решение

  1. Используйте ON CLUSTER для всех DDL-операций в распределённых окружениях: ALTER TABLE t ON CLUSTER '{cluster}' ADD COLUMN col_name Type
  2. Проверьте актуальное состояние схемы на всех шардах: SELECT host, name, type FROM clusterAllReplicas('cluster', system.columns) WHERE table = 'table_name'
  3. Для исправления расхождения: выполните ALTER TABLE ADD COLUMN на отставших узлах вручную или через ON CLUSTER
  4. Настройте 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 массивов объектов.

Решение

  1. Используйте FORMAT JSONEachRow для вставки построчного JSON: каждая строка — отдельный JSON-объект без запятых между строками
  2. Для Python-клиентов: передавайте данные через clickhouse-driver с format='JSONEachRow' или используйте clickhouse-connect
  3. Если источник отдаёт JSON-массив: трансформируйте на стороне клиента — разбейте массив на отдельные строки перед INSERT
  4. Проверьте доступные форматы для 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 и может зависнуть.

Решение

  1. Проверьте статус в system.mutations: SELECT * FROM system.mutations WHERE is_done = 0 — поле latest_fail_reason покажет причину зависания
  2. Убедитесь, что background pool не занят: увеличьте background_pool_size в config.xml для большего параллелизма мутаций
  3. Рассмотрите использование lightweight DELETE (ClickHouse 23.3+) вместо ALTER TABLE DELETE — lightweight DELETE помечает строки без перезаписи кусков
  4. Для отмены застрявшей мутации: 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.

Решение

  1. Проанализируйте план запроса через EXPLAIN PIPELINE SELECT ... — найдите узкие места: большие ReadBuffer, медленные Join или Sort шаги
  2. Добавьте фильтр по первичному ключу WHERE date BETWEEN ... для ограничения читаемых гранул через sparse index
  3. Используйте skip-индексы (bloom_filter, set) на часто фильтруемых колонках не в первичном ключе
  4. Если запрос обоснованно требует времени — временно увеличьте лимит через SETTINGS max_execution_time=600 в сессии

Симптомы

  • Длинный SELECT с большим результатом обрывается на стороне клиента с 'Broken pipe' или 'Connection reset'
  • В логах сервера видно 'max_concurrent_queries exceeded' для нового клиента в момент разрыва
  • Проблема воспроизводится при выполнении нескольких тяжёлых запросов одновременно
  • HTTP-клиент получает пустой response или обрезанный результат без сообщения об ошибке

Причина

Клиент отключился в процессе передачи большого результата (network timeout на клиентской стороне или сам закрыл соединение), либо сервер достиг лимита max_concurrent_queries и отклонил новый запрос. ClickHouse по умолчанию не переотправляет результаты после разрыва соединения.

Решение

  1. Увеличьте send_timeout и receive_timeout в настройках HTTP-клиента для длинных запросов: они должны превышать максимальное время выполнения запроса
  2. Для выгрузки больших результатов используйте INSERT INTO ... SELECT вместо SELECT с передачей данных клиенту
  3. Проверьте max_concurrent_queries в config.xml: при необходимости увеличьте или настройте priority-очереди через workload
  4. Используйте clickhouse-client вместо HTTP API для длинных запросов — TCP-соединение более устойчиво к таймаутам

Симптомы

  • INSERT по HTTP завершается ошибкой сервера об усечённом теле запроса
  • Часть данных вставлена, часть потеряна — ClickHouse не гарантирует атомарность HTTP INSERT при разрыве
  • Ошибка появляется при вставке больших файлов (>1 ГБ) через HTTP с клиентским таймаутом
  • system.query_log показывает read_bytes меньше реального размера данных для упавшего INSERT

Причина

HTTP-соединение было разорвано до передачи всего тела INSERT-запроса: клиентский таймаут, проблема сети или прокси (nginx/HAProxy) закрыл соединение. ClickHouse не может восстановить незавершённый HTTP-запрос.

Решение

  1. Разбейте большой INSERT на меньшие батчи (не более 100-500 МБ каждый) для надёжной передачи по HTTP
  2. Увеличьте таймауты на всех уровнях: клиент, прокси (proxy_read_timeout в nginx), и ClickHouse (http_receive_timeout)
  3. Используйте clickhouse-client с Native protocol вместо HTTP для больших вставок — TCP с keepalive более устойчив к разрывам
  4. Рассмотрите использование 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, который ограничивает один запрос. При высокой конкурентности сумма памяти всех запросов превышает серверный порог.

Решение

  1. Настройте max_server_memory_usage_to_ram_ratio = 0.8 в config.xml для резервирования 20% RAM для ОС и системных процессов
  2. Установите max_concurrent_queries для ограничения числа одновременных тяжёлых запросов
  3. Используйте workload management (ClickHouse 24.6+) для приоритизации запросов и ограничения памяти per-workload
  4. Мониторьте MemoryTracking и MemoryAllocated в system.metrics для раннего обнаружения давления памяти до достижения лимита

Связанные уроки: