Elastic Stack vs ClickStack
- UI and Alerting:用于查询数据、构建仪表盘和管理告警的工具。
- Storage and Query Engine:负责存储可观测性数据并提供分析查询的后端系统。
- Data Collection and ETL:用于收集遥测数据并在摄取前进行处理的 agent 和管道。
| Role | Elastic Stack | ClickStack | Comments |
|---|---|---|---|
| UI & Alerting | Kibana — 仪表盘、搜索和告警 | ClickStack UI (HyperDX) — 实时 UI、搜索和告警 | 两者都是面向用户的主要界面,支持可视化和告警管理。ClickStack UI 专为可观测性打造,并与 OpenTelemetry 语义紧密结合。 |
| Storage & Query Engine | Elasticsearch — 带倒排索引的 JSON 文档存储 | ClickHouse — 采用向量化引擎的列式数据库 | Elasticsearch 使用针对搜索优化的倒排索引;ClickHouse 使用列式存储和 SQL,对结构化和半结构化数据提供高速分析能力。 |
| Data Collection | Elastic Agent、Beats (例如 Filebeat、Metricbeat) | OpenTelemetry Collector (edge + gateway) | Elastic 支持自定义采集器,以及由 Fleet 管理的统一 agent。ClickStack 依赖 OpenTelemetry,从而实现与厂商无关的数据收集和处理。 |
| Instrumentation SDKs | Elastic APM agents (专有) | OpenTelemetry SDKs (由 ClickStack 分发) | Elastic SDK 与 Elastic 技术栈深度绑定。ClickStack 则基于 OpenTelemetry SDK,为主流编程语言提供日志、指标和链路追踪支持。 |
| ETL / Data Processing | Logstash、摄取管道 | OpenTelemetry Collector + ClickHouse materialized views | Elastic 使用摄取管道和 Logstash 执行转换。ClickStack 则通过 materialized views 和 OTel collector 处理器将计算前移到写入时,从而以高效、增量的方式转换数据。 |
| Architecture Philosophy | 垂直集成、专有代理和格式 | 基于开放标准、松耦合组件 | Elastic 构建的是一个紧密集成的生态系统。ClickStack 则强调模块化和标准 (OpenTelemetry、SQL、对象存储) ,以实现更好的灵活性和成本效益。 |
Elasticsearch 与 ClickHouse
核心结构概念
| Elasticsearch | ClickHouse / SQL | 说明 |
|---|---|---|
| 字段 | 列 | 数据的基本单元,包含一个或多个特定类型的值。Elasticsearch 字段既可以存储基本类型,也可以存储数组和对象。字段只能有一种类型。ClickHouse 也支持数组和对象 (Tuples、Maps、Nested) ,以及 Variant 和 Dynamic 等动态类型,允许一列包含多种类型。 |
| 文档 | 行 | 字段 (列) 的集合。默认情况下,Elasticsearch 文档更加灵活,会根据数据动态添加新字段 (类型会自动推断) 。而 ClickHouse 的行默认受 schema 约束,用户需要为一行插入全部列或其中一个子集。ClickHouse 中的 JSON 类型支持类似的半结构化动态列创建,可根据插入的数据生成。 |
| 索引 | 表 | 查询执行和存储的单位。在这两个系统中,查询都是针对索引或表执行的,它们存储行/文档。 |
| 隐式 | schema (SQL) | SQL schema 会将表归入命名空间,通常用于访问控制。Elasticsearch 和 ClickHouse 都没有 schema,但两者都通过角色和 RBAC 支持行级和表级安全控制。 |
| 集群 | 集群 / 数据库 | Elasticsearch 集群是管理一个或多个索引的运行时实例。在 ClickHouse 中,数据库会在逻辑命名空间内组织表,提供与 Elasticsearch 中集群相同的逻辑分组。ClickHouse 集群则是一组分布式节点,类似于 Elasticsearch,但与数据本身解耦并相互独立。 |
数据建模与灵活性
Dynamic、Variant 和 JSON 类型提供了灵活性。这些类型支持摄取半结构化数据,并提供与 Elasticsearch 类似的动态列创建和类型推断能力。同样,Map 类型也允许存储任意键值对——不过键和值都必须是单一类型。
ClickHouse 在类型灵活性方面的方法更透明、也更可控。不同于 Elasticsearch 中类型冲突可能导致摄取错误,ClickHouse 允许在 Variant 列中存储混合类型数据,并可借助 JSON 类型支持 schema 演进。
如果不使用 JSON,schema 就是静态定义的。如果某一行未提供值,这些字段要么会定义为 Nullable (ClickStack 中不使用) ,要么会回退到该类型的默认值,例如 String 的空值。
摄取与转换
enrich、rename、grok) 的摄取管道,在索引前对文档进行转换。在 ClickHouse 中,类似功能可通过 增量materialized view 实现,它可以对传入数据进行过滤、转换或富集,并将结果插入目标表。若你只需要存储 materialized view 的输出,也可以将数据插入 Null 表引擎。这意味着只会保留 materialized view 的结果,原始数据则会被丢弃,从而节省存储空间。
在富集方面,Elasticsearch 支持专用的 enrich processors,用于为文档添加上下文信息。在 ClickHouse 中,字典 可在查询时和摄取时对行进行富集——例如,将 IP 映射到地理位置,或在 insert 时执行 User-Agent 查找。
查询语言
ES|QL 使用 左外连接。ClickHouse 支持完整的 SQL 语法,包括所有 JOIN 类型、窗口函数、子查询 (包括关联子查询) 以及 CTE。如果你需要将可观测性信号与业务或基础设施数据关联起来,这是一项重大优势。
在 ClickStack 中,UI 提供了兼容 Lucene 的搜索界面,便于迁移;同时还可通过 ClickHouse backend 获得完整的 SQL 支持。该语法与 Elastic query string 语法类似。要查看该语法的精确对比,请参阅”在 ClickStack 和 Elastic 中搜索”。
文件格式和接口
索引与存储
Elasticsearch 中的插入处理Ⓐ 新插入的文档 Ⓑ 会先进入内存中的索引缓冲区,默认每秒刷新一次。系统会使用路由公式来确定刷新后文档的目标分片,并在磁盘上为该分片写入一个新的分段。为了提高查询效率,并支持对已删除或已更新文档进行物理删除,系统会在后台持续将分段合并成更大的分段,直到达到 5 GB 的最大大小。不过,也可以强制将其合并为更大的分段。
_source 中 (使用 LZ4、Deflate 或 ZSTD 进行压缩) ,而 ClickHouse 不会存储单独的文档表示。数据会在查询时根据各列重建,从而节省存储空间。Elasticsearch 也可以通过 Synthetic _source 实现同样的能力,但会有一些限制。禁用 _source 也会带来一些影响,而这些问题并不适用于 ClickHouse。
在 Elasticsearch 中,索引映射 (相当于 ClickHouse 中的表 schema) 控制字段类型,以及用于持久化和查询的数据结构。
相比之下,ClickHouse 是列式存储的——每一列都独立存储,但始终按表的主键/排序键排序。这种排序方式使得 稀疏主索引 成为可能,从而让 ClickHouse 能在查询执行期间高效跳过部分数据。当查询按主键字段过滤时,ClickHouse 只读取每一列中相关的部分,显著减少磁盘 I/O 并提升性能——即使没有为每一列都建立完整索引也是如此。
ClickHouse 还支持 跳过索引,可通过为选定列预计算索引数据来加速过滤。这些索引必须显式定义,但可以显著提升性能。此外,ClickHouse 允许你按列指定 压缩编解码器 和压缩算法——而 Elasticsearch 不支持这一点 (它的压缩仅适用于 _source JSON 存储) 。
ClickHouse 也支持分片,但其设计模型更偏向于 垂直扩缩容。单个分片可以存储 数万亿行,只要内存、CPU 和磁盘资源允许,就能持续高效运行。与 Elasticsearch 不同,每个分片没有硬性的行数上限。ClickHouse 中的分片是逻辑概念——实际上就是独立的表——除非数据集超出单个节点的容量,否则无需分区。这种情况通常是由磁盘容量限制导致的,因此只有在确实需要水平扩展时才会引入分片 ①,从而降低复杂性和额外开销。在这种情况下,与 Elasticsearch 类似,一个分片只保存数据的一个子集。单个分片中的数据会组织成一组 ② 不可变的数据分区片段,其中包含 ③ 若干数据结构。
ClickHouse 分片内的处理是完全并行化的,因此建议用户优先采用垂直扩缩容,以避免跨节点移动数据带来的网络成本。
分布与复制
SELECT 查询转发到所有分片并合并结果。对于 INSERT 操作,它们会通过将数据均匀路由到各个分片来平衡负载。ClickHouse 的复制非常灵活:任何副本 (即某个分片的一份副本) 都可以接受写入,所有更改都会异步同步到其他副本。这种架构可在故障或维护期间持续提供查询服务,并自动完成重新同步——无需在数据层强制采用主从模型。
ClickHouse Cloud在 ClickHouse Cloud 中,该架构引入了共享无架构的计算模型,其中单个 分片以对象存储为后端。这取代了传统基于副本的高可用方式,使同一个分片能够被 多个节点同时读取和写入。存储与计算分离后,无需显式管理副本即可实现弹性扩缩容。
- Elastic:分片是与 JVM 内存绑定的物理 Lucene 结构。过度分片会带来性能损耗。复制是同步的,并由主节点协调。
- ClickHouse:分片在逻辑上更灵活,也支持纵向扩展,同时本地执行效率极高。复制是异步的 (但也可采用顺序一致性) ,协调机制较为轻量。
去重与路由
_id 去重,并据此将其路由到相应的分片。ClickHouse 不会默认存储行标识符,但支持插入时去重,因此用户可以安全地重试失败的插入操作。若需要更精细的控制,ReplacingMergeTree 和其他表引擎还支持基于特定列去重。
Elasticsearch 中的索引路由可确保特定文档始终被路由到特定分片。在 ClickHouse 中,你可以定义分片键,或使用 Distributed 表来实现类似的数据局部性。
聚合与执行模型
SELECT count(*) FROM ... GROUP BY ... SQL 查询对应的是 terms aggregation,它属于 Elasticsearch 的一种 bucket aggregation。
就功能而言,ClickHouse 中带 count(*) 的 GROUP BY 与 Elasticsearch 的 terms aggregation 通常是等价的,但两者在实现方式、性能和结果质量上差异很大。
当查询的数据跨多个分片时,Elasticsearch 中的这种聚合会在 “top-N” 查询中对结果进行估算 (例如按计数排序的前 10 个主机) 。这种估算可以提升速度,但可能会影响准确性。你可以通过查看 doc_count_error_upper_bound 并增大 shard_size 参数来降低这种误差——代价是更高的内存占用和更慢的查询性能。
对于所有桶聚合,Elasticsearch 还要求设置 size——无法在不显式设置上限的情况下返回所有唯一分组。高基数聚合可能会触及 max_buckets 限制,或者需要通过 composite aggregation 分页处理,而这通常既复杂又低效。
相比之下,ClickHouse 开箱即用就能执行精确聚合。像 count(*) 这样的函数无需额外调整配置即可返回准确结果,使查询行为更简单,也更可预测。
ClickHouse 没有 size 限制。你可以在大型数据集上执行无界的 group-by 查询。如果超过内存阈值,ClickHouse 可以将数据落盘。按主键前缀分组的聚合尤其高效,通常只需极少的内存即可运行。
执行模型
- SIMD 向量化:对列式数据的操作会使用 CPU SIMD 指令 (例如 AVX512) ,从而实现对值的批次处理。
- 集群级并行化:在分布式部署中,每个节点都会在本地执行查询处理。部分聚合状态会以流式方式传输到发起节点并进行合并。如果查询的
GROUP BY键与分片键一致,则合并过程可以被尽量减少,甚至完全避免。
这种模型支持在核心和节点间高效扩展,使 ClickHouse 非常适合大规模分析。借助部分聚合状态,来自不同线程和节点的中间结果可以在不损失精度的情况下完成合并。 相比之下,Elasticsearch 对于大多数聚合会为每个分片分配一个线程,而不管可用的 CPU 核心有多少。这些线程会返回分片本地的 Top-N 结果,然后在协调节点上进行合并。这种方式可能无法充分利用系统资源,并可能在全局聚合中引入精度问题,尤其是在高频术语分散在多个分片中时。可以通过增大
shard_size 参数来提高准确性,但代价是更高的内存使用量和查询延迟。
总之,ClickHouse 以更细粒度的并行方式执行聚合和查询,并能更充分地利用硬件资源;而 Elasticsearch 则依赖基于分片的执行模型,限制也更为严格。
如需进一步了解这两种技术中聚合的实现机制,我们推荐阅读这篇博客文章:“ClickHouse vs. Elasticsearch: Count 聚合的实现机制”。
数据管理
索引生命周期管理 vs 原生 TTL
存储层级与冷热分层架构
MergeTree 等原生表引擎支持 分层存储,可根据自定义规则自动将较旧的数据在不同的 卷 之间迁移 (例如从 SSD 到 HDD,再到对象存储) 。这可以实现类似 Elastic 的 hot-warm-cold 方案——但无需管理多个节点角色或集群所带来的复杂性。
ClickHouse Cloud在 ClickHouse Cloud 中,这一过程更加无缝:所有数据都存储在 对象存储 (例如 S3) 中,计算与存储相互解耦。数据可以一直保留在对象存储中,直到查询时才被拉取并缓存在本地 (或分布式缓存中) ——既能提供与 Elastic 冻结层相近的成本特征,又具备更好的性能表现。这意味着无需在不同存储层级之间迁移数据,因此冷热分层架构也就变得没有必要了。
Rollup 与增量聚合的对比
blue 文档 (11、12 和 13) 。因此,系统会过滤源索引中所有现有的 blue 文档,然后通过 composite aggregation (以利用结果的分页能力) 重新计算聚合值 (并更新目标索引中的文档,用新的聚合结果替换包含先前聚合值的文档) 。同样,在 ② 和 ③ 处,也会通过检查变更并基于同一 blue bucket 中所有现有文档重新计算聚合值,来处理新的 checkpoint。
ClickHouse 采用的是一种根本不同的方法。ClickHouse 不会周期性地重新聚合数据,而是支持 增量materialized view,可在写入时对数据进行转换和聚合。当新数据写入源表时,materialized view 只会对新写入的块执行预定义的 SQL 聚合查询,并将聚合结果写入目标表。
这种模型之所以可行,是因为 ClickHouse 支持 部分聚合状态——即聚合函数的中间表示形式,可以存储起来并在后续进行合并。这使你能够维护部分聚合后的结果,它们查询速度快、更新成本低。由于聚合是在数据到达时完成的,因此无需运行昂贵的周期性作业,也不需要重新汇总旧数据。
下面我们从抽象层面说明增量 materialized view 的工作机制 (注意:我们用蓝色表示属于同一分组、需要为其预先计算聚合值的所有行) :
在上图中,materialized view 的源表中已经包含一个数据分区片段,其中存储了一些属于同一分组的 blue 行 (1 到 10) 。对于这个分组,view 的目标表中也已经存在一个数据分区片段,其中存储了 blue 分组的一个部分聚合状态。当 ① ② ③ 向源表写入新行时,每次写入都会在源表中创建一个对应的数据分区片段;同时,并行地,仅针对每个新写入行块,计算一个部分聚合状态,并以数据分区片段的形式写入 materialized view 的目标表。④ 在后台数据分区片段合并期间,这些部分聚合状态会被合并,从而实现增量数据聚合。
请注意,所有聚合函数 (超过 90 种) ,以及它们与聚合函数 组合器 的组合,都支持部分聚合状态。
如需查看 Elasticsearch 与 ClickHouse 在增量聚合方面更具体的对比例子,请参见这个示例。
ClickHouse 这种方法的优势包括:
- 聚合结果始终最新:materialized view 始终与源表保持同步。
- 无需后台作业:聚合发生在写入时,而不是查询时。
- 更好的实时性能:非常适合可观测性工作负载和实时分析场景,尤其是在需要立即获得最新聚合结果时。
- 可组合:materialized view 可以分层构建,也可以与其他视图和表联接,以实现更复杂的查询加速策略。
- 不同的 TTL:可以为源表和 materialized view 的目标表设置不同的生存时间 (TTL)。
湖仓支持
- 数据目录集成:ClickHouse 支持与 AWS Glue 等数据目录集成,从而自动发现并访问对象存储中的表。
- 对象存储支持:原生支持直接查询存储在 S3、GCS 和 Azure Blob Storage 中的数据,无需移动数据。
- 查询联邦:能够借助 external dictionaries 和 table functions,关联来自多个来源的数据,包括湖仓表、传统数据库和 ClickHouse 表。
- 增量加载:支持使用 S3Queue 和 ClickPipes 等功能,将湖仓表中的数据持续加载到本地 MergeTree 表。
- 性能优化:可通过 cluster functions 对湖仓数据执行分布式查询,以提升性能。