3. ClickHouse 核心监控指标:从现象到根因

监控 ClickHouse,说白了就是盯住三件事:查得快不快、写得顺不顺、资源够不够。这三条线任何一条出问题,用户那边立刻就能感受到。我在生产环境摸爬滚打这几年,踩过的坑大多都跟指标误读有关——不是没监控,而是看不懂指标在说什么。

这一章,我把 ClickHouse 的核心监控指标拆成三大类,每类都带上我自己的实战经验。你照着这个框架搭监控,至少能覆盖 90% 的故障场景。

ClickHouse 核心监控指标体系 ClickHouse 实例 查询性能 写入性能 资源指标 查询延迟 (P50/P95/P99) QPS (每秒查询数) 扫描行数 / 扫描字节 写入延迟 (INSERT 耗时) 写入吞吐 (行/秒 或 MB/s) Parts 合并队列 & 速度 CPU 使用率 & 等待 I/O 内存 (JIT / 查询内存) 磁盘 IO (延迟 & 队列) 网络 IO (TCP 重传) 三大指标相互关联:查询慢可能是资源瓶颈,写入慢可能是合并积压,资源高可能是查询扫描太多

3.1 查询性能指标:用户感知的第一道防线

查询性能是 ClickHouse 的招牌。用户说「慢」,你得知道慢在哪。

3.1.1 查询延迟:别只看平均值

我个人习惯把查询延迟拆成三个分位:P50、P95、P99。P50 代表典型体验,P95 代表大多数慢查询,P99 代表极端情况。你想想看,如果 P99 比 P50 高了一个数量级,那说明系统里有严重的「长尾」问题。

关键指标获取方式:

-- 查看当前正在执行的查询及其延迟
SELECT 
    query_id,
    query,
    elapsed,
    read_rows,
    read_bytes,
    memory_usage
FROM system.processes
WHERE elapsed > 5
ORDER BY elapsed DESC;

-- 查看历史查询延迟分布(需要开启 query_log)
SELECT 
    quantile(0.5)(query_duration_ms) AS p50,
    quantile(0.95)(query_duration_ms) AS p95,
    quantile(0.99)(query_duration_ms) AS p99,
    count() AS total_queries
FROM system.query_log
WHERE event_date = today()
  AND type = 'QueryFinish';

我在项目中遇到过一件事:某次大促前,P95 延迟从 200ms 飙到了 2s。查了半天,发现是某个业务方写了个 SELECT * 扫全表,每次扫描几十亿行。这种查询就是典型的「老鼠屎」。

我的经验:监控延迟一定要配合 query_log 做明细下钻。只看聚合指标你永远不知道是哪个查询在捣乱。我一般会在 Grafana 上放一个「Top-N 慢查询」面板,按耗时倒排,一眼就能定位问题。

3.1.2 QPS:吞吐的晴雨表

QPS 是每秒查询数。这个指标单独看意义不大,得跟延迟放在一起看。举个例子:QPS 从 1000 掉到 500,延迟反而涨了——这大概率是系统过载了,请求在排队。

-- 计算当前 QPS(基于 query_log)
SELECT 
    toStartOfMinute(event_time) AS minute,
    count() AS qps
FROM system.query_log
WHERE event_date = today()
  AND type = 'QueryFinish'
GROUP BY minute
ORDER BY minute DESC
LIMIT 10;

注意:QPS 下降不一定是坏事。如果是因为缓存命中率提高导致查询变快、并发减少,那反而是好事。一定要结合业务流量一起看。

3.1.3 扫描行数:性能的「照妖镜」

扫描行数是我最看重的指标之一。ClickHouse 是列存,但如果你扫了太多不必要的列,性能一样崩。我见过最夸张的情况:一个查询只取 3 个字段,但表有 200 列,结果扫描字节数高达几十 GB。

-- 查看每个查询的扫描效率
SELECT 
    query,
    read_rows,
    read_bytes,
    result_rows,
    round(read_bytes / result_rows, 2) AS bytes_per_row
FROM system.query_log
WHERE event_date = today()
  AND type = 'QueryFinish'
  AND result_rows > 0
ORDER BY read_bytes DESC
LIMIT 20;

bytes_per_row 这个值特别有用。如果它远大于单行数据的实际大小,说明查询扫描了多余的列。嗯,这里要注意:永远不要在生产环境用 SELECT *,除非你真的需要所有字段。

3.2 写入性能指标:数据进得来,分析才出得去

很多人只关注查询,忽略了写入。但你想,写入慢了,数据延迟高,实时分析就成了「伪实时」。我见过不少团队,查询优化得飞起,结果写入积压了几百万行,用户查到的永远是 10 分钟前的数据。

3.2.1 写入延迟:INSERT 到底慢在哪

写入延迟就是 INSERT 语句从发起到返回的时间。ClickHouse 的写入是异步合并的,所以 INSERT 本身通常很快(毫秒级)。但如果延迟突然升高,你得排查几个方向:

  • 磁盘 IO 瓶颈:写入落盘慢,导致 INSERT 等待 fsync
  • Parts 数量过多:表的分区不合理,每次写入都产生大量小 parts
  • 网络延迟:客户端到 ClickHouse 的网络抖动
-- 查看写入延迟分布
SELECT 
    quantile(0.5)(query_duration_ms) AS p50_write_ms,
    quantile(0.95)(query_duration_ms) AS p95_write_ms,
    count() AS write_count
FROM system.query_log
WHERE event_date = today()
  AND type = 'QueryFinish'
  AND query LIKE 'INSERT%';

我的习惯:写入延迟的 P95 如果超过 500ms,我就会开始排查。大多数情况下,问题出在客户端批量大小不合理——一次 INSERT 的行数太少,导致频繁刷盘。

3.2.2 写入吞吐:你到底能扛多少

写入吞吐通常用「行/秒」或「MB/s」来衡量。这个指标跟硬件配置强相关。我自己的经验是:一台 16 核 64G 的机器,单表写入吞吐大概在 50-100 万行/秒(取决于列数和数据类型)。

-- 计算每分钟写入吞吐
SELECT 
    toStartOfMinute(event_time) AS minute,
    sum(rows) AS rows_inserted,
    formatReadableSize(sum(bytes)) AS bytes_inserted
FROM system.query_log
WHERE event_date = today()
  AND type = 'QueryFinish'
  AND query LIKE 'INSERT%'
GROUP BY minute
ORDER BY minute DESC
LIMIT 10;

避坑指南:我曾经遇到过一个案例,写入吞吐从 80 万行/秒突然掉到 10 万行/秒。查了半天,发现是 MergeTree 的 parts_to_throw_insert 触发了限流——表里积压了太多未合并的 parts,ClickHouse 主动拒绝了写入。所以,写入吞吐下降时,第一件事就是去看 parts 数量。

3.2.3 Parts 合并:被忽视的「隐形杀手」

Parts 合并是 ClickHouse 写入链路里最容易被忽视的环节。写入快不代表系统健康——如果合并跟不上写入速度,parts 会越积越多,最终导致查询性能雪崩。

-- 查看当前合并队列
SELECT 
    database,
    table,
    count() AS pending_merges,
    sum(rows) AS total_rows_to_merge
FROM system.merges
WHERE database != 'system'
GROUP BY database, table
ORDER BY pending_merges DESC;

-- 查看合并速度
SELECT 
    toStartOfMinute(event_time) AS minute,
    count() AS merges_completed,
    sum(rows_read) AS rows_merged
FROM system.part_log
WHERE event_date = today()
  AND event_type = 'MergeParts'
GROUP BY minute
ORDER BY minute DESC
LIMIT 10;

我个人习惯在 Grafana 上放一个「合并积压」面板。如果 pending_merges 持续大于 50,我就会考虑调整合并线程数或者优化分区策略。

合并相关的关键配置:

配置项 默认值 说明
background_pool_size 16 后台合并线程数,CPU 核数多时可以调大
parts_to_throw_insert 300 parts 超过此值会拒绝写入,防止 OOM
merge_max_block_size 8192 单次合并的最大行数,影响合并粒度

3.3 资源指标:系统健康的「体温计」

资源指标是基础。查询和写入的问题,最终都会反映到资源上。反过来,资源瓶颈也会导致查询和写入异常。

3.3.1 CPU:别被「空闲」骗了

CPU 使用率高不一定是坏事——ClickHouse 就是吃 CPU 的。但你要关注的是 CPU 等待 I/O 的时间(iowait)。如果 CPU 使用率只有 30%,但 iowait 占了 50%,那说明磁盘是瓶颈。

-- 通过 system.asynchronous_metrics 查看 CPU 指标
SELECT 
    metric,
    value
FROM system.asynchronous_metrics
WHERE metric LIKE '%CPU%'
   OR metric LIKE '%Load%'
ORDER BY metric;

我的经验:CPU 使用率超过 80% 且持续上升,同时查询延迟开始抖动——这就是过载的前兆。我会提前扩容或者限流,而不是等到 CPU 跑满再救火。

3.3.2 内存:JIT 编译和查询内存

ClickHouse 的内存消耗主要来自三块:查询执行、JIT 编译、MergeTree 缓存。我最怕的是查询内存不受控——一个没加 LIMIT 的聚合查询,能把几十 GB 内存吃光。

-- 查看当前内存使用 Top-N 查询
SELECT 
    query_id,
    query,
    formatReadableSize(memory_usage) AS memory,
    elapsed
FROM system.processes
ORDER BY memory_usage DESC
LIMIT 10;

-- 查看内存限制配置
SELECT 
    name,
    value,
    changed
FROM system.settings
WHERE name IN ('max_memory_usage', 'max_memory_usage_for_user', 'max_bytes_before_external_group_by');

避坑指南:我曾经在生产环境遇到过 OOM。原因是某个分析师跑了一个 GROUP BY 没加 LIMIT,结果聚合后的中间数据膨胀到 40GB,直接把节点打挂了。从那以后,我强制在用户级别设置了 max_memory_usage_for_user,再也没出过类似问题。

3.3.3 磁盘 IO:延迟和队列长度

磁盘 IO 是 ClickHouse 的生命线。查询要读盘,写入要写盘,合并更要读写盘。我一般关注两个指标:磁盘平均 IO 延迟和 IO 队列长度。

-- 通过 system.disks 查看磁盘使用情况
SELECT 
    name,
    path,
    formatReadableSize(total_space) AS total,
    formatReadableSize(used_space) AS used,
    formatReadableSize(free_space) AS free,
    round(used_space / total_space * 100, 2) AS used_pct
FROM system.disks;

-- 通过 system.asynchronous_metrics 查看 IO 指标
SELECT 
    metric,
    value
FROM system.asynchronous_metrics
WHERE metric LIKE '%Disk%'
   OR metric LIKE '%IO%'
ORDER BY metric;

我个人习惯:如果磁盘 IO 延迟持续超过 10ms,或者队列长度大于 2,我就会开始排查。常见的根因有:

  • 合并线程太多,导致 IO 争抢
  • 查询扫描了太多冷数据,触发大量磁盘读
  • 磁盘本身性能不足(比如用了 HDD 而不是 SSD)

3.3.4 网络 IO:TCP 重传是信号

网络 IO 在分布式场景下尤其重要。ClickHouse 的分布式查询需要节点间传输数据,如果网络有丢包或延迟,查询性能会急剧下降。

-- 查看网络相关的系统指标
SELECT 
    metric,
    value
FROM system.asynchronous_metrics
WHERE metric LIKE '%Network%'
   OR metric LIKE '%TCP%'
ORDER BY metric;

网络 IO 的黄金指标:

  • TCP 重传率:超过 0.5% 就要警惕
  • 网络带宽使用率:超过 70% 可能引发拥塞
  • 节点间延迟:P99 超过 10ms 会影响分布式查询

嗯,到这里,三大类核心指标就讲完了。你可能会问:这么多指标,我该先看哪个?我的建议是:先建一个「健康总览」面板,把查询延迟 P95、写入吞吐、合并积压、CPU iowait 这四个指标放上去。这四个指标任何一个出问题,都值得你深入排查。