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

可观测性

多加了一个标签,时间序列翻了一百倍

在 TT Lab 中继续学习

目标

测量当前的时间序列都用在了什么上、用了多少,先预测加上标签之后会变成多少,再真正启动来确认,并用检查脚本固定预算上限,决定保留什么、舍弃什么。

为什么重要

基数是在出事故之前没有人去看的成本。增加一个标签只是一行代码,但如果该标签的值随着用户数而增长,时间序列就会成倍增加,存储、内存和查询时间也随之一起增加。而且这种成本表现为“变慢了”,很难找到原因。所以要养成两个习惯——增加标签之前,先把组合数乘一乘;以及把每个指标的时间序列上限固定成检查,让流水线而不是人来拦截。决定舍弃什么时的标准是“这个标签用来做什么决定”,同时还要确认被舍弃的维度所回答的问题,能不能由日志或跟踪来代替回答。

步骤

  1. 创建 /root/obs-cardinality-budget/inventory.tsv。job="shop-api" 暴露的每个指标各有多少条时间序列,按 <지표이름> <시계열 수>(占位符依次为指标名称、时间序列数)两个字段,按数量从多到少全部写下来(共五行)。之所以不看整个 Pod,而是收窄到这一个 job,是因为 Prometheus 自身产生的指标在实验过程中也会不断增加,使基准不稳定。
  2. 创建 /root/obs-cardinality-budget/labels.tsv。共三行,每行是 <라벨이름> <서로 다른 값의 수>(占位符依次为标签名称、不同取值的数量)。le 和 handler 从直方图指标 http_request_duration_seconds_bucket 中统计,status 从计数器 http_requests_total 中统计(两者的 job 都是 shop-api)。请按数量从多到少写。然后在 /root/obs-cardinality-budget/02-note.txt 中写两行:top_label=<1위 라벨 이름>(占位符为排名第一的标签名称)和 product=<le 값 수 × handler 값 수>(占位符为 le 取值个数乘以 handler 取值个数)。
  3. 在 /root/obs-cardinality-budget/cost.txt 中写三行。series= 是 job="shop-api" 的时间序列数,samples_per_day= 是这些时间序列每天留下的样本数,bytes_per_day= 是这些样本占用的字节数。这个 Pod 的抓取间隔是 15 秒,每个样本按平均 2 字节计算(Prometheus 存储文档所说的 1 到 2 字节之间偏保守的一侧)。这三个值必须能相互乘法对上。
  4. 准备暴露一个新指标 checkout_requests_total。标签有三个:region(ap1、ap2、us1、eu1)、tier(free、pro、team)、endpoint(cart、pay、refund、ship、track)。在 /root/obs-cardinality-budget/forecast.txt 中写两行 formula= 和 predicted_series=。formula 是乘法算式(例如 2*3*4),predicted_series 是它的结果。现在先不要启动任何东西。
  5. 创建 /root/obs-cardinality-budget/exporter.py。传入参数 --print 时,把暴露文本打印到标准输出后结束;没有参数时,在 127.0.0.1:9102 上提供 /metrics。对第 4 步三个标签的每种组合,各输出一行 checkout_requests_total。如果环境变量 USERS 大于 0,就附加 user_id 标签。然后在 /etc/prometheus/prometheus.yml 中加入名为 cardinality-lab 的抓取 job,并让配置重新加载。第一次抓取结束后,在 /root/obs-cardinality-budget/measured.txt 中写一行 measured_series=<수>(占位符为数量)。
  6. 用 USERS=100 重新启动 exporter,附加 user_id 标签。在 /root/obs-cardinality-budget/explode.tsv 中写两行。每行是以制表符分隔的三个字段 <이름> <시계열 수> <하루 바이트>(占位符依次为名称、时间序列数、每日字节数),第一行的名称是 before(没有 user_id),第二行是 after(包含 user_id)。每日字节数与第 3 步采用同样的方式计算(时间序列 × 5760 × 2)。
  7. 创建 /root/obs-cardinality-budget/budget.sh。以 bash budget.sh <상한표>(占位符为上限表)调用时,对上限表的每一行(<지표이름> <최대 시계열 수>,占位符依次为指标名称、最大时间序列数),统计当前的时间序列数,逐行输出 <지표이름> <지금> <상한> OK(占位符依次为指标名称、当前值、上限)或 ... OVER,只要有一个超过上限,就以退出码 1 结束,否则以 0 结束。然后在 /root/obs-cardinality-budget/caps.tsv 中把 checkout_requests_total 的上限写为 500,用这张表运行一次,并把输出保存到 /root/obs-cardinality-budget/gate-out.txt。
  8. 时间序列上限是 500。在 /root/obs-cardinality-budget/decision.txt 中写四行。keep= 写要保留的标签名称,以逗号分隔(例如 region,tier),drop= 写要舍弃的标签名称,以逗号分隔,projected_series= 写只用保留的标签算出的时间序列数,lost_question= 用至少 30 个字符写出舍弃这些标签之后再也无法回答的问题。标签的取值个数是 region 4、tier 3、endpoint 5、user_id 100。

参考

统计一个服务的时间序列都用在了哪里

创建 /root/obs-cardinality-budget/inventory.tsv。job="shop-api" 暴露的每个指标各有多少条时间序列,按 <지표이름> <시계열 수>(占位符依次为指标名称、时间序列数)两个字段,按数量从多到少全部写下来(共五行)。之所以不看整个 Pod,而是收窄到这一个 job,是因为 Prometheus 自身产生的指标在实验过程中也会不断增加,使基准不稳定。

发出 count by (__name__)(last_over_time({job="shop-api"}[1m])),就能得到每个指标的时间序列数。你会立刻看到,一个直方图会产生与桶数相同数量的时间序列。

哪个标签产生的值最多

创建 /root/obs-cardinality-budget/labels.tsv。共三行,每行是 <라벨이름> <서로 다른 값의 수>(占位符依次为标签名称、不同取值的数量)。le 和 handler 从直方图指标 http_request_duration_seconds_bucket 中统计,status 从计数器 http_requests_total 中统计(两者的 job 都是 shop-api)。请按数量从多到少写。然后在 /root/obs-cardinality-budget/02-note.txt 中写两行:top_label=<1위 라벨 이름>(占位符为排名第一的标签名称)和 product=<le 값 수 × handler 값 수>(占位符为 le 取值个数乘以 handler 取值个数)。

某个标签有多少个值,用 count(count by (<라벨>) (...))(占位符为标签)来统计。如果只想看当前存活的时间序列,请用 last_over_time(<선택자>[1m])(占位符为选择器)包起来——否则连已经回填的历史时间序列也会一起被统计进去,使值不稳定。最后的乘积必须与第 1 步看到的直方图的时间序列数相同。

这个服务的指标每天要占用多少

在 /root/obs-cardinality-budget/cost.txt 中写三行。series= 是 job="shop-api" 的时间序列数,samples_per_day= 是这些时间序列每天留下的样本数,bytes_per_day= 是这些样本占用的字节数。这个 Pod 的抓取间隔是 15 秒,每个样本按平均 2 字节计算(Prometheus 存储文档所说的 1 到 2 字节之间偏保守的一侧)。这三个值必须能相互乘法对上。

时间序列数是 count(last_over_time({job="shop-api"}[1m]))。一天是 86400 秒,所以 15 秒的间隔决定了每条时间序列的样本数。这个数字就是“每增加一个标签所增加的成本”的单价。

先预测,再测量

准备暴露一个新指标 checkout_requests_total。标签有三个:region(ap1、ap2、us1、eu1)、tier(free、pro、team)、endpoint(cart、pay、refund、ship、track)。在 /root/obs-cardinality-budget/forecast.txt 中写两行 formula= 和 predicted_series=。formula 是乘法算式(例如 2*3*4),predicted_series 是它的结果。现在先不要启动任何东西。

标签组合的数量就是时间序列的数量。如果各个值相互独立,就把每个标签的取值个数相乘。之所以要先写下预测,是因为如果测量之后再去凑数字,就无从知道自己哪里想错了。

启动 exporter 来确认预测

创建 /root/obs-cardinality-budget/exporter.py。传入参数 --print 时,把暴露文本打印到标准输出后结束;没有参数时,在 127.0.0.1:9102 上提供 /metrics。对第 4 步三个标签的每种组合,各输出一行 checkout_requests_total。如果环境变量 USERS 大于 0,就附加 user_id 标签。然后在 /etc/prometheus/prometheus.yml 中加入名为 cardinality-lab 的抓取 job,并让配置重新加载。第一次抓取结束后,在 /root/obs-cardinality-budget/measured.txt 中写一行 measured_series=<수>(占位符为数量)。

服务器用 http.server 的 ThreadingHTTPServer 就足够了。暴露格式是每行一条 이름{라벨="값",...} 값(占位符依次为名称、标签、标签值、样本值)。使配置生效用 curl -X POST http://127.0.0.1:9090/-/reload,抓取间隔是 15 秒,所以需要稍等一下。时间序列数用 count(checkout_requests_total) 来统计。

一个标签就能让时间序列变成一百倍

用 USERS=100 重新启动 exporter,附加 user_id 标签。在 /root/obs-cardinality-budget/explode.tsv 中写两行。每行是以制表符分隔的三个字段 <이름> <시계열 수> <하루 바이트>(占位符依次为名称、时间序列数、每日字节数),第一行的名称是 before(没有 user_id),第二行是 after(包含 user_id)。每日字节数与第 3 步采用同样的方式计算(时间序列 × 5760 × 2)。

时间序列数只要用 --print 运行 exporter,数一数以 checkout_requests_total 开头的行,立刻就能得到——服务器不必处于运行状态。两行的差值,就是“一个标签的值”。

用代码固定上限

创建 /root/obs-cardinality-budget/budget.sh。以 bash budget.sh <상한표>(占位符为上限表)调用时,对上限表的每一行(<지표이름> <최대 시계열 수>,占位符依次为指标名称、最大时间序列数),统计当前的时间序列数,逐行输出 <지표이름> <지금> <상한> OK(占位符依次为指标名称、当前值、上限)或 ... OVER,只要有一个超过上限,就以退出码 1 结束,否则以 0 结束。然后在 /root/obs-cardinality-budget/caps.tsv 中把 checkout_requests_total 的上限写为 500,用这张表运行一次,并把输出保存到 /root/obs-cardinality-budget/gate-out.txt。

时间序列数用 count(<지표>)(占位符为指标)来统计。如果没有结果,必须当作 0——可以用 jq 的 // 给出默认值。退出码只能在最后给出一次,所以请先用变量汇总起来。评分器会用它自己制作的两张上限表来运行这个脚本,同时确认通过和失败两种情况。

要进入预算之内,该舍弃什么

时间序列上限是 500。在 /root/obs-cardinality-budget/decision.txt 中写四行。keep= 写要保留的标签名称,以逗号分隔(例如 region,tier),drop= 写要舍弃的标签名称,以逗号分隔,projected_series= 写只用保留的标签算出的时间序列数,lost_question= 用至少 30 个字符写出舍弃这些标签之后再也无法回答的问题。标签的取值个数是 region 4、tier 3、endpoint 5、user_id 100。

挑选要舍弃的标签,标准是“这个标签用来做什么决定”。不会改变决定的标签是昂贵的。以人为单位的调查是日志和跟踪能够回答的问题,所以即使从指标中舍弃这个维度,也不会整个丧失调查能力。