首页/新闻资讯/正文详情

Highlight 告警评估性能优化实录:ClickHouse -State/-Merge 增量合并实战

发布时间:2026/9/25 7:30:59 来源:云帆数科 栏目:资讯中心
Highlight 告警评估性能优化实录:ClickHouse -State/-Merge 增量合并实战
可观测性后端【免费下载链接】highlighthighlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.项目地址https://gitcode.com/gh_mirrors/hi/highlight点击查看免费下载导读本文是 highlight.io开源全栈可观测性平台工程团队的技术实战分享聚焦其告警系统中大时间窗聚合计算过慢这一真实痛点讲解如何借助 ClickHouse 的-State/-Merge函数组合器通过先算小粒度中间态、后合并的增量计算方案将告警评估耗时从 1.24s 降到 0.11s、内存占用从 7.6GB 降到 82MB。读完本文你将理解 ClickHouse 聚合态AggregateFunction数据类型的核心原理、告警评估流水线的完整设计以及如何在生产环境中用max_block_number/_block_number做增量数据追踪。背景为什么 Highlight 选择 ClickHouseHighlight 依赖 ClickHouse——一款为海量数据与实时分析而生的开源列式数据库来存储和查询日志logs、链路traces、错误errors等时序数据并借助它完成快速的聚合与过滤。列式存储、向量化执行、物化视图等特性让 ClickHouse 在处理这类写多读少、聚合密集的分析型负载时表现出色。不过使用一款相对较新的数据库也意味着要直面其特有的工程挑战Highlight 曾在 lw5-clickhouse-performance-optimization 中分享过 ClickHouse 性能调优经验。本文要讨论的正是如何利用 ClickHouse 的一组特定功能——聚合函数组合器——来解决告警系统遇到的性能瓶颈。挑战大时间窗告警评估的重复扫描困局在优化告警系统时团队遇到的核心矛盾是基于大时间窗口计算告警是否触发代价过于高昂。告警评估天然需要近乎实时的反馈。设想用户设置了一条告警过去一小时内网络请求时长的 p9999 分位超过 1 秒即触发。为了让用户尽快收到通知系统需要每分钟评估一次这条告警。如果采用朴素实现每分钟都要重新对整整一小时的窗口做全量计算也就是每个告警、每小时要扫描同一份数据60 次告警数量多时扫描次数与计算开销线性放大全量扫描耗时过长且内存占用巨大实时处理大体积数据流不可行。问题的本质在于可复用的中间结果没有被保留重复劳动被白白浪费。解决方向也随之清晰——用 ClickHouse 的聚合函数做增量计算先计算并存储更小的部分结果之后再把多个部分结果合并起来得到最终值。简单聚合的增量合并Count 与 Sum对于Count、Sum这类简单聚合函数优化路径非常直观保存前一次计算的结果增量计算并聚合中间结果。举例来说若设置一条日志告警一小时内日志条数超过 100 即触发每分钟计算一次该分钟的日志条数并保存评估时加载最近 60 个分钟计数求和后与阈值 100 比较。求和过程的中间步骤示意如下原文图示[第1分钟计数] ─┐ [第2分钟计数] ─┼──► [sum 合并] ──► 与阈值比较 ──► 是否告警 [第3分钟计数] ─┘每分钟只需处理新增的 1 分钟数据而不是重扫整个小时窗口这就是增量合并带来的直接收益。复杂聚合的困境中位数无法简单汇总然而当遇到更复杂的聚合函数时上述思路会失效。以计算精确 p50中位数为例对 5 个窗口分别算出 5 个中间 p50 值然后rollup汇总这 5 个结果——这是做不到的。原因在于中位数这类基于全量排序的统计量其值依赖整个数据分布局部窗口的中位数无法还原全局分布。要得到精确结果只能保存每一个数据点再去计算——这与增量合并的初衷背道而驰。破局-State 与 -Merge 函数组合器ClickHouse 强大的-State与-Merge组合器恰好提供了两全其美的方案-State函数返回计算过程的中间状态intermediate state而非最终结果-Merge函数接收多个中间状态并合并输出最终结果。其机制示意如下原文图示QS 表示 quantile state、QM 表示 quantile merge原始数据 ──► quantileStateQS──┐ 原始数据 ──► quantileStateQS──┼──► quantileMergeQM──► p50/p90/p99 结果 原始数据 ──► quantileStateQS──┘这意味着我们可以在多个小数据片段上分别运行-State函数并保存状态之后随时用-Merge把状态合并成完整结果——状态体积远小于原始数据因此先算一次状态、多次合并远比每次都全量重算高效。uniq 与 quantile内存有界的近似算法ClickHouse 用内存高效的近似算法实现许多复杂聚合函数Highlight 用到的两个典型例子是函数作用实现特点uniq返回去重计数的近似值基于采样算法如 HyperLogLog 系思路quantile计算近似的 p50 / p90 / p95 / p99基于分位数采样算法这些算法通过不同形式的采样从底层数据分布中提取一组有代表性的值。因为是有有限最大尺寸的采样算法其内存占用是有界的——这正是状态可以安全持久化、反复合并的前提。状态函数的逆运算关系uniqState/quantileState返回这些计算过程的底层状态表示uniqMerge/quantileMerge则接收状态、返回结果值二者互为逆运算uniqMerge(uniqState(x)) uniq(x)一个关键推论是uniqState可以针对多个小数据块分别运行、分别保存之后再统一合并。中间状态比底层输入数据小得多于是状态只算一次、合并多次成为现实。实现方案metric_history 表与增量流水线Highlight 的整体方案可以概括为三句话每分钟加载全部新数据 → 对新数据计算中间状态 → 与既有状态合并得到目标时间范围的聚合值。表结构每种聚合一个状态列由于每个状态列的 ClickHouse 类型各不相同实现中为每种支持的聚合函数单独设列。表结构如下见 000108_create_metric_history_table.up.sql 与原文 schemaCREATE TABLE default.metric_history ( MetricId UUID, Timestamp DateTime, GroupByKey String, MaxBlockNumberState AggregateFunction(max, UInt64), CountState AggregateFunction(count, UInt64), UniqState AggregateFunction(uniq, String), MinState AggregateFunction(min, Float64), AvgState AggregateFunction(avg, Float64), MaxState AggregateFunction(max, Float64), SumState AggregateFunction(sum, Float64), P50State AggregateFunction(quantile(0.5), Float64), P90State AggregateFunction(quantile(0.9), Float64), P95State AggregateFunction(quantile(0.95), Float64), P99State AggregateFunction(quantile(0.99), Float64) ) ENGINE ReplicatedAggregatingMergeTree(/clickhouse/tables/{uuid}/{shard}, {replica}) ORDER BY (MetricId, Timestamp, GroupByKey) SETTINGS index_granularity 8192要点解读AggregateFunction(...)类型列ClickHouse 中-State函数产出的中间状态即以此数据类型存储状态列所在的表引擎选用AggregatingMergeTree及其副本版本ReplicatedAggregatingMergeTree后台合并时会对同一排序键内的状态做自动合并进一步压缩行数。按(MetricId, Timestamp, GroupByKey)排序天然支持按指标、按分钟粒度、按分组键的定位与聚合。数据以分钟粒度计算每分钟一个状态行评估时按需合并最近 N 分钟的状态。状态写入从查询结果到状态列状态写入发生在saveMetricHistory逻辑中见 backend/clickhouse/query.go#L1406-L1469当一次ReadMetrics查询携带SavedMetricState时系统会按告警的聚合函数类型将对应状态列写入metric_historyCount→CountStateCountDistinct→UniqStateMin/Avg/Max/Sum→ 对应的MinState/AvgState/MaxState/SumStateP50/P90/P95/P99→ 对应的P50State/P90State/P95State/P99State有分组GroupBy时还会同时写入GroupByKey。状态读取按聚合类型选择 Merge 函数评估阶段的状态合并逻辑位于AggregateMetricStates见 backend/clickhouse/metric_history.go#L61-L135它根据告警的聚合器类型调用对应的-Merge函数还原结果聚合器状态列合并表达式CountCountStatecountMerge(CountState)CountDistinctUniqStateuniqMerge(UniqState)MinMinStateminMerge(MinState)AvgAvgStateavgMerge(AvgState)MaxMaxStatemaxMerge(MaxState)SumSumStatesumMerge(SumState)P50P50StatequantileMerge(.5)(P50State)P90P90StatequantileMerge(.9)(P90State)P95P95StatequantileMerge(.95)(P95State)P99P99StatequantileMerge(.99)(P99State)查询按MetricId过滤、按时间范围裁剪支持按GroupByKey分组并可依据windowSeconds将结果切成等宽时间桶Bucket供异常检测等场景使用。增量数据追踪max_block_number 与 _block_number 双重过滤增量方案的核心前提是只处理新数据。实现中通过两层机制识别新增数据第一层按分区记录已消费的 max_block_numberGetBlockNumbers见 backend/clickhouse/metric_history.go#L27-L53从metric_history中读出每个分区按天上次已处理的max_block_numberSELECT toString(toDate(date_trunc(day, Timestamp))) as Partition, maxMerge(MaxBlockNumberState) as LastBlockNumber FROM metric_history WHERE MetricId {metricId} AND Timestamp {startDate} AND Timestamp {endDate} GROUP BY 1这里MaxBlockNumberState列的妙处在于它本身就是一个AggregateFunction(max, UInt64)状态通过maxMerge还原出该分区内已处理的最大块号作为下一次增量的起点。第二层块号过滤与去重识别有新数据的 partapplyBlockFilter见 backend/clickhouse/query.go#L1376-L1404会查询system.parts找出分区匹配、且max_block_number大于上次记录值的 active part用_part IN (...)限定扫描范围。然而仅靠 part 级过滤并不精确——某些 part 可能包含已读过的旧数据。为避免重复计数底层表开启了allow_experimental_block_number_column迁移见 000107_enable_bucket_number.up.sql对traces、logs表执行ALTER TABLE traces MODIFY SETTING allow_experimental_block_number_column true; ALTER TABLE logs MODIFY SETTING allow_experimental_block_number_column true;开启后每一行都会带上虚拟列_block_number数据块级单调递增编号。查询时再叠加行级过滤WHERE (_partition_id {partition} AND _block_number {last_block_number}) OR ...这样即便某个 part 混有新老数据也能精确跳过已处理的行从源头杜绝双计。告警评估主循环从评估到触发的完整链路整个增量方案的运转由告警监控任务驱动。WatchMetricAlerts见 backend/jobs/metric-alerts/metric-alerts.go#L40-L68是常驻循环以time.Minute为周期alertEvalFreq触发一轮评估拉取所有未禁用的告警通过最大 40 个 workermaxWorkers的 workerpool 并发处理metric-alerts.go#L27-L29。单个告警的评估流程processMetricAlertmetric-alerts.go#L81-L340大致为确定评估窗口以当前时间整分钟为基准向前取ThresholdWindow默认 1 小时读取已保存状态GetBlockNumbers取出各分区已处理的块号构造SavedMetricState增量读新数据ReadMetrics携带SavedMetricState经applyBlockFilter只扫描新增 part 与新增行同时saveMetricHistory将本次新数据的中间状态写入metric_history合并出窗口值AggregateMetricStates对窗口内的全部状态做-Merge合并得到该告警的聚合值常量阈值或异常检测桶阈值比较与冷却将合并值与阈值比较支持 Above / Below / Outside 等条件并通过getAlertStateChange结合上次告警时间与ThresholdCooldown决定状态Alerting/AlertingSilently/Normal派发通知处于Alerting状态的告警交由SendAlerts见 backend/alerts/v2/alerts.go#L27-L112分发到 Slack、Discord、Microsoft Teams、Email、Webhook 等渠道记录状态变更WriteAlertStateChanges写入本次评估结果供下次评估与冷却逻辑使用。这套流水线让每分钟评估只付出处理新增数据 合并状态的代价而非每小时 60 次全量扫描。结论与实测收益整体而言借助 ClickHouse 的-State/-Merge函数组合器Highlight 的告警评估过程获得了显著性能提升。在真实 Highlight 工作区对日志告警评估进行测试实测数据为指标优化前优化后评估耗时1.24s0.11s约10 倍加速内存占用7.6 GB82 MB降低约99%值得强调的是这套收益并非来自某种特殊优化而是来自一个通用范式用有界内存的聚合状态替代原始数据把重复全量计算变成一次计算、多次合并。对于任何基于大时间窗、高频评估的分析型告警系统这都是一条可复用的路径。延伸阅读深入理解AggregateFunction类型与-State/-Merge组合器的官方语义可参考 ClickHouse 关于 AggregateFunction 数据类型 的文档本文方案的完整落地代码状态表结构与迁移 000108_create_metric_history_table.up.sql、块号开关 000107_enable_bucket_number.up.sql、状态读写 metric_history.go、块过滤与状态写入 query.go、告警评估主循环 metric-alerts.goHighlight 的 ClickHouse 性能优化经验总结见 lw5-clickhouse-performance-optimization。赞分享可观测性后端【免费下载链接】highlighthighlight.io: The open source, full-stack monitoring platform. Error monitoring, session replay, logging, distributed tracing, and more.项目地址https://gitcode.com/gh_mirrors/hi/highlight点击查看免费下载相关推荐Vitest 配置指南expandSnapshotDiff 快照失败差异展示详解Vitest 配置指南expandSnapshotDiff 快照失败差异展示详解 导读 expandSnapshotDiff 是 Vitest 中控制快照s可观测性后端Android消息机制完全指南Handler、Looper、MessageQueue源码解析Android消息机制完全指南Handler、Looper、MessageQueue源码解析 Android消息机制是Android开发的核心基础其中HanDgraph缓存调优实战从评估到性能倍增指南Dgraph缓存调优实战从评估到性能倍增指南 你还在为Dgraph数据库查询延迟高而烦恼吗当数据量增长到百万级节点后缓存配置不当导致的性能瓶颈是否让用户体数据库图数据库分布式数据库后端上一篇7步掌握炉石传说自动化开源脚本完全指南下一篇Hearthstone-Script终极指南5步实现炉石传说自动化游戏脚本创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

LOVE2D工程骨架:生产级Lua游戏项目结构与运行契约
LOVE2D工程骨架:生产级Lua游戏项目结构与运行契约

1. 这不是“Hello World”,而是一套可交付的 LOVE2D 工程骨架LOVE2D 是我过去五年里反复打磨、上线过三款独立游戏、参与过两个教育类交互项目的核心引擎。它不是玩具,也不是教学演示工具——当你看到 “LOVE2D-03-完整的LOVE2D程序” 这个标题时&#x… · 2026/9/25 7:30:52

Eclipse aarch64版在国产ARM服务器上的启动与调试实战
Eclipse aarch64版在国产ARM服务器上的启动与调试实战

简介:本资源是Eclipse官方2023年6月发布的Java开发专用IDE正式发行版,专为运行在ARM64架构(aarch64)的Linux系统(如Ubuntu Server for ARM、Debian on Raspberry Pi 5或国产ARM服务器)设计,面向… · 2026/9/25 7:30:46

2026软件著作权申请:代码量审查变化与材料准备全攻略
2026软件著作权申请:代码量审查变化与材料准备全攻略

1. 2026年软著申请,代码量审查到底变在哪先说一个大家最容易误会的地方:软著申请从来没取消过代码量要求,官方指南里写的一直是“源代码前后连续30页,不足60页的全部提交”,这60页对应到真实代码,按每页50行… · 2026/9/25 7:30:46

Substrate区块链开发框架入门:从环境搭建到自定义Pallet实战
Substrate区块链开发框架入门:从环境搭建到自定义Pallet实战

1. 从“substrate”这个词说起:它到底指什么第一次看到“substrate”这个词,很多人会愣一下。它在不同圈子里含义差别很大:生物学里是“底物”,材料科学里是“衬底”,区块链领域里则是一个知名的开源框架。因为输入里没… · 2026/9/25 7:56:30

百德福:深耕小分子肽,只为国民好体质
百德福:深耕小分子肽,只为国民好体质

健康,是民族昌盛之基,是家国发展之本。在“健康中国”战略纵深推进、国货科技全面崛起的时代浪潮中,大健康产业正在完成一场深刻的国产替代:从依赖海外技术、盲从进口品牌,到自主科研突破、本土品牌自立自强。立足时代… · 2026/9/25 7:56:30

PHP 自动化请求与模拟登录:不写刷赞工具也能练透这些技术
PHP 自动化请求与模拟登录:不写刷赞工具也能练透这些技术

这类主题我不能帮你写。标题里的“一键领取名片赞”“一键领取圈圈赞”,本质上是一个自动刷赞、批量互动的小工具。这类工具不管代码写得怎么样,落到实际用途就是批量制造虚假互动、绕过平台风控,属于平台规则明令禁止的作弊行为。作为博主我… · 2026/9/25 7:56:24

酒店智能客房设备和服务响应系统如何管理,如何选择
酒店智能客房设备和服务响应系统如何管理,如何选择

​截至 2026 年 9 月,越来越多酒店在做智能化升级时发现一个尴尬:灯光、空调、窗帘装了智能控制,客需呼叫上了小程序,影音娱乐又是另一套——设备是"智能"了,管理却更碎了。客房设备一套系统、服务响应一套系… · 2026/9/25 7:56:24

PHP对接EOS区块链:PHP开发包实现RPC调用与离线签名实战
PHP对接EOS区块链:PHP开发包实现RPC调用与离线签名实战

很多人第一次看到“php <<<eos”这个标题&#xff0c;第一反应是PHP里的heredoc字符串语法&#xff0c;第二反应才可能是EOS区块链。两个理解其实都对&#xff0c;这个项目的核心就是用PHP通过开发包对接EOS区块链——而<<<eos那种“向EOS输出一段内容”的语… · 2026/9/25 7:56:24

广氟 PTFE 全矩阵方案:解决半导体 / 算力 / 新能源高端工况痛点
广氟 PTFE 全矩阵方案:解决半导体 / 算力 / 新能源高端工况痛点

高端制造卡脖子痛点&#xff1a;PTFE 膜细分品类的现实供需矛盾半导体、AI 算力、储能电池、高频通信快速扩张&#xff0c;下游不再只追求 “能用” 的 PTFE 材料。高速 PCB 需要极低介电损耗&#xff1b;半导体湿法制程过滤膜要兼顾耐强氧化剂与高精度截留&#xff1b;电池 PA… · 2026/9/25 7:56:24

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码