DoubleCloud 即将停止运营。在有限时间内享受免费迁移服务,迁移到 ClickHouse。立即联系我们 ->->

博客 / 工程

ClickHouse 中的异步数据插入

author avatar
Tom Schreiber 和 Tony Bonuccelli
2023 年 8 月 1 日

简介

ClickHouse 被设计成不仅查询速度快,插入速度也快。 ClickHouse 表旨在每秒接收数百万行插入,并存储大量(数百 PB)数据。 非常高的摄取吞吐量通常需要适当的客户端数据批处理。

在这篇文章中,我们将描述以高吞吐量摄取数据的另一种方法的动机和机制:ClickHouse 异步数据插入将数据的批处理从客户端转移到服务器端,并支持客户端批处理不可行的用例。 我们将深入了解异步插入,并使用模拟现实场景的示例应用程序来演示、基准测试和调整具有不同设置的传统同步和异步插入。

同步数据插入基础知识

使用传统插入到 MergeTree 引擎家族的表中时,数据以新数据部分的形式快速写入数据库存储,与接收插入查询同步。 下图说明了这一点: async_inserts_01.png 当 ClickHouse ① 接收插入查询时,查询数据将 ② 立即(同步)以(至少)一个新的数据部分(每个分区键)的形式写入数据库存储,之后 ③ ClickHouse 确认插入查询已成功执行

ClickHouse 可以并行(并且以任何顺序)接收和执行其他插入查询(参见上图中的 ④ 和 ⑤)。

数据需要进行批处理才能获得最佳性能

在后台,为了增量优化数据以进行读取,ClickHouse 不断将数据部分合并成更大的部分。 合并后的部分被标记为非活动,并在可配置的分钟数后最终删除。 创建和合并数据部分需要集群资源。 为每个部分创建和处理文件,并且在宽格式中,每个表列都存储在单独的文件中。 在复制设置中,为每个数据部分创建 ClickHouse Keeper 条目。 此外,在写入新部分时,数据将被排序和压缩。 当部分合并时,数据需要解压缩和合并排序。 此外,表引擎特定的优化将在压缩和写入存储之前应用到合并的数据。

用户应该避免创建过多的小插入和过多的初始小部分。 因为这会导致 (1) 文件创建的开销,(2) 写入放大增加(导致更高的 CPU 和 I/O 使用率),以及 (3) ClickHouse Keeper 请求的开销。 由于高 CPU 和 I/O 使用率开销,在频繁的小插入情况下,这些会导致摄取性能下降。 留下更少的资源用于其他操作,例如查询。

实际上,ClickHouse 具有内置的保护措施来保护自己免于在创建和合并部分上花费过多资源:当单个表 T 的分区中存在超过 300 个活动部分时,它会将 Too many parts 错误返回到表 T 的插入查询。 为了防止这种情况发生,我们建议发送较少的但较大的插入,而不是发送许多小的插入,方法是在客户端缓冲数据并将数据作为批处理插入。 最理想的是,至少 1000 行或更多。 默认情况下,单个新部分最多可以包含约 100 万行。 并且,如果单个插入查询包含超过 100 万行,ClickHouse 将为查询数据创建多个新部分。

通常,ClickHouse 能够使用传统的同步插入提供非常高的摄取吞吐量。 用户选择 ClickHouse 的一个重要原因就是这一点。 Uber 使用 ClickHouse 摄取每秒数百万条日志,Cloudflare 每秒在 ClickHouse 中存储 1100 万行,而 Zomato 每天摄取高达 50 TB 的日志数据。

使用 ClickHouse 就像驾驶一辆高性能的 F1 赛车 🏎。 提供了大量的原始马力,你可以达到最高速度。 但是,要获得最佳性能,你需要在正确的时间换档到足够高的档位,并且相应地对数据进行批处理。

有时客户端批处理不可行

在某些情况下,客户端批处理不可行。 想象一个可观测性用例,其中有数百或数千个单用途代理发送日志、指标、跟踪等,而实时传输这些数据对于尽快检测问题和异常至关重要。 此外,观察到的系统中存在事件峰值的风险,这可能导致大型内存峰值以及在尝试在客户端缓冲可观测性数据时出现相关问题。

示例应用程序和基准测试设置

为了演示和基准测试客户端批量处理不可行的场景,我们实现了一个简单的示例应用程序,名为UpClick,用于监控clickhouse.com(或任何其他网站)的全球延迟。 下图描绘了 UpClick 的架构:async_inserts_02.png 我们使用了一个简单的无服务器 Google 云函数,它每n秒被调度并执行一次(n可配置),然后执行以下操作: ① Ping clickhouse.com(URL 可配置,该函数可以轻松适应以支持 URL 数组)

② 通过 HTTP 接口 将 ① 的结果以及云函数的地理位置一起插入到 ClickHouse 云 服务中的目标表中

我们使用一个 ClickHouse 云服务,服务大小为 24 GiB 主内存和 6 个 CPU 内核,用于基准测试。 此服务包含 3 个计算节点,每个节点拥有 8 GiB 主内存和 2 个 CPU 内核。

此外,我们 ③ 实现了一个实时 Grafana 仪表盘,该仪表盘每n秒更新一次,并在地理地图上显示过去m秒内欧洲、北美和亚洲所有部署了云函数位置的平均延迟。 以下屏幕截图显示了欧洲的延迟:async_inserts_03.png 在上面的仪表盘屏幕截图中,我们可以通过颜色编码看到,对 clickhouse.com 的访问延迟在荷兰和比利时低于(可配置的)KPI,而在伦敦和赫尔辛基则高于。

我们将使用 UpClick 应用程序来比较不同设置下的同步和异步插入。

为了匹配现实的观测性场景,我们使用了一个负载生成器,它可以创建和驱动任意数量的模拟云函数实例。 借助它,我们创建并调度了n个云函数实例用于基准测试运行。 每个实例每m秒执行一次。

在每次基准测试运行后,我们使用三个 SQL 查询来查询 ClickHouse 系统表,以分析(并可视化)云函数目标表中随时间的变化:

请注意,其中一些查询必须通过使用 clusterAllReplicas 表函数在具有特定名称的集群上执行。

同步插入基准测试

我们使用以下参数运行基准测试: • 200 个 UpClick 云函数实例
• 每个云函数每 10 秒调度/执行一次

有效地,每 10 秒向 ClickHouse 发送 200 个插入,导致 ClickHouse 每 10 秒创建 200 个新分区。 使用 500 个云函数实例,ClickHouse 将每 10 秒创建 500 个新分区。 等等。 这里可能有什么问题呢?

以下三个图表可视化了基准测试运行期间云函数目标表中的活动分区数量、所有分区数量(活动和非活动)以及 ClickHouse 集群的 CPU 利用率:async_inserts_04.png async_inserts_05.png async_inserts_06.png

基准测试开始后 5 分钟,活动分区数量达到上述 阈值,导致出现“分区过多”错误,我们中止了基准测试。 由于每秒创建约 200 个新分区,ClickHouse 无法以足够快的速度合并目标表的各部分以保持在 300 个活动分区阈值以下,从而保护自己免于陷入无法管理的状况。 当“分区过多”错误出现时,云函数的表中总共存在近 30,000 个分区(活动和非活动)。 请记住,合并的分区会被标记为非活动状态,并在几分钟后被删除。 正如上面提到的,创建和合并(太多)分区会占用大量的资源。 我们试图用最快的速度驾驶我们的 F1 赛车🏎,但档位太低,因为我们过于频繁地发送非常小的插入。

请注意,对于我们的云函数来说,客户端批量处理不是一个可行的设计模式。 我们本可以使用 聚合器或网关架构 来对数据进行批量处理。 但是,这会使我们的架构变得复杂,并需要额外的第三方组件。 幸运的是,ClickHouse 已经为我们的问题提供了完美的内置解决方案。

异步插入。

异步插入

描述

使用传统的插入查询,数据会同步插入到表中:当 ClickHouse 接收到查询时,数据会立即写入数据库存储。

使用异步插入,数据会先插入到缓冲区中,然后才写入数据库存储,或者说是异步写入。 下图说明了这一点:async_inserts_07.png 启用 异步插入后,当 ClickHouse ① 接收到一个插入查询时,查询的数据会 ② 立即先写入内存缓冲区。 与 ① 异步,只有当 ③ 下一次缓冲区刷新发生时,缓冲区中的数据才会被 排序 并写入数据库存储作为分区。 请注意,在数据刷新到数据库存储之前,它无法通过查询进行搜索;缓冲区刷新是 可配置的,我们将在后面给出一些示例。

在缓冲区被刷新之前,来自相同或不同客户端的其他异步插入查询的数据可以被收集到缓冲区中。 从缓冲区刷新创建的分区可能包含来自多个异步插入查询的数据。 一般来说,这些机制将数据的批量处理从客户端转移到服务器端(ClickHouse 实例)。 这非常适合我们的 UpClick 用例。

可能存在多个分区

异步插入缓冲区中缓冲的表行可能包含多个不同的 分区键 值,因此,在缓冲区刷新期间,ClickHouse 至少为缓冲区中包含的每个不同的分区键值创建一个新分区。 此外,对于没有分区键的表,取决于 收集在缓冲区中的行数,缓冲区刷新可能会导致创建多个分区。

可能存在多个缓冲区

每个插入查询的形状(插入查询的语法,不包括值子句/数据)和设置都将有一个缓冲区。 在多节点集群(如 ClickHouse 云)上,每个节点都将存在缓冲区。 下图说明了这一点:async_inserts_08.png 查询 ①、② 和 ③(以及 ④)具有相同的目标表 T,但语法形状不同。 查询 ③ 和 ④ 具有相同的形状,但设置不同。 查询 ⑤ 的形状不同,因为它以表 T2 为目标。 因此,所有 5 个查询都将有一个单独的异步插入缓冲区。 当查询通过分布式表(自管理集群)或负载均衡器(ClickHouse 云)以多节点集群为目标时,缓冲区将位于每个节点上。

根据设置的缓冲区允许对同一表的不同数据的刷新时间进行不同的设置。 在我们的 UpClick 示例应用程序中,可能存在一些重要的网站应该以接近实时的速度进行监控(使用较低的 async_insert_busy_timeout_ms 设置),而不太重要的网站的数据可以以较高的时效性进行刷新(使用较高的 async_insert_busy_timeout_ms 设置),从而减少这些数据对资源的占用。

插入是幂等的

对于 合并树引擎 系列的表,ClickHouse 将默认 自动去重 异步插入。 这使得异步插入 幂等,因此在以下情况下具有容错性

  1. 如果包含缓冲区的节点在缓冲区被刷新之前由于某种原因崩溃,插入查询将超时(或出现更具体的错误)并且不会收到确认。

  2. 如果数据已刷新,但由于网络中断无法将确认返回给查询的发送者,发送者将收到超时或网络错误。

从客户端的角度来看,1. 和 2. 很难区分。 但是,在这两种情况下,未确认的插入都可以立即重试。 只要重试的插入查询包含相同的数据,并且顺序相同,ClickHouse 就会在(未确认的)原始插入成功的情况下自动忽略重试的异步插入。

在缓冲区刷新期间可能会发生插入错误

当缓冲区被刷新时,可能会发生插入错误:即使使用异步插入,也可能会发生“分区过多”错误。 例如,使用分区键选择不当。 或者,在缓冲区刷新时,缓冲区刷新所在的集群节点存在一些操作问题。 此外,来自异步插入查询的数据只有在缓冲区被刷新时才会被解析并根据目标表的模式进行验证。 如果来自插入查询的一些行值由于解析或类型错误无法插入,那么该查询中的所有数据都不会被刷新(来自其他查询的数据刷新不受此影响)。 ClickHouse 会将缓冲区刷新期间发生的插入错误的详细错误消息写入日志文件和系统表。 我们将在后面讨论客户端如何处理此类错误。

异步插入与缓冲表

使用 缓冲区表引擎,ClickHouse 提供了一种类似于异步插入的数据插入机制。 缓冲区表在主内存中缓冲接收到的数据,并定期将其刷新到目标表。 但是,缓冲区表和异步插入之间存在重大差异

  • 缓冲区表需要显式创建(在多节点集群中的每个节点上),并连接到目标表。 异步插入可以通过简单的 设置 更改来启用和禁用。

  • 插入查询需要明确地以缓冲区表为目标,而不是真实目标表。 使用异步插入,情况并非如此。

  • 每次目标表的 DDL 发生变化时,缓冲区表都需要进行 DDL 更改。 使用异步插入则不需要这样做。

  • 缓冲区表中的数据会在节点崩溃时丢失,类似于在fire and forget模式下异步插入。但是,在默认模式下异步插入则不会出现这种情况。

  • 上文所述,即使所有插入查询都针对同一张表,异步插入也会为每个插入查询的shape和设置提供一个缓冲区。这使得可以针对同一张表中的不同数据启用粒度数据刷新策略。缓冲区表没有这样的机制。

总的来说,与缓冲区表相比,从客户端的角度来看,异步插入的缓冲机制对用户完全透明,并且完全由ClickHouse管理。异步插入可以被视为缓冲区表的继任者。

支持的接口和客户端

异步插入同时受到HTTP原生接口的支持,并且像Go客户端这样的流行客户端已经直接支持异步插入,或者在查询设置或用户设置或连接设置级别启用时间接支持异步插入。

配置返回行为

您可以选择异步插入查询何时返回给查询的发送者,以及插入的确认何时发生。可以通过wait_for_async_insert设置进行配置。

  • 默认的返回行为是,插入查询只有在下次缓冲区刷新发生后,并且插入的数据分别位于磁盘上时,才会返回给发送者。

  • 或者,通过将设置设置为0,插入查询将在数据刚刚插入缓冲区后立即返回。我们将在下面称之为fire and forget模式。

两种模式都有相当大的优点和缺点。因此,我们将在接下来的两节中更详细地讨论这两种模式。

默认返回行为

描述

这张图概述了异步插入的默认返回行为(wait_for_async_insert = 1):async_inserts_09.png当ClickHouse ①收到一个插入查询时,查询的数据会先被 ② 立即写入内存缓冲区。当 ③ 下一次缓冲区刷新发生时,缓冲区的数据会被 排序,并作为一个或多个数据部分写入数据库存储。在缓冲区刷新之前,来自其他插入查询的数据可以收集在缓冲区中。④只有在下次定期缓冲区刷新发生后,来自 ① 的插入查询才会返回给发送者,并附带插入确认。换句话说,发送插入查询的客户端调用会被 阻塞,直到下次缓冲区刷新发生。因此,上面图中所示的 3 个插入 不能来自同一个单线程插入循环,而是来自不同的多线程并行插入循环或不同的并行客户端/程序。

优势

这种模式的优势在于持久性保证 + 识别失败批次的简单方法。

  1. 持久性保证:当客户端收到插入确认时,数据保证被写入数据库存储(并且可以通过查询搜索到)。

  2. 插入错误被返回:当插入错误在缓冲区刷新期间 发生时,查询的发送者会收到一个详细的错误消息返回,而不是一个确认消息。因为ClickHouse会等待缓冲区刷新完成之后再返回插入确认。

  3. 识别失败数据集很简单:因为如上所述,插入错误会及时返回,因此很容易识别无法插入的数据集。

劣势

从ClickHouse 24.2版本开始,这个劣势已经被 自适应异步插入解决。

一个缺点是,这种模式会在一个客户端用于使用单线程插入循环来插入数据的场景中产生反压
1. 获取下一批数据
2. 向ClickHouse发送包含数据的插入查询
现在调用会被 阻塞,直到下次缓冲区刷新发生。
3. 接收插入成功的确认
4. 返回到第 1 步

在这样的场景中,可以通过适当的 批量处理客户端数据并使用多线程并行插入循环来提高插入吞吐量。

基准测试

我们运行了两个基准测试。

基准测试 1
• 200个UpClick云函数实例
• 每个云函数每10秒调度/执行一次
• 1秒缓冲区刷新时间

基准测试 2
• 500个UpClick云函数实例
• 每个云函数每10秒调度/执行一次
• 1秒缓冲区刷新时间

我们在两个基准测试运行中使用以下 异步插入设置

async_insert = 1
wait_for_async_insert = 1
async_insert_busy_timeout_ms = 1000
async_insert_max_data_size = 1_000_000
async_insert_max_query_number = 450

① 启用异步插入。通过 ② 我们设置了上面描述的异步插入的默认返回行为。我们配置了缓冲区应该 ③ 每秒刷新一次,或者如果 ④ 1 MB 的数据,或者 ⑤ 来自 450 个插入查询的数据被收集。无论哪个先发生都会触发下次缓冲区刷新。②、③、④、⑤ 是 ClickHouse 中的默认值(③ 在 OSS 中的默认值为 200,在 ClickHouse Cloud 中的默认值为 1000)。

以下三个图表可视化了云函数目标表中活动部分的数量、所有部分的数量(活动和非活动)以及两个基准测试运行的前一个小时内 ClickHouse 集群的 CPU 利用率:async_inserts_10.png async_inserts_11.png async_inserts_12.png您可以看到,活动部分的数量稳定在 8 以下,与我们运行的云函数实例数量无关。所有部分的数量(活动和非活动)稳定在 1300 以下,与我们运行的云函数实例数量无关。

这是使用异步插入来处理 UpClick 云函数的优势。无论我们运行多少个云函数实例——200 个、500 个,甚至 1000 个或更多——ClickHouse 都会将从云函数接收的数据进行批量处理,并每秒创建一个新部分。因为我们设置了 async_insert_busy_timeout_ms1000。我们正在以足够高的档位驾驶我们的一级方程式赛车 🏎 处于最高速度。这最大限度地减少了用于数据摄取的 I/O 和 CPU 周期。正如您所看到的,两个基准测试运行的 CPU 利用率远低于我们在本文前面所做的传统同步插入的基准测试运行。使用 500 个并行客户端的基准测试运行的 CPU 利用率高于使用 200 个并行客户端的基准测试运行。使用 500 个客户端,缓冲区在每秒刷新一次时会包含更多数据。而 ClickHouse 需要在缓冲区刷新期间创建一个新部分时以及更大的部分合并时花费更多的 CPU 周期来排序和压缩这些数据。

请注意,async_insert_max_data_sizeasync_insert_max_query_number 可能会在 1 秒过去之前触发缓冲区刷新,特别是在分别使用大量云函数或客户端的情况下。您可以将这两个设置设置为一个人为的高值,以确保只有时间设置才能触发缓冲区刷新,但可能会牺牲更大的主内存使用,因为需要临时缓冲更多数据。

即发即忘返回行为

描述

这张图展示了可选的 fire-and-forget 返回行为(wait_for_async_insert = 0)用于异步插入:async_inserts_13.png当ClickHouse ①收到一个插入查询时,查询的数据会先被 ② 立即写入内存缓冲区。之后,③ 插入查询会返回给发送者,并附带插入确认。④ 当下次定期缓冲区刷新发生时,缓冲区的数据会被 排序,并作为一个或多个数据部分写入数据库存储。在缓冲区刷新之前,来自其他插入查询的数据可以收集在缓冲区中。

优势

这种模式的一个优势是,使用单线程插入循环的客户端可以实现非常高的插入吞吐量(同时最小化集群资源利用率)。
1. 获取下一批数据
2. 向ClickHouse发送包含数据的插入查询
3. 立即收到一个确认,表明插入已经进入缓冲区
4. 返回到第 1 步

劣势

然而,这种模式也有一些缺点。

  1. 没有持久性保证:即使客户端收到插入查询的确认,也不一定意味着查询的数据已经写入或将会写入数据库存储。插入错误可能会在缓冲区刷新期间 发生。当 ClickHouse 节点在下次定期刷新内存缓冲区之前崩溃或关闭时,可能会发生数据丢失。更糟糕的是,这可能是静默数据丢失,因为客户端很难发现此类事件,因为最初对缓冲区的插入已经成功确认。为了优雅地关闭 ClickHouse 节点,有一个 SYSTEM 命令 可以刷新所有异步插入缓冲区。此外,一个服务器端 设置 决定了是否在优雅关闭时自动刷新异步插入缓冲区。

  2. 插入错误不会被返回:当插入错误在缓冲区刷新期间 发生时,对缓冲区的最初插入仍然会成功确认给客户端。客户端只能通过事后检查日志文件和 系统表来发现这些插入错误。在第二篇文章中,我们将为此提供指导。

  3. 识别失败数据集很复杂:对于上述静默插入错误,识别这些失败数据集很棘手且复杂。ClickHouse 目前不会将这些失败数据集记录在任何地方。fire and forget 模式下,用于失败异步插入的死信队列 可以帮助事后识别无法插入的数据集。

总的来说,fire-and-forget 模式下的异步插入,顾名思义,应该只用于数据丢失可以接受的场景。

基准测试

我们运行了两个基准测试。

基准测试 1
• 500个UpClick云函数实例
• 每个云函数每10秒调度/执行一次
• 5 秒缓冲区刷新时间

基准测试 2
• 500个UpClick云函数实例
• 每个云函数每10秒调度/执行一次
• 30 秒缓冲区刷新时间

我们在两个基准测试运行中使用以下 异步插入设置

async_insert = 1
wait_for_async_insert = 0
async_insert_busy_timeout_ms = 5000 (基准测试 1)
   async_insert_busy_timeout_ms = 30_000 (基准测试 2)
async_insert_max_data_size = 100_000_000
async_insert_max_query_number = 450_000

① 启用异步插入。使用 ② 我们为异步插入启用上面描述的“写入后即忘”返回行为。缓冲区应该 ③ 每 5 秒刷新一次(基准测试 1),每 30 秒刷新一次(基准测试 2)。请记住,只有在缓冲区刷新到数据库存储中的某个部分后,数据才能被查询搜索到。使用 ④ 和 ⑤,我们将另外两个缓冲区刷新阈值设置为一个人为的高值,以确保只有时间设置 ③ 触发缓冲区刷新。

以下三个图表可视化了云函数目标表中活动分区数量、所有分区数量(活动和非活动)以及 ClickHouse 集群在两个基准测试运行的第一个小时内的 CPU 利用率: async_inserts_14.png async_inserts_15.png async_inserts_16.png 两个基准测试运行的活动分区数量都稳定在 7 个以下。当缓冲区刷新频率降低 6 倍时,所有分区(活动和非活动)的数量也降低了约 6 倍。仅每 5 秒或 30 秒刷新一次缓冲区,CPU 利用率将低于每秒刷新一次缓冲区(如我们在本博文中 之前 对默认异步插入返回行为进行的基准测试运行)。但是,这里的两个基准测试运行的 CPU 利用率非常相似。当缓冲区仅每 30 秒刷新一次时,ClickHouse 会创建更少但更大的分区,从而导致对数据排序和压缩的 CPU 需求增加。

总结

在本博文中,我们探讨了 ClickHouse 异步数据插入的机制。我们讨论了传统同步插入在高数据摄取吞吐量场景下需要适当的客户端数据批处理。相反,异步插入通过将数据批处理从客户端转移到服务器端,自动控制分区创建频率,这支持客户端数据批处理不可行的场景。我们使用了一个示例应用程序来演示、基准测试和调整传统同步和异步插入,并使用不同的设置。我们希望您学习了加速 🏎 ClickHouse 使用案例的新方法。

在即将发布的博文中,我们将指导您监控和调试异步插入的执行步骤。

敬请关注!

立即开始使用 ClickHouse Cloud 并获得 300 美元的积分。在您的 30 天试用期结束后,您可以继续使用按使用量付费计划,或者 联系我们 了解有关我们的基于流量的折扣的更多信息。请访问我们的 价格页面 以了解更多详细信息。

分享此文章

订阅我们的新闻简报

及时了解功能发布、产品路线图、支持和云产品信息!
正在加载表单...
关注我们
Twitter imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2024ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。