此博文是“加速大型 ClickHouse 数据加载
”系列的一部分
简介
ClickHouse 被设计为快速且资源高效。如果您允许,ClickHouse 可以充分利用其运行的硬件,直至理论极限,并以闪电般的速度加载数据。或者,您可以减少大型数据加载的资源使用。取决于您想要实现的目标。在本篇三部分博文系列中,我们将提供必要的知识以及指导和最佳实践,以实现大型数据加载的弹性和速度。第一部分奠定了基础,描述了 ClickHouse 中的基本数据插入机制及其三个主要因素,用于控制资源使用和性能。在第二篇博文中,我们将进入赛道,将大型数据加载的速度调整到最大。在本系列的最后一部分,我们将讨论使大型数据加载稳健并能够抵抗网络中断等瞬态问题的措施。
让我们从探索 ClickHouse 基本的数据插入机制开始。
数据插入机制
下图概述了将数据插入到 ClickHouse MergeTree 引擎系列表的常规机制: 服务器接收一些数据部分(例如,来自插入查询),并①形成(至少)一个内存中的插入块(每个分区键)。块的数据被排序,并且应用了特定于表引擎的优化。然后,数据被压缩,并②以新数据部分的形式写入数据库存储。
三个主要因素影响 ClickHouse 数据插入机制的性能和资源使用:**插入块大小**、**插入并行度**和**硬件大小**。我们将在本文的其余部分讨论这些因素以及配置它们的方法。
插入块大小
对性能和资源使用影响
插入块大小会影响 ClickHouse 服务器的磁盘文件I/O 使用情况和内存使用情况。较大的插入块使用更多内存,但会生成更大且更少的初始部分。ClickHouse 需要为加载大量数据创建的部分越少,所需的磁盘文件 I/O 和自动后台合并就越少。
如何配置插入块大小取决于数据是如何摄取的。ClickHouse 服务器本身是否拉取数据,或者外部客户端是否推送数据?
ClickHouse 服务器拉取数据时的配置
当结合使用集成表引擎或表函数使用INSERT INTO SELECT 查询时,数据将由 ClickHouse 服务器本身拉取: 在数据完全加载之前,服务器执行一个循环:① 拉取并解析下一部分数据,并从中形成一个内存数据块(每个分区键一个)。
② 将块写入存储上的新部分。
转到①
在①中,部分大小取决于插入块大小,这可以通过两个设置来控制
- min_insert_block_size_rows(默认值:1048545 行)
- min_insert_block_size_bytes(默认值:256 MB)
当在插入块中收集到指定行数或达到配置的数据量时(以先发生者为准),这将触发将块写入新部分。插入循环在步骤①继续。
请注意,min_insert_block_size_bytes
值表示未压缩的内存块大小(而不是压缩的磁盘部分大小)。此外,请注意,创建的块和部分很少精确包含配置的行数或字节数,因为 ClickHouse 正在流式传输和处理数据行-块方式。因此,这些设置指定了最小阈值。
客户端推送数据时的配置
根据客户端或客户端库使用的数据传输格式和接口,内存数据块由 ClickHouse 服务器或客户端本身形成。这决定了块大小可以在哪里以及如何控制。这还进一步取决于是否使用了同步或异步插入。
同步插入
当服务器形成块时
当客户端获取一些数据并使用非原生格式(例如,同步插入查询)发送时(例如,JDBC驱动程序 使用 RowBinary进行插入),服务器会解析插入查询的数据并从中形成(至少)一个内存块(每个分区键),该块以part的形式写入存储: 插入查询的行值数量自动控制块的大小。但是,块的最大大小(每个分区键)可以通过 max_insert_block_size 行设置进行配置。如果从插入查询数据形成的单个块包含超过 max_insert_block_size
行(默认值 为 ~ 100万行),则服务器会分别创建额外的块和part。
为了最大程度地减少创建的(以及需要合并的)part数量,我们通常 建议发送较少但较大的插入操作,而不是发送许多小的插入操作,方法是缓冲客户端侧的数据并将数据作为批处理插入。
当客户端形成块时
ClickHouse命令行客户端(clickhouse-client)以及一些特定于编程语言的库(如Go、Python和C++客户端)在客户端侧形成插入块,并通过原生接口以原生格式发送到ClickHouse服务器,服务器会直接将块写入存储。
例如,如果使用ClickHouse命令行客户端插入一些数据
./clickhouse client --host ... --password … \
--input_format_parallel_parsing 0 \
--max_insert_block_size 2000000 \
--query "INSERT INTO t FORMAT CSV" < data.csv
那么客户端会自行解析数据并从数据中形成内存块(每个分区键),并通过原生ClickHouse协议以原生格式发送到服务器,服务器会将块作为part写入存储: 客户端侧内存块大小(以行数计)可以通过 max_insert_block_size
(默认值:1048545百万行)命令行选项控制。
请注意,我们在上面的示例命令行调用中禁用了并行解析。否则,clickhouse-client将忽略 max_insert_block_size
设置,而是将并行解析产生的多个块压缩到一个插入块中。
另外,请注意,客户端侧的 max_insert_block_size
设置特定于 clickhouse-client。您需要检查客户端库的文档和设置以了解类似的设置。
异步数据插入
或者,除了客户端侧批处理之外,您还可以使用异步插入。使用异步数据插入,无论使用哪个客户端、格式和协议,块始终由服务器形成: 使用异步插入,来自接收到的插入查询的数据首先被放入内存缓冲区(参见上图中的①、②和③),然后④当缓冲区根据配置设置(例如,收集到特定数量的数据后)刷新时,服务器会解析缓冲区的数据并从中形成一个内存块(每个分区键),该块⑤以part的形式写入存储。如果单个块包含超过 max_insert_block_size
行,则服务器会分别创建额外的块和part。
更多part = 更多后台part合并
配置的插入块大小越小,为大型数据加载创建的初始part就越多,并且与数据摄取同时并发执行的后台part合并就越多。这可能导致资源争用(CPU和内存),并且在摄取完成后需要额外的时间(才能达到健康的part数量)。
ClickHouse将持续合并part为更大的part,直到它们达到 ~150 GiB的压缩大小。此图显示了ClickHouse服务器如何合并part: 单个ClickHouse服务器利用多个后台合并线程来执行并发part合并。每个线程执行一个循环:① 确定接下来要合并哪些part,并将这些part作为块加载到内存中。
② 将加载到内存中的块合并为更大的块。
③ 将合并后的块写入磁盘上的新part。
返回到①
请注意,ClickHouse不一定一次将所有要合并的part都加载到内存中。基于几个因素,为了减少内存消耗(以牺牲合并速度为代价),所谓的垂直合并会分块加载和合并part,而不是一次性完成。此外,请注意增加CPU核心数量和RAM大小会增加后台合并吞吐量。
合并为更大part的part会被标记为非活动状态,并在可配置的分钟数后最终被删除。随着时间的推移,这会创建一个合并part的树。因此得名MergeTree表: 每个part属于特定级别,表示导致该part的合并次数。
插入并行性
对性能和资源使用影响
ClickHouse服务器可以并行处理和插入数据。插入并行性的级别会影响ClickHouse服务器的摄取吞吐量和内存使用情况。并行加载和处理数据需要更多主内存,但由于数据处理速度更快,因此会提高摄取吞吐量。
插入并行性的级别如何配置再次取决于数据是如何摄取的。
ClickHouse 服务器拉取数据时的配置
一些集成表函数,如s3、url和hdfs,允许通过glob模式指定要加载的文件名集。当glob模式匹配多个现有文件时,ClickHouse可以并行化这些文件之间和内部的读取,并通过利用并行运行的插入线程(每个服务器)将数据并行插入表中: 在所有文件的所有数据都处理完成之前,每个插入线程都会执行一个循环:① 获取下一部分未处理的文件数据(部分大小基于配置的块大小),并从中创建一个内存数据块。
② 将块写入存储上的新part。
返回到①。
此类并行插入线程的数量可以通过 max_insert_threads 设置进行配置。OSS的默认值为1,ClickHouse Cloud的默认值为4。
对于大量文件,多个插入线程的并行处理效果很好。它可以充分利用可用的CPU核心和网络带宽(用于并行文件下载)。在仅将少量大型文件加载到表的场景中,ClickHouse会自动建立高水平的数据处理并行性,并通过为每个插入线程生成其他读取线程来优化网络带宽使用,以并行读取(下载)大型文件中的更多不同范围。对于高级读取器,设置 max_download_threads 和 max_download_buffer_size 可能会有用。此机制目前已实现用于s3和url表函数。此外,对于太小而无法并行读取的文件,为了提高吞吐量,ClickHouse会自动预取数据,方法是异步预读这些文件。
并行服务器
一些具有用于并行加载多个文件的glob模式的集成表函数也存在于集群版本中,例如s3Cluster、hdfsCluster和urlCluster。这些表函数通过并行利用多个服务器上的上述多个并行插入线程来进一步提高插入并行性级别: 最初接收插入查询的服务器首先解析glob模式,然后将每个匹配文件的处理动态分派到其他服务器(以及自身)。
客户端推送数据时的配置
ClickHouse服务器可以并发接收和执行插入查询。客户端可以通过运行并行客户端侧线程来利用这一点: 每个线程执行一个循环:① 获取下一部分数据并从中创建一个插入操作。
② 将插入操作发送到ClickHouse(并等待确认插入操作成功)。
返回到①
或者,或者此外,多个客户端可以并行向ClickHouse发送数据:
并行服务器
在ClickHouse Cloud中,插入操作通过负载均衡器均匀分布在多个ClickHouse服务器上。传统的无共享ClickHouse集群可以使用分片和分布式表的组合来平衡多个服务器上的插入操作:
硬件大小
对性能的影响
可用CPU核心数量和RAM大小会影响
以及因此,整体的摄取吞吐量。
CPU 内核数量和 RAM 大小更改的便捷程度取决于数据摄取是在 ClickHouse Cloud 中进行还是在传统的共享无状态 ClickHouse 集群中进行。
ClickHouse Cloud
因为存储与 ClickHouse Cloud 中的 ClickHouse 服务器完全分离,所以您可以自由地更改现有服务器的大小(CPU 和 RAM)或快速添加额外的服务器(无需任何物理分片或数据重新平衡): 然后,新增服务器会自动参与并行数据摄取和后台数据合并,这可以大幅缩短整体摄取时间。
传统的共享无状态 ClickHouse 集群
在传统的共享无状态 ClickHouse 集群中,数据存储在每个服务器的本地: 添加额外的服务器需要手动配置更改,并且比 ClickHouse Cloud 需要更多时间。
总结
您可以通过允许 ClickHouse 的数据插入机制充分利用其运行的硬件来让 ClickHouse 以极快的速度加载数据。或者,您可以减少大型数据加载的资源使用。具体取决于您的数据加载场景。为此,我们探讨了 ClickHouse 的数据插入机制的工作原理,以及如何控制和配置 ClickHouse 中大型数据加载的三个主要性能和资源使用因素:**插入块大小**、**插入并行度**和**硬件大小**。
有了这些,我们为后续的两篇文章设定了场景,将这些知识转化为快速且弹性的大型数据加载最佳实践。在本系列的下一篇文章中,我们将进入赛道,使大型数据插入速度比默认设置快 3 倍。在相同的硬件上。
敬请期待!