博客 / 工程

ClickHouse 与 Elasticsearch:十亿行数据对决

author avatar
Tom Schreiber
2024 年 5 月 7 日 - 53 分钟阅读

简介

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

同样,在 日志用例 中(或更普遍的 可观测性用例),聚合最常见的应用之一是计算特定日志消息或事件发生的频率(并在频率 异常 时发出警报)。

在 Elasticsearch 中,与 ClickHouse SELECT count(*) FROM ... GROUP BY ... SQL 查询等效的是 terms aggregation,它是 Elasticsearch bucket aggregation

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

基准测试设置

数据

我们将使用 公共 PyPI 下载统计数据集。此数据集中的每一行(不断增长)表示用户下载 Python 包的一次下载(使用 pip 或类似技术)。去年,我的同事 Dale 构建了 上述分析 应用程序,该应用程序基于该数据集实时分析了近 9000 亿行数据(截至 2024 年 5 月), ClickHouse 聚合提供支持。

我们使用了托管在公共 GCS 存储桶中 Parquet 文件格式的此数据集版本。

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

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

硬件

本博客重点介绍单节点数据分析性能。多节点设置的基准测试将在未来的博客中进行。

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

此外,我们还比较了 ClickHouse Cloud 服务的性能,该服务由在 CPU 核心数和内存方面具有类似规格的节点组成。

数据加载设置

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

Parquet 正日益成为 2024 年分发分析数据的通用标准。ClickHouse 开箱即用地支持这种格式,而 Elasticsearch 在撰写本文时还不原生支持此文件格式。其推荐的 ETL 工具 Logstash 也不支持此文件格式。

Elasticsearch

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

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

ClickHouse

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

我们没有尝试优化摄取吞吐量,因为本博客不是关于比较 ClickHouse 和 Elasticsearch 的摄取吞吐量。我们将在未来的博客中讨论这一点。即便如此,通过我们的测试,我们确实发现 Elasticsearch 加载数据的时间明显更长,即使对 LogStash 的批处理和并行设置进行了一些调整也是如此。当我们尝试加载 1000 亿行数据集时,加载约 300 亿行数据花费了 4 天时间,我们原本计划包含 Elasticsearch 的基准测试结果,但我们 无法 成功将该数据量加载到 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 亿个文档或分片大小在 10GB 到 50GB 之间)。此外,为了 提高 搜索速度并释放磁盘空间,我们 配置 了滚动更新索引的 强制合并 为单个段。

索引设置

分片数

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

索引编解码器

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

索引排序

为了支持 最佳 索引编解码器压缩率以及 doc_values 的紧凑且访问高效的文档 ID 编码,我们启用了 索引排序,并使用所有现有的索引字段对 存储字段(尤其是 _source)和磁盘上的 doc_values 进行排序。我们按照基数升序 列出 了索引排序字段(这 确保 了尽可能高的压缩率)。

索引映射

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

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

  • country_code
  • project
  • url
  • timestamp

为了与 ClickHouse 进行公平的存储和性能比较,我们关闭了所有 段数据结构,除了 倒排索引doc_valuesBkd 树**,这些字段仅使用 keyworddate 数据类型在我们的 索引映射 中。上述 3 种数据结构与数据分析访问模式(如聚合和排序)相关。

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

这样,Elasticsearch 倒排索引(按字典顺序排序的所有 唯一 标记列表,指向文档 ID 列表,使用 二分搜索 查找)就变成了 ClickHouse 主索引 的近似等价物(稀疏的按字典顺序排序的主键列值列表,指向行块,使用二分搜索查找)。

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

date 类型 在内部long 数字的形式存储在 doc_values(以支持聚合和排序)和 Bkd 树 中(日期查询 在内部 转换为范围查询,利用 Bkd 树)。由于我们 也使用 date 列作为 ClickHouse 中的主键列,因此我们没有在 Elasticsearch 中关闭 date 字段的 Bkd 树。

**在本博客中,我们专注于比较面向列的 doc_values 数据结构与 ClickHouse 的列式存储格式的聚合性能,并将其他数据结构的比较留给未来的博客。

_source

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

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

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

Transforms

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

我们 优化 了为转换后的目标索引自动推导出的索引映射,以消除 _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 在此基准测试中存储需求远低于 Elasticsearch 的主要原因。在这种情况下,对于 PyPi 数据集,与为 country_code 列使用完整的 String 类型相比,存储节省可以忽略不计,因为当数据按 country_code排序时,该列的低基数值(仅为 2 个字母的代码)可以很好地压缩。

物化视图

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

ClickHouse Cloud 设置

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

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

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

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

基准测试查询

Elasticsearch 中等效于 ClickHouse count(*) 聚合(使用带有 count(*) 聚合函数的 SQL GROUP BY 子句)的是 terms aggregation。我们在 此处描述了 Elasticsearch 和 ClickHouse 如何在底层处理此类查询。

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

我们进一步测试了上述相同查询在 预聚合数据集上运行时的性能

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

**我们注意到,当使用 Elasticsearch 转换预先计算存储桶大小时,存在一个轻微的问题。例如,预先计算每个项目的计数。执行此操作的方法是按 project分组,然后使用 terms aggregation(也在 project 上)来预先计算每个项目的计数。转换摄入到目标索引中的相应文档如下所示

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

转换目标索引映射是这样的

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

请注意 flattened 字段类型的使用。对于 terms aggregation 的结果值,使用此类型是有意义的。否则,每个唯一的项目名称都需要其自己的映射条目,这是无法预先完成的(我们不知道存在哪些项目),并且会导致使用 动态映射映射爆炸

这为我们的基准查询带来了两个问题

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

  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 的 日志文件中。

我们知道查询运行时也在 search slow log 中可用,但仅在每个分片级别,而不是在跨所有涉及分片的完整查询执行的合并形式中可用。

ClickHouse

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

禁用缓存

Elasticsearch

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

对于 DSL 查询,我们使用 request_cache-查询字符串参数在每个请求的基础上禁用了 request cache。但是,对于 ESQL 查询,这是不可能的。

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

没有用于删除或忽略 filesystem cache 的 Elasticsearch API 或设置,因此我们使用一个简单的流程手动删除它。

ClickHouse

与 Elasticsearch 类似,ClickHouse 利用操作系统 filesystem cachequery cache 进行查询处理。

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

我们使用查询的 SETTINGS clause 为每个查询禁用这两个缓存

… 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 RAM 的一半在启动时分配。Elasticsearch 不直接跟踪 JVM 内存中查询的峰值内存消耗。search profiling API 在 Kibana 图形化 search profiler 的底层使用,仅分析查询的 CPU 使用率。同样,search slow log 仅跟踪运行时,但不跟踪内存。Cluster stats API 返回集群和节点级别的指标和统计信息,例如当前峰值 JVM 内存使用量,例如此调用返回的信息:GET /_nodes/stats?filter_path=nodes.*.jvm.mem.pools.old。将这些统计信息与特定的查询运行相关联可能很棘手,因为这些是节点级指标,考虑了所有查询和更广泛的进程,包括 后台段合并,这可能是内存密集型的。因此,我们的基准测试结果不报告 Elasticsearch 查询的峰值内存使用量。

基准测试结果

总结

在我们全面展示基准测试结果之前,我们先提供一个简要摘要。

10 亿行数据集

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



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
当 10 亿行数据集以预聚合形式存在以加速聚合查询 ① 时,ClickHouse 需要的磁盘空间比 Elasticsearch 少 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,并且聚合完整数据集的速度快 5 倍,聚合过滤后的数据集的速度快 7 倍。



存储大小聚合性能
链接
Markdown Image
链接
Markdown Image
当原始数据被预聚合以支持加速聚合查询 ① 时,ClickHouse 需要的磁盘空间少 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。正如预期的那样,我们的 索引排序配置为索引编解码器启用了高压缩率:在没有索引排序的情况下,使用 LZ4,索引大小为 135.6 GB with,使用 DEFLATE,索引大小为 91.5 GB with

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

索引编解码器(LZ4DEFLATE应用于 stored fields包括 _source),但应用于 doc_values,这是我们的索引中剩余的主要数据结构,其中 _source 已禁用。在 doc_values 中,每列基于数据类型和基数单独编码(不使用 LZ4DEFLATE)。索引排序确实支持更好的 前缀压缩,用于 doc_values,并且启用了紧凑且访问高效的 doc-ids 编码。但总的来说,doc_values 的压缩率不如 stored fields 从索引排序中获益那么多,例如,在没有索引排序且没有 stored fields(禁用 _source)的情况下,Elasticsearch 索引的存储大小 37.7 GB (LZ4) 35.4 (DEFLATE)。(这些大小甚至略小于具有索引排序的大小的事实与稍微不同的滚动更新时间有关,导致分片和段的数据结构开销略有不同。)

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

在 Elasticsearch 中,_source 字段是功能上等同于 ClickHouse 所必需的(例如,启用 update 操作并运行 reindex 操作,相当于 ClickHouse INSERT INTO SELECT 查询)。当使用相同的压缩级别时(LZ4 vs. LZ4DEFLATE vs. ZSTD),ClickHouse 需要的存储空间少 9 到 12 倍

100 亿行

10b.png

与 10 亿个事件数据集一样,我们测量了Elasticsearch 的存储大小,无论是否包含 _source,以及默认的 LZ4 和更重的 DEFLATE 编解码器。同样,索引排序允许更好的压缩率(例如,在没有索引排序且包含 _source 的情况下,使用 LZ4,索引大小为 1.3 TB with)。

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

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

1000 亿行

100b.png

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

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

用于加速查询 ① 的预聚合数据

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

10 亿行

1b-pre-q1.png

每个项目的预聚合计数数据集仅包含 43.4 万行,而不是原始的 10 亿行,因为每个现有项目(共 43.4 万个)都有一行包含预先计算的计数。我们为 Elasticsearch 和 ClickHouse 使用了标准 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

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

100 亿行

10b-pre-q1.png

每个项目的预聚合计数数据集仅包含 46.5 万行,而不是 100 亿行,因为每个现有项目(共 46.5 万个)都有一行包含预先计算的计数。我们为 Elasticsearch 和 ClickHouse 使用了标准 LZ4 压缩编解码器,并为 Elasticsearch 禁用了 _source

ClickHouse 需要的存储空间比 Elasticsearch少 8 倍以上

1000 亿行

100b-pre-q1.png

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

ClickHouse 使用 LZ4 压缩需要 16 MB

用于加速查询 ② 的预聚合数据

此外,为了加速 特定国家/地区最受欢迎的 3 个项目聚合查询 ②,我们创建了单独的预聚合数据集,其存储大小将在下面列出。

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 需要的存储空间比 Elasticsearch少 7 倍

1000 亿行

100b-pre-q2.png

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

ClickHouse 使用 LZ4 压缩需要 480 MB

聚合性能

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

查询 ① - 完整数据聚合

本节介绍了我们的基准查询 ① 的运行时,该查询聚合和排序完整数据集。

10 亿行 - 原始数据

这些是在 冷启动 查询运行时,用于在 原始(未预聚合)的 10 亿行数据集上运行我们的 最受欢迎的 3 个项目聚合查询: 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 计算节点的规模,并在单个节点上运行查询。在单个 ClickHouse Cloud 节点上,**使用 8 个 CPU 核而不是 32 个**,聚合查询在 2763 毫秒运行(禁用缓存的冷启动时间)。32 核 CPU 的 EC2 机器是 c6a.8xlarge 实例,起价为 每小时 1.224 美元。8 核 CPU 的实例将是 c6a.2xlarge,起价为 每小时 0.306 美元便宜 4 倍

10 亿行 - 预聚合数据

以下是在包含预聚合计数的数据集上运行 top 3 most popular projects 查询,而不是在原始 10 亿行数据集上运行的运行时: q1_1b_pre.png 正如讨论的那样,ESQL 目前不支持扁平字段类型,该类型用于(需要用于)生成预聚合数据集的 Elastic 转换中。

ClickHouse 运行查询的速度比 Elasticsearch9 倍,并且使用约 75 MB 的 RAM。同样,由于查询延迟低,因此对于此查询而言,使用并行 ClickHouse Cloud 计算节点没有意义。

100 亿行 - 原始数据

以下是在原始(非预聚合)100 亿行数据集上运行我们的 top 3 most popular projects 聚合查询的冷启动查询运行时: q1_10b_raw.png 使用 ESQL 和 query DSL 查询,Elasticsearch 分别需要 32 秒和 33 秒。

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

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

100 亿行 - 预聚合数据

以下是在包含预聚合计数的数据集上运行 top 3 most popular projects 查询,而不是在原始 100 亿行数据集上运行的运行时: q1_10b_pre.png

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

1000 亿行 - 原始数据

q1_100b_raw.png

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

我们在此处仅为完整性而展示 ClickHouse 查询运行时。在我们的测试机器上,ClickHouse 在 83 秒内运行查询。

1000 亿行 - 预聚合数据

q1_100b_pre.png

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

ClickHouse 在 25 毫秒内运行查询。

查询 ② - 筛选数据聚合

本节展示了我们的聚合基准查询 ② 的运行时,该查询在应用和排序每个项目的 count(*) 聚合之前,先按特定国家/地区筛选数据集。

10 亿行 - 原始数据

以下图表显示了在原始(非预聚合)10 亿行数据集上运行查询(计算按特定国家/地区筛选数据集时的前 3 个项目)的冷启动运行时: q2_1b_raw.png

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

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

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

10 亿行 - 预聚合数据

以下是在包含预聚合计数的数据集上运行基准查询 ②(计算按特定国家/地区筛选数据集时的前 3 个项目),而不是在原始 10 亿行数据集上运行的运行时: q2_1b_pre.png

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

100 亿行 - 原始数据

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

Elasticsearch ESQL 对于此查询的表现不佳,运行时为 96 秒

Elasticsearch query DSL 运行时相比,ClickHouse 运行查询的速度快了将近 7 倍,消耗约 273 MB RAM。

100 亿行 - 预聚合数据

以下是在包含预聚合计数的数据集上运行基准查询 ②,而不是在原始 100 亿行数据集上运行的运行时: q2_10b_pre.png

ClickHouse 运行此查询的速度比 Elasticsearch 大约快 5 倍,并且使用约 19 MB 的 RAM。

1000 亿行 - 原始数据

q2_100b_raw.png

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

ClickHouse 在 2.9 秒内运行查询。

1000 亿行 - 预聚合数据

q2_100b_pre.png

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

ClickHouse 在 46 毫秒内运行查询。

总结

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

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

我们的配套博文深入技术性地解释了为什么 ClickHouse 如此快速高效。

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

敬请关注!

分享这篇文章

订阅我们的新闻资讯

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