多加了一个标签,时间序列翻了一百倍
目标
测量当前的时间序列都用在了什么上、用了多少,先预测加上标签之后会变成多少,再真正启动来确认,并用检查脚本固定预算上限,决定保留什么、舍弃什么。
为什么重要
基数是在出事故之前没有人去看的成本。增加一个标签只是一行代码,但如果该标签的值随着用户数而增长,时间序列就会成倍增加,存储、内存和查询时间也随之一起增加。而且这种成本表现为“变慢了”,很难找到原因。所以要养成两个习惯——增加标签之前,先把组合数乘一乘;以及把每个指标的时间序列上限固定成检查,让流水线而不是人来拦截。决定舍弃什么时的标准是“这个标签用来做什么决定”,同时还要确认被舍弃的维度所回答的问题,能不能由日志或跟踪来代替回答。
步骤
- 创建
/root/obs-cardinality-budget/inventory.tsv。job="shop-api"暴露的每个指标各有多少条时间序列,按<지표이름> <시계열 수>(占位符依次为指标名称、时间序列数)两个字段,按数量从多到少全部写下来(共五行)。之所以不看整个 Pod,而是收窄到这一个 job,是因为 Prometheus 自身产生的指标在实验过程中也会不断增加,使基准不稳定。 - 创建
/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 取值个数)。 - 在
/root/obs-cardinality-budget/cost.txt中写三行。series=是job="shop-api"的时间序列数,samples_per_day=是这些时间序列每天留下的样本数,bytes_per_day=是这些样本占用的字节数。这个 Pod 的抓取间隔是 15 秒,每个样本按平均 2 字节计算(Prometheus 存储文档所说的 1 到 2 字节之间偏保守的一侧)。这三个值必须能相互乘法对上。 - 准备暴露一个新指标
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 是它的结果。现在先不要启动任何东西。 - 创建
/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=<수>(占位符为数量)。 - 用
USERS=100重新启动 exporter,附加user_id标签。在/root/obs-cardinality-budget/explode.tsv中写两行。每行是以制表符分隔的三个字段<이름> <시계열 수> <하루 바이트>(占位符依次为名称、时间序列数、每日字节数),第一行的名称是before(没有 user_id),第二行是after(包含 user_id)。每日字节数与第 3 步采用同样的方式计算(时间序列 × 5760 × 2)。 - 创建
/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。 - 时间序列上限是 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。 - TSDB 状态:
curl -s 'http://127.0.0.1:9090/api/v1/status/tsdb?limit=5' | jq——查看headStats、seriesCountByMetricName、labelValueCountByLabelName。 - 使配置生效:
curl -X POST http://127.0.0.1:9090/-/reload。抓取间隔是 15 秒,所以要等一会儿才有第一个值。 - 常见错误:
count()的结果为空时没有当作 0 处理,导致门禁以错误结束。 - 常见错误:假定标签的值相互独立而直接相乘,但实际上只存在部分组合——预测是上限,如果实测比它小,这个差值也是信息。
- Storage、TSDB status API、Instrumentation practices、Naming
统计一个服务的时间序列都用在了哪里
创建 /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。
挑选要舍弃的标签,标准是“这个标签用来做什么决定”。不会改变决定的标签是昂贵的。以人为单位的调查是日志和跟踪能够回答的问题,所以即使从指标中舍弃这个维度,也不会整个丧失调查能力。