建立一个量化 coprocessor 请求对 tikv 资源消耗的模型

2022-05-20

衡量一条 SQL 的请求的资源消耗是很复杂的,因为不同 SQL 差异太大了。一个点查,跟一个非常复杂的多表 join,可能会隔许多个数量级。单个大查询能够一条 SQL 就把整个分布式数据库集群都击垮。 如何保证这样的请求被拒绝掉,从而保护整体系统呢?在 SQL 这一层预计算代价可能不太适合,我认为在 coprocessor 这一块做些工作可能更靠谱。

因为一个复杂 SQL 请求,会被转化成很多的 coprocessor(协处理器) 查询,在 tidb 里面是计算下推到 tikv,然后再将结果汇总。相比 SQL 的复杂度,coprocessor 的复杂度会低很多。 coprocessor 请求内部,会有一个 encoded plan,告诉 tikv 要执行啥,比如 table scan,index scan,或者有 selection / top /limit / aggregation 这些。这还是不够简化,简化到直观地反映一条 coprocessor 请求,跟另一条简单点查的请求,他们的执行代价是几倍的区别。

所以我想,能不能建立一个量化 coprocessor 请求的资源消耗的模型。比如说,以一个简单点查的请求的基准代价,算成1,那么一个扫 3000 个 key-value 的 coprocessor 代价是多少?

我们看一下有哪些影响因素。

  1. 只扫一个 kv 的点,跟扫一块 range,它们之间的代价肯定不一样。比如这个差异叫查询的 range 系数。
  2. 请求的 range 不一定有值,比如一个请求扫了 [1, 3000) 这么大的 range,返回了一条数据。另一个请求也是扫了 [1, 3000) 这么大的 range,但是最后返回了 3000 条数据,那么这两个请求代价其实是不一样的。我们把实际返回多少条数据可以记为 返回结果条数系数。
  3. 两个请求,输入 range 范围一样大,返回结果条数也一样,但是其中一个请求是因为 range 里面只有那么多数据,另一个请求是因为带了 filter 条件过滤掉了部分剩下的,那么 filter 是需要开销的,所以这两个请求的代价也不应该一样。我们可以把 filter 记一个过滤系数。
  4. 不同的算子情况还不太一样,比如 agg / top 这种,可能需要处理全部的 range,可能只吐一点数据出去。而 limit 可能达到条数后很快就返回了。不同的算子可能有不同的算子系数,这个还不太好衡量
  5. 我们可以观察到,不同的 coprocessor 请求耗时是不同的,如果把前面的不同的参数作为输入,那么响应时间可以作为输出参数
  6. 单纯在网络传输上面的时间,也可以算成 coprocessor 代价中的一部分,不管对哪种 cop,都需要走网络,可以把这个定一个网络常量参数
  7. 可能还可以收集到一些其它信息,参数越多,这个模型就越复杂...

通过旁路记日志之类的手段,可以从一个运行着的系统中,把上面需要的参数全部都收集下来。我们现在假设一个代价模型:

(a x range系数) + (b x 结果条数系数) + (c x 过滤系数) + (d x 算子系数) + e (网络常量系数) = f x 响应时间

这个模型假设不一定对,不一定是线性的问题,也不一定是影响因素简单加和的关系,只是为了表达这个意思。

我们有收集到的数据,我们有假设的这样一个模型。然后我们把数据代入进去,就可以计算出模型的参数 a b c d e f 这些,很有点机器学习的味道。 拟合的结果,需要是让模型的误差尽量小的。一旦参数确定了,我们就可以用这个模型去计算代价了。

比如说把点查的各种系数代进去,算一个代价,再把某一个 coprocessor 的各种系数丢进去算一个代价,就可以知道该 cop 请求的代价是点查代价的多少倍。

我们建立这样一个资源消耗的代价模型有什么实际用途呢?

比如说流控,某个 SQL 查询很重,我们就需要对这个负载限流,从而保护系统不要被打垮了。

比如说通过估算负载自动扩缩容。我们计算出系统的能承受的负载是多重,然后看当前 coprocessor 的实际负载多重,然后决定是否需要扩容。识别出这样的场景后,可以将扩容做成自动化的,而不是靠人工观察监控去决策。

比如说租户的资源隔离。假设系统,做 severless 支持多租户,下层 kv 存储层共享,那么对多租户的计费,以及对多租户间的资源隔离也是需要量化 coprocessor 的代价的。

coprocessortidb流控