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

Loki — 不索引日志的日志库

push 返回 204,查询却是空的

在 TT Lab 中继续学习

一句话总结

放进 Loki 的行先堆积在 ingester 的内存里,之后才作为数据块落到存储中。查询只会向 ingester 询问“最近几个小时”,所以用比这更早的时间戳放进去的行,会被接收,却看不到。

为什么需要它

有个团队正在做故障恢复。采集器停了两个小时,他们把这期间积压的文件恢复出来,推给了 Loki。push 全都返回了 204。可是在 Grafana 里打开那个时间段,什么也没有。

大家以为“push 在撒谎”。实际上,行已经好好地进了 ingester,只是查询没有去问ingester 那个区间而已。强制 flush,把数据块落到存储后,同样的查询就全部出来了。

工作原理

写入路径是这样的。

  1. push 到达后,ingester 把行追加到每个流打开着的数据块里。同样的内容也会写进 WAL,这样即使 ingester 挂了也能恢复。
  2. 数据块遇到三个条件之一就会关闭——达到目标大小(chunk_target_size),一段时间没有新行(chunk_idle_period),或者打开得太久(max_chunk_age)。
  3. 关闭的数据块上传到对象存储,索引里记录该流和时间范围。

读取路径有两条。查询会问存储,如果是最近的区间,为了那些还没落到存储的行,也会问 ingester。决定“最近”的是 querier.query_ingesters_within,默认值是 3 小时。如果区间的终点比这更早,查询就会跳过 ingester。

所以用过去的时间戳放进去的行,会掉进这两条路径之间的缝隙。存储里还没有,ingester 里有,却没有人去问。等到时间过去,数据块自然关闭并上传,就开始能看到了——于是就有了“过了很久自己出现了”的说法。

Loki 的写入路径从 push 经过 ingester 打开着的数据块再到对象存储,读取路径总是询问存储、只有在最近 3 小时以内才询问 ingester,因此用五小时前的时间戳放进去的行,会掉进两条路径之间的缝隙的位置

这个设计不是失误。询问 ingester 的代价很高,如果连很旧的区间每次都问,所有查询都会变慢。只是在事后补推的恢复作业里,这个假设就不成立了。

运维上要记住三点。第一,如果为了恢复把过去的数据推了进去,就要等待 flush,或者强制执行。第二,过旧的数据可能一开始就会被拒绝——reject_old_samples 和 reject_old_samples_max_age 就是这个旋钮(本实验的设置特意把它关掉了)。第三,这个 Pod 里的 Loki 是所有组件都在一个进程里的单一二进制模式。在生产里,ingester、querier、compactor 分开运行时,同样的原理会原样适用,只是调用 flush 的位置和要盯的指标不同。

在现场相遇的样子

最常见的症状是“仪表板上最近 15 分钟好好的,昨天那段却是空的”。最近的区间由 ingester 回答,过去的区间由存储回答,如果上传到存储的路被堵住了,恰好就是这个样子。对象存储凭证过期的事故里,总是这样出现。

第二种是 ingester 重启之后的空白区间。WAL 重放结束之前,那个区间看起来是空的,重放结束就补上了。所以如果在滚动 ingester 的部署过程中收到“日志不见了”的反馈,先等几分钟才是对的。

第三种是数据块大小调优。目标大小设得太小,对象就会多得数不清,索引和查询都会变慢;设得太大,ingester 的内存就会变大,重启时能丢掉的东西也变多。

下一项实验要做什么

向 Pod 里的 Loki 分别放入最近的数据和五小时前的数据,亲眼看到第二份被接收了,查询里却是空的。在服务器的 /config 里找到决定那条界线的设置并确认,用数字记录强制 flush 前后,存储里的数据块文件数和查询结果有什么变化。最后亲手放入一条六小时前的行,让它变得可见。