被悄悄截断的结果最危险
一句话总结
limit 会悄悄地截断答案,而服务器的限额会让查询被 400 拒绝。毁掉事故调查的,总是悄悄的那一种。
为什么需要它
一个在找事故原因的人,对两小时区间发出了 {app="gateway"} |= "timeout"。结果是 1000 行,他把最早一行的时刻报告为事故开始。后来发现,实际的开始比它还早 40 分钟。
limit 的默认值是 1000,方向是 backward。也就是说,只返回了最近的 1000 行,比这更早的行没有出现在画面上。响应的任何地方都没有“被截断了”的标记。返回的行数恰好等于 limit,这一点是唯一的线索。
工作原理
决定日志查询结果数量的有三样。
| 旋钮 | 性质 | 超过时 |
|---|---|---|
请求的 limit |
由客户端选择 | 悄悄被截断 |
limits_config.max_entries_limit_per_query |
服务器定的上限 | 以 400 拒绝 |
limits_config.max_query_length |
区间长度上限 | 以 400 拒绝 |
direction 决定哪些会被截掉。backward(默认)从最近的开始填,所以旧的行被截掉,forward 则相反。找事故的开始时,如果原样使用默认值,被截掉的恰好就是想要找的东西。
查询还会按 split_queries_by_interval 被切成时间分片并行运行。响应统计中的 splits 就是这个分片数。分片多,结束得更快,但调度器和 querier 会承受更多负载。反过来,分片为 0,说明是作为一个分片运行的。
所以需要扫描很宽的区间时,正确的方法不是把 limit 调大。而是把区间切开,问很多次再合并。如果每个分片的结果数都小于 limit,就能保证那个分片是完整的,合计也就可信。自动化脚本几乎总是应该是这个样子。
提高限额成为答案的情况很少见。把 max_entries_limit_per_query 调大,querier 的内存就会变大,一个人的查询就可能撼动整个集群。限额是防止事故的安全装置,不是阻碍。
在现场相遇的样子
第一,“返回的行数 == limit”时,一律怀疑。如果是自动化,就要明确检查这个条件并发出警告。如果是给人用的仪表板,光是把 limit 写在面板标题里,误解就会大大减少。
第二,找调查开始时刻时,要用 direction=forward。这一个词,就会改变报告里的时间线。
第三,400 响应的正文通常很友好——连超过了哪个限额、超过了多少,都会用数字写出来。如果自动化丢掉这个正文,只在日志里留下状态码,以后找原因时就得手工把同样的查询再发一遍。
下一项实验要做什么
放入跨度两小时的数据,用数字确认 limit 给得小时会悄悄截断。故意越过服务器限额,亲手收到 400 及其正文,区间长度限额也用同样的方法确认。测量 splits 随区间如何变化,最后写一个在限额之内把区间切开、不遗漏地数出两小时数据的脚本。