告警:凌晨三点的慢查询邮件
四月某天早上,运维把一封告警邮件转给我:「日志检索接口 P95 超过 8 秒,ES 集群 CPU 打满」。我们的应用日志一直存在 Elasticsearch 里,单日写入约 30 亿条,随着业务量涨,ES 的堆内存和查询延迟都快撑不住了。老板提了个方向:能不能把明细日志搬一部分到 ClickHouse?
排查:为什么 ES 扛不住
ES 是行存 + 倒排,适合做关键词检索和聚合,但我们的日志场景 80% 的查询是「按时间范围 + 几个固定字段做统计」,并不需要全文检索。ES 为了灵活性付出的存储和内存代价太重了。
ClickHouse 是列式存储,正好反过来——不擅长随意的全文检索,但做这种固定模式的大宽表聚合,吞吐能差一个数量级。
根因:列式存储的优势到底在哪
列存的奥妙在于:一次「统计每个接口的报错数」的查询,ES 要把整行文档捞出来再过滤,ClickHouse 只读 status、path、ts 三列,磁盘 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 在日志分析场景的实践》里同一个坑绊住,回来翻这篇,能省半小时。