Administrator
发布于 2023-04-06 / 4340 阅读
83

ClickHouse 在日志分析场景的实践

告警:凌晨三点的慢查询邮件

四月某天早上,运维把一封告警邮件转给我:「日志检索接口 P95 超过 8 秒,ES 集群 CPU 打满」。我们的应用日志一直存在 Elasticsearch 里,单日写入约 30 亿条,随着业务量涨,ES 的堆内存和查询延迟都快撑不住了。老板提了个方向:能不能把明细日志搬一部分到 ClickHouse?

排查:为什么 ES 扛不住

ES 是行存 + 倒排,适合做关键词检索和聚合,但我们的日志场景 80% 的查询是「按时间范围 + 几个固定字段做统计」,并不需要全文检索。ES 为了灵活性付出的存储和内存代价太重了。

ClickHouse 是列式存储,正好反过来——不擅长随意的全文检索,但做这种固定模式的大宽表聚合,吞吐能差一个数量级。

根因:列式存储的优势到底在哪

列存的奥妙在于:一次「统计每个接口的报错数」的查询,ES 要把整行文档捞出来再过滤,ClickHouse 只读 statuspathts 三列,磁盘 IO 直接砍掉一大半。而且列存压缩率极高,我们同样的日志,ES 存了 12TB,ClickHouse 只有 1.6TB。

解决方案:表引擎与写入

表引擎选了 MergeTree 家族,日志场景用 ReplacingMergeTree 没必要,直接 MergeTree 配好分区和排序键:

CREATE TABLE app_log (
  ts DateTime,
  trace_id String,
  path String,
  status UInt16,
  cost_ms UInt32,
  msg String
) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(ts)
ORDER BY (path, status, ts);

这里 ORDER BY 是稀疏索引的依据,把高频过滤字段放前面。我们一开始把 ts 放第一,结果按 path 查还是全分区扫;调换顺序后单查询扫描范围从 40 亿行降到 2000 万行。

写入与查询优化

  • 写入:走 Kafka 表引擎(Kafka 引擎 + 物化视图)批量落盘,单批 5 万行,避免小批导致大量小 part。part 过多会拖慢 merge,我们限制了 min_insert_block_size_rows=100000
  • 查询:时间范围必须带分区键,否则跨所有分区;用 PREWHERE 提前过滤;聚合用 uniqExact 太重,报错数改用 uniq 近似去重,误差 0.1% 内业务能接受。

一条典型查询:

SELECT path, uniq(trace_id) AS err_cnt
FROM app_log
WHERE ts BETWEEN '2023-04-01 00:00:00' AND '2023-04-01 23:59:59'
  AND status >= 500
GROUP BY path
ORDER BY err_cnt DESC
LIMIT 10;

改造后同样的查询从 ES 的 8.2 秒降到 ClickHouse 的 240 毫秒,存储成本降了 87%,ES 集群 CPU 均值从 75% 掉到 30%。

就写到这。如果哪天你也被《ClickHouse 在日志分析场景的实践》里同一个坑绊住,回来翻这篇,能省半小时。

参考