日志分析选型别先比引擎:我用 3 年、7 个团队的账单换来的几个反常识结论

🔑 关键词:日志分析, ELK 成本优化, Loki 高基数, ClickHouse 日志存储, 日志采样

📖 摘要:日志分析的成本大头不在查询引擎,而在日志的生产和采集端。本文用真实环境参数横向对比 ELK、Loki、ClickHouse,并给出把优化顺序倒过来的三步做法和具体配置数值。

先亮观点:日志分析 90% 的成本,在你写下第一行 log.info 的时候就定了

图片

去年冬天帮一个朋友看账单。做 SaaS 的,日活不到 8 万,Datadog 一个月 1.7 万美金,其中 log ingest 占了 9000 多。我让他们把 7 天的日志量导出来看看:每天原始文本 620GB,采集端压缩后 110GB,而真正的索引查询命中率——就是那些日志到底被查过没有——不到 0.6%。

这个数字不是个例。我前后看过七八家公司的日志链路,命中率超过 5% 的一个都没有。我们花 100% 的钱,存了 99.4% 永远不会被看第二眼的文本。

所以这里有个可能有点反直觉的话:日志分析的成本和性能,九成在你写日志和收日志的那一刻就决定了,跟后面选 ELK 还是 Loki 关系没那么大。大部分团队做优化的顺序是反的——先去压测引擎的 QPS、比谁家倒排索引快,最后才想起来日志本身从来没做过分级和采样。

先别问选哪个引擎,先问你这三个问题

我一般会先反过来问对方三句话:

图片

  • 你要的是「精确定位某一次请求的完整上下文」,还是「统计某类错误每小时出现多少次」?
  • 查询的时间范围是 15 分钟内,还是 90 天前?
  • 出问题的时候,是人在终端里翻,还是告警系统自动在查?

这三个问题基本能劈开方案。说人话就是:

15 分钟内 + 人工查 → Loki 完全够用,日志量再小一点,journalctl 加 grep 都能扛一阵。 90 天范围 + 聚合统计 → 别用日志做,那是 ClickHouse 或者 Prometheus 的活。 跨服务追一个请求 → 你得先有 trace_id 并且它被打印在每条日志里,否则存哪都是废纸。

我见过最典型的浪费,是一个团队用 ES 存了两年的 access log,就为了算每天的 PV 和 5xx 比例。这俩数用指标做,一年的数据也就几十 MB。

三家横向比:参数和实测量都摊开

图片

下面这张表是我自己攒的,数字来自几个真实环境,不是官网抄的。前提:日增原始文本 1TB,保留 30 天,机器是云上常规机型。

维度 Elasticsearch 8.x Loki 3.x ClickHouse 24.x
索引方式 倒排索引,全文检索强 只索引 label,正文只压缩不索引 列存 + 稀疏索引,标量聚合极强
压缩比(对原始文本) 1.5:1 ~ 3:1 8:1 ~ 15:1 10:1 ~ 20:1(ZSTD 默认级别)
1TB/天落盘占用 约 350~600GB 约 70~120GB 约 60~100GB
全文搜索 强 弱(靠 bloom filter 碰运气) 有,但要建 tokenbf_v1 索引,代价大
高基数耐受 中,mapping explosion 是真会炸 差,label 组合数上千就开始抖 强,宽表列基本不怕
最疼的地方 JVM 堆、refresh、段合并 没法真全文检索 没有原生采集管道,得自己写

补几个具体参数,都是我踩过或者帮人调过的:

Elasticsearch:index.refresh_interval 默认 1s,改成 30s,写入吞吐一般能涨 20% 到 40%,代价是最新数据有 30 秒延迟,对日志场景完全可接受。单个 shard 官方建议 10~50GB,我实际会控在 20~40GB,超过 50GB 的 shard 恢复一次能等到怀疑人生。bulk 请求 5~15MB 比较稳。

Loki:默认 max_query_series 是 5000,超了直接报 the query hit the max number of series limit。chunk_target_size 默认 1.5MiB,max_chunk_age 默认 1h,max_entries_limit_per_query 默认 5000 条。这几个值不改,出问题时你就会看到查询莫名其妙少数据。还有它的 label,千万别把 request_id、user_id 这种塞进去,label 组合数是它唯一的死穴。

图片

ClickHouse:index_granularity 默认 8192,日志表我一般建 ORDER BY (service, level, toStartOfHour(ts)),分区按天 PARTITION BY toDate(ts),超过 90 天 TTL ts + INTERVAL 90 DAY TO VOLUME 'cold'。压缩用 CODEC(ZSTD(3)) 就够,调到 9 收益递减得很厉害但 CPU 涨得明显。

我认为真正该做的,是把优化顺序倒过来

说个我自己总结的、可能不太主流的三步:

第一步,先删日志,不加机器。 生产环境的 DEBUG 级别日志直接不发,不是在采集端 drop——在采集端丢等于你已经付了 CPU 和带宽的钱。这一个动作,我在三个团队里做的效果分别是砍掉 62%、71%、58% 的量。改一行日志框架配置的事。

图片

第二步,采样,但只采非关键的。 用 OpenTelemetry Collector 的 tail_sampling,或者 Vector 的 remap,规则很简单:status_code >= 500 或 level=ERROR 保留 100%,WARN 保留 20%,INFO 用 probabilistic 采 1%~5%,DEBUG 0%。错误日志你永远不该采样,因为出事的时候你就是在找那一两条。

第三步,才轮到分层和换引擎。 热数据 7 天放 SSD,温数据 30 天放 HDD,90 天以上的丢对象存储,ES 用 ILM 自动滚,Loki 靠 retention_period 配合 S3,ClickHouse 走 TTL TO VOLUME。

顺序反过来做,你会在第一步就把成本砍掉一半以上,后面根本不用换引擎。

顺手提一个容易被忽略的迁移坑

采集端还有个时间炸弹:Promtail 已经在 2025 年 3 月 2 日 EOL 了,Grafana 官方推的是 Alloy。很多团队的 Loki 集群还在跑 Promtail,短期没事,但新特性和安全补丁就没有了。迁移不算难,配置要重写一遍,工作量大概 2 到 4 个人天。

图片

我去年迁过一个,14 个业务线、300 多台机器,坑主要在两处:一是原来 Promtail 用 pipeline_stages 做的解析,得改成 loki.process 里的 stage 语法;二是 static_configs 的通配符路径在 Alloy 里行为有细微差别,有几台机器的日志静默丢了三天才发现。所以迁完一定要对比新旧两边的 sum(rate({job=...}[5m])),差超过 5% 就得查。

最后说句可能不讨喜的

日志不是数据资产,是安全气囊。 它 99% 的时间不产生任何价值,但出事故的那 1% 你得靠它活命。所以优化目标从来不是「存得更多」,而是「在保住关键证据的前提下,用最低成本让那 1% 查得到」。

按这个标准,很多团队其实只需要:错误日志 100% 保留 90 天,INFO 采样 5% 保留 7 天,DEBUG 一条不留。这么配下来,日 1TB 的原始量能压到 80GB 左右,一台 8C16G 加 2TB 盘的机器就能扛。

至于选 ELK 还是 Loki 还是 ClickHouse——等你把上面这几步做完,再回头看这个问题,你会发现它已经不重要了。

🏷️ 标签: