我把 ELK 日志栈切到 VictoriaLogs 后,存储成本降了 70%:轻量可观测性改造实录

我把 ELK 日志栈切到 VictoriaLogs 后,存储成本降了 70%:轻量可观测性改造实录

说实话,我早就看我们的 ELK 堆栈不顺眼了。

倒不是 Elasticsearch 不好用,而是它越来越像一个“吞金兽”。三节点数据、一个主节点、Logstash、Kibana,光是热数据盘就挂了 45TB SSD。每天 1.5TB 日志进来,30 天保留期一卡,成本每个月都在账单上跳舞。更要命的是,某些全字段检索的查询 P99 能跑到 8 秒以上,on-call 同学一边查日志一边骂。

上个月我们把 80% 的日志流切到了 VictoriaLogs。迁移后单块存储降了 70%,集群节点从 4 台变成 1 台二进制进程,查询延迟稳了很多。这篇文章把整个过程、踩的坑和留下的 checklist 记下来,给同样被 ELK 账单压得喘不过气的同学参考。

背景:ELK 为什么成了成本黑洞

我们的日志链路比较标准:

  • 业务服务 → Fluent Bit → Logstash → Elasticsearch
  • Kibana 做查询和看板
  • 3 个数据节点,每个 32C128G,挂载 15TB 高性能云盘
  • 1 个专用主节点 + 1 个 Logstash 节点

每天大约 1.5TB 原始日志,保留 30 天。看起来中规中矩,但几个隐藏成本很扎心:

  1. 倒排索引占空间:为了支持全文检索,ES 对几乎所有字段建索引,磁盘占用经常是原始日志的 1.5 倍以上。
  2. JVM 内存贵:ES 是 Java 堆大户,数据节点堆内存配到 64GB 还经常被 GC 警告。
  3. 分片和节点规划麻烦:日志量一涨,就要考虑 rollover、索引模板、冷热分层,运维成本持续增加。
  4. 查询慢:业务同学爱用message:*timeout*这种 wildcard 搜索,数据节点 CPU 直接拉满。

我们当时的念头很简单:能不能用更轻量的方案,只保留我们最需要的日志检索能力?

选型:为什么不是 Loki,而是 VictoriaLogs

其实 Loki 也考虑过。但 Loki 的 label 设计对我们的日志格式要求比较严格,而且当时我们没有 Grafana 全栈,迁移成本不低。后来同事推荐了 VictoriaLogs,我试了一周,发现它有几个特别对我们胃口的地方:

  • 单二进制:一个可执行文件,没有 JVM、没有 ZooKeeper、没有复杂集群角色,部署简单到离谱。
  • 压缩比高:它采用列式存储 + 轻量索引,存储占用大概只有 Elasticsearch 的 20%-30%。
  • 生态兼容:Fluent Bit、Promtail、Vector 都能直接发数据;Grafana 有官方数据源插件。
  • 查询语言接近 LogQLLogsQL上手很快,团队基本不用培训就能写。

当然也有取舍:复杂全文检索、聚合、嵌套文档支持不如 ES。但我们的日志场景主要是按服务、环境、日志级别过滤,然后看时间线和关键字,VictoriaLogs 完全够用。

迁移:三步走,没有停服

第一步:拉起 VictoriaLogs 实例

我们用 Docker Compose 部署,配置极简:

services:victorialogs:image:victoriametrics/victoria-logs:v1.5.0-victorialogscontainer_name:victorialogscommand:-"--storageDataPath=/vlogs"-"--retentionPeriod=30d"-"--httpListenAddr=:9428"-"--memory.allowedPercent=60"ports:-"9428:9428"volumes:-/data/vlogs:/vlogsrestart:unless-stopped

相比 ES 的一堆 JVM 参数和角色配置,这个启动几乎可以说是“解压即用”。我们把数据目录挂到了一块成本更低的 SATA 盘上,准备用它扛主要日志流。

第二步:让 Fluent Bit 双写

不想停服,所以先让 Fluent Bit双写:一份继续给 ES,一份给 VictoriaLogs。等 VictoriaLogs 的查询和看板稳定后,再逐步关闭 ES 链路。

[OUTPUT] Name http Match app.* Host victorialogs Port 9428 URI /insert/jsonline Format json_lines Header Content-Type application/json Header AccountID 0 Header ProjectID 0 json_date_key timestamp json_date_format iso8601

这里有几个细节:

  • json_date_format一定要统一成iso8601,我们刚开始用毫秒时间戳,VictoriaLogs 解析后时间戳乱了,查了半天。
  • 默认按AccountIDProjectID做多租户隔离,header 必须带,否则数据会进默认租户。
  • Match规则建议先按app.*分批切,不要一次性全切,方便回滚。

第三步:Grafana 数据源和查询改造

Grafana 装VictoriaLogs数据源后,之前的 Kibana 看板可以逐步迁。最常用的是下面两类查询。

按服务查最近 ERROR 日志:

service:order-service AND level:ERROR AND _time:1h

按状态码统计失败请求:

service:order-service AND status_code:>=500 AND _time:30m | stats by (status_code) count()

做趋势聚合:

service:payment-service AND level:ERROR | stats by (_time:1m) count()

LogsQL 的语法和 LogQL 很像,业务同学基本 copy-paste 改改字段名就能用。唯一要适应的是没有 Kibana 那种点选式界面,一开始有些人不习惯,但写了三天 queries 后都真香了。

效果:账单和延迟都变了

迁移完成一个月后,我们对比了主要指标:

指标迁移前 ELK迁移后 VictoriaLogs
30 天日志存储占用45TB13TB
数据节点数3 台 32C128G1 台 8C32G
全文 wildcard 查询 P998-12s1-3s
按 stream 字段过滤查询2-5s200-500ms
月度存储+计算成本基准 100%约 30%

存储成本降了大约 70%,主要来自三点:

  1. VictoriaLogs 的列式压缩和轻量索引让磁盘占用大幅下降。
  2. 单节点即可顶住我们的日志量,省了两台数据节点的计算成本。
  3. 不需要为全文检索预留大量 JVM 堆内存,内存成本也少了。

当然,这个 70% 是基于我们保留 30 天、以结构化日志为主的场景。如果你的日志全是非结构化文本、需要复杂聚合,数字会不一样。

踩过的 4 个坑

坑 1:高基数字段当 stream 字段,内存原地爆炸

VictoriaLogs 的stream字段(类似 Loki 的 label)会参与索引。如果直接把trace_idrequest_id这种每条日志都不同的字段当 stream 字段,内存和查询性能会炸。

我们的做法:只把serviceenvlevelhost作为 stream 字段,高基数字段保留在_msg里用 LogsQL 过滤。

service:order-service AND _msg:trace_id="abc123" AND _time:1h

坑 2:时间戳格式不一致导致日志重复或缺失

Fluent Bit 默认json_date_formatdouble,VictoriaLogs 期望的是秒级 Unix 时间戳或 ISO8601。我们刚开始没统一,结果看到同一条日志出现了两次,或者某段时间完全空白。

统一改成iso8601后问题解决。这个配置建议在迁移前就定好,否则后面洗数据很痛苦。

坑 3:Kibana 的某些聚合看板没法直接平迁

VictoriaLogs 不支持 ES 那种多层嵌套聚合和复杂的bucket_script。我们有几个 Kibana 看板需要重新设计,改成预聚合指标(用 VictoriaMetrics 或自定义 exporter)+ LogsQL 简单统计的方式。

经验是:不要试图 1:1 复制 Kibana,先把最常用的看板迁了,其他的慢慢改。

坑 4:多租户隔离 header 忘记加,团队数据串了

VictoriaLogs 用AccountIDProjectIDheader 做租户隔离。我们测试环境忘了加 header,结果测试数据混进了生产租户。虽然后来清理了,但最好在一开始就通过 Nginx 统一注入 header,避免各个客户端自己配置。

location /insert/ { proxy_pass http://victorialogs:9428; proxy_set_header AccountID $account_id; proxy_set_header ProjectID $project_id; }

写在最后

VictoriaLogs 不是银弹。它牺牲了部分全文检索和复杂聚合能力,换来了极低的存储和运维成本。如果你的日志场景以“按服务/级别/时间过滤 + 关键字检索”为主,它很可能是比 ELK 更划算的解法。

我们现在的策略是:

  • 80% 的常规应用日志进 VictoriaLogs;
  • 需要复杂全文检索和安全审计的日志继续留在 Elasticsearch;
  • 所有新服务默认对接 VictoriaLogs,老服务逐步迁移。

如果你也在被 ELK 账单追着跑,不妨先找一台闲置机器,拿一天的日志做下对比测试。数据不会骗人。


参考资料:

  • VictoriaLogs 官方文档:https://docs.victoriametrics.com/victorialogs/
  • Fluent Bit HTTP 输出插件:https://docs.fluentbit.io/manual/pipeline/outputs/http
  • Grafana VictoriaLogs 数据源:https://docs.victoriametrics.com/victorialogs/grafana/