博客 / 工程

SQL-based observability 的演进

author avatar
Dale McDiarmid & Ryadh Dahimene
2024 年 11 月 11 日 - 33 分钟阅读

简介

大约一年前,我们写了一篇关于 基于 SQL 的可观测性的现状 的博客,探讨了两种既定范式的并行背景:SQL 和可观测性。我们解释了它们是如何碰撞并共同在可观测性领域创造了一系列新机遇,同时试图回答“基于 SQL 的可观测性何时适用于我的用例?”这篇博文引起了广泛关注,并被证明在向新用户讨论 ClickHouse 及其在可观测性领域中的作用时非常有用。

尽管最初博文的总体主题仍然成立,但一年对于 ClickHouse 的发展来说已经很长了!除了我们认为将进一步推动 ClickHouse 成为可观测性数据的实际数据库的众多新功能外,围绕 ClickHouse 的生态系统也已经成熟,简化了新用户的采用。

在这篇文章中,我们将探讨其中一些新功能,并展望一些实验性工作,我们希望这些工作将构成 ClickHouse 在可观测性中较少探索的指标支柱中使用的基础。

2023 年基于 SQL 的可观测性现状

我们最初的文章提出,可观测性只是另一个数据问题,SQL 和 基于 OLAP 的系统(如 ClickHouse)非常适合解决这个问题。我们探讨了集中式日志记录的历史,以及 Splunk 和 ELK 堆栈等解决方案如何从 syslog 和 NoSQL 时代中涌现出来。尽管前者广受欢迎,但 SQL 的独特优势使其保持了相关性,即使在 NoSQL 繁荣时期,它仍然是 第三大最广泛采用的结构化数据管理语言

通过将可观测性重新思考为一个数据问题,我们提出可以将 OLAP 原则和 SQL 应用于可观测性的存储层。这种方法带来了关键优势:高效压缩减少了存储需求,加速了数据摄取和检索,以及通过使用对象存储实现的无限存储。SQL-based observability 的多功能性因互操作性而得到增强,例如 ClickHouse 支持广泛的数据格式和集成引擎,简化了其在各种可观测性管道中的包含。SQL 的广泛功能提供了无与伦比的分析表达能力,这些功能共同降低了可观测性数据的总拥有成本 (TCO)。

img08_9763861f2e

这种转变的一个重要推动因素是 OpenTelemetry,它标准化了跨平台的数据收集,并将数据收集从差异化因素转变为商品。一年后,我们可以自信地说,OTel 正朝着赢得“收集器之战”的方向发展。

这种行业范围内的采用减少了供应商锁定,并使将可观测性数据与基于 SQL 的存储解决方案集成变得更加容易。

img09_4566662115

我们得出的结论是,由此产生的基于 SQL 的可观测性堆栈既简单又不带偏见,为用户个性化、调整和集成到现有 IT 环境中留下了许多选择。

考虑到这一点,并为了确保我们的用户获得成功,我们提供了一个简单的清单,以帮助用户确定基于 SQL 的可观测性是否适合他们。具体来说,

如果符合以下条件,则基于 SQL 的可观测性适合您

  • 您或您的团队熟悉 SQL(或想学习它)
  • 您更喜欢遵守 OpenTelemetry 等开放标准,以避免供应商锁定并实现可扩展性。
  • 您愿意运行一个由从收集到存储和可视化的开源创新驱动的生态系统。
  • 您设想管理下的可观测性数据量会有一定程度的增长到中等或大型(甚至非常大的量)
  • 您希望控制 TCO(总拥有成本)并避免可观测性成本螺旋上升。
  • 您可以或不想为了管理成本而陷入可观测性数据的小数据保留期。

如果符合以下条件,则基于 SQL 的可观测性可能不适合您

  • 学习(或生成!)SQL 对您或您的团队没有吸引力。
  • 您正在寻找打包的端到端可观测性体验。
  • 您的可观测性数据量太小,无法产生任何显着差异(例如,<150 GiB),并且预计不会增长。
  • 您的用例是指标密集型的,需要 PromQL。在这种情况下,您仍然可以将 ClickHouse 用于日志和跟踪,Prometheus 用于指标,并在表示层使用 Grafana 将其统一起来。
  • 您更喜欢等待生态系统更加成熟,并且基于 SQL 的可观测性变得更加交钥匙。

问题是,随着 2024 年接近尾声,鉴于 ClickHouse 的发展,这种情况是否发生了变化?

JSON 支持!可观测性的游戏规则改变者?

在讨论 JSON 支持的最新进展之前,重要的是概述我们所说的 JSON 支持是什么意思,为什么它对可观测性很重要,以及我们用户目前面临的挑战。

我们所说的 JSON 支持是什么意思?

长期以来,ClickHouse 一直支持 JSON 作为输入和输出格式,这为其早期集成到可观测性工具中铺平了道路。JSON 数据可以通过 ClickHouse 的原生和 HTTP 接口发送,并具有多种 输出格式以满足特定需求。这种灵活性使其能够直接与 OpenTelemetry 和 Grafana 等工具集成,从而实现流畅的数据摄取和可视化。此外,它还允许用户 轻松构建自定义界面,使 ClickHouse 能够适应许多可观测性用例。

Blog_SQLObservabilityDiagrams_202411_V1.0-01.png

但是,JSON 集成与 完全支持 JSON 作为列类型 不同,后者在可观测性中变得越来越重要。我们所说的意思是将列指定为 JSON 的能力,它可以存储嵌套的动态结构,这些结构的值对于相同的路径可能具有不同的数据类型(可能不兼容且事先未知)。

Blog_SQLObservabilityDiagrams_202411_V1.0-02.png

为什么 JSON 支持很重要?

部署可观测性解决方案的用户需要能够“发送事件”,而无需担心架构优化或优化。即使可以花时间定义架构,集中式可观测性系统通常也会聚合来自不同来源的数据,每个来源都具有独特或不断发展的数据结构。这些数据可能来自不同的应用程序、团队,甚至完全独立的组织,尤其是在 SaaS 解决方案中。在这些情况下,为所有数据实现通用命名约定或架构具有挑战性。

虽然结构化日志记录和 OpenTelemetry 架构标准以及 语义约定 有助于解决此问题,但用户仍然需要捕获任意标签和字段。这些字段的数量、类型和嵌套级别可能会因来源而异。为了解决这个问题,专用于“非结构化 JSON”数据的列非常宝贵,它可以灵活地存储动态属性,例如 Kubernetes 标签或自定义应用程序特定的元数据。

理想情况下,ClickHouse 将支持 JSON 列类型,使用户能够直接发送半结构化数据。这种方法将提供与传统数据类型相同的功能、压缩和性能,同时减少架构管理和定义所需的工作量。

当前方法的挑战

目前,用于 ClickHouse 的 OTel 导出器为日志、跟踪和指标提供了默认架构。如下面的日志架构所示,这些架构在很大程度上依赖于传统的经典类型,例如 DateTime 和 String(以及诸如 LowCardinality 和编解码器 之类的优化)

  
1CREATE TABLE otel.otel_log (
2	Timestamp DateTime64(9) CODEC(Delta(8), ZSTD(1)),
3	TimestampTime DateTime DEFAULT toDateTime(Timestamp),
4	TraceId String CODEC(ZSTD(1)),
5	SpanId String CODEC(ZSTD(1)),
6	TraceFlags UInt8,
7	SeverityText LowCardinality(String) CODEC(ZSTD(1)),
8	SeverityNumber UInt8,
9	ServiceName LowCardinality(String) CODEC(ZSTD(1)),
10	Body String CODEC(ZSTD(1)),
11	ResourceSchemaUrl LowCardinality(String) CODEC(ZSTD(1)),
12	ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
13	ScopeSchemaUrl LowCardinality(String) CODEC(ZSTD(1)),
14	ScopeName String CODEC(ZSTD(1)),
15	ScopeVersion LowCardinality(String) CODEC(ZSTD(1)),
16	ScopeAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
17	LogAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
18       --indexes omitted
19) ENGINE = MergeTree
20PARTITION BY toDate(TimestampTime)
21PRIMARY KEY (ServiceName, TimestampTime)
22ORDER BY (ServiceName, TimestampTime, Timestamp)

但是,请注意 Map 类型是如何用于列 LogAttributes, ResourceAttributes, 和 ScopeAttributes. 的。OpenTelemetry 中的这些列捕获不同级别的元数据:LogAttributes 保存特定于单个日志事件的详细信息,ResourceAttributes 包含有关生成数据的源的信息,以及 ScopeAttributes 存储有关应用程序代码或检测的上下文。这些属性共同为过滤和分组提供了必要的上下文。重要的是,它们完全取决于用例,并且可能会根据来源和应用程序以及随时间变化而显着变化。

虽然 Map 类型允许用户通过支持动态添加键来存储这些字段,但它会产生一些明显的缺点

  • 类型精度损失 - 此表示形式中的字段名称和值都以字符串形式保存,Map 仅支持单个类型作为值(用户现在可以使用 Variant 作为 Map 的值)。此处应用 String 类型作为“统一”类型。这意味着对数值进行更复杂的比较需要查询时转换。这既冗长又效率低下,影响查询延迟和查询的内存开销。
  • 单列实现,具有线性复杂性 - 所有嵌套的 JSON 路径都保存在单列值中。当访问 Map 中的特定已知键时,ClickHouse 需要 加载 Map 中的所有值并进行线性扫描。当仅读取单个路径时,这会产生显着的 IO 开销,从而有效地浪费了 IO 开销,这使得对具有大量键的 Map 进行查询的效率低下且速度比用户预期的慢。为了解决这个问题,用户将在插入时使用 物化列 从 Map 中提取值
  • 可用键的模糊处理 - 通过将 JSON 路径存储为 Map 键,用户无法在没有查询的情况下识别可用于查询的路径。虽然 可能,但这需要昂贵的线性扫描 才能识别所有可用键。虽然可以使用物化视图在插入时计算此集合来解决此问题,但这会增加额外的复杂性,并且需要 UI 工具来与输出集成,以便提供有用的查询指导功能。
  • 不支持嵌套结构 - 使用 String 作为 Map 值类型将其限制为仅一级。ClickHouse 中的其他类型,例如 TupleNested,支持嵌套结构,但这些结构需要在接收数据之前指定结构。

新的 JSON 类型解决了所有这些挑战,提供了一种有效的方式来支持动态嵌套结构,同时保留与静态类型列相关的精度和性能。

高效的 JSON 支持

当我们最初的博文发布时,ClickHouse 的 JSON 支持还处于起步阶段。该功能作为实验性功能存在了一段时间,但存在重大挑战。其中大多数挑战源于其无法有效处理大量 JSON 路径,以及其尝试通过动态统一其类型来处理同一路径的不同类型。事实证明这是一个根本性的设计缺陷,需要我们从头开始重写该功能。在 24.8 中,我们宣布 这种重写已基本完成,并且正在走向 beta 版,我们对其实现的信心很高,认为其实现已达到理论上的最佳水平。

正如我们在最近的博客中详细介绍的那样,ClickHouse 中的这种 新 JSON 实现 专门用于解决在列式数据库中处理 JSON 的独特挑战,同时避免类型统一或强制执行的陷阱,这可能会扭曲数据或使查询更具挑战性。

许多 其他数据存储 旨在通过从遇到路径的第一个事件中推断类型来解决这些挑战 - 这是我们最初的 JSON 实现采用的方法。对于路径具有不同类型的未来事件,要么被强制转换,要么被拒绝。这对于可观测性来说是次优的,因为很少能保证事件是干净且一致的。除非执行查询时转换,否则结果是查询性能差或用户可用的运算符集减少。

如下所示,ClickHouse 以真正的列式格式存储每个唯一 JSON 路径的值,甚至支持路径具有不同类型的情况。这是通过为每个唯一路径和类型对创建单独的子列来实现的。

json_column_per_type.png

例如,当插入两个具有不同类型的 JSON 路径时,ClickHouse 将每个 具体类型的值存储在不同的子列中。可以独立访问这些子列,从而最大限度地减少不必要的 I/O。请注意,当查询具有多种类型的列时,其值仍作为单个列式响应返回。

此外,通过 利用偏移量,ClickHouse 确保这些子列保持密集,对于不存在的 JSON 路径不存储默认值。这种方法最大限度地提高了压缩率并进一步减少了 I/O。

json_offsets_column.png

通过将每个唯一的 JSON 路径捕获为不同的子列,ClickHouse 还可以有效地提供可用路径列表,从而使 UI 能够利用自动完成等功能。

此外,该类型不会因大量唯一 JSON 路径而导致子列爆炸问题。请注意,这不会阻止存储这些路径;相反,如果超过限制,它只是将新路径存储在单个共享数据列中(带有加速查询的统计信息)。

json_overflow.png

借助这种增强的 JSON 数据类型,用户可以从复杂、嵌套的 JSON 结构中受益于可扩展、高性能的分析 - 使 ClickHouse 的 JSON 支持与其系统中任何经典数据类型一样高效和快速。

对于好奇这种新型列类型的实现的用户,我们建议阅读我们的 详细博客文章

OTel 的 JSON

我们希望此功能在 ClickHouse 的 OpenTelemetry 架构中公开。例如,考虑以下将 JSON 类型应用于我们的非结构化列的日志架构(省略 JSON 类型的配置)

  
1CREATE TABLE otel_log (
2	Timestamp DateTime64(9) CODEC(Delta(8), ZSTD(1)),
3	TimestampTime DateTime DEFAULT toDateTime(Timestamp),
4	TraceId String CODEC(ZSTD(1)),
5	SpanId String CODEC(ZSTD(1)),
6	TraceFlags UInt8,
7	SeverityText LowCardinality(String) CODEC(ZSTD(1)),
8	SeverityNumber UInt8,
9	ServiceName LowCardinality(String) CODEC(ZSTD(1)),
10	Body String CODEC(ZSTD(1)),
11	ResourceSchemaUrl LowCardinality(String) CODEC(ZSTD(1)),
12	ResourceAttributes JSON,
13	ScopeSchemaUrl LowCardinality(String) CODEC(ZSTD(1)),
14	ScopeName String CODEC(ZSTD(1)),
15	ScopeVersion LowCardinality(String) CODEC(ZSTD(1)),
16	ScopeAttributes JSON,
17	LogAttributes JSON,
18       --indexes omitted
19) ENGINE = MergeTree
20PARTITION BY toDate(TimestampTime)
21PRIMARY KEY (ServiceName, TimestampTime)
22ORDER BY (ServiceName, TimestampTime, Timestamp)

所有这些都意味着用户将能够在上述 OTel 列中发送任意数据,并且知道它们将被有效地存储和轻松查询。

请注意,我们仍然建议遵守更广泛的 OTel 架构,其中列存在于根目录,而不是仅使用单个 JSON 列!这些更显式的列受益于更广泛的 ClickHouse 功能,例如支持编解码器和二级索引。因此,将常用查询的列提取到根目录仍然有意义。

示例

考虑一个结构化日志数据集。

  
1{"remote_addr":"40.77.167.129","remote_user":"-","run_time":0,"time_local":"2019-01-22 00:26:17.000","request_type":"GET","request_path":"\/image\/14925\/productModel\/100x100","request_protocol":"HTTP\/1.1","status":"500","size":1696.22,"referer":"-","user_agent":"Mozilla\/5.0 (compatible; bingbot\/2.0; +http:\/\/www.bing.com\/bingbot.htm)","response_time":23.2}
2{"remote_addr":"91.99.72.15","remote_user":"-","run_time":"0","time_local":"2019-01-22 00:26:17.000","request_type":"GET","request_path":"\/product\/31893\/62100\/----PR257AT","request_protocol":"HTTP\/1.1","status":200,"size":"41483","referer":"-","user_agent":"Mozilla\/5.0 (Windows NT 6.2; Win64; x64; rv:16.0)Gecko\/16.0 Firefox\/16.0","response_time":""}

虽然在很大程度上结构良好,但此处使用的列类型存在差异 - statusresponse_time​​sizerun_time 既是数值又是字符串。使用 OTel 收集器配置 从文件摄取此示例将导致以下结果

  
1SELECT
2	Timestamp,
3	LogAttributes
4FROM otel_logs
5FORMAT Vertical
6
7Row 1:
8──────
9Timestamp: 	2019-01-22 00:26:17.000000000
10LogAttributes: {'response_time':'23.2','remote_addr':'40.77.167.129','remote_user':'-','request_path':'/image/14925/productModel/100x100','size':'1696.22','request_type':'GET','run_time':'0','referer':'-','user_agent':'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)','time_local':'2019-01-22 00:26:17.000','request_protocol':'HTTP/1.1','status':'500','log.file.name':'simple.log'}
11
12Row 2:
13──────
14Timestamp: 	2019-01-22 00:26:17.000000000
15LogAttributes: {'request_protocol':'HTTP/1.1','status':'200','user_agent':'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0)Gecko/16.0 Firefox/16.0','size':'41483','run_time':'0','remote_addr':'91.99.72.15','request_type':'GET','referer':'-','log.file.name':'simple.log','remote_user':'-','time_local':'2019-01-22 00:26:17.000','response_time':'','request_path':'/product/31893/62100/----PR257AT'}
16
172 rows in set. Elapsed: 0.003 sec.

OTel 收集器已将我们的列放在 LogAttributes 中,导致所有列值都映射到 String。因此,即使是简单的查询也变得语法复杂,例如

  
1SELECT LogAttributes['status'] AS status,
2	max(if((LogAttributes['response_time']) != '', 
3	  LogAttributes['response_time']::Float32, 0)) AS max
4FROM otel_logs
5GROUP BY status
6
7┌─status─┬──max─┐
850023.29200010└────────┴──────┘
11
122 rows in set. Elapsed: 0.016 sec.

如果使用 JSON 类型为我们的 LogAttributes 列的 OTel 架构插入相同的 数据,我们可以看到类型已保留。

  
1SELECT
2	Timestamp,
3	LogAttributes
4FROM otel_logs
5FORMAT Vertical
6SETTINGS output_format_json_quote_64bit_integers = 0
7
8Row 1:
9──────
10Timestamp: 	2019-01-22 00:26:17.000000000
11LogAttributes: {"referer":"-","remote_addr":"91.99.72.15","remote_user":"-","request_path":"\/product\/31893\/62100\/----PR257AT","request_protocol":"HTTP\/1.1","request_type":"GET","response_time":"","run_time":"0","size":"41483","status":200,"time_local":"2019-01-22 00:26:17.000000000","user_agent":"Mozilla\/5.0 (Windows NT 6.2; Win64; x64; rv:16.0)Gecko\/16.0 Firefox\/16.0"}
12
13Row 2:
14──────
15Timestamp: 	2019-01-22 00:26:17.000000000
16LogAttributes: {"referer":"-","remote_addr":"40.77.167.129","remote_user":"-","request_path":"\/image\/14925\/productModel\/100x100","request_protocol":"HTTP\/1.1","request_type":"GET","response_time":23.2,"run_time":0,"size":1696.22,"status":"500","time_local":"2019-01-22 00:26:17.000000000","user_agent":"Mozilla\/5.0 (compatible; bingbot\/2.0; +http:\/\/www.bing.com\/bingbot.htm)"}
17
182 rows in set. Elapsed: 0.012 sec.

默认情况下,64 位整数在输出中被引用(对于 Javascript)。我们在此处禁用此设置以用于示例目的,设置 output_format_json_quote_64bit_integers=0

我们之前的查询现在更加自然,并且尊重数据的类型差异

  
1SELECT LogAttributes.status as status, max(LogAttributes.response_time.:Float64) as max FROM otel_logs GROUP BY status
2
3┌─status─┬──max─┐
4200	│ ᴺᵁᴸᴸ │
550023.26└────────┴──────┘
7
82 rows in set. Elapsed: 0.006 sec.

除了显式访问 response_time 列的 Float64 变体之外,我们的响应也略有不同,因为我们的空值被视为 Null 而不是 0。最后,我们可以使用 distinctJSONPathsAndTypes 函数查看列的多种类型的存在。

  
1SELECT distinctJSONPathsAndTypes(LogAttributes)
2FROM otel_logs
3FORMAT Vertical
4
5Row 1:
6──────
7distinctJSONPathsAndTypes(LogAttributes): {'referer':['String'],'remote_addr':['String'],'remote_user':['String'],'request_path':['String'],'request_protocol':['String'],'request_type':['String'],'response_time':['Float64','String'],'run_time':['Int64','String'],'size':['Float64','String'],'status':['Int64','String'],'time_local':['DateTime64(9)'],'user_agent':['String']}
8
91 row in set. Elapsed: 0.008 sec.

可观测性生态系统的日益成熟

虽然核心 ClickHouse 中 JSON 功能的开发将简化采用并加快查询速度,但我们还继续投资更广泛的可观测性生态系统。这主要集中在两个方面:在 Open Telemetry Collector 中支持 ClickHouse,以及增强 用于 ClickHouse 的 Grafana 插件

OpenTelemetry

在 OpenTelemetry 中,我们 专注于设计用于 ClickHouse 的开源架构的挑战,该架构在多功能性和性能之间取得平衡。可观测性数据在结构和用途上可能差异很大,并且针对 ClickHouse 优化架构需要考虑特定的用例。我们投入精力为 OpenTelemetry Collector 提供默认架构,这些架构经过优化,可以开始使用“一刀切”的方法。

更重要的是要强调这些架构旨在作为起点,而不是生产实现。我们鼓励具有独特需求的用户(例如,按服务或 Kubernetes pod 名称进行特定过滤)自定义其架构以获得最佳性能。为了实现这种灵活性,导出器允许用户创建自定义表以满足其数据要求,而无需修改导出器代码本身。此外,物化视图可用于调整默认架构以支持自定义属性,从而提供更好地服务于特定查询模式的定制表结构。

随着我们对“入门”架构更有信心,用于日志和跟踪的 ClickHouse 导出器现在已进入 beta 版。 这种成熟度级别现在与其他导出器(如 Elasticsearch 和 Datadog)一致。

Grafana

自 2022 年 5 月首次发布用于 Grafana 的 ClickHouse 插件以来,这两个平台都已显着成熟。在今年早些时候的 一篇博文中,我们认识到对于新用户来说,在可观测性中使用 SQL 的挑战。作为回应,版本 4 的发布引入了新的查询构建器体验,其中日志和跟踪被视为一等公民。这种重新设计的界面减少了手动编写 SQL 的需要,使基于 SQL 的可观测性对于 SRE 和工程师来说更容易访问。

traces_to_logs_f56012eeed

此外,版本 4.0 强调 OpenTelemetry,允许用户指定符合 OTel 标准的日志和跟踪配置。通过这种转变,新插件为 Otel 采用者提供了更精简、更直观的体验。

logs_config_94ddef3f4a

自版本 4.0 发布以来,工作重点一直是丰富用户体验,最近添加了对日志上下文的支持。此功能允许用户查看与特定日志行相关的日志,从而可以快速、上下文地调查事件。

该实现的工作原理是修改基本日志查询以删除所有现有过滤器和 ORDER BY 子句。然后应用上下文过滤器,在数据源配置中定义的键列(例如服务名称、主机名或容器 ID)上匹配所选日志行的值。还添加了基于日志时间戳的时间范围过滤器,以及 ORDER BYLIMIT 来控制显示。生成两个查询,每个方向一个查询,允许用户向上或向下滚动并在初始行周围加载其他日志。

log_context.png

结果是用户能够快速确定错误日志消息的原因,而无需手动删除现有过滤器并在其原始流中查找日志。

show_log_context.png

经验教训

协助我们的用户部署 OTel-ClickHouse 堆栈带来了许多有用的经验教训。

最主要的教训是确保用户尽早识别其查询访问模式,从而为其表选择明智的主键/排序键。虽然 ClickHouse 在线性扫描方面速度很快,但在较大的部署中,如果用户期望亚秒级响应时间,这就不现实了。通常,过滤后的列应始终 位于主键中,并明智地排序以与向下钻取行为保持一致,即第一个也是最常用的过滤列应位于第一个。这与我们之前为用户生成更好的入门架构的努力相一致。

此外,物化视图显然对于满足性能期望和转换 OTel 数据以适应用户的访问模式至关重要。我们越来越多地看到用户将其数据发送到 Null 表引擎,物化视图对插入的行执行查询。此视图的结果将发送到负责服务用户查询的目标表。

materialized_views.png

这有几个明显的优势

  • 转换 - 允许用户在存储之前修改数据结构。虽然 OTel 架构具有广泛的适用性,但用户通常需要从非结构化数据(例如 Body)中提取字段,以便进行高效查询或在主键中使用。虽然可以在 OTel 收集器中完成字符串解析或 JSON 处理等转换,但在 ClickHouse 中执行此工作效率更高,因为它消耗的资源更少且速度更快。有关示例,请参阅架构设计指南的 使用 SQL 提取结构 部分。

  • 替代排序键 - 通常,用户的访问模式过于多样化,无法与单个主键对齐。在这种情况下,我们看到用户要么利用 投影,要么使用物化视图将数据子集复制到具有不同排序键的表中。然后,这些单独的表为具有已知工作流程的子团队或特定仪表板提供服务。或者,我们发现用户使用这些子表充当值的快速查找表,然后用于过滤主表。有关示例,请参阅架构设计指南的 使用物化视图(增量)进行快速查找 部分。

  • 过滤子集 - 物化视图越来越多地用于过滤子集,将这些子集发送到生成警报的表。这允许将这些查询的成本从查询时间转移到插入时间。

  • 预计算聚合 - 与过滤类似,在物化视图中预计算聚合查询(例如,随时间推移的错误计数)允许将计算开销从插入时间转移到查询时间。有关示例,请参阅架构设计指南的 使用物化视图(增量)进行聚合 部分。

最后,我们发现 用户在 ClickHouse 中利用列别名 - 尤其是在早期识别的 map 列具有许多键的情况下。这简化了用户的查询,并避免了用户记住可用键的需要。虽然我们预计随着 JSON 支持的开发,这不再是一个问题,但我们仍然预计这些别名是相关的。因此,对于喜欢通过界面层管理这些别名的用户,用于 Grafana 的 ClickHouse 插件 也支持别名。

我们已将这些经验教训反映在我们今年早些时候发布的 可观测性指南 中。

一种尺寸并不适合所有情况,并非总是 OTel

虽然 OpenTelemetry 为遥测数据带来了标准化、灵活和开放的方法,但使用功能强大的 实时分析 数据库(如 ClickHouse)使其能够有效地存储超出 OTel 范例的遥测数据。通过在同一位置集中不同的数据集,用户可以获得对复杂系统内部结构的整体视图。

这种方法的一个很好的例子是我们在 ClickHouse 的内部日志记录用例。我们已经在 另一篇博文 LogHouse 中记录了我们基于 OTel 的日志记录平台,该平台管理着超过 43 PB 的数据(截至 2024 年 10 月)。今年早些时候,我们还发布了 LogHouse 的另一个配套系统:SysEx(Systems Tables Exporter 的缩写),这是一个专门的收集器,用于集中来自所有 ClickHouse Cloud 数据库服务的信号,并且基于收集 ClickHouse system 数据库 _log 表。截至今天(2024 年 11 月),SysEx 管理着近 100 万亿行,并帮助我们的团队高效运行 ClickHouse Cloud。

syslog.png

时间序列引擎 - ClickHouse 作为 Prometheus 数据的存储引擎

作为收集、存储和分析时间序列指标的工具包,Prometheus 已在各行业中普及,用于跟踪应用程序和基础设施性能。Prometheus 以其灵活的数据模型、强大的查询语言 (PromQL) 以及与 Kubernetes 等现代容器化环境的无缝集成而备受赞誉,Prometheus 以其可靠性和简单性而著称,其基于拉取的模型最大限度地减少了依赖性。尽管它被广泛采用并取得了巨大成功,但它也存在公认的局限性:不支持多节点,仅支持垂直扩展和内存密集型实现。此外,它在处理高基数数据方面声名狼藉,并且多租户支持有限。

ClickHouse 24.8 版本引入了 Timeseries 表引擎,目前为实验性功能。此表引擎允许 ClickHouse 用作 Prometheus 指标的后端存储。其目标是让用户继续享受 Prometheus 的优势和体验,同时直接解决其局限性:提供可扩展、高性能的存储,具有原生多节点支持,内存高效架构,并支持高基数数据。通过将 ClickHouse 作为存储后端,用户可以保持熟悉的 Prometheus 界面和功能,同时受益于 ClickHouse 的水平可扩展性、高效数据压缩和查询性能。

这是通过 remote-write 和 remote-read 协议实现的。

5_24_8blog_d62da0e02b

我们在最近的发布文章中提供了配置表引擎的完整示例。这需要在 ClickHouse 配置文件中指定读写端点,然后在 Prometheus 配置中使用相同的端点。这样,就可以创建Timeseries 引擎类型的表

  
1CREATE TABLE test_time_series ENGINE=TimeSeries

这将创建三个目标表,分别负责存储指标、标签/labels 和数据点。

  
1SHOW TABLES LIKE '.inner%'
2
3┌─name───────────────────────────────────────────────────┐
4│ .inner_id.data.c23c3075-5e00-498a-b7a0-08fc0c9e32e9	│
5│ .inner_id.metrics.c23c3075-5e00-498a-b7a0-08fc0c9e32e9 │
6│ .inner_id.tags.c23c3075-5e00-498a-b7a0-08fc0c9e32e9	│
7└────────────────────────────────────────────────────────┘
8
93 rows in set. Elapsed: 0.002 sec.

其目的是规范化 Prometheus 数据以减少复制(例如标签的复制)并最大化压缩。然而,当检索指标时(通过 remote-read 协议),此模式需要在查询时进行连接 - 通常在生成的标识符上。 按照发布文章中的说明进行操作的用户,可以通过简单的 Prometheus UI 或 Grafana 可视化指标。

grafana_timeseries_engine.png

默认情况下,Prometheus 生成内部指标,允许用户执行功能测试。 上面,我们可视化了关于 Prometheus go 进程的指标。

上面,我们绘制了描述 Prometheus go 堆中分配的字节数的 go_memstats_heap_alloc_bytes 指标。 这需要在数据表和标签表之间进行 SEMI LEFT JOIN,按 metric_name 和 tags 字段分组。

通过 Prometheus remote-read 与 ClickHouse 的集成,用户可以使用 PromQL 直接从 Prometheus 查询存储在 ClickHouse 中的历史数据。这种集成实现了时间序列查询,让用户可以在一个地方访问最近和较旧的数据。然而,当前的实现意味着 Prometheus 从 ClickHouse 拉取所有原始数据,并在本地执行过滤、聚合和 PromQL 操作。这对于小型或短时长的查询效果良好,但查询较大数据集和较长时间范围的数据会很快变得效率低下。 Prometheus 没有将聚合操作下推到 ClickHouse 以充分利用其性能,因此目前对于长时间范围内复杂或高基数的查询并非最佳。

总而言之,时间序列表引擎为存储和查询 Prometheus 数据提供了基础功能。 我们应该强调,这种集成仍处于高度实验性阶段,是未来工作和增强的基础。 随着未来几个月计划进行的持续改进,我们的目标是在 Prometheus 中释放 ClickHouse 的更多性能潜力,为稳健的长期指标存储和分析提供更高效、可扩展的解决方案。

展望未来 & 结束语

在 2024 年剩余时间和 2025 年初,我们的可观测性工作将集中在三个主要方面:生产就绪的 JSON、倒排索引和时间序列表引擎。

将 JSON 迁移到生产就绪状态并使其成为 OTel Collector 模式中的默认设置仍然是我们的主要优先事项。 这需要进行大量开发,以在底层的 go 客户端中高效支持。 Grafana 也使用了此客户端,因此这项工作也将使我们能够可视化此列类型。

ClickHouse 中已在实验形式下支持倒排索引一段时间。 虽然我们不希望用户在所有数据上都使用此功能,但倒排索引对于在较新数据上进行快速文本查询至关重要。 这避免了线性扫描,并且类似于跳跃索引,它应该允许我们最大限度地减少需要读取的 granules 数量(特别是对于较稀有的术语)。 与 JSON 类似,我们认为此功能需要进行重大改进才能达到生产就绪状态,但我们预计在新的一年里会看到改进。

请注意,我们无意支持相关性统计并成为搜索引擎! 这些功能在可观测性用例中不是必需的,用户通常按时间排序。 倒排索引将仍然是一个辅助索引,专门用于加速字符串的 LIKE 和令牌匹配查询。

总之,最近的进展是否改变了 ClickHouse 是否适合您的可观测性解决方案的标准? 是的,也不是。

虽然用户仍然可以遵循我们在本博客开头概述的决策过程,但他们可以更加自信地这样做。 最近的进展解决了最后一个标准:“您更喜欢等待生态系统更加成熟,并等待基于 SQL 的可观测性变得更加开箱即用。”

随着 JSON 支持的添加以及 OTel 稳健性和 Grafana 可用性的不断提高,我们认为 ClickHouse 可观测性故事已从管道的两端都得到了提升。 这些功能虽然没有改变根本性的决策,但确保 ClickHouse 比以往任何时候都更易于访问、更易于采用,并且对于可观测性工作负载也更加稳健。

立即开始使用 ClickHouse Cloud,并获得 300 美元的信用额度。 在 30 天试用期结束时,继续使用按需付费计划,或联系我们以了解有关基于用量的折扣的更多信息。 访问我们的定价页面了解详情。

分享这篇文章

订阅我们的新闻资讯

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