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

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

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

к сведению

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

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

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

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

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

Механика размера вставляемого блока в ClickHouse

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

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

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

Извлечение данных из внешних источников в ClickHouse

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

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

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

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

Имейте в виду слияния

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

к сведению

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

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

Фоновые слияния в ClickHouse

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

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

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

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

Использование ресурсов для параллелизма вставки

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

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

Параллельные потоки вставки в ClickHouse

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

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

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

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

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

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

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

Влияние размера оборудования на производительность ClickHouse

Количество доступных ядер ЦП и размер оперативной памяти влияют на:

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

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

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

Форматы

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

  • Форматы с закодированными именами колонок, такие как Native, Parquet, CSVWithNames и TabSeparatedWithNames, будут менее многословными при запросе, поскольку пользователю не нужно будет указывать имя колонки в функции s3. Имена колонок позволяют сделать этот вывод.
  • Форматы будут различаться по производительности с точки зрения пропускной способности чтения и записи. Native и parquet представляют собой наиболее оптимальные форматы для производительности чтения, так как они уже колонно-ориентированные и более компактные. Кроме того, нативный формат извлекает выгоду из согласования с тем, как 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 узла, каждый с 32 ГБ оперативной памяти и 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 МБ (max_download_buffer_size=52428800), с целью обеспечения загрузки меньших файлов одним потоком. Это может сократить время, которое каждый поток тратит на вызовы S3, и тем самым снизить время ожидания S3. Смотрите этот блог для примера этого.

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

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

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

Чтобы достичь максимальной производительности загрузки, вам необходимо выбрать (1) размер вставляемого блока и (2) подходящий уровень параллелизма вставки на основе (3) доступного количества ядер ЦП и оперативной памяти. В общем:

Существует конфликтующий компромисс между этими двумя факторами производительности (плюс компромисс с фоновыми слияниями частей). Объем доступной основной памяти для серверов 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 ГБ (100% ресурсов узла) или 34359738368 байт.
  • min_insert_block_size_bytes = 34359738368/(3*4) = 2863311530

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

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

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

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

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

примечание

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

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

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

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

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

Функция s3Cluster в ClickHouse

Мы повторяем наш предыдущий запрос на чтение, распределяя нагрузку по 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.