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

PCA — Prometheus 认证助理

仪表盘上的 p90 是 0.48 秒,日志里是 0.42 秒

在 TT Lab 中继续学习

目标

用桶边界不同的两个版本聚合同样的请求,亲自测量 histogram_quantile 的估算误差,确认丢掉 le 的表达式、混用版本的表达式、去掉 rate 的表达式分别会得出什么数字,然后设计符合 SLO 的桶。

为什么重要

直方图只用桶的计数来记住样本。因此分位数是假定桶内样本均匀分布后做线性插值的结果,误差的大小由排名所落入的那个桶的宽度决定。落在边界之外的尾部则完全看不到。只有能够判断仪表板上的数字与日志不一致时,该怀疑查询还是该怀疑桶设计,才能信任 SLO。

已准备的环境

python3 /opt/fixtures/pca_histogram_lab.py init 会在 /root/pca-histogram/ 中放入 v2 Pod 的请求日志(access-log-v2.csv)和 README。时间序列是 http_request_duration_seconds_bucket/_count/_sum,标签有 route(/checkout、/search)、version(v1、v2)和 le。v1 的边界是 0.1、0.5、1,v2 的边界是 0.05、0.1、0.2、0.3、0.4、0.5、1、2.5。从第 0 分钟到第 60 分钟,每分钟抓取一次,当前时刻是 60m。

python3 /opt/fixtures/pca_histogram_lab.py eval -f 파일 --at 60m(占位符为文件名)使用 lab-k8s 镜像中 promtool 3.0.1 的 PromQL 引擎对表达式求值。不会启动 Prometheus 服务器,评分器也用同一个引擎重新计算你的表达式和基准表达式,并与你写下的数字核对。

步骤

  1. 从 /root/pca-histogram/access-log-v2.csv 中选出 /checkout 第 56 分钟到第 60 分钟的 100 个请求,排序后取第 ceil(q×N) 个值(nearest-rank)来求 p90 和 p99。在 /root/pca-histogram/01-raw.txt 中用 raw_p90= 和 raw_p99= 两行(以秒为单位)写下。如果没有材料,先运行 python3 /opt/fixtures/pca_histogram_lab.py init。
  2. 在 /root/pca-histogram/02-v1-p90.promql 中写出 version="v1"、route="/checkout" 最近 5 分钟的 p90 表达式(使用 histogram_quantile(0.9, ...)、rate(...[5m]),并保留 le)。用 python3 /opt/fixtures/pca_histogram_lab.py eval -f /root/pca-histogram/02-v1-p90.promql --at 60m 查看结果,并在 /root/pca-histogram/02-v1.txt 中写下 v1_p90= 和 error=(v1_p90 减去 raw_p90 的值)。
  3. 在 /root/pca-histogram/03-v2-p90.promql 中用 version="v2" 写出同样的表达式并确认结果。在 /root/pca-histogram/03-v2.txt 中写下 v2_p90=、error=(v2_p90 减去 raw_p90 的值)和 closer=(更接近日志 p90 的版本,v1 或 v2)。
  4. 用 eval 确认 v1、v2 两个版本 /checkout 最近 5 分钟的 p99,并在 /root/pca-histogram/04-slow.promql 中写出表达式:用 increase(..._bucket{le="+Inf"}[5m]) 减去 le="1" 桶,得到 v1 /checkout 最近 5 分钟内超过 1 秒的请求数(两者的 le 值不同,所以需要匹配条件)。在 /root/pca-histogram/04-p99.txt 中写下 v1_p99=、v2_p99= 和 slow_requests=。
  5. 在 /root/pca-histogram/05-broken.promql 中写出用 sum by (route) 丢掉 le 的 v2 p90 表达式,在 /root/pca-histogram/05-fixed.promql 中写出同时保留 le 和 route 的 v2 p90 表达式。对这两个表达式执行 eval,并在 /root/pca-histogram/05-le.txt 中写下 broken_series=(结果时间序列的数量)、fixed_series= 和 search_p90=(/search 的值)。
  6. 金丝雀(Canary)发布期间,仪表板不区分 version,而是把 /checkout 的桶合并在一起。在 /root/pca-histogram/06-mixed.promql 中写出不选 version、只选 route="/checkout" 的 p90 表达式,并用 count by (le) (http_request_duration_seconds_bucket{route="/checkout"}) 统计 le 值有多少种。在 /root/pca-histogram/06-mixed.txt 中写下 mixed_p90= 和 distinct_le=。
  7. 在 /root/pca-histogram/07-lifetime.promql 中写出不使用 rate、直接把 v2 /checkout 的累积桶相加的 p90 表达式。在 /root/pca-histogram/07-lifetime.txt 中写下 lifetime_p90= 和 window_p90=(第 3 步中最近 5 分钟的 v2 p90),比较第 31 分钟开始变慢这件事在哪一边更容易看出来。
  8. 在 /root/pca-histogram/08-buckets.txt 中用一行写出新的桶边界(升序,有限边界不超过 10 个,不写 +Inf)。条件有三个——包含 SLO 边界 0.3,最大的有限边界在最大延迟 1.8 秒以上,与日志 p90 的误差在 0.01 以下。运行 python3 /opt/fixtures/pca_histogram_lab.py design 查看用新边界重新聚合同样请求的结果,并在 /root/pca-histogram/08-design.txt 中写下 designed_p90= 和 under_300ms_ratio=。

参考

从日志中数出真正的 p90、p99

从 /root/pca-histogram/access-log-v2.csv 中选出 /checkout 第 56 分钟到第 60 分钟的 100 个请求,排序后取第 ceil(q×N) 个值(nearest-rank)来求 p90 和 p99。在 /root/pca-histogram/01-raw.txt 中用 raw_p90= 和 raw_p99= 两行(以秒为单位)写下。如果没有材料,先运行 python3 /opt/fixtures/pca_histogram_lab.py init。

使用线性插值的工具(如 numpy 的默认值)会返回两个样本之间的值。这里要选出与排名对应的那一个实际样本。

粗桶(v1)给出的 p90

在 /root/pca-histogram/02-v1-p90.promql 中写出 version="v1"、route="/checkout" 最近 5 分钟的 p90 表达式(使用 histogram_quantile(0.9, ...)、rate(...[5m]),并保留 le)。用 python3 /opt/fixtures/pca_histogram_lab.py eval -f /root/pca-histogram/02-v1-p90.promql --at 60m 查看结果,并在 /root/pca-histogram/02-v1.txt 中写下 v1_p90= 和 error=(v1_p90 减去 raw_p90 的值)。

分位数在桶内做线性插值。v1 在 0.1 和 0.5 之间没有边界,所以会假定整个区间内的样本均匀分布。

与细桶(v2)比较

在 /root/pca-histogram/03-v2-p90.promql 中用 version="v2" 写出同样的表达式并确认结果。在 /root/pca-histogram/03-v2.txt 中写下 v2_p90=、error=(v2_p90 减去 raw_p90 的值)和 closer=(更接近日志 p90 的版本,v1 或 v2)。

v2 在 0.4 和 0.5 处有边界。排名所落入的桶越窄,插值误差的上限就越小。

被困在最后一个有限边界上的 p99

用 eval 确认 v1、v2 两个版本 /checkout 最近 5 分钟的 p99,并在 /root/pca-histogram/04-slow.promql 中写出表达式:用 increase(..._bucket{le="+Inf"}[5m]) 减去 le="1" 桶,得到 v1 /checkout 最近 5 分钟内超过 1 秒的请求数(两者的 le 值不同,所以需要匹配条件)。在 /root/pca-histogram/04-p99.txt 中写下 v1_p99=、v2_p99= 和 slow_requests=。

如果排名落在 +Inf 桶上,histogram_quantile 会返回最大的有限边界。两条桶时间序列只有 le 标签不同,所以需要 ignoring(le)。

修复丢掉 le 的仪表板

在 /root/pca-histogram/05-broken.promql 中写出用 sum by (route) 丢掉 le 的 v2 p90 表达式,在 /root/pca-histogram/05-fixed.promql 中写出同时保留 le 和 route 的 v2 p90 表达式。对这两个表达式执行 eval,并在 /root/pca-histogram/05-le.txt 中写下 broken_series=(结果时间序列的数量)、fixed_series= 和 search_p90=(/search 的值)。

histogram_quantile 只会把带有 le 标签的时间序列当作桶来读取。没有报错却得到空结果,是最危险的形式。

合并边界不同的两个版本

金丝雀(Canary)发布期间,仪表板不区分 version,而是把 /checkout 的桶合并在一起。在 /root/pca-histogram/06-mixed.promql 中写出不选 version、只选 route="/checkout" 的 p90 表达式,并用 count by (le) (http_request_duration_seconds_bucket{route="/checkout"}) 统计 le 值有多少种。在 /root/pca-histogram/06-mixed.txt 中写下 mixed_p90= 和 distinct_le=。

合并结果的 le 是两个版本边界的并集。在只有一边才有的边界处,累积计数会被扭曲,引擎只能强行让它保持单调。

不用 rate 直接读取累积桶

在 /root/pca-histogram/07-lifetime.promql 中写出不使用 rate、直接把 v2 /checkout 的累积桶相加的 p90 表达式。在 /root/pca-histogram/07-lifetime.txt 中写下 lifetime_p90= 和 window_p90=(第 3 步中最近 5 分钟的 v2 p90),比较第 31 分钟开始变慢这件事在哪一边更容易看出来。

计数器的累积值包含进程启动以来的全部请求。变慢之前的 30 分钟混进来,就会稀释最近的变化。

按 SLO 重新设计桶

在 /root/pca-histogram/08-buckets.txt 中用一行写出新的桶边界(升序,有限边界不超过 10 个,不写 +Inf)。条件有三个——包含 SLO 边界 0.3,最大的有限边界在最大延迟 1.8 秒以上,与日志 p90 的误差在 0.01 以下。运行 python3 /opt/fixtures/pca_histogram_lab.py design 查看用新边界重新聚合同样请求的结果,并在 /root/pca-histogram/08-design.txt 中写下 designed_p90= 和 under_300ms_ratio=。

误差来自排名所落入的桶的宽度。不必处处加密,只需把 p90 所在的区间和 SLO 边界收窄即可。