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

博客 / 工程

ClickHouse 与 Elasticsearch:十亿行数据对决

author avatar
Tom Schreiber
2024 年 5 月 7 日

介绍

Elasticsearch_blog1_header.png

这篇博客文章考察了 ClickHouse 与 Elasticsearch 在大型数据分析和可观察性用例中常见的负载情况下的性能,即对数十亿行表的 count(*) 聚合。结果表明,ClickHouse 在处理大型数据集上的聚合查询时,其性能远远超过 Elasticsearch。具体来说

  • ClickHouse 比 Elasticsearch 的数据压缩效果好得多,对于大型数据集,其存储空间可减少 **12 到 19 倍**,从而可以使用更小、更便宜的硬件。

Elasticsearch_blog1_01.png

  • ClickHouse 中的 count(*) 聚合查询 利用 硬件的效率极高,与 Elasticsearch 相比,处理大型数据集时的延迟至少降低 **5 倍**。这意味着对于可比的 Elasticsearch 延迟,需要更小,正如我们将在后面 展示 的, **便宜 4 倍的硬件**。

Elasticsearch_blog1_02.png

Elasticsearch_blog1_03.png

由于以上原因,我们越来越多地看到用户从 Elasticsearch 迁移到 ClickHouse,客户强调

  • PB 级可观察性用例的成本大幅降低

“从 Elasticsearch 迁移到 ClickHouse 后,我们的可观察性硬件成本降低了 30% 以上。” 滴滴出行

  • 数据分析应用程序的技术限制得到提升

“这为新功能、增长和更轻松的扩展释放了潜力。” Contentsquare

  • 监控平台的可扩展性和查询延迟大幅提升

“ClickHouse 帮助我们从每月数百万行扩展到数十亿行。”
“切换后,我们发现平均读取延迟提高了 100 倍” The Guild

在本篇文章中,我们将比较存储大小和 count(*) 聚合查询性能,以模拟典型的数据分析场景。为了使文章范围适合一篇博客文章,我们将对比在大型数据集上单独运行 count(*) 聚合查询的单节点性能。

本文的其余部分首先解释了我们专注于对 count(*) 聚合进行基准测试的原因。然后我们描述了基准测试设置,并解释了我们的 count(*) 聚合性能测试查询和基准测试方法。最后,我们将展示基准测试结果。

在阅读基准测试结果时,您可能会想,“为什么 ClickHouse 这么快,效率这么高?” 简短的答案是 关注 大量细节,以优化和并行化大规模数据存储和聚合执行。我们建议您阅读 ClickHouse 与 Elasticsearch:计数聚合的机制,以深入了解该问题的技术答案。

ClickHouse 和 Elasticsearch 中的计数聚合

数据分析场景中聚合的常见用例是计算和排名数据集中值的频率。例如,来自 ClickPy 应用程序 的此屏幕截图中(分析近 9000 亿行 Python 包下载事件)的所有数据可视化都使用 SQL GROUP BY 子句与 count(*) 聚合结合, 底层 使用: Elasticsearch_blog1_04.png

类似地,在 日志记录用例(或更广泛的 可观察性用例)中,聚合最常见的应用之一是统计特定日志消息或事件发生的频率(并在频率 异常 时发出警报)。

与 ClickHouse 中的 SELECT count(*) FROM ... GROUP BY ... SQL 查询等效的 Elasticsearch 查询是 terms 聚合,这是 Elasticsearch 的 桶式聚合

我们在 一篇配套的博客文章 中介绍了 Elasticsearch 和 ClickHouse 如何在底层处理这种计数聚合。在本篇文章中,我们将比较这些 count(*) 聚合的性能。

基准测试设置

数据

我们将使用 公开的 PyPI 下载统计数据集。该数据集(持续增长)中的每一行都代表用户(使用 pip 或类似技术)下载一个 Python 包。去年,我的同事 Dale 构建 了上面提到的分析 应用程序,它基于该数据集,对近 9000 亿行数据(截至 2024 年 5 月)进行实时分析,利用 ClickHouse 聚合。

我们使用该数据集的版本,该版本以 Parquet 文件的形式托管在公共 GCS 存储桶中。

我们将从该存储桶中加载 110100* 亿行数据到 Elasticsearch 和 ClickHouse,以对典型数据分析查询的性能进行基准测试。

*我们 无法100 亿行原始数据集 加载到 Elasticsearch 中。

硬件

这篇博客文章重点介绍单节点数据分析性能。我们将把多节点设置的基准测试留待以后的文章。

我们对 Elasticsearch 和 ClickHouse 都使用单个专用 AWS c6a.8xlarge 实例。该实例具有 32 个 CPU 内核64 GB RAM、一个本地连接的 SSD(16k IOPS)以及 Ubuntu Linux 操作系统。

此外,我们还比较了 ClickHouse Cloud 服务的性能,该服务由具有类似规格的节点组成,这些节点在 CPU 内核和 RAM 数量方面与上述实例相同。

数据加载设置

我们从托管在 GCS 存储桶中的 Parquet 文件加载数据: Elasticsearch_blog1_05.png

Parquet 正在逐渐成为 2024 年分发分析数据的普遍标准。虽然 ClickHouse 原生支持此文件格式,但 Elasticsearch 没有原生支持此文件格式。Logstash 是其推荐的 ETL 工具,在撰写本文时也不支持此文件格式。

Elasticsearch

为了将数据加载到 Elasticsearch,我们使用 clickhouse-localLogstash。Clickhouse-local 是 ClickHouse 数据库引擎变成了一个 (极速) 命令行工具。ClickHouse 数据库引擎原生支持 90+ 种文件格式,并提供 50+ 个集成表函数和引擎,用于与外部系统和存储位置连接,这意味着它可以从几乎任何数据源中以几乎任何格式读取(拉取)数据。开箱即用。并且高度 并行。因为 ClickHouse 是一个关系型数据库引擎,所以我们可以在发送到 `Logstash` 之前,利用 SQL 提供的所有功能来实时过滤、丰富和转换 `clickhouse-local` 中的数据。 这里 是用于 Logstash 的配置文件,这里是 命令行调用,用于将数据加载到 Elasticsearch。

我们本可以使用 ClickHouse 的 url 表函数 将数据直接发送到 Elasticsearch 的 REST API,使用 clickhouse-local。但是,Logstash 允许更轻松地调整批处理和并行设置,支持将数据发送到多个输出(例如,具有不同设置的多个 Elasticsearch 数据流),并且具有内置的弹性,包括反压和失败批次的重试,以及中间缓冲和死信队列。

ClickHouse

因为,如上所述,ClickHouse 可以原生读取大多数云提供商的对象存储桶中的 Parquet 文件,我们只需使用 这个 SQL 插入语句将数据加载到 ClickHouse 和 ClickHouse Cloud 中。对于 ClickHouse Cloud,我们通过 利用 所有服务节点进行数据加载,进一步提高了并行级别。

我们没有尝试优化摄取吞吐量,因为这篇博文不是关于比较 ClickHouse 和 Elasticsearch 的摄取吞吐量。我们将把这留待以后的博文。也就是说,通过我们的测试,我们发现 Elasticsearch 加载数据需要更长的时间,即使对 LogStash 的批处理和并行设置进行了一些调整。当我们尝试加载我们计划为其包含 Elasticsearch 基准结果的 1000 亿行数据集时,它花了 4 天的时间加载了大约 300 亿行,但我们 无法 成功将该数据量加载到 Elasticsearch 中。我们的 ClickHouse 实例加载完整的 1000 亿行数据集所花费的时间明显更短(不到一天)。

Elasticsearch 设置

Elasticsearch 配置

我们在 一台 机器上安装了 Elasticsearch 版本 8.12.2 (输出 `GET /`),因此它具有所有 角色,并且默认情况下,将一半可用的 64 GB 内存用于捆绑的 JVM 的堆 (输出 `GET _nodes/jvm`)。Elasticsearch 节点启动日志条目 `heap size [30.7gb], compressed ordinary object pointers [true]` 确认 JVM 可以使用空间高效的压缩对象指针,因为我们不会 超过 32 GB 的堆大小限制。Elasticsearch 将间接利用机器可用的剩余一半 64 GB 内存,用于缓存从磁盘加载到操作系统文件系统缓存中的数据。

数据流

我们使用 数据流 摄取数据,其中每个数据流都由一系列自动 滚动 的索引支持。

从 8.5 版开始,Elasticsearch 还支持专门针对带时间戳的指标数据的 时间序列数据流。将针对指标用例的性能进行比较将留待以后的博文。

ILM 策略

对于滚动阈值,我们使用了一个 索引生命周期管理 策略 配置 推荐的最佳实践,以获得最佳的碎片大小(每个碎片最多 2 亿个文档或碎片大小在 10 GB 到 50 GB 之间)。此外,为了 提高 搜索速度并释放磁盘空间,我们 配置 的滚动索引将被 强制合并 成一个段。

索引设置

分片数量

我们 配置 数据流的备份索引包含 1 个主分片和 0 个副本分片。如 这里 所述,Elasticsearch 为 `terms aggregations`(我们在查询中使用)的每个分片使用一个并行查询处理线程。因此,为了获得最佳搜索性能,分片数量应该理想地与我们的测试机器上可用的 32 个 CPU 内核数量相匹配。但是,考虑到巨大的摄取数据量(数十亿行),数据流的自动索引滚动将已经创建许多分片。此外,这对于实时流式传输场景也是一个更现实的设置(原始 PyPi 数据集在不断增长)。

索引编解码器

我们使用更重的 `best_compression` 选项测试了存储大小,它使用 `DEFLATE` 编解码器而不是默认的 `LZ4` 编解码器。

索引排序

为了支持 最佳 索引编解码器的压缩率以及紧凑且访问效率高的 doc-id 编码,用于 doc_values,我们启用了 索引排序,并使用所有现有的索引字段对 存储字段(特别是 _source)和 `doc_values` 进行排序。我们 列出了 按基数升序排列的索引排序字段(这 确保 了尽可能高的压缩率)。

索引映射

我们 使用 组件模板 创建 了 Elasticsearch 的 PyPi 数据流。下图概述了 PyPi 数据流:Elasticsearch_blog1_06.png

插入的文档包含 4 个字段,我们将其存储在索引中

  • country_code
  • project
  • url
  • timestamp

为了与 ClickHouse 进行公平的存储和性能比较,我们关闭了所有 段数据结构,除了 `inverted index`、`doc_values` 和 `Bkd trees`** 以外,这些字段通过仅使用 keyworddate 数据类型在我们的 索引映射 中实现。这三个上述数据结构与数据分析访问模式(如聚合和排序)相关。

`keyword` 类型填充 `inverted index`(以启用快速过滤)和 `doc_values`(用于聚合和排序)。此外,对于 `inverted index`,`keyword` 类型意味着对字段值没有进行 规范化分词。相反,它们被未经修改地插入到反向索引中,以支持 精确匹配过滤

这样,Elasticsearch 反向索引(一个按字典顺序排序的所有 唯一 标记的列表,指向文档 ID 列表,并具有 二分搜索 查找)成为 ClickHouse 主索引(一个按字典顺序排序的主键列值的稀疏列表,指向行块,并具有二分搜索查找)的近似等效项。

我们可以通过为所有我们的 基准查询 不进行过滤的字段(例如 `project` 和 `url`)禁用 `inverted index` 来进一步优化 Elasticsearch 数据存储。这可以通过在索引映射中将 `keyword` 类型的 `index` 参数 设置为 `false` 来实现。但是,由于这些字段(`project` 和 `url`)也是我们 ClickHouse 表的主键的一部分(因此,ClickHouse 主索引 数据结构 被填充了来自这些字段的值),我们也保留了这些字段的 Elasticsearch `inverted index`。

date 类型 内部存储为 long 数值,无论是在 doc_values(用于支持聚合和排序)还是在 Bkd 树(对日期的查询 会被 内部转换为利用 Bkd 树的范围查询 进行)中。因为我们 使用 日期列作为 ClickHouse 中的主键列,所以我们没有关闭 Elasticsearch 中日期字段的 Bkd 树。

**在本博文中,我们将重点比较面向列的 doc_values 数据结构与 ClickHouse 的列式存储格式的聚合性能,并将其他数据结构的比较留待以后的博文介绍。**

_source

为了比较 _source 的存储影响,我们使用了两种不同的 索引映射

  1. 一种存储 _source(参见 分段数据结构 内部结构)
  2. 另一种不存储 _source

请注意,通过禁用 _source,我们 减少 了数据存储大小。但是,重新索引(索引到索引的复制)操作(例如,为了在事后更改索引映射或为了 升级 索引到新的主版本)和 更新 操作将 不再可能。一些查询在性能方面也会受到影响,因为 _source 是在所有或大部分索引文档字段都被请求的情况下检索字段值的最快速方法。尽管 ClickHouse 的存储需求 要低得多,但 ClickHouse 始终允许进行 表到表的复制更新 操作。

转换

对于 连续数据转换,我们 创建 了用于预先计算聚合的转换。

我们 优化 了为转换后的目标索引自动推断的索引映射,以消除 _source

ClickHouse 设置

与 Elasticsearch 相比,配置 ClickHouse 简单得多,需要的前期规划和设置代码也更少。

ClickHouse 配置

ClickHouse 部署为原生二进制文件。我们安装了默认设置的 ClickHouse 版本 24.4。无需配置内存设置。ClickHouse 服务器进程大约需要 1 GB 的 RAM,再加上执行查询的峰值内存使用量。与 Elasticsearch 一样,ClickHouse 将利用机器剩余的可用内存来缓存从磁盘加载到操作系统级文件系统缓存中的数据。

表格

我们 创建 了存储不同大小的 PyPi 数据集的表格,这些表格使用不同的压缩编解码器。

列压缩编解码器

与 Elasticsearch 相似,我们测试了可选地使用更重的 ZSTD 而不是默认的 LZ4 列压缩编解码器 的存储大小。

表格排序

为了公平地比较存储,我们使用与 Elasticsearch 中相同的 数据排序方案,以支持 最佳 列压缩编解码器的压缩率。为此,我们 添加 了表格的所有列到表格的 主键 中,按照基数升序排列(因为这 确保 了最高的压缩率)。

表格架构

以下图表概述了 PyPi ClickHouse 表格:Elasticsearch_blog1_07.png

因为 ClickHouse 运行在 单台 机器上,所以默认情况下每个表格都由一个分片组成。插入操作会 创建在后台合并的部分

插入的行完全包含与摄取到 Elasticsearch 中的文档中的 相同的 4 个字段。我们将这些字段存储在我们 ClickHouse 表格中的 4 列中。

  • country_code
  • project
  • url
  • timestamp

根据我们的表格 架构,将为我们表格的所有 4 列 创建列数据文件。将创建一个 稀疏 主索引文件,并使用表格 排序键列 的值进行填充。所有其他 部分数据结构 必须显式配置,并且不会被我们的基准查询使用。

请注意,我们 使用 country_code 列的 LowCardinality 类型来对它的字符串值进行字典编码。虽然这对 ClickHouse 中的低基数列来说是一种最佳实践,但它并不是 ClickHouse 在此基准测试中存储需求低得多的主要原因。在本例中,使用 PyPi 数据集时,与对 country_code 列使用完整的 String 类型相比,存储节省 可以忽略不计,因为该列的低基数值只是 2 个字母的代码,当数据按 country_code 排序 时,这些代码可以很好地压缩。

物化视图

对于 连续数据转换,我们 创建 了相当于创建的 Elasticsearch 转换 的物化视图,用于预先计算聚合。

ClickHouse 云设置

作为一项附带实验,我们还在 ClickHouse 云 上运行了相同的基准测试。

为此,我们创建了上面提到的相同的表格和物化视图,并将相同的数据量加载到一个 ClickHouse 云服务中,该服务具有与我们 EC2 测试机 几乎相同的硬件规格:每个计算节点有 30 个 CPU 内核和 120 GB 的 RAM 是最接近的匹配。请注意,ClickHouse 使用 1:4 的 CPU 与内存比例。此外,存储和计算是 分离 的。所有水平和垂直可扩展的 ClickHouse 计算节点都可以访问存储在对象存储中的相同物理数据,并且实际上是单个无限分片的多个副本:Elasticsearch_blog1_08.png

默认情况下,每个 ClickHouse 云服务都包含三个计算节点。传入的查询通过负载均衡器路由到运行该查询的特定节点。可以轻松地(手动或自动)扩展计算节点的大小或数量。根据 并行副本 设置,可以由多个节点并行处理单个查询。这不需要对实际数据进行任何物理分片或重新平衡。

我们将使用 ClickHouse 云服务中的单个节点和多个并行节点数量运行我们的一些基准测试查询。

基准测试查询

在 Elasticsearch 中等同于 ClickHouse count(*) 聚合(使用具有 count(*) 聚合函数的 SQL GROUP BY 子句)的是 terms 聚合。我们 在这里 描述了 Elasticsearch 和 ClickHouse 在幕后如何处理此类查询。

我们测试了以下 count(*) 聚合查询在原始(未预先聚合)数据集上的(冷运行)性能。

我们还测试了上面相同的查询在运行 预先聚合的数据集 时的性能。

请注意,对于 Elasticsearch,我们在基准测试中没有增加 term 聚合的 shard size 参数值。

**我们在使用 Elasticsearch 转换来预先计算桶大小方面发现了一个小问题。例如,要预先计算每个项目的计数。要做到这一点,需要按 project 分组,然后使用 terms 聚合(同样在 project 上)来预先计算每个项目的计数。由转换摄取到目标索引中的相应文档如下所示:**

{
  "project_group": "boto3",
  "project": {
    "terms": {
      "boto3": 28202786
    }
  }
}

转换目标索引映射如下:

{
  "project_group": {
    "type": "keyword"
  },
  "project": {
    "properties": {
      "terms": {
        "type": "flattened"
      }
    }
  }
}

请注意 flattened 字段类型的使用。对 terms 聚合的结果值使用此类型是有意义的。否则,每个唯一的项目名称都需要自己的映射条目,这在事先是不可能做到的(我们不知道哪些项目存在),并且会导致具有 动态映射映射爆炸

这会为我们的基准测试查询带来两个问题:

  1. flattened 类型目前 不支持 ESQL。

  2. 所有值 都被 视为关键字。在排序时,这意味着我们将对预先计算的计数值进行词法比较,而不是数值比较。因此,在转换索引上运行的 Elasticsearch 查询需要 使用 一个小的 painless 脚本,以对预先计算的计数值启用数值排序。

基准测试方法

启用缓存,特别是查询结果缓存,Elasticsearch 和 ClickHouse 几乎可以立即提供结果,只需从缓存中获取即可。我们感兴趣的是在缓存冷的情况下运行聚合查询,在这种情况下,查询处理引擎必须从头加载和扫描数据并计算聚合结果。

查询运行时间

我们在所有基准查询上运行数据集,这些数据集

  • 使用 Elasticsearch 和 ClickHouse 标准的 LZ4 编码进行压缩
  • 不在 Elasticsearch 中存储 _source

所有查询都使用冷缓存执行三次。我们一次执行一个查询,即仅测量延迟。在我们这篇博文中的图表中,我们以平均执行时间作为最终结果,并链接到详细的基准运行结果。

Elasticsearch

我们通过Search REST API 运行 Elasticsearch 查询(DSL),并使用 JSON 响应主体中的took 时间,代表总的服务器端执行时间。

ESQL 查询使用ESQL REST API 执行。Elasticsearch ESQL 查询的响应不包含任何运行时信息。ESQL 查询的服务器端执行时间记录在 Elasticsearch 的日志文件 中。

我们知道查询运行时间也可以在搜索慢日志 中找到,但仅在每个分片级别,而不是所有参与分片的完整查询执行的汇总形式。

ClickHouse

所有 ClickHouse SQL 查询都通过ClickHouse 客户端 执行,服务器端执行时间取自query_log 系统表(来自 query_duration_ms 字段)。

禁用缓存

Elasticsearch

对于查询处理,Elasticsearch 利用 操作系统级别的文件系统缓存、分片级别的请求缓存 和段级别的查询缓存

对于 DSL 查询,我们使用 request_cache-query-string 参数 在每个请求的基础上禁用了 请求缓存。对于 ESQL 查询,这是不可能的。

查询缓存 只能 索引启用或禁用,而不能按请求启用或禁用。相反,我们在每次查询运行之前使用清除缓存 API 手动删除请求和查询缓存。

没有 Elasticsearch API 或设置用于删除或忽略 文件系统缓存,因此我们使用一个简单的流程 手动删除它。

ClickHouse

与 Elasticsearch 一样,ClickHouse 利用操作系统 文件系统缓存查询缓存 进行查询处理。

可以使用SYSTEM DROP CACHE 语句 手动删除这两个缓存。

我们使用查询的SETTINGS 子句 在每个查询中禁用了这两个缓存

… SETTINGS enable_filesystem_cache=0, use_query_cache=0;

查询峰值内存使用量

ClickHouse

我们使用 ClickHouse 的query_log 系统表来跟踪和报告查询的峰值内存使用量(memory_usage 字段)。另外,我们还报告了某些查询的数据处理吞吐量(rows/sGB/s),如 ClickHouse 客户端所报告。这也可以从 query_log 字段(例如,read_rowsread_bytes 除以 query_duration_ms)计算得出。

Elasticsearch

Elasticsearch 在 Java JVM 中运行所有查询,在启动时分配了机器可用 64 GB 内存的一半。Elasticsearch 不会直接跟踪 JVM 内存中查询的峰值内存使用量。在 Kibana 图形搜索分析器 的底层使用的搜索分析 API 仅分析查询的 CPU 使用量。同样,搜索慢日志 仅跟踪运行时间,而不是内存。 集群统计信息 API 返回集群和节点级别的指标和统计信息,例如当前峰值 JVM 内存使用量,例如,此调用返回:GET /_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old。将这些统计信息与特定查询运行相关联可能很棘手,因为这些是节点级指标,会考虑所有查询和更广泛的进程,包括后台段合并,这可能是内存密集型的。因此,我们的基准结果没有报告 Elasticsearch 查询的峰值内存使用量。

基准测试结果

总结

在我们详细介绍基准结果之前,我们将提供一个简要的摘要。

10 亿行数据集

存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
链接
Markdown Image
ClickHouse 在磁盘上存储 10 亿行数据集所需磁盘空间是 Elasticsearch 的 1/12。聚合查询①(执行完整数据集聚合)在原始数据(未预先聚合)上使用 ClickHouse 比使用 Elasticsearch(查询 DSL)快 5 倍。聚合查询②(聚合过滤后的数据集)使用 ClickHouse 比使用 Elasticsearch(查询 DSL)快 6 倍。



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
当 10 亿行数据集以预先聚合的形式存在以加快聚合查询①的速度时,ClickHouse 所需磁盘空间是 Elasticsearch 的 1/10,并且在该数据上运行聚合查询①的速度是 Elasticsearch 的 9 倍。



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
当 10 亿行数据集以预先聚合的形式存在以加快聚合查询②的速度时,这是存储大小差异。ClickHouse 将该数据存储的大小是 Elasticsearch 的 1/9,并以比 Elasticsearch 快 5 倍的速度过滤和聚合数据。

100 亿行数据集

存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
链接
Markdown Image
ClickHouse 可以将原始数据存储的大小是 Elasticsearch 的 1/19,并将完整数据集聚合的速度是 Elasticsearch 的 5 倍,过滤后的数据集聚合的速度是 Elasticsearch 的 7 倍。



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
当原始数据以预先聚合的形式存在以支持加快聚合查询①的速度时,ClickHouse 所需磁盘空间是 Elasticsearch 的 1/10,并且运行聚合查询的速度是 Elasticsearch 的 12 倍。



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image

当原始数据以预先聚合的形式存在以支持加快聚合查询②的速度时,ClickHouse 将数据存储的大小是 Elasticsearch 的 1/7,并以比 Elasticsearch 快 5 倍的速度过滤和聚合数据。

在本节的剩余部分,我们将首先详细介绍PyPi 数据集 在原始(未预先聚合)和预先聚合 形式中的存储大小。之后,我们将展示在这些数据集上运行我们的聚合查询的详细运行时间。

存储大小

原始数据

以下是 10 亿行、100 亿行和 1000 亿行 PyPi 原始(未预先聚合)数据集的存储大小。

10 亿行

1b.png

_source 启用时,Elasticsearch 使用其默认的 LZ4 压缩需要51.3 GB。使用 DEFLATE 压缩,这将减少到44.7 GB。正如预期 的那样,我们的索引排序配置 允许索引编解码器具有很高的压缩率:如果没有索引排序,索引大小将为 135.6 GB使用 LZ4 和 91.5 GB使用 DEFLATE

禁用 _source 将存储大小减少到38.3 GB(LZ4)和36.3 GB(DEFLATE)之间。

索引编解码器(LZ4DEFLATE 应用于 存储字段包括 _source),但 应用于 doc_valuesdoc_values 是我们禁用 _source 后索引中剩余 的主要数据结构。在 doc_values 中,每个列 根据数据类型和基数进行单独编码(不使用 LZ4DEFLATE)。但是,索引排序确实支持 doc_values 的更好的前缀压缩,并支持 紧凑且访问高效的文档 ID 编码。但总的来说,doc_values 的压缩率并不能像 存储字段 那样从索引排序中获益,例如,如果没有索引排序,并且没有 存储字段(禁用了 _source),Elasticsearch 索引的存储大小 37.7 GB(LZ4) 35.4 GB(DEFLATE)。(这些大小甚至比使用索引排序时略小的事实与略微不同的滚动时间有关,导致分片和段的不同的数据结构开销。)

与没有 _source 字段且压缩级别相同的 Elasticsearch 索引(LZ4 与 LZ4DEFLATE 与 ZSTD)相比,ClickHouse 表使用 LZ4 压缩所需的存储空间大约少 7 倍,使用 ZSTD 压缩所需的存储空间大约少 10 倍

Elasticsearch 中的 _source 字段需要与 ClickHouse 功能等效(例如,要启用 更新 操作并运行 重新索引 操作,相当于 ClickHouse INSERT INTO SELECT 查询)。当使用相同的压缩级别(LZ4 与 LZ4DEFLATE 与 ZSTD)时,**ClickHouse 所需存储空间是 Elasticsearch 的 1/9 到 1/12**。

100 亿行

10b.png

与 10 亿行事件数据集一样,我们测量了Elasticsearch 的存储大小,有无 _source,以及使用默认的 LZ4 编解码器和更重的 DEFLATE 编解码器。同样,索引排序允许更高的压缩率(例如,如果没有索引排序,并且有 _source,索引大小将为 1.3 TB使用 LZ4)。

与没有_source字段且压缩级别相同的 Elasticsearch 索引(LZ4 vs. LZ4DEFLATE vs. ZSTD)相比,ClickHouse 表需要 少 9 倍 的存储空间,比 Elasticsearch 少,并且比 少 14 倍Elasticsearch 使用 ZSTD 压缩。

但是,当 Elasticsearch 在功能上等同于 ClickHouse(当我们保留 Elasticsearch 中的 _source)时,以及当使用相同的压缩级别(LZ4 vs. LZ4DEFLATE vs. ZSTD)时,ClickHouse 需要少 12 到 19 倍的存储空间

1000 亿行

100b.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 使用 LZ4 压缩需要 412 GB,使用 ZSTD 压缩需要 142 GB

预聚合数据以加速查询 ①

为了显着加快我们的聚合查询 ①(计算最受欢迎的三个项目),我们在 Elasticsearch 中使用了 转换,并在 ClickHouse 中使用了 物化视图。这些会自动将摄取的原始(未预聚合)数据转换为单独的预聚合数据集。在本节中,我们将介绍这些数据集的存储大小。

10 亿行

1b-pre-q1.png

每个项目预聚合计数的数据集仅包含 434k 行,而不是原始的 10 亿行,因为每 434k 个现有项目存在一行带有预计算计数的行。我们对 Elasticsearch 和 ClickHouse 使用了标准的 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

ClickHouse 需要比 Elasticsearch大约 10 倍 的存储空间。

100 亿行

10b-pre-q1.png

每个项目预聚合计数的数据集仅包含 465k 行,而不是 100 亿行,因为每 465k 个现有项目存在一行带有预计算计数的行。我们对 Elasticsearch 和 ClickHouse 使用了标准的 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

ClickHouse 需要比 Elasticsearch超过 8 倍 的存储空间。

1000 亿行

100b-pre-q1.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 使用 LZ4 压缩需要 16 MB

预聚合数据以加速查询 ②

此外,为了加快特定国家/地区的排名前三个项目聚合查询 ② 的速度,我们创建了单独的预聚合数据集,我们将在下面列出它们的存储大小。

10 亿行

1b-pre-q2.png

每个国家/地区和项目预聚合计数的数据集包含 350 万行,而不是原始的 10 亿行,因为每种现有的国家/地区和项目组合存在一行带有预计算计数的行。我们对 Elasticsearch 和 ClickHouse 使用了标准的 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

ClickHouse 需要比 Elasticsearch大约 9 倍 的存储空间。

100 亿行

10b-pre-q2.png

每个国家/地区和项目预聚合计数的数据集包含 880 万行,而不是原始的 100 亿行,因为每种现有的国家/地区和项目组合存在一行带有预计算计数的行。我们对 Elasticsearch 和 ClickHouse 使用了标准的 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

ClickHouse 需要比 Elasticsearch7 倍 的存储空间。

1000 亿行

100b-pre-q2.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 使用 LZ4 压缩需要 480 MB

聚合性能

本节介绍了对原始(未预聚合)和 预聚合 数据集运行我们的 聚合基准测试查询 的运行时间。

查询 ① - 全部数据聚合

本节介绍了我们的基准测试查询 ① 的运行时间,该查询聚合并对整个数据集进行排名。

10 亿行 - 原始数据

以下是在 原始(未预聚合)10 亿行数据集 上运行我们的最受欢迎的三个项目聚合查询的 查询运行时间: q1_1b_raw.png

Elasticsearch 使用其新的 ESQL 查询语言在 6.8 秒 内运行查询。通过传统查询 DSL,运行时间为 3.5 秒

我们注意到,在这个数据集上,查询 DSL 似乎比 ESQL 更好地利用了 索引排序。当我们选择性地在 未排序索引 上运行查询时,查询 DSL 运行时间为 9000 秒,ESQL 为 9552 秒

在相同机器大小下,ClickHouse 的查询运行速度比 Elasticsearch 快大约 5 倍

在 ClickHouse Cloud 中,当在单个(大小类似)计算节点上运行查询时,冷运行时间比开源 ClickHouse 慢一些(因为数据需要先从对象存储中获取到节点的缓存中)。但是,启用 节点并行 查询处理后,单个 3 节点 ClickHouse Cloud 服务可以更快地运行查询。通过水平扩展可以进一步缩短此运行时间。当使用 9 个计算节点并行运行查询时,ClickHouse 每秒处理 52 亿行,数据吞吐量接近每秒 100 GB。

请注意,我们使用查询的峰值 主内存使用情况 对 ClickHouse 运行时间进行了注释,这些使用情况对于完全聚合的数据量来说是适中的。

我们很好奇要找到 ClickHouse 在其上运行聚合查询的速度能够与在 32 核 EC2 机器上运行的 Elasticsearch 查询相匹配的最小机器大小。或者换句话说,我们试图查看什么更少的资源会导致 ClickHouse 变得更慢,因此与 Elasticsearch 更可比。为此,最简单、最快捷的方法是缩减 Elastic Cloud 计算节点的大小,并在单个节点上运行查询。使用** 8 个 CPU 内核而不是 32 个 CPU 内核**,聚合查询 运行 在单个 ClickHouse Cloud 节点上的 2763 毫秒(冷运行时间,禁用缓存)。32 个 CPU 内核的 EC2 机器是 c6a.8xlarge 实例,其价格从 $1.224 每小时 开始。8 个 CPU 内核的实例将是 c6a.2xlarge,其价格从 $0.306 每小时 开始,便宜了 4 倍

10 亿行 - 预聚合数据

以下是在 预聚合计数数据集 而不是原始 10 亿行数据集上运行最受欢迎的三个项目查询的运行时间: q1_1b_pre.png 正如 所讨论的,ESQL 目前不支持在生成预聚合数据集的 Elastic 转换中使用(需要使用)的扁平化字段类型。

ClickHouse 使用约 75 MB 的 RAM,以 快 9 倍 的速度比 Elasticsearch 运行查询。同样,由于查询延迟很低,因此对这个查询使用并行 ClickHouse Cloud 计算节点没有意义。

100 亿行 - 原始数据

以下是在 原始(未预聚合)100 亿行数据集 上运行我们的最受欢迎的三个项目聚合查询的冷查询运行时间: q1_10b_raw.png Elasticsearch 使用 ESQL 和查询 DSL 查询分别需要 3233 秒。

ClickHouse 使用约 600 MB 的 RAM,以 快大约 5 倍 的速度比 Elasticsearch 运行查询。

使用 9 个计算节点并行运行,ClickHouse Cloud 为聚合整个 100 亿行数据集提供了亚秒级延迟,查询处理吞吐量为每秒 102 亿行/每秒 192 GB。

100 亿行 - 预聚合数据

以下是在 预聚合计数数据集 而不是原始 100 亿行数据集上运行最受欢迎的三个项目查询的运行时间: q1_10b_pre.png

ClickHouse 使用约 67 MB 的 RAM,以 快 12 倍 的速度比 Elasticsearch 运行查询。

1000 亿行 - 原始数据

q1_100b_raw.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

我们在这里介绍 ClickHouse 查询运行时间只是为了完整性。在我们的测试机器上,ClickHouse 在 83 秒 内运行查询。

1000 亿行 - 预聚合数据

q1_100b_pre.png

我们无法将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 在 25 毫秒 内运行查询。

查询 ② - 过滤后的数据聚合

本节展示了我们的聚合基准测试查询 ② 的运行时间,该查询在应用和对每个项目进行count(*)聚合排名之前,先对数据集进行特定国家的过滤。

10 亿行 - 原始数据

以下图表展示了在数据集中过滤特定国家/地区后,运行计算前3个项目的查询的冷运行时间,数据集中包含 原始(未预聚合)的 10 亿行数据q2_1b_raw.png

Elasticsearch ESQL 查询的运行时间最长,为 9.2 秒。等效的 Query DSL 变量运行速度明显更快 (256 毫秒)。

ClickHouse 运行此查询大约 快 6 倍,并且使用的内存不到 20 MB。

由于查询延迟很低,因此对该查询使用并行 ClickHouse Cloud 计算节点没有意义。

10 亿行 - 预聚合数据

以下是使用 预聚合计数数据(而不是原始 10 亿行数据)运行基准查询 ②(在数据集中过滤特定国家/地区后,计算前 3 个项目)的运行时间:q2_1b_pre.png

ClickHouse 运行此查询的速度比 Elasticsearch5 倍以上,使用约 14 MB 内存。

100 亿行 - 原始数据

此图表显示了在 原始(未预聚合)的 100 亿行数据上运行基准查询 ② 的冷运行时间:q2_10b_raw.png

对于此查询,Elasticsearch ESQL 的表现并不理想,运行时间为 96 秒

Elasticsearch Query DSL 运行时间 相比,ClickHouse 运行此查询的速度几乎 快 7 倍,消耗约 273 MB 内存。

100 亿行 - 预聚合数据

以下是使用 预聚合计数数据(而不是原始 100 亿行数据)运行基准查询 ② 的运行时间:q2_10b_pre.png

ClickHouse 运行此查询的速度比 Elasticsearch大约 5 倍,使用约 19 MB 内存。

1000 亿行 - 原始数据

q2_100b_raw.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 在 2.9 秒 内运行了查询。

1000 亿行 - 预聚合数据

q2_100b_pre.png

我们 无法 将 1000 亿行数据集加载到 Elasticsearch 中。

ClickHouse 在 46 毫秒 内运行了查询。

总结

我们的基准测试表明,对于现代数据分析用例中常见的大型数据集,ClickHouse 可以更有效地存储数据,并且运行 count(*) 聚合查询的速度比 Elasticsearch 更快。

  • ClickHouse 的存储空间需求是 Elasticsearch 的 12 到 19 倍,因此可以使用更小、更便宜的硬件。
  • ClickHouse 在原始(未预聚合)和预聚合数据集上运行聚合查询的速度 至少快 5 倍,对于可比的 Elasticsearch 延迟,需要更便宜 4 倍的硬件。
  • ClickHouse 功能 是一种更高效的存储和计算连续数据汇总技术,从而进一步降低了计算和存储成本。

我们的 配套博客文章 提供了有关 ClickHouse 为什么如此快且更高效的深入技术解释。

潜在的后续文章可以比较多节点集群性能、查询并发、摄取性能和指标用例。

敬请关注!

分享此文章

订阅我们的通讯

随时了解功能发布、产品路线图、支持和云服务!
加载表单中...
关注我们
Twitter imageSlack imageGitHub image
Telegram imageMeetup imageRss image