ClickHouse 运行一个采样分析器,可用于分析查询执行情况。 使用该分析器,您可以找出在查询执行期间最常使用的源代码例程。 您可以跟踪所耗费的 CPU 时间和挂钟时间,包括空闲时间。
在 ClickHouse Cloud 中,查询分析器默认自动启用。 下面的示例查询会找出某个已分析查询中最常见的堆栈跟踪,并解析出函数名称和源代码位置。
默认情况下,分析器会在采集时对堆栈跟踪进行符号解析,并将结果存储在 system.trace_log 的 symbols 和 lines 列中,因此以下示例会直接读取这些列,无需使用内部信息函数。符号解析由 trace_log 服务器配置部分中的 symbolize 设置控制 (默认启用) ,支持 ELF 平台 (如 Linux) 和 macOS;在 FreeBSD 上,symbols 和 lines 列始终为空。symbols 中的函数名称来自二进制文件的符号表,默认可用。lines 中的源代码位置会尽力解析:它们需要调试信息 (在 macOS 上,需要二进制文件旁的 .dSYM 包) ;在 ELF 平台上,仅会解析主 ClickHouse 二进制文件内的帧,因此无法解析的帧 (例如共享库中的帧) 对应条目将保留为空。如果禁用了符号解析,请改用 addressToSymbol、demangle 和 addressToLine 内部信息函数 来解析 trace 列中的原始地址。这些函数支持与符号解析相同的平台 (如 Linux 等 ELF 平台及 macOS) ;在 FreeBSD 上它们同样未被编译,因此必须在服务器外部解析 trace 中的地址。
在 ClickHouse Cloud 中,您可以点击查询结果表上方工具栏最右侧的 "…" (位于表/图表切换按钮旁) 来获取查询 ID。这会打开一个菜单,您可以点击 "Copy query ID"。
使用 clusterAllReplicas(default, system.trace_log) 从集群中的所有节点查询:
SELECT
count(),
arrayStringConcat(arrayMap((symbol, line) -> concat(symbol, '\n ', line), any(symbols), any(lines)), '\n') AS sym
FROM clusterAllReplicas(default, system.trace_log)
WHERE query_id = '<query_id>' AND trace_type = 'CPU' AND event_date = today()
GROUP BY trace
ORDER BY count() DESC
LIMIT 10SELECT
count(),
arrayStringConcat(arrayMap((symbol, line) -> concat(symbol, '\n ', line), any(symbols), any(lines)), '\n') AS sym
FROM system.trace_log
WHERE query_id = '<query_id>' AND trace_type = 'CPU' AND event_date = today()
GROUP BY trace
ORDER BY count() DESC
LIMIT 10在自管理部署中使用查询分析器
在自管理部署中,如需使用查询分析器,请按以下步骤操作:
安装带调试信息的 ClickHouse
安装 clickhouse-common-static-dbg 软件包:
- 按照步骤 "设置 Debian 仓库" 中的说明操作
- 运行
sudo apt-get install clickhouse-server clickhouse-client clickhouse-common-static-dbg,安装包含调试信息的 ClickHouse 已编译二进制文件 - 运行
sudo service clickhouse-server start启动服务器 - 运行
clickhouse-client。clickhouse-common-static-dbg中的调试符号会被服务器自动加载,无需执行任何额外操作来启用它们
检查服务器配置
确保 服务器配置文件中的 trace_log 部分已配置。默认情况下它处于启用状态:
<!-- Trace log. Stores stack traces collected by query profilers.
See query_profiler_real_time_period_ns and query_profiler_cpu_time_period_ns settings. -->
<trace_log>
<database>system</database>
<table>trace_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<max_size_rows>1048576</max_size_rows>
<reserved_size_rows>8192</reserved_size_rows>
<buffer_size_rows_flush_threshold>524288</buffer_size_rows_flush_threshold>
<!-- Indication whether logs should be dumped to the disk in case of a crash -->
<flush_on_crash>false</flush_on_crash>
<symbolize>true</symbolize>
</trace_log>此部分用于配置 trace_log 系统表,该表包含分析器运行的结果。
symbolize 选项 (默认启用) 会让 ClickHouse 在收集时解析每个堆栈帧,并将反修饰后的函数名称和源代码位置存储在 symbols 和 lines 列中。
symbols 中的函数名称来自符号表,默认可用;而 lines 中的源代码位置需要调试信息 (macOS 上为 .dSYM 包) ,且在 ELF 平台上仅会解析主 ClickHouse 二进制文件内的帧;未解析帧的 lines 条目为空。
请注意,与预先符号化的列相比,trace 列中的原始地址在重启和升级后稳定性较差。
在除 FreeBSD 外的 ELF 平台上,主 ClickHouse 二进制文件中的帧会存储为物理文件偏移量,因此只要二进制文件未发生变化,重启后仍可解析;在 macOS 和 FreeBSD 上,它们会存储为运行时虚拟地址,重启后可能失效。
主二进制文件外的帧 (例如共享库中的帧) 始终存储为运行时虚拟地址,重启后可能失效;而由于代码布局发生变化,二进制文件升级后所有原始地址都将无法解析。
ClickHouse 不会在重启时清理该表,因此可能会保留过时的原始地址。
相反,预先符号化的 symbols 和 lines 列在重启和升级后仍然有效,因此分析历史数据时应优先使用它们。
配置分析器计时器
设置 query_profiler_cpu_time_period_ns 或 query_profiler_real_time_period_ns。
这两个设置可以同时使用。
这些设置可用于配置分析器计时器。 由于它们是会话设置,因此你可以为整个服务器、单个用户或用户 profile、当前交互式会话以及每个单独的查询设置不同的采样频率。
默认采样频率为每秒一个样本,并且 CPU 计时器和实时时间计时器均已启用。 该频率既能收集关于 ClickHouse 集群 的足够信息,又不会影响服务器性能。 如果你需要分析每个单独的查询,请使用更高的采样频率。
[object Object]
要获取某个查询的 profile,需要对 trace_log 表中的数据进行聚合。
你可以按单个函数或整个堆栈跟踪聚合数据。
启用符号化时 (默认启用) ,symbols 和 lines 列中已提供反修饰后的函数名称和源代码位置,因此无需额外设置。FreeBSD 不支持符号化,在该系统上这些列始终为空。对于缺少调试信息或位于主 ClickHouse 二进制文件之外的帧,lines 条目可能为空 (参见上文) 。
如果禁用了符号化,或者想动态解析 trace 列中的原始地址 (例如,展开内联帧) ,请通过 allow_introspection_functions 设置启用内部信息函数:
SET allow_introspection_functions=1使用 addressToLine、addressToLineWithInlines、addressToSymbol 和 demangle 内部信息函数 获取函数名称及其在 ClickHouse 代码中的位置。与符号化功能一样,这些函数可在 ELF 平台 (如 Linux) 和 macOS 上使用,但不支持 FreeBSD。
使用 flameGraph 函数生成火焰图
ClickHouse 提供聚合函数 flameGraph,可直接根据存储在 trace_log 中的堆栈跟踪生成火焰图。
输出为 String 数组,格式与 flamegraph.pl 兼容。
语法:
flameGraph(traces, [size = 1], [ptr = 0])参数:
traces— 一条栈追踪。Array(UInt64)。size— 用于内存分析的分配大小。Int64。ptr— 分配地址。UInt64。
当 ptr 非零时,flameGraph 会将大小和指针相同的分配 (size > 0) 与释放 (size < 0) 对应起来。
只显示尚未释放的分配。
不匹配的释放会被忽略。
CPU 火焰图
SET query_profiler_cpu_time_period_ns = 10000000;运行查询,然后生成火焰图:
clickhouse client --allow_introspection_functions=1 \
-q "SELECT arrayJoin(flameGraph(arrayReverse(trace)))
FROM system.trace_log
WHERE trace_type = 'CPU' AND query_id = '<query_id>'" \
| flamegraph.pl > flame_cpu.svg内存火焰图——全部分配
SET memory_profiler_sample_probability = 1, max_untracked_memory = 1;运行查询,然后生成火焰图:
clickhouse client --allow_introspection_functions=1 \
-q "SELECT arrayJoin(flameGraph(trace, size))
FROM system.trace_log
WHERE trace_type = 'MemorySample' AND query_id = '<query_id>'" \
| flamegraph.pl --countname=bytes --color=mem > flame_mem.svg内存火焰图——未释放的内存分配
这种形式会按指针将分配与释放对应起来,并且只显示查询期间未释放的内存。
SET memory_profiler_sample_probability = 1, max_untracked_memory = 1,
use_uncompressed_cache = 1,
merge_tree_max_rows_to_use_cache = 100000000000,
merge_tree_max_bytes_to_use_cache = 1000000000000;运行以下查询以生成火焰图:
clickhouse client --allow_introspection_functions=1 \
-q "SELECT arrayJoin(flameGraph(trace, size, ptr))
FROM system.trace_log
WHERE trace_type = 'MemorySample' AND query_id = '<query_id>'" \
| flamegraph.pl --countname=bytes --color=mem > flame_mem_unfreed.svg内存火焰图——某一时刻的活跃分配
这种方法可帮助你找出峰值内存占用,并直观展示该时刻分配了哪些内存。
SET memory_profiler_sample_probability = 1, max_untracked_memory = 1;查看内存使用量随时间的变化
SELECT
event_time,
formatReadableSize(max(s)) AS m
FROM (
SELECT
event_time,
sum(size) OVER (ORDER BY event_time) AS s
FROM system.trace_log
WHERE query_id = '<query_id>' AND trace_type = 'MemorySample'
)
GROUP BY event_time
ORDER BY event_time;找到内存使用量最大的时间点
SELECT
argMax(event_time, s),
max(s)
FROM (
SELECT
event_time,
sum(size) OVER (ORDER BY event_time) AS s
FROM system.trace_log
WHERE query_id = '<query_id>' AND trace_type = 'MemorySample'
);构建该时间点的活跃分配火焰图
clickhouse client --allow_introspection_functions=1 \
-q "SELECT arrayJoin(flameGraph(trace, size, ptr))
FROM (
SELECT * FROM system.trace_log
WHERE trace_type = 'MemorySample'
AND query_id = '<query_id>'
AND event_time <= '<time_point>'
ORDER BY event_time
)" \
| flamegraph.pl --countname=bytes --color=mem > flame_mem_time_point_pos.svg生成该时间点之后的释放火焰图 (以了解后续释放了哪些内容)
clickhouse client --allow_introspection_functions=1 \
-q "SELECT arrayJoin(flameGraph(trace, -size, ptr))
FROM (
SELECT * FROM system.trace_log
WHERE trace_type = 'MemorySample'
AND query_id = '<query_id>'
AND event_time > '<time_point>'
ORDER BY event_time DESC
)" \
| flamegraph.pl --countname=bytes --color=mem > flame_mem_time_point_neg.svg示例
以下代码片段:
- 按查询标识符和当前日期过滤
trace_log数据。 - 读取已预先符号化的
symbols和lines列,以生成包含以下内容的报告:- 符号名称及对应的源代码函数。
- 这些函数在源代码中的位置。
- 按原始堆栈跟踪 (
trace列) 聚合,已符号化的列仅用于显示,确保不同的堆栈跟踪不会因尽力而为的符号化而被合并。
SELECT
count(),
arrayStringConcat(arrayMap((symbol, line) -> concat(symbol, '\n ', line), any(symbols), any(lines)), '\n') AS sym
FROM system.trace_log
WHERE (query_id = '<query_id>') AND (event_date = today())
GROUP BY trace
ORDER BY count() DESC
LIMIT 10