写点什么

理解 TiDB 集群的 P99 计算方式

  • 2023-08-25
    北京
  • 本文字数:2504 字

    阅读完需:约 8 分钟

原文来源:https://tidb.net/blog/c38dd8ac

一、背景简介

在学习 prometheus 时,会遇到一个 histogram_quantile() 函数,用于对 histogram 类型的指标进行分位数计算,实际上这个函数就是 histogram 这个指标类型最常用的函数。


此函数在 tidb 的监控图表中有一个比较明显地方使用:计算 P99/P999 Duration 等延迟指标。


新人们对此函数的理解是可能是一个较漫长的过程,而我观察到很多解释此函数的博主们在解释这个函数的应用场景和原理时非常容易陷入 “知识诅咒” 的陷阱,即,写作者自身非常了解这个领域的知识,但潜意识中很容易忽略读者可能对该主题或领域不了解的事实。于是在涉及某些较为简单的专有名词时会一笔带过,在涉及某些较为核心的逻辑时容易高屋建瓴的做出解释但读者却一头雾水。


不同的人认知水平不同,而即便是同一个人的认知水平也随时间增长,因此所有人都不可避免的陷入知识诅咒的陷阱,为此本文因此尝试从另一种通俗的角度来理解,希望对读者有所帮助,同时也可以极大加深自己的理解。

二、基础知识

在介绍这个函数之前,简单的介绍一下 prometheus 的几种指标类型:官网: Prometheus 指标类型


  1. Counter:理解为一个单调递增的数字即可,适合进行请求数、错误数等累计监控,例如 t1 时间 request_count 为 0,t1+10s 时间计数为 100,则容易得到过去 10s 内的请求速度约为 10 个 /s

  2. Gauge:理解为一个非单调递增的数字即可,适合进行瞬时值监控,例如当前温度、内存使用量等,由于上报频率不可能设置的很高,因此相比 Counter 可能会丢失一些信息,但应用场景也很广

  3. Histogram:直方图,可以搜一下网上的直方图图片,即 prometheus 将位于同一范围内的数字归类到一个柱状体 (bucket) 内进行计数,形成一个向量 (线性代数名词, 这里理解为数组) 除此之外还会记录实际的指标总数和总值,即一个 Histogram 包含一个直方图向量和 2 个类似 Counter 的指标 (Prometheus server 并不区分指标类型,对 server 来说只有 time series)

  4. Summary:略,Histogram 的进阶版本,不过一些编程语言的驱动可能并没有很好的支持这种类型,使用 Histogram 可以覆盖他的使用场景。


上述简短的解释不能真正展示 4 种数据类型的实质,但他们不是本篇重点因此不多说了。

三、从 P99 计算开始

首先列出 tidb 计算 P99 的查询语句,可以从 tidb grafana 中获取到:



histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{k8s_cluster="$k8s_cluster", tidb_cluster="$tidb_cluster"}[1m])) by (le))
复制代码


因为同一个集群下的这俩标签都是一样的,我们将其简化为:


histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le))
复制代码


使用 postman 可以快速查出他的结果:


GET /api/v1/query?query=histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le)) HTTP/1.1Host: x.x.x.x:9090
复制代码


{   "status": "success",   "data": {     "resultType": "vector",     "result": [       {         "metric": {},         "value": [ 1691657038.265, "0.051201495327102595" ]       }     ]   } }
复制代码


可以知道当前集群的 P99 指标约为 51ms。


但是这个计算过程看起来较为繁琐,先是使用 rate 对 tidb_server_handle_query_duration_seconds_bucket 计算了过去 1min 的速率,然后使用 sum by (le) 进行了求和,最后才使用 histogram_quantile 函数计算 99 分位数。


该如何理解这种计算方式?

四、流程解析

首先思考一个问题,当我们想要获取一个 P99 值时,一个最直观的存储监控值的办法是什么?


当然是 tidb 每处理一个请求都把耗时以 Gauge 形式存起来,然后等 prometheus 来收集!


然后我们发现这种办法是在是太差了,qps 高的时候监控代理和 prometheus 都容易爆炸。


再来分析一下实际需求,我们实质上并不是要看所有请求的耗时,只是想了解一下某个时间点 (或某一段时间内) 数据库中 99% 的请求都低于多少 ms,即 P99。


然后发现我们似乎不用存储具体的耗时值,我们只需要先创建一个直方图 (一个包含多个 bucket 的容器),例如:


{  "耗时低于1s": 0,    // bucket 1, 标签 {le: 1}  "耗时低于10s": 0,   // bucket 2, 标签 {le: 10}  "耗时低于100s": 0,  // bucket 3, 标签 {le: 100}  ...                // bucket n, 标签 {le: ......}}
复制代码


然后每当有请求耗时需要存储时,在对应的 bucket 中计数 +1,这样只要 bucket 的设置合理一些我们就可以轻易获得一个执行耗时的分布图。在此基础上计算 P99 就比较容易了。


当然,除了上述的向量之外,histogram 还会存储一个总耗时值和总请求次数的计数器,不过计算 P99 用不到。


直方图指标有了,我们把他叫做tidb_server_handle_query_duration_seconds_bucket,prometheus 每个收集间隔过来取一次即可。


接下来呢?


可以看到每个 bucket 其实也都是一个计数器,计数器的瞬时值一般没有多少意义,因此我们需要使用 rate() 函数进行速率计算: rate(tidb_server_handle_query_duration_seconds_bucket[1m]


tidb_server_handle_query_duration_seconds_bucket 如之前所说,他其实是一个向量值,向量值和标量值 (60s) 进行除法这个了解一些线代基础的可以知道结果是一个每个元素都除以 60s 的新向量。这个新向量的 label 和以前一样,都是{le: 1}, {le: 10}这种。


而 tidb 的请求类型很多,有 select,update,insert 还有 analyze table,begin,commit 等,我们希望统一进行计算,因此执行一个 sum() group by le 的操作,即:sum(rate(tidb_server_handle_query_duration_seconds_bucket[1m])) by (le)


同样的其结果依然是一个向量,我们只是按 le 进行了一次 sql_type 的聚合,至此我们终于可以使用histogram_quantile(φ scalar, b instant-vector)函数啦。这个函数接收 2 个参数,一个分位数值 (例如 0.99,0.999 等),一个瞬时向量 (这里就是 rate 处理之后的 _bucket 指标)。


至此我们就得到了 tidb 的 P99。至于 histogram_quantile 内部如何计算 99 分位数这里不再解释,因为为了更贴近真实结果有一些小的 tricks 解释起来需要多些几句,这不是本文重点。


发布于: 刚刚阅读数: 3
用户头像

TiDB 社区官网:https://tidb.net/ 2021-12-15 加入

TiDB 社区干货传送门是由 TiDB 社区中布道师组委会自发组织的 TiDB 社区优质内容对外宣布的栏目,旨在加深 TiDBer 之间的交流和学习。一起构建有爱、互助、共创共建的 TiDB 社区 https://tidb.net/

评论

发布
暂无评论
理解TiDB集群的P99计算方式_数据库架构设计_TiDB 社区干货传送门_InfoQ写作社区