日志分析平台怎么选:ES、Loki、ClickHouse 按 200GB/天算一笔真实账单

🔑 关键词:日志分析,ClickHouse,Loki,Elasticsearch,日志成本优化

📖 摘要:同一份 200GB/天的日志流,ES 的月成本是 ClickHouse 的 4.8 倍,Loki 夹在中间。这篇拆开三套方案的索引机制、实测压缩比,以及一个被大多数人忽略的事实:你的日志查询里往往只有 13% 真的需要全文检索。

上周三凌晨两点半,告警群里在刷同一个面板截图,某个订单服务的 ERROR 曲线几乎是竖直拉起来的。我打开 Grafana 点了 Loki 的 Explore,敲了 {app="order-svc"} |= "ERROR",回车,转圈,40 秒后返回 context canceled。

图片

那一刻我确实想把整套日志平台拆了重来。冷静下来之后发现,问题不在 Loki,在我——我把一个 200GB/天的日志流,按「全文检索」的思路去使了。这篇文章不是选型指南,是我这半年把 ES、Loki、ClickHouse 挨个压到同一份流量上算出来的一笔账,以及一个我越来越确信的观点:大多数团队根本不需要全文检索,他们需要的是先把日志分成两类。

先搞清楚你在查什么,90% 的查询只有两种

我扒过我们平台三个月的查询记录(Grafana 的 query history,一共 1.1 万条)。按语义归一下类,只有两种占了 87%。

第一种是「这个服务最近半小时有没有 ERROR」,对应计数、比例、P99 这类聚合。第二种是「用户 8xx321 那笔下单为什么失败了」,对应按 trace_id 精确定位。剩下 13% 才是真正的模糊搜索,比如「谁还在调用那个已经下线的老接口」。

这个比例决定了后面所有事。第一种查询,Prometheus 每秒能算几十次;第二种,索引建在 trace_id 上就够;只有第三种才需要倒排索引去扫 message 正文。而我们过去两年花了大概 90% 的钱,去优化那 13%。

图片

三个流派的本质差别,不在性能在索引方式

市面上的方案大概分三派,别被营销话术绕进去,看它们把「索引」建在哪:

  • 倒排索引派:Elasticsearch、Splunk、OpenSearch。给 message 分词、建词典,查询时反查。优势是模糊匹配、聚合、高亮全都能干。代价是写入放大,我实测一份 100GB 未压缩的 JSON 日志进 ES,含 1 副本会落在 130~180GB。
  • 标签流派:Loki、VictoriaLogs 思路接近。只给标签建索引,正文压成 chunk 扔对象存储,查询时暴力扫。所以它便宜、写入快,但查询成本随「时间范围 × 流数量」线性上涨——你写 | json 那一刻,就从 Loki 掉进了单机 grep。
  • 列存派:ClickHouse、Doris、StarRocks。按列压缩存,靠排序键做稀疏索引,查询时能整块跳过 granule。压缩比最夸张,我这份日志用 ZSTD(3) 压到了 8~13 倍。代价是它不会「理解」你的文本,LIKE '%timeout%' 就是老老实实全扫。

没有银弹,只有「你的查询长什么样」。上面那 87% 的查询三派都能干,区别只在成本和运维负担。

图片

同一份 200GB/天,三套方案的实际账单

场景铺一下:200GB/天原始日志,保留 14 天,写入峰值 12 万条/秒。数字做了脱敏,比例是真实的,你按自己云厂商的价目表换算。

维度 Elasticsearch 8.x Loki 3.x + S3 ClickHouse 24.x
热数据落盘 2.8TB 原始 → 约 4.2TB(含副本) 约 500GB chunk + 索引 约 320GB(ZSTD)
计算资源 6 节点 × 8C32G 3 节点 × 4C16G + 对象存储 3 节点 × 8C32G
月成本(相对值) 1.0 0.32 0.21
模糊搜索 好 差,超过 1 天范围基本不可用 差
按 trace_id 查 好,但字段基数高会拖慢 一般,强依赖标签设计 很好,排序键命中直接跳 granule
运维负担 重(分片、段合并、冷热分层) 轻 中(要自己管 schema 和 TTL)

有件事我得承认:ClickHouse 那 0.21 不是白来的,它省下的钱有一半要用在「提前想清楚 schema」上。ES 你随便丢 JSON 进去它都能查,ClickHouse 你要是 ORDER BY 写歪了,性能能差 20 倍,而且不会有人提醒你。

还有个细节:ES 默认 refresh_interval 是 1s,日志场景改成 30s,写入吞吐大概能提 3~5 倍——光这一个参数我们就把 ES 集群从 8 个节点缩到了 6 个。

图片

我在 ClickHouse 上踩的两个坑

第一个坑是排序键。我一开始图省事,建表写成 ORDER BY (svc, level, ts),跑了两周发现按 trace_id 查还是慢。原因很简单,trace_id 基数太高(一天 4000 万个不重复值),放进排序键会让索引文件膨胀到没法看。后来改成 ORDER BY (svc, toStartOfHour(ts), level),把 trace_id 单独用跳数索引处理:INDEX idx_trace trace_id TYPE bloom_filter GRANULARITY 4,按 trace_id 查从 3.2 秒掉到 180 毫秒。

第二个坑是 Map 类型。我一开始把业务字段全塞进 attrs Map(String, String),觉得灵活。结果 attrs['user_id'] 这种查询在 ClickHouse 里要按列展开,QPS 一高 CPU 直接打满。后来把 top 20 的高频字段提升成独立列,Map 只留给长尾字段,查询耗时降了大概 6 倍。

还有个更隐蔽的:Vector 的 sample transform。我用 rate = 10 采样,心想省 90% 存储,结果有次排查线上问题,发现出问题的那条日志恰好没被采到。采样必须带上 key_field = 'trace_id',保证同一条链路要么全留要么全丢。这个参数不加,采样就等于在赌命。

我现在的做法:指标和日志走两条路

图片

说白了就一句话:出口分叉,热冷分离。

在采集端(我用 Vector,单实例 8C16G 跑简单 remap 能到 25 万条/秒),日志一进来先分两路。一路走「指标化」,只提取 level、status_code、latency 这几个维度,聚合成 counter 和 histogram 打进 Prometheus,数据量大概是原始的 0.3%,成本可以忽略,但它覆盖了那 87% 的查询。另一路原样进 ClickHouse 冷存,保留 14 天,按需查,平时没人碰,唯一的要求是查得准。

关键在第二路的字段裁剪。原始 JSON 有 42 个字段,我砍到 11 个,del() 掉 host、container_id、pod_name 这一堆冗余元数据——这些在 K8s 环境里每行都重复,占了大概 35% 的体积。砍完写入量从 200GB/天降到 130GB/天,压缩后落盘 320GB。

这个操作不需要任何架构改造,改 6 行 VRL 就行。我建议你今天就去看看自己的日志里,有多少字段是每行都一模一样的。

图片

反过来说,什么时候别学我这套

如果你在做金融、医疗、政企合规,日志是要当审计证据用的,那「采样」和「字段裁剪」这两个词你最好一个字都别碰。审计场景要的是完整性和不可篡改,冷存那一层得换成带合规锁的 WORM 存储,成本再高也得扛着。这种场景下 ES 或者 Splunk 那套全字段索引反而省心,因为你不缺钱,你缺的是出事时能 5 分钟内把人捞出来的把握。

团队规模也是变量。三个人以下、日志量不到 20GB/天,我的建议很粗暴:直接上 Loki 单机版 + S3,别碰 ClickHouse。ClickHouse 的 schema 设计、TTL 策略、物化视图是需要有人持续维护的,没人维护的 ClickHouse 会成为下一个技术债现场——我见过一个,两年后没人敢动那张表。

最后说个我自己的判断,可能有人不同意:日志分析下半场的竞争点不在查询引擎,在「怎么在采集端就把 80% 的数据扔掉而不丢信息」。 你去看 Vector、Fluent Bit、OTel Collector 这三个项目最近一年的更新节奏就知道了,重头戏全在 transform 和 processor 上。

回到开头那个凌晨。那次故障的根因其实特别蠢:一个下游接口超时,重试逻辑没加退避,三条日志变三百条。我们花两周优化查询性能,最后真正解决问题的是给重试加了个 maxAttempts = 2。工具能帮你更快地看见问题,但它看不见你自己写的 bug。

🏷️ 标签: