这篇文章来自 SAP HANA 开发团队,第一作者是 HANA 任务调度框架的主要开发者之一,不过他写完这篇 paper 之后没多久就跳去 Oracle 了。
很多我们熟悉的MPP系统调度方面都很粗糙,通常是所谓的静态调度。比如,当前有N台节点,每个节点有P个worker,就把整个任务切成N*P份。这种方法简单粗暴,但现实世界的workload往往都是有skew的,效果可能差强人意。
本文介绍了 NUMA 的架构下如何实现 Adaptive 的任务调度,即,根据实际 Workload 的资源使用情况(主要是内存throughput和CPU),自动平衡各个 socket 上的负载,从而更有效的利用资源。为什么标题中除了 task scheduling 还有 data placement 呢?这是因为很多任务(比如scan)必须要放在数据所在的 socket 上,如果强行work stealing用其他socket去做,效果反而会下降,想真的做到平衡必须也要搬运数据才行。
基本概念
一张图就可以说明白本文想做什么事:Socket 12 的负载很高,我们通过 data replacement,
- 将 TBL2 从Socket 1 搬到 Socket 3 ,因为原本TBL2和TBL1都在吃 Socket 1 的资源
- 将 TBL3 拆分成 part12,拆一半给 Socket4,因为原本一个TBL3已经打满了 Socket 2
- 将 TBL4 的 part12 合并成一个,因为 TBL4 基本没啥负载
The Need For Adaptivity
文中把 Scan 分成 Scan IV (index vector) 和 Materialization (用 vid 取出对应的record数据) 两个步骤,Scan IV 是 memory-intensive 的,而 Materialization 是 CPU-intensive 的。对于 memory-intensive 的 task,work stealing 不仅没有卵用还可能会降低性能,因为 socket 之间的内存带宽是有限的资源,一不小心还可能打满对面 socket 的 memory controller。
作者又说了一通,不仅对于 Scan,Aggregation、Join 其实也都有 memory-intensive 的步骤,它们也一样要 data placement 才能算的更快。
一句话总结下,要想负载均衡必须要搬数据,work stealing 没有用。
跟踪资源使用情况
文章首先把任务分成各种 Task Class,比如对于 Scan 算子,他其中有两个 Task 分别是 Scan IV 和 Scan Mat. 。对于每种 Task Class,我们观察它memory throughput的历史数据的指数平均值,以这个作为该 Task 的资源使用情况。
注意,所有 work stealing 的执行是不被统计的。我们想看的就是 task 本身的消耗。在我们的模型下 work stealing 应当被视作一个待优化的 bad case。
对 task class 的估计会被实时地汇总成 TBP (table partition) 和 TGP (table-group partition) 的资源消耗统计, 之后,一个后台异步 sample 线程又回每隔 100ms 汇总 socket 上的资源消耗统计。这些统计数据就是我们做 adaptive data placement 的依据。
上图中列出了3种Task。RUH即Resource Utilization History (RUH),他记录了资源消耗的历史情况,包括 memory throughput (由 task 的估算 throughput 加总得到)和 CPU load (这里仅仅用线程数表示)
上面说到,各种 task 的 memory throughput 是通过它的历史 memory throughput 的指数平均数估算的,历史的 memory throughput 是怎么得到的呢?本文利用了 Intel 提供的硬件计数器,计数器前后数值的差值也就是当前这个core、当前这个时间片的 memory access 量,除以时间长度就得到了 throughput。
Adaptive Data Placement
负责数据迁移的模块称为 Data Placer (DP),它是一个后台异步任务,每隔一段时间会启动并检查是否有什么可以做的,比如 move 或 repartition。
DP 的目标是让各个 socket 的负载更均衡(aka. 资源利用率更高),缓解单 socket 性能瓶颈;同时也要注意,不能因为 data placement 打满内存带宽。
DP 首先通过一系列的条件选出适合迁移的 TBP 或 TGP,称之为 eligible RUH's owner。如果找不到,那就到此为止,下一次循环再看看。选择 eligible 的过程如下:
下面这张表上是一些阈值之类的筛选条件:
下面这张图是估算 move 或 repartition 能多大程度上改善不均衡:
具体算法就不再说了,这边写的过于细节,有兴趣的读者请看paper。
Adaptive Task Stealing
这一节不是本文的核心工作,像是为了立意的完整性而补上的。之前说了那么多 data placement 其实都是为了优化 scheduling,但是无论怎么优化仍然还是会有 skew 的。这时候仍然需要 work stealing 来托底。
考虑到之前已经发现了一个fact:memory-intensive 的任务 work stealing 用处不大甚至可能会起到反效果,这里作者尝试去找一个 switch point,如果一个任务的 memory-intensive 程度大于这个点,那么 work stealing 有效,否则无效。
听起来似乎不难,但是这个 switch point 实际上很难界定,它和 workload 和硬件都有很大关系,文中只给出了一种基于试验的测量方法(似乎没有什么实用价值)。
下面的图中,横轴 Power 表示乘法的次数,Power 越大代表 CPU-intensive 的程度越大、memory-intensive 的程度越小。可以看到,在图中,三个配置下的最优 switch point (即绿线和蓝线的交叉点)各不相同。