跳到主要内容
跳到主要内容

选择插入策略

高效的数据摄取是高性能 ClickHouse 部署的基础。选择正确的插入策略可以显著影响吞吐量、成本和可靠性。本节概述了最佳实践、权衡和配置选项,以帮助您为工作负载做出正确的决策。

备注

以下内容假设您是通过客户端将数据推送到 ClickHouse。如果您是通过例如使用内置表函数 s3gcs 将数据拉入 ClickHouse,我们建议您查看我们的指南 “优化 S3 插入和读取性能”

默认的同步插入

默认情况下,插入到 ClickHouse 是同步的。每个插入查询立即在磁盘上创建一个存储部分,包括元数据和索引。

如果可以在客户端对数据进行批处理,请使用同步插入

如果不能,请查看下面的 异步插入

我们简要回顾 ClickHouse 的 MergeTree 插入机制:

插入过��程

客户端步骤

为了获得最佳性能,数据必须 ①批处理,使批处理大小成为 第一项决定

ClickHouse 按照表的主键列(列)在磁盘上存储插入的数据,有序第二项决策是是否在传输到服务器之前 ②对数据进行预排序。如果一个批次按照主键列预排序到达,ClickHouse 可以跳过 ⑨ 排序步骤,从而加快摄取速度。

如果要摄取的数据没有预定义格式,关键决策是选择一种格式。ClickHouse 支持以 超过 70 种格式 插入数据。但是,当使用 ClickHouse 命令行客户端或编程语言客户端时,这个选择通常是自动处理的。如果需要,也可以显式覆盖该自动选择。

下一个 主要决定是 ④ 是否在传输到 ClickHouse 服务器之前压缩数据。压缩可以减少传输大小,提高网络效率,从而加快数据传输速度并降低带宽使用,尤其是对于大数据集。

数据 ⑤ 被传输到 ClickHouse 网络接口——可以是 本机HTTP 接口(我们会在本文后面对此进行 比较)。

服务器端步骤

在 ⑥ 接收到数据后,如果使用了压缩,ClickHouse 将 ⑦ 解压缩数据,然后 ⑧ 从最初发送的格式中解析数据。

利用格式化数据的值和目标表的 DDL 语句,ClickHouse ⑨ 在内存中构建以 MergeTree 格式的 ,如果行尚未预排序,则 ⑩ 按主键列排序,⑪ 创建 稀疏主索引,⑫ 应用 每列压缩,并 ⑬ 将数据作为新的 ⑭ 数据部分 写入磁盘。

如果同步则批量插入

上述机制展示了无论插入大小如何,始终存在的恒定开销,这使得批量大小成为提高摄取吞吐量的最重要优化因素。批量插入减少了总插入时间的开销比例,并提高了处理效率。

我们建议以至少 1,000 行为一个批次插入数据,理想情况下在 10,000 到 100,000 行之间。更少的、更大的插入减少了写入的分区片段数量,最小化了合并负载,降低了整体系统资源使用。

要使同步插入策略有效,客户端必须进行批量处理。

如果您无法在客户端进行数据批量处理,ClickHouse 支持将批量处理转移到服务器的异步插入()。

提示

无论插入的大小如何,我们建议将插入查询的数量保持在每秒一个插入查询左右。这一建议的原因在于,创建的分区片段在后台合并为更大的分区片段(以优化您的数据以供读取查询),而每秒发送过多的插入查询可能导致后台合并无法跟上新分区片段的数量。但是,当您使用异步插入时,可以使用更高的每秒插入查询速率(见异步插入)。

确保幂等重试

同步插入也是 幂等的。使用 MergeTree 引擎时,ClickHouse 默认会去重插入。这能保护您免受歧义失败情况的影响,例如:

  • 插入成功,但客户端因网络中断而未收到确认。
  • 插入在服务器端失败并超时。

在这两种情况下,安全的 重试插入 是安全的——只要批处理的内容和顺序保持不变。因此,客户端的一致重试至关重要,不能修改或重新排序数据。

选择正确的插入目标

对于分片集群,您有两个选择:

  • 直接插入到 MergeTreeReplicatedMergeTree 表中。当客户端可以在分片之间执行负载均衡时,这是最有效的选项。在 internal_replication = true 的情况下,ClickHouse 透明地处理复制。
  • 插入到 分布式表。这允许客户端将数据发送到任何节点,并让 ClickHouse 将其转发到正确的分片。这更简单,但由于额外的转发步骤,性能略低。仍然建议使用 internal_replication = true

在 ClickHouse Cloud 中,所有节点都读取和写入同一个分片。插入会自动在节点之间平衡。用户只需将插入发送到公开终端即可。

选择正确的格式

选择正确的输入格式对于 ClickHouse 的高效数据摄取至关重要。支持超过 70 种格式时,选择性能最好的选项可以显著影响插入速度、CPU 和内存使用,以及整个系统的效率。

虽然灵活性对数据工程和基于文件的导入很有用,但 应用应该优先考虑面向性能的格式

  • 本机格式(推荐):效率最高。列式,服务器端所需的解析最小。在 Go 和 Python 客户端中默认使用。
  • RowBinary:高效的行式格式,适合在客户端进行列式转换困难时使用。在 Java 客户端中使用。
  • JSONEachRow:使用简单但解析开销较大。适用于低容量用例或快速集成。

使用压缩

压缩在减少网络开销、加快插入速度和降低 ClickHouse 的存储成本中发挥着关键作用。有效使用它可以提升摄取性能,而无需更改数据格式或架构。

压缩插入数据可以减小通过网络发送的负载大小,最小化带宽使用,加快传输速度。

对于插入操作,当与本机格式一起使用时,压缩尤其有效,因为这种格式已经与 ClickHouse 的内部列式存储模型匹配。在这种设置中,服务器可以有效地解压缩并直接存储数据,而无需进行大量转化。

使用 LZ4 提升速度,使用 ZSTD 提升压缩比

ClickHouse 在数据传输期间支持几种压缩编解码器。其中两个常见选项是:

  • LZ4:快速且轻量。它在 CPU 开销最小的情况下显著降低数据大小,适合高吞吐量的插入,是大多数 ClickHouse 客户端中的默认选项。
  • ZSTD:更高的压缩比但 CPU 开销更多。当网络传输成本较高(例如跨区域或云提供商场景)时非常有用,尽管它略微增加客户端计算和服务器端解压缩时间。

最佳实践:使用 LZ4,除非您有带宽限制或产生数据出站成本 - 然后考虑 ZSTD。

备注

FastFormats 基准 的测试中,LZ4 压缩的本机插入将数据大小减少了超过 50%,将 5.6 GiB 数据集的摄取时间从 150 秒减少到 131 秒。切换到 ZSTD 将同一数据集压缩到 1.69 GiB,但略微增加了服务器端处理时间。

压缩减少资源使用

压缩不仅减少了网络流量,还提高了服务器的 CPU 和内存效率。通过压缩数据,ClickHouse 接收到更少的字节,并花费更少的时间解析大型输入。当从多个并发客户端摄取时,这一好处尤其重要,比如在可观测性场景中。

对于 LZ4,压缩对 CPU 和内存的影响是适度的;对于 ZSTD,则是中等的。即使在负载下,由于减少了数据量,服务器端的效率也会提高。

将压缩与批处理和高效输入格式(如本机格式)结合使用可获得最佳摄取性能。

在使用本机接口(例如 clickhouse-client)时,LZ4 压缩默认为启用状态。您可以通过设置选择切换到 ZSTD。

对于 HTTP 接口,使用 Content-Encoding 头来应用压缩(例如 Content-Encoding: lz4)。整个负载必须在发送之前完成压缩。

如果低成本,则进行预排序

在插入之前按主键对数据进行预排序可以提高 ClickHouse 的摄取效率,特别是对于大批量数据。

当数据以预排序方式到达时,ClickHouse 可以跳过或简化在创建部分期间的内部排序步骤,从而降低 CPU 使用并加快插入过程。预排序还提高了压缩效率,因为相似值会组合在一起 - 使得像 LZ4 或 ZSTD 这样的编解码器能够实现更好的压缩比。这在与大批量插入和压缩结合使用时尤为有益,因为它减少了处理开销和传输数据量。

也就是说,预排序是一种可选的优化——不是强制要求。 ClickHouse 使用并行处理高效地对数据进行排序,在许多情况下,服务器端排序的速度可能更快或更方便,而不是在客户端进行预排序。

我们建议仅在数据已经几乎有序或客户端资源(CPU、内存)充分且未被利用时进行预排序。 在那些对延迟敏感或高吞吐量的用例中,例如可观测性,数据随着顺序或来自许多代理到达,通常最好跳过预排序,依赖 ClickHouse 的内置性能。

异步插入

异步插入在 ClickHouse 中提供了一种强大的替代方案,当客户端批处理不可行时显得尤为重要。这在可观察性工作负载中尤其有价值,其中数百或数千个代理持续发送数据 - 日志、指标、跟踪 - 通常以小的实时有效负载进行。为了在这些环境中客户端缓冲数据,复杂性增加,需要一个集中队列来确保可以发送足够大的批量。

备注

不推荐以同步模式发送许多小批次,这样会导致创建许多 parts。这将导致查询性能差和 "too many part" 错误。

异步插入通过将传入数据写入内存缓冲区,然后根据可配置的阈值刷新到存储中,将批处理责任从客户端转移到服务器。这种方法显著降低了部分创建的开销,降低了 CPU 使用率,并确保即使在高并发下,数据摄取也保持高效。

核心行为通过 async_insert 设置进行控制。

异步插入

启用时 (1),插入被缓冲,只有在满足其中一个刷新条件时才写入磁盘:

(1) 缓冲区达到指定大小 (async_insert_max_data_size) (2) 已经过了时间阈值 (async_insert_busy_timeout_ms) 或 (3) 累积的插入查询达到最大数量 (async_insert_max_query_number)。

这个批处理过程对客户端是不可见的,并帮助 ClickHouse 有效地合并来自多个源的插入流量。然而,在发生刷新之前,数据无法被查询。重要的是,对于每个插入形状和设置组合有多个缓冲区,并且在集群中,缓冲区按节点维护 - 这使得在多租户环境中可以进行细粒度控制。插入机制与 同步插入 描述的相同。

选择返回模式

异步插入的行为通过 wait_for_async_insert 设置进一步细化。

当设置为 1(默认值)时,ClickHouse 只有在数据成功刷新到磁盘后才确认插入。这确保了强大的耐久性保证,并使错误处理变得简单:如果在刷新过程中出现问题,错误将返回给客户端。这个模式推荐用于大多数生产场景,特别是在必须可靠地跟踪插入失败时。

基准测试 显示它在并发时扩展良好 - 无论你是运行 200 还是 500 个客户端 - 这要归功于自适应插入和稳定的部分创建行为。

wait_for_async_insert = 0 设置为“ fire-and-forget”模式。在这里,服务器在数据缓冲后立即确认插入,而不等待其到达存储。

这提供了超低延迟插入和最大吞吐量,非常适合高速、低重要性数据。然而,这带来了权衡:没有保证数据会被持久化,错误可能仅在刷新期间浮现,并且难以追踪失败的插入。仅在你的工作负载能够容忍数据丢失时使用此模式。

基准测试还显示 当缓冲刷新不频繁(例如每 30 秒一次)时,部分减少和 CPU 使用率降低,但静默失败的风险仍然存在。

我们强烈建议使用 async_insert=1,wait_for_async_insert=1,如果使用异步插入。使用 wait_for_async_insert=0 风险很大,因为你的 INSERT 客户端可能无法意识到是否存在错误,并且在 ClickHouse 服务器需要减慢写入并产生一定的反压以确保服务的可靠性时,可能会导致潜在的过载。

去重和可靠性

默认情况下,ClickHouse 对于同步插入执行自动去重,这使得在出现故障场景时重试很安全。然而,对于异步插入,此功能默认是禁用的,除非显式启用(如果你有依赖的物化视图,则不应启用 - 参见问题)。

实际上,如果去重被开启,并且由于超时或网络中断重试了相同的插入,ClickHouse 可以安全地忽略重复插入。这有助于保持幂等性并避免数据的重复写入。然而,值得注意的是,插入验证和模式解析只在缓冲刷新期间进行 - 所以错误(如类型不匹配)只会在那时显现出来。

启用异步插入

异步插入可以为特定用户或特定查询启用:

  • 在用户级别启用异步插入。这个例子使用用户 default,如果你创建了不同的用户则替换该用户名:
ALTER USER default SETTINGS async_insert = 1
  • 你可以通过使用插入查询的 SETTINGS 子句来指定异步插入设置:
INSERT INTO YourTable SETTINGS async_insert=1, wait_for_async_insert=1 VALUES (...)
  • 你还可以在使用 ClickHouse 编程语言客户端时将异步插入设置作为连接参数指定。

    作为示例,在使用 ClickHouse Java JDBC 驱动程序连接到 ClickHouse Cloud 时,这就是你可以在 JDBC 连接字符串中做到的:

"jdbc:ch://HOST.clickhouse.cloud:8443/?user=default&password=PASSWORD&ssl=true&custom_http_params=async_insert=1,wait_for_async_insert=1"

选择接口 - HTTP 或本机

本机

ClickHouse 提供两种主要的数据摄取接口:本机接口HTTP 接口 - 每种接口在性能和灵活性之间都有取舍。本机接口用于 clickhouse-client 和一些语言客户端(如 Go 和 C++),是为性能量身定制的。它始终以 ClickHouse 高效的本机格式传输数据,支持以 LZ4 或 ZSTD 进行块级压缩,并通过将解析和格式转换等工作委派给客户端来最小化服务器端的处理。

它甚至允许在客户端计算 MATERIALIZED 和 DEFAULT 列的值,使服务器能够完全跳过这些步骤。这使得本机接口非常适合对效率要求极高的高吞吐量摄取场景。

HTTP

与许多传统数据库不同,ClickHouse 还支持 HTTP 接口。相反,它优先考虑兼容性和灵活性。 它允许以 任何支持的格式 发送数据——包括 JSON、CSV、Parquet 等,并在大多数 ClickHouse 客户端(包括 Python、Java、JavaScript 和 Rust)中广泛支持。

这通常比 ClickHouse 的本机协议更可取,因为它允许通过负载均衡器轻松切换流量。我们预计使用本机协议时的插入性能会有小幅差异,其开销较小。

但是,它缺乏本机协议的更深层次集成,无法进行客户端优化,如计算物化值或自动转换为本机格式。虽然 HTTP 插入仍然可以使用标准 HTTP 头压缩(例如 Content-Encoding: lz4),但压缩是应用于整个负载,而不是单独数据块。这种接口通常在协议简化、负载均衡或广泛格式兼容性比原始性能更重要的环境中更受欢迎。

有关这些接口的更详细描述,请见 这里