行过滤器并不会减少读取量
一句话总结
Loki 查询的价格是按读取的字节数计的。而能减少读取量的只有流选择器和时间区间两样——行过滤器和解析器只是把已经读进来的行筛掉而已。
为什么需要它
凌晨调查支付错误的人,在 Grafana 里对一整天的区间发出了 {cluster="prod"} |= "payment" |= "timeout"。查询跑了 4 分钟就超时被切断。旁边的人把它换成 {cluster="prod", app="pay"},并把区间缩到 30 分钟,2 秒就出了答案。两个查询的结果是一样的。
差别在读取量。第一个查询把那个集群所有的流读了一整天,然后才过滤;第二个只读了一个流的 30 分钟。两个行过滤器连一个字节的读取量也没有减少。
工作原理
Loki 不为正文建立索引。索引里有的只是标签组合(流)以及该流的数据块处于哪个时间段。所以查询的执行顺序始终相同。
- 选择器查看索引,选出打开哪些流的哪些数据块。
- 时间区间只留下与之重叠的数据块。
- 把剩下的数据块全部读出来,依次应用行过滤器、解析器和标签过滤器。
只有第 1 步和第 2 步决定读取量。第 3 步是在丢弃已经读进来的东西。所以响应附带的统计中,totalLinesProcessed 和 totalBytesProcessed 只对选择器和区间有反应,只有 totalPostFilterLines 和返回的行数才对过滤器有反应。把这两组数分开来读,几乎就是调优 Loki 查询的全部方法。
这并不是说行过滤器没用。行过滤器比解析器便宜得多。解析器要逐行解析语法来生成标签,标签过滤器要比较这些标签。所以接解析器时,在前面放一个行过滤器、减少解析器要看的行数,仍然是划算的——只是这份收益来自 CPU,而不是读取的字节数。把两者混在一起说,就会卡在“行过滤器明明放在前面了,为什么没变快”上。
limit 不是可靠的成本旋钮。在简单的日志查询里,Loki 收集够需要的行后可以提前停止,读取量有时会减少。不过能减少多少,取决于查询被切成几片并行运行,所以同一个查询在同一个区间发两次,读取的行数也会不同(在本实验环境里确实如此)。所以“把 limit 调小来节省成本”这个计划是制定不出来的,对指标查询,limit 根本不适用。
标签设计最终会变成查询性能,原因就在这里。没有 app 标签,就根本没有办法缩小范围;反过来,把 user_id 这样的东西设为标签,流就会爆炸,连索引查询本身都变贵。只在能缩小范围的程度上用标签,其余放在正文——这个平衡是 Loki 运维的核心。
在现场相遇的样子
成本事故大多出在仪表板上。人手工发出的查询一天只有几次,而仪表板面板每 30 秒自动运行一次。一个选择器宽松的面板,一个月就能读几十 TB。所以做仪表板时,要给每个面板打一次统计,读取量大的面板,要缩小选择器或缩短默认区间。
还有一点。人们调查时有把区间设得很宽的习惯。“不知道是什么时候,先来一整天”。但事故时刻通常已经从指标或告警里知道了。改成先在指标里把时刻钉死,日志只看它前后 15 分钟,同样的调查会快十倍。
下一项实验要做什么
把四个流的 2400 行放进 Pod 里的 Loki,用四种方式发出查询,找出偶然混入的一行标记。每次都读取响应里的 stats.summary,记录读取的行数和字节数,做出表格,看出只有缩小选择器和缩小区间时这些数字才会减少,最后亲手写出一个在规定预算内给出同样答案的查询并提交。