本博客文章是 加速您的 ClickHouse 大型数据加载
系列的一部分
简介
ClickHouse 的设计宗旨是快速且资源高效。如果您允许,ClickHouse 可以将其运行的硬件利用到理论极限,并以惊人的 速度加载数据。或者您可以减少大型数据加载的资源使用量。这取决于您想要实现的目标。在本三部分博客系列中,我们将提供必要的知识、指导和最佳实践,以实现大型数据加载的弹性和速度。第一部分通过描述 ClickHouse 中的基本数据插入机制及其控制资源使用和性能的三个主要因素奠定基础。在第二篇文章中,我们将进入赛道,并将大型数据加载的速度调整到最大。在本系列的最后一部分,我们将讨论使您的大型数据加载变得强大且能够抵御网络中断等瞬时问题的措施。
让我们从探索基本的 ClickHouse 数据插入机制开始。
数据插入机制
下图概述了将数据插入 MergeTree 引擎系列的 ClickHouse 表中的一般机制: 服务器接收一些数据部分(例如,来自 insert 查询),并 ① 从接收到的数据中形成(至少)一个内存插入 块(每个分区键)。块的数据经过 排序,并应用特定于表引擎的 优化 。然后数据被 压缩 并 ② 以新数据 part 的形式写入数据库存储。
请注意,在某些情况下,客户端会形成插入块,而不是服务器。
三个主要因素影响 ClickHouse 数据插入机制的性能和资源使用: 插入块大小、插入并行度和硬件大小。我们将在本文的剩余部分讨论这些因素以及配置它们的方法。
插入块大小
对性能和资源使用的影响
插入块大小会影响 ClickHouse 服务器的 磁盘文件 i/o 使用率和内存使用率。较大的插入块使用更多的内存,但会生成更大且更少的初始 part。ClickHouse 为加载大量数据需要创建的 part 越少,所需的磁盘文件 i/o 和自动 后台合并 就越少。
插入块大小的配置方式取决于数据的摄取方式。是由 ClickHouse 服务器本身拉取数据,还是由外部客户端推送数据?
当数据由 ClickHouse 服务器拉取时进行配置
当结合 集成表引擎或表函数使用 INSERT INTO SELECT 查询时,数据由 ClickHouse 服务器本身拉取: 在数据完全加载之前,服务器执行一个循环:
① 拉取并解析下一部分数据,并从中形成一个内存数据块(每个分区键一个)。
② 将块写入存储上的新 part。
转到 ①
在 ① 中,部分大小取决于插入块大小,这可以通过两个设置来控制
- min_insert_block_size_rows(默认值:1048545 百万行)
- min_insert_block_size_bytes(默认值:256 MB)
当在插入块中收集到指定的行数,或者达到配置的数据量时(无论哪个先发生),都将触发将块写入新的 part。插入循环在步骤 ① 继续。
请注意,min_insert_block_size_bytes
值表示未压缩的内存块大小(而不是压缩的磁盘 part 大小)。另请注意,创建的块和 part 很少精确地包含配置的行数或字节数,因为 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 的树。因此得名 merge tree 表: 每个 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),或者快速添加额外的服务器(无需对数据进行任何物理重新分片或重新平衡): 然后,额外的服务器将自动 参与 并行数据摄取和 后台 part 合并,这可以大大减少整体摄取时间。
传统的无共享 ClickHouse 集群
在传统的 无共享 ClickHouse 集群中,数据本地存储在每个服务器上: 添加额外的服务器 需要 手动配置 更改,并且比在 ClickHouse Cloud 中花费更多时间。
总结
您可以让 ClickHouse 以惊人的速度加载数据,方法是允许其数据插入机制充分利用其运行的硬件。或者,您可以减少大型数据加载的资源使用量。这取决于您的数据加载场景是什么。为此,我们探索了 ClickHouse 的数据插入机制如何工作,以及您如何控制和配置 ClickHouse 中大型数据加载的三个主要性能和资源使用因素:插入块大小、插入并行度和硬件大小。
有了这些知识,我们为接下来的两篇后续文章奠定了基础,将这些知识转化为快速且有弹性的大型数据加载的最佳实践。在本系列的 下一篇文章中,我们将进入赛道,并将大型数据插入速度提高到比默认设置快 3 倍。在相同的硬件上。
敬请期待!