ClickStack四月新:SQL原生告警,你的观测全链路打通!
本文字数:11514;估计阅读时间:29分钟
作者:ClickStack Team
欢迎阅读 ClickStack 四月版新功能。
四月版本重点在于优化查询、告警和仪表盘等核心体验。
基于 SQL 的告警功能已于本月上线,完善了我们上月开始构建的(https://clickhouse.com/blog/whats-new-in-clickstack-march-2026#sql-charts-with-grafana-style-macros) SQL 原生可观测性 (Observability) 工作流。现在,您可以直接从编写查询和构建仪表盘过渡到告警,无需切换查询语言或管理独立的规则管道。
在底层,我们基于 ClickHouse 的文本索引重新设计了默认日志 schema。再配合后续对 map 属性过滤的优化,在我们的基准测试中,对于常见的搜索和过滤工作负载,性能提升高达 Nx 倍。
自动补全功能也进行了全面升级。元数据发现现在通过幕后的物化视图 (materialized-view) rollup 机制运行,显著提升了建议的响应速度和用户体验,尤其是在处理大型数据集时。
热力图 (Heatmaps) 现在是仪表盘中随处可用的核心图表类型,不再局限于 Event Deltas。这极大地简化了延迟分布、密度和异常值的可视化,使其能够直接与其他运营视图并列显示。
除了这些重大更新,四月版本还包含了一系列在告警、表格布局、饼图和按系列数据格式化等方面的易用性改进。
如果您参加 Open House(https://clickhouse.com/openhouse/san-francisco),我们今年还将设有一个专门的可观测性分会场,届时将有多位 ClickStack 开发者和贡献者发表演讲,同时也有客户团队分享他们如何在生产环境中利用 ClickHouse 运行可观测性工作负载的经验。
如果您无法亲临现场,所有会议都将录制并于会后发布。
一如既往,感谢我们的开源贡献者和用户,他们的宝贵反馈持续帮助 ClickStack 发展。
如果您不擅长代码贡献,也欢迎通过仓库(https://github.com/hyperdxio/hyperdx/tree/v2)提交文档改进、创意、功能建议、错误报告及其他反馈。每一份贡献,无论大小,都将助力整个社区的产品栈更臻完善。
SQL 告警(SQL alerting)提供了一种核心方式,用于表达无法通过查询构建器(query builder)实现的可观测性逻辑。现在,用户可以直接通过任意 ClickHouse SQL 查询来构建图表和告警,从而在 ClickStack UI 内部实现更高级的分析。
静态阈值告警(Static thresholding)在团队开始进行哪怕是轻微的统计分析之前都能正常工作。然而,一旦告警本身受限于 UI 模型,滚动基线(rolling baselines)、百分位漂移(percentile drift)、分组异常检查(grouped anomaly checks)或历史比较等功能都将变得难以实现。实际上,许多用户尽管数据已存储在 ClickHouse 中,最终仍不得不将高级告警逻辑推送到外部系统或自定义管道中。
SQL 告警直接基于 上个月推出的 SQL 图表支持(https://clickhouse.com/blog/whats-new-in-clickstack-march-2026#sql-charts-with-grafana-style-macros) 构建。一旦任意 SQL 可以在仪表板和可视化中表达,将其扩展到告警领域就成为了一个自然的下一步。
用于告警的 SQL 查询可用于计算历史时间段的滚动基线、计算标准差区间(standard deviation bands),以及将当前行为与历史趋势进行比较。除此之外,查询还能封装更复杂的条件。例如,查询可以仅在出现异常时返回 1。如此一来,告警本身就变得极其简单:当查询返回 1 时触发,所有告警条件均由该查询定义。
ClickHouse 的窗口函数(window functions)使得表达这类工作流变得非常直接。例如,以下查询能够为错误量构建一个滚动统计基线,并在当前时间段与近期历史行为显著偏离时返回 1。
WITH buckets AS (SELECT$__timeInterval(Timestamp) AS ts,count() AS bucket_countFROM $__sourceTableWHERE Timestamp >= fromUnixTimestamp64Milli({startDateMilliseconds:Int64})- toIntervalSecond($__interval_s * 30)AND Timestamp < fromUnixTimestamp64Milli({endDateMilliseconds:Int64})AND SeverityText = 'error'AND $__filtersGROUP BY tsORDER BY tsWITH FILL STEP toIntervalSecond($__interval_s)),baselines AS (SELECTts,bucket_count,avg(bucket_count) OVER (ORDER BY ts ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING) AS rolling_avg,stddevPop(bucket_count) OVER (ORDER BY ts ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING) AS rolling_stddevFROM buckets)SELECTts,if(bucket_count > rolling_avg + 2 * rolling_stddev, 1, 0) AS anomalyFROM baselinesWHERE rolling_avg IS NOT NULLAND ts >= fromUnixTimestamp64Milli({startDateMilliseconds:Int64})ORDER BY ts ASC;
告警还支持与 SQL 可视化中已使用的 $__filters、$__sourceTable 和 {intervalSeconds:Int64} 等宏(macros),包括兼容 Grafana 的时间间隔处理。这意味着告警查询无需额外配置,即可自动继承仪表板和源过滤器的上下文。
如需深入了解滚动基线、异常检测(anomaly detection)模式和支持的宏,请参阅专门的 SQL Charting and Alerting(https://clickhouse.com/blog/clickstack-sql-charting-and-alerting) 文章。完整的宏和参数文档也可在 告警文档(https://clickhouse.com/docs/use-cases/observability/clickstack/alerts) 中查阅。
在对常见的可观测性工作负载(observability workloads)进行基准测试和性能分析后,我们重新设计了 ClickStack 使用的默认 otel_logs 模式。
我们最初是在分析基准测试和生产追踪中反复出现的查询模式时启动了这项工作。一段时间后,我们发现问题已不再局限于通过调整设置就能解决。默认模式本身需要重新审查,特别是在索引策略和排序布局方面。
我们将单独更详细地介绍基准测试流程、工具和方法论,包括一些我们最终可能会开源的内部基准测试工具。对于对这些改动背后的存储和索引深层细节感兴趣的用户,我们还将在 Open House 活动中进行讨论(https://clickhouse.com/openhouse/san-francisco)。
新模式:
CREATE TABLE IF NOT EXISTS otel_logs(`Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),`TraceId` String CODEC(ZSTD(1)),`SpanId` String CODEC(ZSTD(1)),`TraceFlags` UInt8,`SeverityText` LowCardinality(String) CODEC(ZSTD(1)),`SeverityNumber` UInt8,`ServiceName` LowCardinality(String) CODEC(ZSTD(1)),`Body` String CODEC(ZSTD(1)),`ResourceSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),`ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),`ScopeSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),`ScopeName` String CODEC(ZSTD(1)),`ScopeVersion` LowCardinality(String) CODEC(ZSTD(1)),`ScopeAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),`LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),`EventName` String CODEC(ZSTD(1)),`__hdx_materialized_k8s.cluster.name` LowCardinality(String) MATERIALIZEDResourceAttributes['k8s.cluster.name'] CODEC(ZSTD(1)),-- ... seven more __hdx_materialized_* columns omitted for brevityINDEX idx_trace_id TraceId TYPE text(tokenizer = 'array'),INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE text(tokenizer = 'array'),INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE text(tokenizer = 'array'),INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE text(tokenizer = 'array'),INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE text(tokenizer = 'array'),INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE text(tokenizer = 'array'),INDEX idx_log_attr_value mapValues(LogAttributes) TYPE text(tokenizer = 'array'),INDEX idx_lower_body lower(Body) TYPE text(tokenizer = 'splitByNonAlpha'))ENGINE = MergeTreePARTITION BY toDate(Timestamp)ORDER BY (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp)TTL toDateTime(Timestamp) + INTERVAL 14 DAYSSETTINGS index_granularity = 8192, ttl_only_drop_parts = 1,enable_block_number_column = 1, enable_block_offset_column = 1;
主键现在是 (toStartOfFiveMinutes(Timestamp), ServiceName, Timestamp)。之前的模式同时包含 Timestamp 和一个辅助的 TimestampTime DateTime DEFAULT toDateTime(Timestamp) 列,其中后者用于排序键,以提高秒级粒度的剪枝效率。
采用新的五分钟分桶布局后,该额外的列不再必要,已被完全移除。更粗粒度的前置分桶能够让 相邻的日志行在物理上保持分组(https://clickhouse.com/docs/guides/best-practices/sparse-primary-indexes),以满足常见的按时间范围查询的需求,同时在扫描期间仍能实现高效的剪枝操作。
跳跃索引(skip indices)策略也发生了显著变化。之前的模式主要依赖于在属性映射(attribute maps)上使用 bloom_filter 索引,以及在 Body 列上使用 tokenbf_v1 索引。新模式转而采用 ClickHouse 的全文索引(https://clickhouse.com/docs/engines/table-engines/mergetree-family/textindexes),它覆盖了 ResourceAttributes、ScopeAttributes 和 LogAttributes 的键和值。同时,一个使用 splitByNonAlpha 分词器的专用文本索引现在覆盖 lower(Body)。
在我们的基准测试中,对于面向搜索的工作负载,text 索引的性能始终优于之前的布隆过滤器方法,同时只增加了极低的摄取开销。这些新索引还支持在下一节中介绍的属性搜索优化。
对于运行 ClickHouse 26.2 之前版本的用户(在该版本之前,全文搜索索引尚未全面推出),ClickStack 会自动回退到使用 bloom_filter 和 tokenbf_v1 索引的兼容模式,同时保持 UI 兼容性和相同的查询语义。
在我们所有的样本查询中,新模式将性能提升了 70% 以上。
大部分查询性能提升都符合我们的预期:文本搜索、属性过滤以及混合搜索和过滤工作负载。插入开销与之前的模式大致相当,这是我们进行基准测试时的主要考量。
希望采用新模式的用户应遵循我们现有的主键修改指南(https://clickhouse.com/docs/use-cases/observability/clickstack/performance_tuning#changing-the-primary-key),因为同样的迁移过程也适用于此场景。
一个非常常见的可观测性查询模式是根据属性过滤日志,例如 http.status_code = 500 或 k8s.namespace.name = payments。
在 ClickStack 的默认 OpenTelemetry 模式中,这些属性存储在 LogAttributes、ResourceAttributes 和 ScopeAttributes 等 map 列中。对这些列的过滤会生成如下查询:
LogAttributes['http.status_code'] = '500'或者
ResourceAttributes['k8s.namespace.name'] = 'payments'这种灵活的模式设计是 OpenTelemetry 在各种异构工作负载中都能良好运行的原因之一,但它也给查询执行带来了挑战。在读取和解包底层 map 数据本身之前,ClickHouse 需要一种高效的方法来确定哪些 granules(https://clickhouse.com/docs/guides/best-practices/sparse-primary-indexes#data-is-organized-into-granules-for-parallel-data-processing) 可能包含给定的属性键或值。
该模式的早期版本主要依赖于在 map 键和值上使用 bloom_filter。这有助于避免不必要的读取,但搜索密集型属性查询仍然意味着更多的 I/O 和更慢的查询。
新模式启用了一种基于 ClickHouse 文本索引的新属性搜索路径。启用后,ResourceAttributes、ScopeAttributes 和 LogAttributes 中的值会使用 text(tokenizer = 'array') 进行索引。在我们的基准测试中,这对于搜索密集型属性过滤工作负载表现出显著的性能提升。
这里的一个复杂之处在于,查询的结构与索引的表达式不直接匹配。索引是基于 mapValues(LogAttributes) 构建的,而过滤器本身则是通过键控映射查找来表达的。尽管这仍能为 ClickHouse 提供有用的剪枝信息,但仅凭索引不足以完全解析该谓词。
为避免这种不匹配,我们为属性映射引入了另一种索引表示形式。每个映射都会被展平为一个由 key=value 字符串组成的数组,并辅以一个相应的 text(tokenizer = 'array') 索引。
LogAttributes['http.status_code'] = '500'可以重写为:
has(LogAttributeItems, 'http.status_code=500')这种结构与数组分词器完全对齐,使得 ClickHouse 能够直接从索引评估谓词,而无需解包原始映射列。在我们的内部基准测试中,命中此优化路径的属性过滤器比等效的映射下标查询运行速度快 1.4 到 10 倍。
绿条代表先前实现中与自动完成相关查询的性能。黄条代表直接读取优化方案的等效性能。
ClickStack 现在能够检测到当某个源拥有与其 OpenTelemetry (OTel) 属性映射之一兼容的伴随列时,并自动重写匹配的属性谓词。用户无需修改其查询,并且属性仍将像以前一样显示在搜索栏中。
目前,此优化是选择性启用的,而非默认 schema 的一部分。目前,伴随列需要被声明为 MATERIALIZED 才能启用直接读取路径,这会增加存储开销。一个允许 ALIAS 列以相同方式工作的 ClickHouse 修复程序已经合并,并已反向移植到 26.2 版本。一旦该修复程序推出,我们预计将默认启用此功能,且无需增加存储成本。
希望今天就启用物化路径的用户,可以通过针对每个属性映射执行一对 ALTER 语句来实现。以下示例展示了 ResourceAttributes;相同的模式也适用于 ScopeAttributes 和 LogAttributes。
ALTER TABLE otel_logsADD COLUMN ResourceAttributeItems Array(String)MATERIALIZED arrayMap((arr) -> concat(arr.1, '=', arr.2),CAST(ResourceAttributes, 'Array(Tuple(String, String))'))CODEC(ZSTD(1));ALTER TABLE otel_logsADD INDEX idx_res_attr_items ResourceAttributeItemsTYPE text(tokenizer = 'array');
一旦列和索引存在,像 ResourceAttributes.k8s.namespace.name:"payments" 这样的 Lucene 查询就会被 ClickStack 自动转换为针对已索引伴随列的 has(ResourceAttributeItems, 'k8s.namespace.name=payments') 谓词,而非对 ResourceAttributes 本身执行映射查找。
过去,自动补全功能直接查询实时的 otel_logs 和 otel_traces 表,以发现属性键和值。这种方法虽然简单,但一旦数据集规模足够大,导致建议查找开始与常规查询工作负载竞争时,其扩展性就变得很差。
新的实现将自动补全功能迁移到与主 OTel 表并行创建的物化视图(Materialized View)汇总表上。ClickStack 不再需要扫描实时的可观测性数据来获取建议,而是专门为自动补全查找维护了紧凑的元数据表。
每个源表都会生成两个物化视图:{table}_kv_rollup_15m 用于存储 15 分钟时间窗口内的键/值频率,而 {table}_key_rollup_15m 则存储从相同数据派生的聚合键级计数。用户界面(UI)直接利用这些表来提供自动补全建议和排序。
现在,源(Sources)会公开一个 metadataMaterializedViews 配置,用于定义汇总表和时间窗口间隔。
在源发现(Source Discovery)过程中,ClickStack 会自动检查是否存在兼容的汇总表,如果存在,则将其集成到自动补全功能中。默认的 Docker、Helm 和嵌入式部署已经自动创建了这些视图。
该汇总表整合了所有三个 OTel 属性映射以及少量常用的原生查询列:
CREATE MATERIALIZED VIEW IF NOT EXISTS otel_logs_attr_kv_rollup_15m_mvTO otel_logs_kv_rollup_15mAS WITH elements AS (SELECT 'ResourceAttributes' AS ColumnIdentifier,toStartOfFifteenMinutes(Timestamp) AS Timestamp,replaceRegexpAll(entry.1, '\[\d+\]', '[*]') AS Key,CAST(entry.2 AS String) AS ValueFROM otel_logs ARRAY JOIN ResourceAttributes AS entryUNION ALLSELECT 'LogAttributes' AS ColumnIdentifier, ...FROM otel_logs ARRAY JOIN LogAttributes AS entryUNION ALLSELECT 'ScopeAttributes' AS ColumnIdentifier, ...FROM otel_logs ARRAY JOIN ScopeAttributes AS entryUNION ALLSELECT 'NativeColumn' AS ColumnIdentifier,toStartOfFifteenMinutes(Timestamp) AS Timestamp,'SeverityText' AS Key,CAST(SeverityText AS String) AS ValueFROM otel_logs-- similar UNION ALL branches for ServiceName, ScopeName, etc.)SELECT Timestamp, ColumnIdentifier, Key, Value, count() AS countFROM elementsGROUP BY Timestamp, ColumnIdentifier, Key, Value;
这些汇总表显著降低了大型数据集上的自动补全延迟。更值得一提的是,建议现在会根据频率进行排序,这通常能在下拉列表中提供更优的默认选项。
上个月,我们 发布了对 Event Deltas 的多项改进(https://clickhouse.com/blog/whats-new-in-clickstack-march-2026#improvements-for-event-deltas),包括始终开启的基线分布、比例比较评分、从属性比较条中筛选和排除操作,以及确定性热力图采样。但仍存在一个限制:热力图渲染器仅在 Event Deltas 搜索工作流中可用。如果用户希望在产品的其他地方可视化延迟分布,则无法复用。
四月,我们将热力图渲染器集成到仪表盘和图表编辑器所使用的共享图表系统中,从而使热力图能够在所有可创建图表的位置使用。
在图表编辑器中,用户选择 Heatmap 标签页,定义一个 WHERE 子句和值表达式,之后此前仅限于 Event Deltas 的分布视图现在可以直接添加到仪表盘。
对于 Trace 源,ClickStack 会自动为图表初始化持续时间表达式和 count() 聚合。Y 轴也会自动切换为持续时间格式,因此标签会以毫秒、秒或分钟显示,而非原始数值。
若要亲身体验,请前往我们的 演示环境(https://play-clickstack.clickhouse.com/),针对 OpenTelemetry 演示数据集,向仪表盘添加一个 Heatmap 区块。
在 SQL 告警之外,四月更新还包含了用户要求的一些小型告警改进。这些改动虽然是渐进式的,但它们共同让告警工作流程显著更加灵活,不再受限于编辑器本身。
更多阈值类型
阈值选择器现在支持 全部比较运算符(https://clickhouse.com/docs/use-cases/observability/clickstack/alerts):>、≥、<、≤、=、≠,同时支持 BETWEEN 和 NOT BETWEEN,用于进行范围检查。
等式和范围运算符也使得直接表达一些常见模式变得更加容易,包括心跳监测、固定容量检查,以及在预期上下限内工作的告警。
通知渲染功能与运算符变更同步进行了更新。告警消息现在会直接描述评估条件,而非采用通用的比较措辞。例如,通知现在显示“发现 3 个错误,等于 3 个错误的阈值”,而非旧的“发现 3 个,预期不等于 3”。
编辑器中的告警历史与确认/静默
告警编辑器现在包含了与专用告警页面相同的功能,即告警历史记录和确认/静默控制。用户可以查看告警上次触发的时间,检查触发告警的值,并能直接在编辑器中静默或确认告警,无需切换视图。
UI 中的告警执行错误
过去,如果警报查询编译失败或 Webhook (Webhook) 返回非 2xx 响应,警报就会简单地停止生成历史记录条目。在许多情况下,用户界面 (UI) 中根本没有明显的迹象表明执行已失败。
现在,最新的执行错误会被持久化,并在警报 UI 中以错误指示器和可展开的消息详情的形式展示。这使得区分最近未触发的警报和正在主动执行失败的警报成为可能。
本月还推出了一些小改动,它们单独来看不太明显,但却显著减少了日常使用中的不便。
饼图图例
饼图现在包含一个可滚动的图例,显示每个扇区的颜色、标签和数值。图例的宽度限制在图表宽度的 40%,以避免占据过多图表空间,并且一旦扇区数量超出可用空间便会自动滚动。扇区数值也遵循图表配置的数字格式规则。
按系列划分的数值格式
以前,数值格式是在图表级别配置的,这意味着线图、条形图或表格图中的每个数据系列都使用相同的单位和后缀。现在,数据系列可以独立定义自己的 numberFormat,而图表级别的格式则作为备选方案。
这一改动也适用于外部仪表盘 API (Application Programming Interface),因此通过编程创建或更新的仪表盘也支持相同的按系列格式化行为。
表格中分组列居左
表格图现在支持一项显示选项,可以将分组列移动到表格的左侧,而不是先显示所有数据系列列。这主要适用于较宽的表格,因为在这种情况下,分组键可能会被大量指标列推到屏幕之外。
默认布局保持不变,但用户现在可以在每个表格中调整列顺序,以便更轻松地查看数据。
/END/
征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:[email protected]
关于我们
ClickHouse 是全球速度最快,资源利用最高效的在线分析列式数据库管理系统。现在,ClickHouse可以作为一个安全可扩展的无服务器应用在云中提供服务。通过云服务,ClickHouse使得任何人都能轻松获取高效的实时分析处理能力。2023年,ClickHouse正式进入中国,请访问clickhouse.com以获取更多信息。