Перейти к основному содержимому
Версия: 6.2

Траблшутинг переливки данных в 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. Подробнее о соотношении потребления памяти и времени слияния см. в разделе Горизонтальные и вертикальные слияния.

Рекомендации​

  1. Оцените потребление памяти одним INSERT по формуле выше и подберите max_memory_usage_for_user в ClickHouse. При пуле от 16 потоков один INSERT должен занимать не более ≈ 60 % лимита (по замерам: перенос при 58 % лимита прошел успешно, при 75 % завершился ошибкой). При пуле из 1-2 потоков - не более ≈ 88 %; большие значения не проверялись
  2. Для широких таблиц (примерно от 1000 колонок) постепенно уменьшайте sm.ism.move_to_click.threadpool.size, пока перенос не будет укладываться в лимит памяти. После каждого изменения требуется перезапуск Data Storage. При лимите max_memory_usage_for_user 8 ГиБ пул из 16 потоков по замерам успешно перенес таблицы с 990 и 1040 колонками, но не с 1340; ориентир - примерно 1000-1100 колонок. Для пула из 1-2 потоков проверены таблицы только до 1590 колонок (88 % лимита, успешный перенос). Таблицы с большим числом колонок при таком размере пула не тестировались. Около 1800 колонок один INSERT достигает 8 ГиБ по расчетной модели: это теоретическая граница, не подтвержденная замерами. Для таблиц до примерно 590 колонок риск нехватки памяти ниже даже при большом пуле, а перенос выполняется быстрее
  3. Не уменьшайте fetch_size для экономии памяти. На таблице с 590 колонками при fetch_size 2500 потребление памяти одним INSERT почти не снизилось по сравнению с fetch_size 10000 (≈ 2,67 ГиБ фиксированной части и ≈ 31 КиБ на строку против 2,68 ГиБ). При этом перенос чаще достигает лимита из-за увеличения частоты операций INSERT. На другой таблице с 290 колонками увеличение fetch_size до 40000, напротив, заметно повысило потребление памяти (1,88 ГиБ против 1,36 ГиБ при fetch_size 10000). Рекомендуемое значение fetch_size - 10000 (значение по умолчанию для действия)
  4. Учитывайте общий лимит ClickHouse max_server_memory_usage отдельно от max_memory_usage_for_user. Пиковое потребление памяти сервером складывается из фоновой нагрузки, параллельных операций INSERT и слияний и может превышать лимит одного пользователя (по замерам для пула из 16 потоков: ≈ 10,3-10,5 ГиБ при max_memory_usage_for_user 8 ГиБ)
  5. По возможности сокращайте число колонок целевой таблицы: удаляйте неиспользуемые поля из маппинга или шаблона. Потребление памяти растет линейно с числом колонок

Горизонтальные и вертикальные слияния​

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.

Что делать​

  1. Оцените потребление памяти одним INSERT по формуле из раздела Память одного INSERT и сравните с лимитом
  2. Уменьшите sm.ism.move_to_click.threadpool.size и перезапустите Data Storage. Это сократит число одновременно выполняющихся операций INSERT, но не потребление памяти одним INSERT
  3. Если ресурсы хоста ClickHouse позволяют, увеличьте max_memory_usage_for_user
  4. Сократите число колонок целевой таблицы

Пример ошибки в статусе задачи:

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): соединение разорвано после прерывания запроса, отдельного устранения эта ошибка не требует.

Что делать​

  1. Включите вертикальные слияния, если они еще не включены (см. Горизонтальные и вертикальные слияния)
  2. Снизьте размер пула sm.ism.move_to_click.threadpool.size
  3. Проверьте, не делит ли ClickHouse хост с другими ресурсоемкими процессами
  4. Не снимайте ограничение 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) замедлили перенос данных и вызвали обрывы соединения по таймауту.

  1. Проверьте примененные опции запросом GET _nodes/jvm?filter_path=nodes.*.jvm.input_arguments (поле jvm.input_arguments)
  2. Если обнаружены нестандартные опции GC, удалите их и перезапустите узел
  3. Если опции 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>'

Что делать​

  1. Проверьте имя подключения к ClickHouse в конфигурации Data Storage с учетом регистра и специальных символов
  2. Посмотрите список настроенных подключений в Навигационное меню - Параметры системы - Настройки модулей - Список конфигураций (см. Настройки конфигураций 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. Для реплицируемых и распределенных таблиц оно отдельно не проверялось.

  1. Дождитесь, пока число документов в целевой таблице перестанет расти, и повторите проверку через несколько минут. Одной проверки недостаточно: пул записывает данные пакетами, и паузу между ними можно принять за завершение работы очереди
  2. После этого повторно запустите перенос в ту же таблицу. При каждой новой попытке модуль удаляет партиции предыдущего запуска для соответствующих индекса и шарда (ALTER TABLE ... DROP PARTITION) перед записью новых данных. Новое имя таблицы и ручная очистка не требуются для автоматически созданных таблиц (см. предупреждение выше)
  3. Если ждать нежелательно, перезапустите узел 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 не растет.

Что делать​

  1. Дождитесь поступления первого нового пакета данных: после этого статус обновится

  2. Статус переноса хранится отдельно для каждого шарда в документах с идентификаторами вида <uuid индекса>:<номер шарда>. Сначала получите UUID индекса:

    GET /_cat/indices/<имя_индекса>?h=uuid
  3. Запросите статусы всех шардов индекса (число идентификаторов соответствует числу шардов, нумерация начинается с 0):

    POST .sm_ism_data_migration_statuses/_mget
    {
    "ids": [
    "<uuid индекса>:0",
    "<uuid индекса>:1"
    ]
    }

    Добавьте по одному идентификатору для каждого шарда индекса. Повторите запрос через некоторое время и сравните docsWritten по каждому шарду. Если значение меняется, перенос продолжается

  4. При необходимости проверьте данные непосредственно в system.query_log или system.part_log ClickHouse

Полезные ссылки​