同样结果的两条查询,一条四分钟,一条两秒
目标
读取 Loki 响应附带的 stats.summary,测量查询实际读了多少行、多少字节,亲自确认什么能减少这些数字,什么不能。
为什么重要
Loki 的索引里只有标签组合和数据块的时间范围。正文不被索引。所以查询总是按同一个顺序运行——选择器选出要打开的数据块,时间区间只留下其中重叠的,把剩下的全部读出来应用过滤器。只有前两步决定读取量,后面是丢弃已经读进来的东西。不了解这个顺序,就会一边把行过滤器放前面、一边把 limit 调小,等着查询变快。在成本按字节计的存储里,这个差别就是账单。
步骤
- 在
/root/lk-filters中启动 Loki,把date +%s写入/root/lk-filters/anchor.txt,然后用python3 /opt/lab/d5/gen.py filters "$(cat anchor.txt)"放入数据。接着对{app=~"edge|cart|ship|batch"},以从基准时刻往前一小时的区间发出查询,把响应stats.summary中读取的行数和字节数,以processed=<정수>和bytes=<정수>(占位符为整数)两行写入/root/lk-filters/01-base.txt。 - 在同一个一小时区间里,把以四个流整体为对象、查找正文含有
SETTLE-LATE的行的查询写入/root/lk-filters/02-marker.logql,并把结果行数和读取行数,以lines=<정수>和processed=<정수>(占位符为整数)两行写入/root/lk-filters/02-marker.txt。 - 在给出同样答案(同样的行)的前提下,只缩小流选择器,把这个查询写入
/root/lk-filters/03-stream.logql,并把结果行数和读取行数,以lines=<정수>、processed=<정수>(占位符为整数)写入/root/lk-filters/03-stream.txt。再在/root/lk-filters/03-ratio.txt中以ratio=<소수 둘째 자리>(占位符为保留到小数点后第二位的数)一行,写下读取行数相对于第 2 步的比率。 - 用与第 3 步相同的查询,只把区间改为 30 分钟(从基准时刻往前 1800 秒)发出。把结果行数和读取行数,以
lines=<정수>、processed=<정수>(占位符为整数)写入/root/lk-filters/04-window.txt,并在/root/lk-filters/04-note.txt中写一个以tradeoff=开头的句子(去掉空格后至少 40 个字),用自己的话写出缩小区间时得到什么、失去什么。 - 在四个流整体、一小时区间里发出两个查询,比较读取的行数。一个是完全没有过滤器的
{app=~"edge|cart|ship|batch"},另一个是在其上再加两个行过滤器的查询。把结果写成四行存入/root/lk-filters/05-linefilter.txt——plain_processed=、plain_lines=、filtered_processed=、filtered_lines=。 - 对四个流整体、一小时区间,发出接上了
| logfmt | status="500"的查询,把三个数字写入/root/lk-filters/06-parser.txt——processed=<읽은 줄 수>、post_filter=<필터를 통과해 남은 줄 수>、lines=<결과 줄 수>(占位符依次为读取的行数、通过过滤器后剩下的行数、结果行数)。把这三个数字与第 1 步的基准线比较,看看什么相同、什么变了。 - 把到目前为止测得的内容整理成
/root/lk-filters/cost.tsv。不要表头,共四行,每行是用制表符分隔的三列<방법><탭><읽은줄수><탭><결과줄수>(占位符依次为方法、制表符、读取行数、制表符、结果行数)。方法名称依次为all(第 2 步)、stream(第 3 步)、window(第 4 步)、parser(第 6 步)。 - 在
/root/lk-filters/08-budget.logql中写一个查询。条件有三个——(1)以从基准时刻往前一小时的区间发出时,返回与第 2 步完全相同的行;(2)读取的行数在第 1 步基准线的三分之一以下;(3)不要依赖limit。再在/root/lk-filters/08-budget.txt中以processed=<정수>(占位符为整数)一行写下该查询读取的行数。
参考
- 工作目录是
/root/lk-filters。Loki 要在第 1 步里亲手启动。 - 数据生成器是
/opt/lab/d5/gen.py,使用filters数据。评分器不读这个文件。 - 统计用
curl ... | jq '.data.stats.summary'来看。本实验要看的项是totalLinesProcessed、totalBytesProcessed、totalPostFilterLines。execTime即使是同一个查询,每次运行也不同,所以不要用它作判断依据。 - 由正确答案生成的
cost.sh是方便使用的辅助工具。直接用curl发送也可以。 - 常见错误:用
since=1h来测量。时间一流逝,答案就变了,重新评分时会失败。 - 常见错误:变便宜的查询连答案也改变了,却没有察觉就放过了。每次降低成本,都要同时确认结果行数。
- LogQL 概述 · 日志查询 · 标签 · 架构 · HTTP API
放入四个流,测量基准线
在 /root/lk-filters 中启动 Loki,把 date +%s 写入 /root/lk-filters/anchor.txt,然后用 python3 /opt/lab/d5/gen.py filters "$(cat anchor.txt)" 放入数据。接着对 {app=~"edge|cart|ship|batch"},以从基准时刻往前一小时的区间发出查询,把响应 stats.summary 中读取的行数和字节数,以 processed=<정수> 和 bytes=<정수>(占位符为整数)两行写入 /root/lk-filters/01-base.txt。
统计在 query_range 响应的 data.stats.summary 里——用 jq '.data.stats.summary' 整个看一遍。是 totalLinesProcessed 和 totalBytesProcessed 这两项。这个数字就是本实验自始至终要比较的基准线。
为了找五行,读了多少行
在同一个一小时区间里,把以四个流整体为对象、查找正文含有 SETTLE-LATE 的行的查询写入 /root/lk-filters/02-marker.logql,并把结果行数和读取行数,以 lines=<정수> 和 processed=<정수>(占位符为整数)两行写入 /root/lk-filters/02-marker.txt。
一个行过滤器就够了。重要的不是答案,而是为得到答案读取的量——把两个数字并排放着看。也要确认读取的行数是否与第 1 步的基准线相同。
缩小选择器,读取量就会减少
在给出同样答案(同样的行)的前提下,只缩小流选择器,把这个查询写入 /root/lk-filters/03-stream.logql,并把结果行数和读取行数,以 lines=<정수>、processed=<정수>(占位符为整数)写入 /root/lk-filters/03-stream.txt。再在 /root/lk-filters/03-ratio.txt 中以 ratio=<소수 둘째 자리>(占位符为保留到小数点后第二位的数)一行,写下读取行数相对于第 2 步的比率。
标记出自哪个服务,看第 2 步结果的 stream 标签就知道。把选择器缩小成那一个。结果行数必须保持不变——不改变答案,只降低成本,这就是这一步的要点。比率是用第 3 步读取的行数除以第 2 步的值。
缩小区间,读取量会减少,答案也会减少
用与第 3 步相同的查询,只把区间改为 30 分钟(从基准时刻往前 1800 秒)发出。把结果行数和读取行数,以 lines=<정수>、processed=<정수>(占位符为整数)写入 /root/lk-filters/04-window.txt,并在 /root/lk-filters/04-note.txt 中写一个以 tradeoff= 开头的句子(去掉空格后至少 40 个字),用自己的话写出缩小区间时得到什么、失去什么。
查询语句保持不变,只改 start。读取的行数会减少一半左右,结果行数也一起减少——区间之外的答案根本看不到。想一想,在不知道事故时刻的情况下就先缩小区间,会有什么危险。
加上行过滤器,读取量也不变
在四个流整体、一小时区间里发出两个查询,比较读取的行数。一个是完全没有过滤器的 {app=~"edge|cart|ship|batch"},另一个是在其上再加两个行过滤器的查询。把结果写成四行存入 /root/lk-filters/05-linefilter.txt——plain_processed=、plain_lines=、filtered_processed=、filtered_lines=。
行过滤器用什么都行(例如 |= "level=error" 和 != "route=/a")。要看的是四个数字之间的关系——哪一对相同,哪一对不同。相同的一边是“读取量”,不同的一边是“剩下的量”。
接上解析器,读取量也不变
对四个流整体、一小时区间,发出接上了 | logfmt | status="500" 的查询,把三个数字写入 /root/lk-filters/06-parser.txt——processed=<읽은 줄 수>、post_filter=<필터를 통과해 남은 줄 수>、lines=<결과 줄 수>(占位符依次为读取的行数、通过过滤器后剩下的行数、结果行数)。把这三个数字与第 1 步的基准线比较,看看什么相同、什么变了。
totalPostFilterLines 是统计的第三项。cost.sh 不输出这一项,所以直接用 curl ... | jq '.data.stats.summary' 来看,或者修改辅助工具来用。解析器做的事比行过滤器更贵,但在读取量这个标准上,两者处于同一个位置。
应用 ① ——四种方法的成本表
把到目前为止测得的内容整理成 /root/lk-filters/cost.tsv。不要表头,共四行,每行是用制表符分隔的三列 <방법><탭><읽은줄수><탭><결과줄수>(占位符依次为方法、制表符、读取行数、制表符、结果行数)。方法名称依次为 all(第 2 步)、stream(第 3 步)、window(第 4 步)、parser(第 6 步)。
从前面步骤里做出的文件中取出值汇总即可。表做出来之后一眼就能看出来——读取行数真正减少的行有几个。
应用 ② ——在预算内给出同样答案的查询
在 /root/lk-filters/08-budget.logql 中写一个查询。条件有三个——(1)以从基准时刻往前一小时的区间发出时,返回与第 2 步完全相同的行;(2)读取的行数在第 1 步基准线的三分之一以下;(3)不要依赖 limit。再在 /root/lk-filters/08-budget.txt 中以 processed=<정수>(占位符为整数)一行写下该查询读取的行数。
减少读取量的旋钮只有两个。这一步不能改区间,所以要用剩下的那一个来解决。第 2 步结果的 stream 标签会告诉你答案。改完查询之后,一定要重新测一下结果行数是否保持不变——变便宜了,答案却变了,那就不是调优,而是事故。