
ClickHouse 采样式查询分析器实战基于 system.trace_log 的 CPU 热点定位与剖析方法论【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse本篇技术文章围绕 ClickHouse 仓库中.claude/skills/cpu-profile/SKILL.md定义的一套 CPU Profile 分析技能展开它说明了如何利用 ClickHouse 内置的采样式查询分析器sampling query profiler与system.trace_log系统表对单条查询的 CPU 耗时进行端到端定位——从以可控采样周期执行查询、按 query_id 收集调用栈、聚合 Top 热点函数与完整调用路径到导出 collapsed stack 生成火焰图。读完本文你将掌握完整的 trace 采集与分析 SQL、采样参数query_profiler_cpu_time_period_ns等的取值策略并能结合 ClickHouse 源码TraceLog、QueryProfiler设置定义理解采样数据从采集到符号化的实现链路。技能定位它解决什么问题该技能.claude/skills/cpu-profile/SKILL.md的目标非常聚焦找出查询的 CPU 热点分析时间花在哪里定位性能瓶颈。它依赖 ClickHouse 两个原生能力内置采样式查询分析器通过query_profiler_cpu_time_period_ns/query_profiler_real_time_period_ns两个 Settings 控制采样周期在查询执行期间周期性抓取当前线程的调用栈system.trace_log系统表将采样到的调用栈地址数组、CPU 核号、线程 ID、query_id 等落盘可像查询普通表一样用 SQL 分析。仓库中对应的系统表实现见 TraceLog.h每条 trace 记录TraceLogElement包含的关键字段包括event_time_microseconds微秒级事件时间、trace_typeCPU / Real / Memory 等类型、cpu_id、thread_id、query_id以及std::vectorUInt64 trace——即以数组形式存储的调用栈地址列表。这正对应技能文档中“system.trace_log里的堆栈是地址数组下标 1 是最内层叶子帧”的说明。第一步确定剖析对象技能的输入$ARGUMENTS是可选的分三种情况处理输入形态处理方式形如 UUID如a1b2c3d4-e5f6-...视为已有查询的query_id直接跳到第 3 步分析已存在的 traceSQL 查询或查询描述进入第 2 步带剖析设置执行该查询为空询问用户给出 query_id / 执行一条 SQL / 查看最近的慢查询当用户希望先看最近有哪些慢查询可剖析时技能给出如下 SQL注意system.query_log的查询需要allow_introspection_functions权限SELECT query_id, query_duration_ms, formatReadableSize(memory_usage) AS peak_memory, left(query, 120) AS query_preview FROM system.query_log WHERE type QueryFinish AND event_date today() - 1 AND query_duration_ms 1000 AND query NOT LIKE %system.% ORDER BY query_duration_ms DESC LIMIT 20 SETTINGS allow_introspection_functions 1该查询按耗时降序列出近两天内执行超过 1 秒的非系统库查询并附带峰值内存帮助快速圈定值得剖析的目标。第二步以剖析设置执行查询核心动作是生成一个唯一的 query_id 并带激进采样设置运行查询。技能文档推荐使用 100us 采样周期约 10,000 次采样/秒PROFILE_QIDcpu-profile-$(uuidgen) clickhouse-client --query_id $PROFILE_QID -q SELECT ... SETTINGS query_profiler_cpu_time_period_ns 100000, query_profiler_real_time_period_ns 100000 要点说明用clickhouse-client非交互模式并显式指定--query_id可避免与并发查询产生竞争race——这是技能文档明确要求的原因若只能在交互模式下运行则需要解析clickhouse-client在每条查询前打印的Query id: uuid行来获取 query_id。执行完毕后先验证查询完成并收集元数据SELECT query_id, query_duration_ms, formatReadableSize(memory_usage) AS peak_memory FROM system.query_log WHERE type QueryFinish AND query_id {query_id} SETTINGS allow_introspection_functions 1然后等待约 2 秒让trace_log异步落盘trace_log是异步系统表采样数据经内部队列批量写入再进入第 3 步。采样参数与源码中的默认值这两个 Settings 的完整定义在 Settings.cpp 中query_profiler_cpu_time_period_nsCPU 时钟定时器周期只统计 CPU 时间query_profiler_real_time_period_ns真实时钟定时器周期统计 wall-clock 时间含 IO 等待其 Cloud 默认值为30000000003 秒两者的默认值都来自常量QUERY_PROFILER_DEFAULT_SAMPLE_RATE_NS在 Defines.h 中定义为1000000000——即默认每秒 1 个采样设为0可关闭对应定时器。官方设置文档中的推荐值与技能文档一致单条查询剖析建议10000000100 次/秒量级集群级剖析用1000000000每秒 1 次。技能文档给出的经验值是短查询用 100,000100us做精细剖析长查询用 1,000,0001ms。采样越密trace_log中写入的样本越多开销也越大需要按查询时长权衡。第三步采集与分析 trace 数据技能文档要求并行执行三类分析Top 函数、Top 调用栈、火焰图导出下面逐一给出完整 SQL。分析 ACPU 样本最多的 Top 函数trace[1]取的是堆栈数组第 1 个元素即最内层叶子帧——采样命中的那一刻真正在执行的函数这是热点分析的核心视角SELECT count() AS samples, round(100.0 * count() / (SELECT count() FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU), 2) AS pct, demangle(addressToSymbol(trace[1])) AS function FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU GROUP BY function ORDER BY samples DESC LIMIT 30 SETTINGS allow_introspection_functions 1该查询输出每个叶子函数的样本数与占比pct回答“CPU 时间花在了哪些函数上”。分析 BTop 完整调用栈只看到叶子函数还不够需要看完整的调用路径谁在什么场景下调用了它SELECT count() AS samples, arrayStringConcat( arrayMap(x - demangle(addressToSymbol(x)), trace), \n ) AS stack FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU GROUP BY trace ORDER BY samples DESC LIMIT 15 SETTINGS allow_introspection_functions 1按trace整条地址数组分组并符号化后得到采样数最多的 15 条完整调用链展示从外层调用者到最内层帧的完整上下文。分析 C导出 collapsed stacks 供火焰图使用将每条去重后的堆栈反转arrayReverse使最外层函数在前、叶子帧在后用;连接并追加样本计数输出符合 flamegraph 工具要求的 collapsed 格式SELECT concat( arrayStringConcat( arrayReverse(arrayMap(x - demangle(addressToSymbol(x)), trace)), ; ), , toString(count()) ) FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU GROUP BY trace ORDER BY count() DESC SETTINGS allow_introspection_functions 1 FORMAT TSVRaw技能文档要求将该结果保存为tmp/cpu_profile_{query_id}.collapsed供第 5 步生成火焰图。元数据统计同时收集整体剖析元数据总样本数、首末样本时间、剖析持续时长SELECT count() AS total_samples, min(event_time_microseconds) AS first_sample, max(event_time_microseconds) AS last_sample, dateDiff(millisecond, min(event_time_microseconds), max(event_time_microseconds)) AS profile_duration_ms FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU SETTINGS allow_introspection_functions 1其中event_time_microseconds对应 TraceLog.h 中TraceLogElement的Decimal64 event_time_microseconds字段用它推算剖析窗口可以验证采样是否覆盖了查询执行的完整生命周期。第四步综合输出结构化报告技能文档规定把上述三路分析结果综合为一份结构化报告包含六个部分Profile 概要query_id、总样本数、剖析时长、采样率Top 15 CPU 热点函数表样本数 占比Top 5 完整调用栈按最外层到最内层的可读格式展示调用链子系统归类把函数归入以下类别做占比拆分——查询执行HashJoin、Aggregator、MergeSorter 等表达式求值ExpressionActions、内置函数IOReadBuffer、WriteBuffer、S3、disk网络Exchange、Connection、Protocol压缩LZ4、ZSTD 等 codec内存管理Arena、Allocator、PODArray优化器Cascades、JoinOrder、Statistics其他可执行结论哪些热点不符合预期、哪些地方值得优化collapsed stack 文件位置供后续生成火焰图。第五步可选的深入下钻报告输出后技能提供四个继续下钻的选项并循环直到用户选择结束1. 钻入某个函数按函数名过滤包含该函数的 trace查看它的所有调用上下文。2. 对比 CPU 时间 vs 真实时间对trace_type Real重复同样的分析。两者相减即可暴露 wall-clock 上多出来的部分——即IO 等待与锁竞争trace_type CPU只计 CPU 时间Real计 wall-clock 时间。3. 生成火焰图若环境中有flamegraph.plflamegraph.pl --title CPU Profile: {query_id} --countname samples --width 1800 \ tmp/cpu_profile_{query_id}.collapsed tmp/cpu_flamegraph_{query_id}.svg或者把 collapsed 文件导入 speedscope 这类在线剖析可视化工具。4. 显示源码位置用addressToLine把符号映射到源文件:行号适合在本地源码树中精确定位热点代码SELECT count() AS samples, demangle(addressToSymbol(trace[1])) AS function, addressToLine(trace[1]) AS source_location FROM system.trace_log WHERE query_id {query_id} AND trace_type CPU GROUP BY function, source_location ORDER BY samples DESC LIMIT 30 SETTINGS allow_introspection_functions 1注意addressToLine依赖调试信息需要安装带符号的调试包技能文档明确指出clickhouse-common-static-dbg必须已安装本仓库对应的 RPM 打包定义见 clickhouse-common-static-dbg.yaml。关键约束与源码级注意事项技能文档 Notes 一节列出的约束逐条结合仓库实现确认如下约束说明与仓库依据采样频率由query_profiler_cpu_time_period_ns控制默认 1,000,000,000每秒 1 样本见 Defines.h短查询用 100,000100us长查询用 1,000,0001mstrace_type CPUvsRealCPU 计数 vs wall-clock含 IO 等待对应trace_type 0关闭定时器allow_introspection_functions 1addressToSymbol、demangle、addressToLine属于 introspection 类函数必须开启该设置符号解析需调试包clickhouse-common-static-dbg未安装时符号化结果会退化为不可读的地址/空符号集群/Cloud 环境用FROM clusterAllReplicas(default, system.trace_log)汇总所有节点的 trace堆栈数组的方向索引 1 是最内层叶子帧导出火焰图时必须arrayReverse使其变根在前从源码结构看整条数据链路是查询执行期间QueryProfiler被 Settings.cpp 中这两个设置驱动周期性抓取当前线程调用栈 → 通过内部 trace 发送队列异步写入 TraceLog 系统表 → 落盘的trace_log表中trace列为 UInt64 地址数组见 TraceLog.h 的std::vectorUInt64 trace→ 查询时用addressToSymbol等函数在线符号化。这也解释了第 2 步中“等待 2 秒再查询”的必要性trace 写入是异步的。官方文档中与该流程对应的页面为 采样式查询分析器指南 与 trace_log 系统表参考可与本文的 SQL 对照使用。技能的使用示例技能文档给出的三种调用形态对应/cpu-profile斜杠命令/cpu-profile交互式由用户选择要剖析的查询。/cpu-profile a1b2c3d4-e5f6-7890-abcd-ef1234567890分析已有 query_id 的 trace跳过执行步骤直接从第 3 步开始。/cpu-profile SELECT count() FROM lineitem WHERE l_shipdate 1995-01-01立即执行并剖析一条 SQL以 TPC-H 的 lineitem 表 count 查询为例。小结这套 CPU Profile 技能把 ClickHouse 原生剖析能力组织成了一条可复制的流水线带--query_id以 100us 采样执行查询 → 等trace_log落盘 → 三路并行 SQLTop 函数 / Top 调用栈 / collapsed 导出→ 结构化报告含子系统归类→ 按需下钻函数过滤、CPU vs Real 对比、火焰图、源码行号。所有步骤只依赖system.query_log与system.trace_log两张系统表及allow_introspection_functions权限无需 perf 等外部工具而采样周期的取值、符号化前提dbg 包、堆栈方向等细节在 Settings.cpp、Defines.h 与 TraceLog.h 中都有明确的源码依据可按当前仓库实际内容核对。【免费下载链接】ClickHouseClickHouse® is a real-time analytics database management system项目地址: https://gitcode.com/GitHub_Trending/cli/ClickHouse创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考