TT Lab
开始
学习 学习路径 课程

Loki — 不索引日志的日志库

选择不做索引

在 TT Lab 中继续学习

一句话总结

Loki 不为日志正文建立索引,只为标签集建立索引。所以存储便宜,代价是搜索变成“先用标签缩小候选范围,再扫描正文”的方式。

为什么需要它

Elasticsearch/OpenSearch 会把日志的每一个词元都放进倒排索引。所以能立即找到“包含 error 这个词的日志”。代价很大——索引比原始数据还大是常有的事,建立索引这项工作本身就要消耗大量 CPU 和内存。

Loki 换了一种问法。“我们找日志的时候,实际是怎么做的?”

通常是这样。先把范围缩小到“支付服务的、生产环境的、最近 30 分钟”,然后在这个范围内查找字符串。前面的缩小只需要几个标签,后面的扫描只要范围小,就很快。

所以 Loki 只为标签建立索引,正文则压缩后存成数据块(chunk)。采用与 Prometheus 相同的标签模型也是有意为之——在指标里发现异常之后,可以沿用相同的标签跳转到日志。

工作原理

{namespace="labhub-prod", app="labhub"} |= "error" | json | status >= 500
 ^--------- 라벨 셀렉터 (색인됨) -------^  ^--- 본문 필터 (훑음) ---^

前面的花括号选出流。一个标签组合就是一个流。后面的管道依次扫描这些流的正文。

性能完全取决于前面缩小了多少。标签选择器选出 10 个流就很快,选出 10 万个就很慢。

基数——在 Loki 中更致命

Prometheus 中标签基数是个问题,这已经众所周知。在 Loki 里情况更糟。

每一个标签组合都会产生单独的流和单独的数据块文件。把 trace_id 或 user_id 设为标签,流就会达到数百万个,每个流各自产生一个很小的数据块文件。存储被小文件铺满,查询必须把这些文件全部打开。

标签为 namespace、app、level 三个时有四个流,每个数据块都是写满的整块;而再加上 trace_id 作为标签,每一行日志就会产生一个流,产生与行数一样多的小数据块文件,查询必须把这些文件全部打开的样子

规则:标签只用取值种类少、不常变化的。

适合作标签 不适合作标签
namespace, app, pod, level, env trace_id, user_id, request_id, ip, 时间戳

不适合的那些留在正文里,需要时用 |= 或 | json 去找。这就是 Loki 的设计意图。

与 OpenSearch 的分界

问题 选哪一边
“最近 30 分钟这个 Pod 的错误” Loki——能用标签缩小,范围也小
“最近 3 个月全部日志中的这个订单号” OpenSearch——没有可缩小的标签,范围又大
“按错误消息统计频次前 20” OpenSearch——需要聚合
“指标里看到的异常时刻的日志” Loki——与 Prometheus 的标签相同

两者不是竞争关系,而是回答不同的问题。实际上很多地方两者都部署——用 Loki 以低成本保存近期的,用 OpenSearch 让较旧的可供调查。

常见误解

“Loki 一定便宜”——标签贴错了,可能比 OpenSearch 还糟。流爆炸也很难挽回(旧的数据块还留在那里)。

“grep 就够了”——日志分散在多个节点上,Pod 一死就消失了。集中收集的原因不是搜索,而是保存。

该留下什么、该丢掉什么

日志是三种信号中生成得最便宜、保管得最昂贵的。所以如果在采集阶段不做出减少量的决定,无论是成本还是搜索速度,迟早会出问题。

先从可以丢掉的丢起。每个正常请求都留下的访问日志,通常可以用指标替代。每秒请求数和状态码分布,指标回答起来便宜得多,所以日志最好集中在偏离正常流程的内容上。像健康检查和就绪状态检查这样每秒打印好几次的内容,尤其是最先要过滤掉的候选。

相同的行重复出现时就合并。陷入重试循环的组件每秒喷出几百行是常有的事,这些行的内容全都一样。在应用一侧数一下相同消息的重复次数,汇总成一行,量会减少好几个数量级。

把保留期限分成等级。与其全部保留 90 天,不如大部分保留得短、只有需要审计的保留得长,要便宜得多。不过,哪个属于哪个等级必须能用标签区分,这就与前面看到的标签设计衔接上了。

而且日志的格式,就是以后的搜索成本。以结构化形式留下,解析一次就完成,还能用条件缩小范围;但如果只留下便于人阅读的句子,每次都得扫描字符串。事先确定一行里放什么,在这里同样是答案,前面讲跟踪时提到的四项——请求标识符、部署版本、处理时间、失败时的对象名称——在日志里同样有效。

最后,必须至少检查一次日志是否含有个人信息。如果某处有代码把请求体整个留下,那份日志整体就成了另一个等级的资产,保留期限和访问权限都要相应地重新确定。

实际工作中真正重要的事

Loki 查询慢的时候,首先要看的是被选中的流数量。

count(count by (pod) ({namespace="labhub-prod"}))

这个值大,说明要么标签选择器还要再缩小,要么标签设计从一开始就错了。要修改的不是查询调优,而是采集配置。