Перейти к основному содержимому
Перейти к основному содержимому

Оптимизация производительности вставки и чтения из S3

В этом разделе рассматривается оптимизация производительности при чтении и вставке данных из S3 с использованием табличных функций s3.

к сведению

Урок, описанный в этом руководстве, может быть применен к другим реализациям объектного хранилища с собственными специализированными табличными функциями, таким как GCS и Azure Blob storage.

Прежде чем настраивать потоки и размеры блоков для улучшения производительности вставки, мы рекомендуем пользователям понять механику вставки в S3. Если вы знакомы с механикой вставки или просто хотите получить несколько быстрых советов, переходите к нашему примеру ниже.

Механика вставки (одиночный узел)

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

Размер блока вставки

При выполнении INSERT INTO SELECT ClickHouse получает некоторую часть данных и ① формирует (по крайней мере) один блок вставки в памяти (на каждый ключ партиционирования) из полученных данных. Данные блока сортируются, и применяются оптимизации, специфичные для движка таблицы. Затем данные компрессируются и ② записываются в хранилище базы данных в виде новой части данных.

Размер блока вставки влияет как на использование ввода-вывода файлов диска, так и на использование памяти сервера ClickHouse. Более крупные блоки вставки используют больше памяти, но создают большие и меньшие начальные части. Чем меньше частей ClickHouse нужно создать для загрузки большого объема данных, тем меньше требуется ввода-вывода файлов диска и автоматических фоновый слияний.

При использовании запроса INSERT INTO SELECT в сочетании с интеграционным движком таблицы или табличной функцией, данные извлекаются сервером ClickHouse:

Пока данные не будут полностью загружены, сервер выполняет цикл:

В ① размер зависит от размера блока вставки, который можно контролировать с помощью двух настроек:

Когда либо заданное количество строк собирается в блок вставки, либо достигается заданное количество данных (что происходит первым), то это вызывает запись блока в новую часть. Цикл вставки продолжается на шаге ①.

Обратите внимание, что значение min_insert_block_size_bytes обозначает размер блока в памяти до сжатия (а не размер части на диске после сжатия). Также обратите внимание, что созданные блоки и части редко точно содержат заданное количество строк или байтов, потому что ClickHouse обрабатывает и обрабатывает данные построчно-блоками. Поэтому эти настройки задают минимальные пороговые значения.

Будьте внимательны к слияниям

Чем меньше установленный размер блока вставки, тем больше стартовых частей создается для большой загрузки данных, и тем больше фоновых слияний частей выполняется одновременно с приемом данных. Это может привести к конфликту ресурсов (CPU и памяти) и потребовать дополнительного времени (для достижения здорового (3000) количества частей) после завершения загрузки.

к сведению

Производительность запросов ClickHouse будет отрицательно затронута, если количество частей превысит рекомендуемые пределы.

ClickHouse будет постоянно сливать части в более крупные части до тех пор, пока они не достигнут сжатого размера ~150 GiB. Эта диаграмма показывает, как сервер ClickHouse сливает части:

Единственный сервер ClickHouse использует несколько потоков фонового слияния для выполнения параллельных слияний частей. Каждый поток выполняет цикл:

Обратите внимание, что увеличение количества ядер CPU и объема RAM увеличивает пропускную способность фоновых слияний.

Части, которые были слиты в более крупные части, помечаются как неактивные и в конечном итоге удаляются после настраиваемого количества минут. Со временем это создает дерево слитых частей (отсюда и название MergeTree таблицы).

Параллелизм вставки

Сервер ClickHouse может обрабатывать и вставлять данные параллельно. Уровень параллелизма вставки влияет на пропускную способность загрузки и использование памяти сервера ClickHouse. Параллельная загрузка и обработка данных требует больше основной памяти, но увеличивает пропускную способность загрузки, так как данные обрабатываются быстрее.

Такие табличные функции, как s3, позволяют указывать наборы имен загружаемых файлов с помощью шаблонов glob. Когда шаблон glob соответствует нескольким существующим файлам, ClickHouse может параллелизировать чтение по этим файлам и вставлять данные в таблицу, используя параллельно работающие потоки вставки (по серверу):

Пока все данные из всех файлов не будут обработаны, каждый поток вставки выполняет цикл:

Количество таких параллельных потоков вставки можно настроить с помощью настройки max_insert_threads. Значение по умолчанию равно 1 для открытого кода ClickHouse и 4 для ClickHouse Cloud.

При большом количестве файлов параллельная обработка несколькими потоками вставки работает хорошо. Она может полностью задействовать как доступные ядра CPU, так и пропускную способность сети (при параллельных загрузках файлов). В сценариях, когда будет загружено лишь несколько крупных файлов, ClickHouse автоматически устанавливает высокий уровень параллелизма обработки данных и оптимизирует использование пропускной способности сети, создавая дополнительные потоки чтения на каждый поток вставки для чтения (загрузки) более четких диапазонов в крупных файлах параллельно.

Для функции и таблицы s3 параллельная загрузка отдельного файла определяется значениями max_download_threads и max_download_buffer_size. Файлы будут загружаться параллельно только в том случае, если их размер превышает 2 * max_download_buffer_size. По умолчанию значение max_download_buffer_size установлено на 10MiB. В некоторых случаях вы можете безопасно увеличить этот размер буфера до 50 MB (max_download_buffer_size=52428800), ставя цель, чтобы каждый файл загружался одним потоком. Это может сократить время, которое каждый поток проводит на вызовах S3, и тем самым снизить время ожидания S3. Более того, для файлов, которые слишком малы для параллельного чтения, чтобы увеличить пропускную способность, ClickHouse автоматически предварительно загружает данные, предварительно читая такие файлы асинхронно.

Измерение производительности

Оптимизация производительности запросов с использованием табличных функций S3 необходима как при выполнении запросов к данным на месте, т.е. при произвольном запросе, где используется только вычисление ClickHouse, а данные остаются в S3 в своем исходном формате, так и при вставке данных из S3 в движок таблиц ClickHouse MergeTree. Если иное не указано, следующие рекомендации применимы к обоим сценариям.

Влияние размера оборудования

Количество доступных ядер CPU и объем RAM влияют на:

и, следовательно, на общую пропускную способность загрузки.

Региональная локализация

Убедитесь, что ваши корзины находятся в том же регионе, что и ваши экземпляры ClickHouse. Эта простая оптимизация может значительно улучшить производительность пропускной способности, особенно если вы разворачиваете свои экземпляры ClickHouse на инфраструктуре AWS.

Форматы

ClickHouse может читать файлы, хранящиеся в корзинах S3, в поддерживаемых форматах с использованием функции s3 и движка S3. Если читать необработанные файлы, некоторые из этих форматов имеют явные преимущества:

  • Форматы с закодированными именами столбцов, такие как Native, Parquet, CSVWithNames и TabSeparatedWithNames, будут менее громоздкими для запросов, так как пользователю не придется указывать имя столбца в функции s3. Имена столбцов позволяют вывести эту информацию.
  • Форматы будут отличаться по производительности в отношении чтения и записи. Native и parquet представляют собой наиболее оптимальные форматы для производительности чтения, поскольку они уже ориентированы на столбцы и более компактны. Кроме того, native формат выгодно сопоставляется с тем, как ClickHouse хранит данные в памяти, что снижает накладные расходы на обработку при потоковой передаче данных в ClickHouse.
  • Размер блока часто влияет на задержку чтения больших файлов. Это очень заметно, если вы только извлекаете данные, например, возвращая верхние N строк. В случае форматов, таких как CSV и TSV, файлы должны быть обработаны для возвращения набора строк. Однако такие форматы, как Native и Parquet, позволят извлекать данные быстрее.
  • Каждый формат сжатия приносит свои плюсы и минусы, часто балансируя уровень сжатия скоростью и смещая производительность сжатия или разжатия. Если сжимать необработанные файлы, такие как CSV или TSV, lz4 предлагает наилучшую производительность разжатия, жертвуя уровнем сжатия. Gzip обычно сжимает лучше за счет немного более медленных скоростей чтения. Xz идет еще дальше, обычно обеспечивая лучшее сжатие с самой низкой производительностью сжатия и разжатия. Если экспортировать, Gz и lz4 предлагают сопоставимые скорости сжатия. Балансируйте это с вашими скоростями соединения. Любые выгоды от более быстрого разжатия или сжатия будут легко нивелированы медленным соединением с вашими корзинами s3.
  • Форматы, такие как native или parquet, обычно не оправдывают накладные расходы на сжатие. Любые сэкономленные объемы данных, вероятно, будут минимальными, поскольку эти форматы по своей природе компактны. Время, затрачиваемое на сжатие и разжатие, редко компенсирует время передачи по сети - особенно поскольку s3 доступно глобально с более высокой пропускной способностью сети.

Пример набора данных

Чтобы продемонстрировать дополнительные возможные оптимизации, мы будем использовать посты из набора данных Stack Overflow - оптимизируя как производительность запросов, так и вставки этих данных.

Этот набор данных состоит из 189 файлов Parquet, по одному на каждый месяц с июля 2008 года по март 2024 года.

Обратите внимание, что мы используем Parquet для производительности, в соответствии с нашими рекомендациями выше, выполняя все запросы на кластере ClickHouse, расположенном в том же регионе, что и корзина. Этот кластер имеет 3 узла, каждый из которых имеет 32GiB оперативной памяти и 8 vCPUs.

Без настройки мы демонстрируем производительность вставки этого набора данных в движок MergeTree, а также выполнение запроса для вычисления пользователей, задающих наибольшее количество вопросов. Оба этих запроса требуют полной выборки данных.

В нашем примере мы возвращаем только несколько строк. Если измерять производительность запросов SELECT, при которых большие объемы данных возвращаются клиенту, либо используйте формат null для запросов, либо направляйте результаты в Null engine. Это должно предотвратить переполнение клиента данными и насыщение сети.

к сведению

При чтении из запросов начальный запрос может казаться медленнее, чем если повторить тот же запрос. Это можно объяснить как кэшированием самого S3, так и Кэшем вывода схемы ClickHouse. Этот кэш хранит выведенную схему для файлов и означает, что шаг вывода может быть пропущен при последующих обращениях, тем самым уменьшая время запроса.

Использование потоков для чтений

Производительность чтения из S3 будет увеличиваться линейно с количеством ядер, при условии, что вы не ограничены пропускной способностью сети или локальным вводом-выводом. Увеличение количества потоков также имеет дополнительные накладные расходы по памяти, о которых пользователи должны знать. Следующие параметры могут быть изменены для потенциального улучшения производительности чтения:

  • Обычно значение по умолчанию max_threads является достаточным, т.е. равно количеству ядер. Если объем памяти, используемой для запроса, высок, и это необходимо сократить, или если LIMIT на результаты низок, это значение можно установить ниже. Пользователи с большим объемом памяти могут желать поэкспериментировать с увеличением этого значения для возможного повышения производительности чтения из S3. Обычно это полезно только на машинах с меньшим количеством ядер, т.е. < 10. Польза от дальнейшей параллелизации обычно уменьшается, так как другие ресурсы выступают в роли узкого места, например, конкуренция за сеть и ЦП.
  • Версии ClickHouse до 22.3.1 параллелизировали чтения только среди нескольких файлов при использовании функции s3 или движка S3. Это требовало от пользователя обеспечения разбиения файлов на части в S3 и чтения с использованием шаблона glob для достижения оптимальной производительности чтения. Более поздние версии теперь параллелизируют загрузки в пределах файла.
  • В сценариях с низким количеством потоков пользователи могут извлечь выгоду от установки remote_filesystem_read_method в "read", чтобы вызвать синхронное чтение файлов из S3.
  • Для функции и таблицы s3 параллельная загрузка отдельного файла определяется значениями max_download_threads и max_download_buffer_size. В то время как max_download_threads контролирует количество используемых потоков, файлы будут загружаться параллельно только в том случае, если их размер превышает 2 * max_download_buffer_size. По умолчанию значение max_download_buffer_size установлено на 10MiB. В некоторых случаях вы можете безопасно увеличить этот размер буфера до 50 MB (max_download_buffer_size=52428800), чтобы гарантировать, что меньшие файлы загружаются только одним потоком. Это может сократить время, которое каждый поток проводит на вызовах S3, и тем самым снизить время ожидания S3. См. этот блог для примера.

Прежде чем вносить изменения для улучшения производительности, убедитесь, что вы проводите измерения должным образом. Поскольку вызовы API S3 чувствительны к задержкам и могут повлиять на время клиента, используйте журнал запросов для высококачественных временных показателей, т.е. system.query_log.

Рассмотрим наш предыдущий запрос, удвоив max_threads до 16 (по умолчанию max_thread - это количество ядер на узле) и улучшив производительность нашего запроса на чтение в 2 раза за счет большей памяти. Дальнейшее увеличение max_threads имеет ухудшающуюся отдачу, как показано.

Настройка потоков и размера блока для вставок

Для достижения максимальной производительности загрузки необходимо выбрать (1) размер блока вставки и (2) соответствующий уровень параллелизма вставки на основе (3) количества доступных ядер CPU и RAM. В итоге:

Существует конфликтующий компромисс между этими двумя факторами производительности (плюс компромисс с фоновым слиянием частей). Объем доступной основной памяти серверов ClickHouse ограничен. Большие блоки используют больше основной памяти, что ограничивает количество параллельных потоков вставки, которые мы можем использовать. Обратная сторона — больший число параллельных потоков вставки требует больше основной памяти, так как количество потоков вставки определяет количество блоков вставки, создаваемых в памяти одновременно. Это ограничивает возможный размер блоков вставки. Кроме того, может происходить конфликт ресурсов между потоками вставки и потоками фонового слияния. Высокое количество настроенных потоков вставки (1) создает больше частей, которые нужно слить, и (2) отнимает ядра ЦП и память у потоков фонового слияния.

Для детального описания того, как поведение этих параметров влияет на производительность и ресурсы, мы рекомендуем прочитать этот блог. Как описано в этом блоге, настройка может требовать тщательного баланса между двумя параметрами. Это исчерпывающее тестирование часто является непрактичным, поэтому в резюме мы рекомендуем:

С помощью этой формулы вы можете установить min_insert_block_size_rows в 0 (чтобы отключить порог на основе строк), при этом установив max_insert_threads на выбранное значение и min_insert_block_size_bytes на рассчитанный результат из вышеуказанной формулы.

Используя эту формулу с нашим ранее приведенным примером Stack Overflow.

  • max_insert_threads=4 (8 ядер на узел)
  • peak_memory_usage_in_bytes - 32 GiB (100% ресурсов узла) или 34359738368 байт.
  • min_insert_block_size_bytes = 34359738368/(3*4) = 2863311530

Как показано, настройка этих параметров улучшила производительность вставки более чем на 33%. Мы оставим это читателям, чтобы они могли попытаться улучшить производительность одиночного узла.

Масштабирование с ресурсами и узлами

Масштабирование с ресурсами и узлами применяется как к запросам на чтение, так и на вставку.

Вертикальное масштабирование

Все предыдущие настройки и запросы использовали только один узел в нашем кластере ClickHouse Cloud. Пользователи также часто имеют более одного узла ClickHouse. Мы рекомендуем пользователям сначала масштабировать вертикально, улучшая пропускную способность S3 линейно с количеством ядер. Если мы повторим наши предыдущие запросы на вставку и чтение на более крупном узле ClickHouse Cloud с удвоенной количеством ресурсов (64GiB, 16 vCPUs) с соответствующими настройками, оба будут выполняться приблизительно в два раза быстрее.

примечание

Отдельные узлы также могут быть ограничены сетевыми и S3 GET запросами, что мешает линейному масштабированию производительности вертикально.

Горизонтальное масштабирование

В конечном итоге горизонтальное масштабирование часто необходимо из-за доступности оборудования и экономичности. В ClickHouse Cloud производственные кластеры имеют как минимум 3 узла. Пользователи также могут желать принимать во внимание использование всех узлов для вставки.

Использование кластера для чтения из S3 требует использования функции s3Cluster, как описано в Использовании кластеров. Это позволяет распределять чтение между узлами.

Сервер, который первоначально получает запрос на вставку, сначала разрешает шаблон glob, а затем динамически распределяет обработку каждого соответствующего файла на себя и другие серверы.

Мы повторяем наш предыдущий запрос на чтение, распределяя рабочую нагрузку по 3 узлам и настраивая запрос для использования s3Cluster. Это выполняется автоматически в ClickHouse Cloud, ссылаясь на кластер default.

Как указано в Использовании кластеров, эта работа распределена на уровне файлов. Чтобы воспользоваться этой функцией, пользователям потребуется достаточное количество файлов, т.е. больше, чем количество узлов.

Аналогично, наш запрос на вставку может быть распределен, используя улучшенные настройки, выявленные ранее для одиночного узла:

Читатели заметят, что чтение файлов улучшило производительность запросов, но не вставок. По умолчанию, несмотря на то, что чтения распределены с использованием s3Cluster, вставки произойдут на узле инициаторе. Это означает, что хотя чтения произойдут на каждом узле, результирующие строки будут отправлены на инициатор для распределения. В сценариях с высокой пропускной способностью это может оказаться узким местом. Чтобы решить эту проблему, установите параметр parallel_distributed_insert_select для функции s3cluster.

Установив значение parallel_distributed_insert_select=2, вы гарантируете, что SELECT и INSERT будут выполняться на каждом шарде от/к подлежащей таблице распределенного движка на каждом узле.

Как ожидалось, это снизило производительность вставки в 3 раза.

Дальнейшая настройка

Отключить дедупликацию

Операции вставки иногда могут завершаться ошибками, такими как тайм-ауты. Когда операции вставки терпят неудачу, данные могли быть успешно вставлены или нет. Чтобы позволить клиенту безопасно повторить вставку, по умолчанию в распределенных развертываниях, таких как ClickHouse Cloud, ClickHouse пытается определить, были ли данные успешно вставлены ранее. Если вставленные данные помечены как дублирующиеся, ClickHouse не вставляет их в целевую таблицу. Тем не менее, пользователь все равно получит статус успешной операции, как если бы данные были вставлены нормально.

Хотя такое поведение, которое подразумевает дополнительные затраты на вставку, имеет смысл при загрузке данных с клиента или партиями, оно может быть ненужным при выполнении INSERT INTO SELECT из объектного хранилища. Отключив эту функциональность во время вставки, мы можем улучшить производительность, как показано ниже:

Оптимизация при вставке

В ClickHouse настройка optimize_on_insert контролирует, выполняется ли слияние частей данных в процессе вставки. При включенной настройке (optimize_on_insert = 1 по умолчанию) небольшие части объединяются в более крупные по мере вставки, что улучшает производительность запросов за счет сокращения количества частей, которые необходимо прочитать. Однако это слияние добавляет дополнительные нагрузки к процессу вставки, что может замедлить вставку данных с высокой пропускной способностью.

Отключение этой настройки (optimize_on_insert = 0) пропускает слияние при вставках, позволяя быстрее записывать данные, особенно при частых небольших вставках. Процесс слияния откладывается на фон, что обеспечивает лучшую производительность вставок, но временно увеличивает количество небольших частей, что может замедлить выполнение запросов до завершения фонового слияния. Эта настройка идеальна, когда производительность вставки является приоритетом, и фоновый процесс слияния может эффективно выполнить оптимизацию позже. Как показано ниже, отключение настройки может улучшить пропускную способность вставки:

Разные заметки

  • В сценариях с низким объемом памяти рассмотрите возможность уменьшения max_insert_delayed_streams_for_parallel_write, если вставляете в S3.