Описанный в этом руководстве материал применим и к другим Объектным хранилищам с собственными специализированными табличными функциями, таким как GCS и Azure Blob Storage.
Механизм вставки (один узел)
Размер блока вставки
INSERT INTO SELECT ClickHouse получает некоторую порцию данных и ① формирует из полученных данных (как минимум) один блок вставки в памяти (на каждый ключ партиционирования). Данные в блоке сортируются, и к ним применяются оптимизации, специфичные для движка таблицы. Затем данные сжимаются и ② записываются в хранилище базы данных в виде новой части данных.
Размер блока вставки влияет как на операции дискового ввода-вывода, так и на использование памяти сервера ClickHouse. Более крупные блоки вставки требуют больше памяти, но создают более крупные и менее многочисленные начальные части. Чем меньше частей ClickHouse нужно создать для загрузки большого объёма данных, тем меньше требуется операций дискового ввода-вывода и автоматических фоновых слияний.
При использовании запроса INSERT INTO SELECT в сочетании с интеграционным движком таблицы или табличной функцией данные извлекаются сервером ClickHouse:
Пока данные не будут полностью загружены, сервер выполняет цикл:
min_insert_block_size_rows(по умолчанию:1048545строк)min_insert_block_size_bytes(по умолчанию:256 MiB)
min_insert_block_size_bytes обозначает размер несжатого блока в памяти (а не размер сжатой части на диске). Также учтите, что создаваемые блоки и части редко содержат ровно заданное количество строк или байтов, поскольку ClickHouse передаёт и обрабатывает данные блоками строк. Поэтому эти настройки задают минимальные пороги.
Учитывайте слияния
MergeTree).
Параллелизм вставки
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 и движка 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 глобально доступен и обычно обеспечивает высокую пропускную способность сети.
Пример набора данных
SELECT, при которых клиенту возвращаются большие объёмы данных, используйте формат Null или направляйте результаты в движок Null. Это позволит избежать перегрузки клиента данными и насыщения сети.
При выполнении запросов на чтение первый запрос часто оказывается медленнее, чем повторный запуск того же запроса. Это может быть связано как с собственным кэшированием S3, так и с кэшем автоопределения схемы ClickHouse. В нём хранится определённая схема файлов, поэтому при последующих обращениях этап определения схемы можно пропустить, что сокращает время выполнения запроса.
Использование потоков для чтения
- Обычно значения
max_threadsпо умолчанию достаточно, то есть оно равно числу ядер. Если запрос потребляет много памяти и это нужно сократить, либо еслиLIMITдля результатов невелик, это значение можно уменьшить. Если памяти достаточно, можно поэкспериментировать с увеличением этого значения, чтобы потенциально повысить пропускную способность чтения из S3. Как правило, это полезно только на машинах с небольшим числом ядер, то есть < 10. Эффект от дальнейшего распараллеливания обычно снижается, поскольку узким местом становятся другие ресурсы, например сеть и конкуренция за CPU. - Версии 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равно 10 MiB. В некоторых случаях можно безопасно увеличить размер этого буфера до 50 MB (max_download_buffer_size=52428800), чтобы файлы меньшего размера загружались только одним потоком. Это может сократить время, которое каждый поток тратит на вызовы S3, и тем самым уменьшить время ожидания S3. Пример см. в этом посте блога.
system.query_log.
Вернемся к нашему предыдущему запросу: увеличение max_threads вдвое, до 16 (значение max_thread по умолчанию равно числу ядер на узле), повышает производительность запроса на чтение в 2 раза ценой большего потребления памяти. Дальнейшее увеличение max_threads дает убывающую отдачу, как показано ниже.
Настройка потоков и размера блока для вставок
- Чем больше размер блока вставки, тем меньше частей ClickHouse нужно создавать и тем меньше требуется дискового ввода-вывода и фоновых слияний.
- Чем больше число параллельных потоков вставки, тем быстрее будут обрабатываться данные.
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%. Предлагаем читателю самостоятельно проверить, удастся ли ещё больше повысить производительность на одном узле.
Масштабирование за счет ресурсов и узлов
Вертикальное масштабирование
Отдельные узлы также могут упираться в ограничения пропускной способности сети и запросов S3 GET, что не позволяет добиться линейного роста производительности при вертикальном масштабировании.
Горизонтальное масштабирование
s3Cluster, как описано в разделе Использование кластеров. Это позволяет распределить чтение между узлами.
Сервер, который первым получает запрос на вставку, сначала разворачивает glob-шаблон, а затем динамически распределяет обработку каждого совпавшего файла между собой и другими серверами.
Мы повторяем предыдущий запрос на чтение, распределяя рабочую нагрузку между 3 узлами и изменяя запрос так, чтобы использовать s3Cluster. В ClickHouse Cloud это выполняется автоматически при обращении к кластеру default.
Как отмечено в разделе Использование кластеров, эта работа распределяется на уровне файлов. Чтобы воспользоваться этой возможностью, потребуется достаточное количество файлов, то есть как минимум больше, чем число узлов.
s3Cluster, операции вставки выполняются через узел-инициатор. Это означает, что, хотя чтение происходит на каждом узле, полученные строки отправляются на инициатор для дальнейшего распределения. В сценариях с высокой пропускной способностью это может стать узким местом. Чтобы избежать этого, задайте параметр parallel_distributed_insert_select для функции s3cluster.
Если задать parallel_distributed_insert_select=2, SELECT и INSERT будут выполняться на каждом сегменте — из/в базовую таблицу движка Distributed на каждом узле.
Дополнительные настройки
Отключение дедупликации
INSERT INTO SELECT из объектного хранилища. Если отключить эту функциональность во время вставки, можно повысить производительность, как показано ниже:
Оптимизация при вставке
optimize_on_insert определяет, будут ли части данных объединяться в процессе вставки. Когда он включен (optimize_on_insert = 1 по умолчанию), небольшие части объединяются в более крупные по мере вставки, что повышает производительность запросов за счет сокращения числа частей, которые нужно читать. Однако такое слияние создает дополнительную нагрузку на процесс вставки и может замедлять вставки при высокой интенсивности записи.
Отключение этого параметра (optimize_on_insert = 0) пропускает слияние во время вставки, позволяя записывать данные быстрее, особенно при частых небольших вставках. Процесс слияния откладывается и выполняется в фоновом режиме, что повышает производительность вставки, но временно увеличивает количество небольших частей, из-за чего запросы могут выполняться медленнее, пока не завершится фоновое слияние. Этот параметр оптимален, когда приоритетом является производительность вставки, а фоновый процесс слияния может позже эффективно выполнить оптимизацию. Как показано ниже, отключение этого параметра может повысить пропускную способность вставки:
Прочие замечания
- При нехватке памяти, если выполняете вставку в S3, можно уменьшить значение
max_insert_delayed_streams_for_parallel_write.