选择不做索引
一句话总结
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, 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"}))
这个值大,说明要么标签选择器还要再缩小,要么标签设计从一开始就错了。要修改的不是查询调优,而是采集配置。