Траблшутинг переливки данных в ClickHouse
В статье описана диагностика проблем при переносе данных из Smart Monitor Data Storage (далее - Data Storage) в ClickHouse с помощью действия Move to ClickHouse. Формула расчета памяти, пороговые значения и примеры ошибок основаны на замерах для Smart Monitor 5.1.3 и ClickHouse 25.7.4.11: использовались таблицы с 290, 590, 990, 1040, 1340 и 1590 колонками, fetch_size 10000 и лимит max_memory_usage_for_user 8 ГиБ. Результаты применимы к этой конфигурации. При другом лимите памяти, версии ClickHouse или существенно ином профиле документов уточните значения для своей инсталляции.
Память одного INSERT
Потребление памяти одним INSERT в ClickHouse при переносе данных зависит прежде всего от числа колонок целевой таблицы (C), а не от числа строк в пакете или размера пула. По замерам на таблицах от 290 до 1590 колонок отклонение расчетной модели от измеренного потребления памяти не превышает 1 %:
INSERT ≈ 85 МиБ + 4,5 МиБ × C
Размер пула sm.ism.move_to_click.threadpool.size (статическая настройка узла Data Storage, применяется после перезапуска) не влияет на потребление памяти одним INSERT. Он определяет, сколько операций INSERT выполняются одновременно, а значит, влияет на пиковое потребление памяти сервером ClickHouse и частоту возникновения ошибки 241. Фактический размер пула можно проверить запросом GET _nodes/stats/thread_pool (пул ism_data_migration).
Еще один значительный потребитель памяти - фоновые слияния кусков. По умолчанию ClickHouse применяет вертикальный алгоритм слияния только к кускам, содержащим не менее vertical_merge_algorithm_min_rows_to_activate строк (по умолчанию 131072) и vertical_merge_algorithm_min_columns_to_activate колонок (по умолчанию 11). Для меньших кусков используется горизонтальный алгоритм. На таблицах примерно от 300 колонок горизонтальные слияния мелких кусков могут потреблять в несколько раз больше памяти, чем один INSERT. Подробнее о соотношении потребления памяти и времени слияния см. в разделе Горизонтальные и вертикальные слияния.
Рекомендации
- Оцените потребление памяти одним
INSERTпо формуле выше и подберитеmax_memory_usage_for_userв ClickHouse. При пуле от 16 потоков одинINSERTдолжен занимать не более ≈ 60 % лимита (по замерам: перенос при 58 % лимита прошел успешно, при 75 % завершился ошибкой). При пуле из 1-2 потоков - не более ≈ 88 %; большие значения не проверялись - Для широких таблиц (примерно от 1000 колонок) постепенно уменьшайте
sm.ism.move_to_click.threadpool.size, пока перенос не будет укладываться в лимит памяти. После каждого изменения требуется перезапуск Data Storage. При лимитеmax_memory_usage_for_user8 ГиБ пул из 16 потоков по замерам успешно перенес таблицы с 990 и 1040 колонками, но не с 1340; ориентир - примерно 1000-1100 колонок. Для пула из 1-2 потоков проверены таблицы только до 1590 колонок (88 % лимита, успешный перенос). Таблицы с большим числом колонок при таком размере пула не тестировались. Около 1800 колонок одинINSERTдостигает 8 ГиБ по расчетной модели: это теоретическая граница, не подтвержденная замерами. Для таблиц до примерно 590 колонок риск нехватки памяти ниже даже при большом пуле, а перенос выполняется быстрее - Не уменьшайте
fetch_sizeдля экономии памяти. На таблице с 590 колонками приfetch_size2500 потребление памяти однимINSERTпочти не снизилось по сравнению сfetch_size10000 (≈ 2,67 ГиБ фиксированной части и ≈ 31 КиБ на строку против 2,68 ГиБ). При этом перенос чаще достигает лимита из-за увеличения частоты операцийINSERT. На другой таблице с 290 колонками увеличениеfetch_sizeдо 40000, напротив, заметно повысило потребление памяти (1,88 ГиБ против 1,36 ГиБ приfetch_size10000). Рекомендуемое значениеfetch_size- 10000 (значение по умолчанию для действия) - Учитывайте общий лимит ClickHouse
max_server_memory_usageотдельно отmax_memory_usage_for_user. Пиковое потребление памяти сервером складывается из фоновой нагрузки, параллельных операцийINSERTи слияний и может превышать лимит одного пользователя (по замерам для пула из 16 потоков: ≈ 10,3-10,5 ГиБ приmax_memory_usage_for_user8 ГиБ) - По возможности сокращайте число колонок целевой таблицы: удаляйте неиспользуемые поля из маппинга или шаблона. Потребление памяти растет линейно с числом колонок
Горизонтальные и вертикальные слияния
ClickHouse поддерживает два алгоритма слияния кусков:
- горизонтальный (по умолчанию для кусков ниже пороговых значений) - читает и объединяет все колонки куска одновременно; буферы выделяются сразу для каждой колонки и каждого куска. На широких таблицах это наиболее затратный по памяти вариант
- вертикальный - сначала объединяет только колонки сортировки (
ORDER BY) и сохраняет порядок перестановки строк, затем обрабатывает остальные колонки по одной. Потребляет меньше памяти, чем горизонтальный алгоритм, но увеличивает время слияния
Для мелких кусков вертикальный алгоритм можно включить на конкретной таблице:
ALTER TABLE <db>.<table> MODIFY SETTING vertical_merge_algorithm_min_rows_to_activate = 1;
По замерам, применение этой настройки сразу после создания целевой таблицы снижает пиковое потребление памяти сервером на таблице с 290 колонками с 7,2 до 4,5 ГиБ.
Снижение порога не уменьшает расход ресурсов процессора и диска на слияние. Оно снижает пиковое потребление памяти за счет увеличения времени слияния. Если слияния уже завершаются между запусками переноса данных, снижение порога может не дать выигрыша.
Задача завершается с ошибкой User memory limit exceeded (код 241)
Потребление памяти одной или нескольких одновременно выполняющихся операций INSERT превысило лимит max_memory_usage_for_user ClickHouse.
Что делать
- Оцените потребление памяти одним
INSERTпо формуле из раздела Память одного INSERT и сравните с лимитом - Уменьшите
sm.ism.move_to_click.threadpool.sizeи перезапустите Data Storage. Это сократит число одновременно выполняющихся операцийINSERT, но не потребление памяти однимINSERT - Если ресурсы хоста ClickHouse позволяют, увеличьте
max_memory_usage_for_user - Сократите число колонок целевой таблицы
Пример ошибки в статусе задачи:
Code: 241. DB::Exception: User memory limit exceeded: would use 8.02 GiB (attempt to allocate chunk of 4.16 MiB bytes), maximum: 8.00 GiB
Задача завершается с ошибкой (total) memory limit exceeded (код 241)
Совокупная нагрузка на сервер ClickHouse (фоновые процессы, операции INSERT, слияния и другие задачи) превысила общий лимит max_server_memory_usage. Вместе с этой ошибкой может появиться код 210 (NETWORK_ERROR): соединение разорвано после прерывания запроса, отдельного устранения эта ошибка не требует.
Что делать
- Включите вертикальные слияния, если они еще не включены (см. Горизонтальные и вертикальные слияния)
- Снизьте размер пула
sm.ism.move_to_click.threadpool.size - Проверьте, не делит ли ClickHouse хост с другими ресурсоемкими процессами
- Не снимайте ограничение
max_memory_usage_for_userполностью. Это не поможет при достижении общего лимита сервера и может повлиять на другие процессы на хосте
Пример ошибки в статусе задачи:
Code: 241. DB::Exception: (total) memory limit exceeded: would use 14.03 GiB, current RSS: 8.24 GiB, maximum: 14.03 GiB
В логе задачи много ошибок 241 (ExceptionWhileProcessing), но итоговый статус DONE
Промежуточные ошибки 241 в журнале не означают сбой, если итоговый статус задачи - DONE. По замерам такие ошибки чаще возникают на широких таблицах при большом пуле: на таблицах с 990-1040 колонками при пуле из 16 потоков зафиксировано 25-70 повторов ExceptionWhileProcessing с кодом 241, а при пуле из 1-2 потоков - единичные повторы. Сами по себе эти записи не означают, что задача завершилась с ошибкой.
Что делать
Выполните действия из раздела Задача завершается с ошибкой User memory limit exceeded, не дожидаясь перехода задачи в состояние FAILED.
Перенос данных замедлен: в журнале ClickHouse появляется SOCKET_TIMEOUT или сообщение об обрыве соединения при нормальном потреблении памяти
Клиент не успевает передать данные за время, заданное параметром ClickHouse http_receive_timeout (по умолчанию 30 секунд).
Что делать
Перед изменением таймаута проверьте настройки сборщика мусора в jvm.options узла Data Storage. По замерам, нестандартные опции GC (например, -XX:ConcGCThreads=1 -XX:ParallelGCThreads=1 -XX:+ShenandoahVerify -XX:ShenandoahRegionSize=256K) замедлили перенос данных и вызвали обрывы соединения по таймауту.
- Проверьте примененные опции запросом
GET _nodes/jvm?filter_path=nodes.*.jvm.input_arguments(полеjvm.input_arguments) - Если обнаружены нестандартные опции GC, удалите их и перезапустите узел
- Если опции JVM соответствуют ожидаемым, увеличьте
http_receive_timeoutпользователя ClickHouse, от имени которого выполняется перенос данных
Задача move_to_click не запускается: нет статуса и целевой таблицы
Возможная причина - неверный connection_id. В этом случае модуль не создает статус задачи и целевую таблицу, а ошибка записывается только в журнал узла Data Storage:
[ERROR][c.s.i.a.m.MoveToClickTransportAction] Move to click action ended exceptionally: Failed execution
...
Caused by: java.lang.Exception: Can't find connection with id '<connection_id>'
Что делать
- Проверьте имя подключения к ClickHouse в конфигурации Data Storage с учетом регистра и специальных символов
- Посмотрите список настроенных подключений в
Навигационное меню-Параметры системы-Настройки модулей-Список конфигураций(см. Настройки конфигураций Search Anywhere и ClickHouse)
Растет parent.tripped или возникают ошибки 429 circuit_breaking_exception при запросах к Data Storage во время переноса данных
Фактическое использование памяти кучи приближается к лимиту родительского Circuit Breaker. Это возможно при большом пуле и широких таблицах (примерно от 1000 колонок): по замерам, при пуле из 16 потоков использование кучи достигало около 95 % лимита. Сам по себе рост parent.tripped не привел к сбою переноса данных: запросы, завершившиеся с кодом 429, модуль повторяет автоматически. Число повторов и задержки задаются параметрами cb_retries_max_retries, cb_retries_initial_delay_millis и cb_retries_max_delay_millis действия Move to ClickHouse (см. таблицу параметров). После исчерпания лимита повторов задача завершится с ошибкой. Если parent.tripped систематически растет, уменьшите размер пула до исчерпания лимита повторов.
Что делать
Если это мешает другим задачам в кластере, уменьшите размер пула sm.ism.move_to_click.threadpool.size.
После FAILED или отмены (cancel) в целевой таблице больше строк, чем показывал docsWritten
После отмены или сбоя пул еще некоторое время выполняет задачи, ранее поставленные в очередь (по замерам - несколько минут): cancel не очищает очередь пула. За это время в целевую таблицу могут попасть дополнительные строки сверх значения docsWritten на момент отмены или сбоя.
Что делать
Автоматическая очистка из пункта 2 ниже работает только для таблиц, автоматически созданных действием Move to ClickHouse: они партиционированы по индексу и шарду. Если целевая таблица создана вручную с другим ключом партиционирования, автоматическая очистка может не обнаружить данные для удаления. Перед повторным переносом очистите такую таблицу вручную или укажите новое имя через table_name_template. Поведение проверено для обычной локальной таблицы без параметров действия replication и distributed. Для реплицируемых и распределенных таблиц оно отдельно не проверялось.
- Дождитесь, пока число документов в целевой таблице перестанет расти, и повторите проверку через несколько минут. Одной проверки недостаточно: пул записывает данные пакетами, и паузу между ними можно принять за завершение работы очереди
- После этого повторно запустите перенос в ту же таблицу. При каждой новой попытке модуль удаляет партиции предыдущего запуска для соответствующих индекса и шарда (
ALTER TABLE ... DROP PARTITION) перед записью новых данных. Новое имя таблицы и ручная очистка не требуются для автоматически созданных таблиц (см. предупреждение выше) - Если ждать нежелательно, перезапустите узел Data Storage, на котором выполняется перенос, чтобы немедленно остановить все задачи пула. Определите узел запросом
GET _nodes/stats/thread_pool?filter_path=nodes.*.name,nodes.*.thread_pool.ism_data_migration: у него ненулевые значенияactiveилиqueueдля пулаism_data_migration, а имя указано в полеname
docsWritten не растет после повторного запуска переноса индекса, ранее перенесенного полностью или с ошибкой
Счетчик docsWritten в статусе задачи обновляется по мере поступления новых пакетов данных. Шарды индекса обрабатываются последовательно. Если индекс ранее был перенесен полностью или частично, до поступления первого нового пакета в статусе отображается результат предыдущего запуска. Поэтому может показаться, что docsWritten не растет.
Что делать
-
Дождитесь поступления первого нового пакета данных: после этого статус обновится
-
Статус переноса хранится отдельно для каждого шарда в документах с идентификаторами вида
<uuid индекса>:<номер шарда>. Сначала получите UUID индекса:GET /_cat/indices/<имя_индекса>?h=uuid -
Запросите статусы всех шардов индекса (число идентификаторов соответствует числу шардов, нумерация начинается с 0):
POST .sm_ism_data_migration_statuses/_mget{"ids": ["<uuid индекса>:0","<uuid индекса>:1"]}Добавьте по одному идентификатору для каждого шарда индекса. Повторите запрос через некоторое время и сравните
docsWrittenпо каждому шарду. Если значение меняется, перенос продолжается -
При необходимости проверьте данные непосредственно в
system.query_logилиsystem.part_logClickHouse