当前位置:

首页 > 系统应用 > 性能竞赛优秀项目|分得干脆、合得高效,用Shuffle优化TiDB算子

性能竞赛优秀项目|分得干脆、合得高效,用Shuffle优化TiDB算子

本文目录

    作者介绍:黄建博,云计算领域技术开发工程师;金灵, Shopee 软件研发工程师。他们的队伍 huang-b 在性能竞赛中斩获一等奖,本文将介绍 Shuffle 优化 TiDB 算子项目的设计与实践过程。在我们往常的印象中,分与合是一对矛盾的概念,但是这次比赛留给我们队伍一个很深刻的印象是,分与合是

    作者介绍:黄建博,云计算领域技术开发工程师;金灵, Shopee 软件研发工程师。

    他们的队伍 huang-b 在性能竞赛中斩获一等奖,本文将介绍 Shuffle 优化 TiDB 算子项目的设计与实践过程。

    在我们往常的印象中,分与合是一对矛盾的概念,但是这次比赛留给我们队伍一个很深刻的印象是,分与合是一对互相促进的矛盾,只有干净利落地分解,才能高效地合并。这种印象一方面来自于我们的参赛思路,我们选择的方向是使用 Shuffle 操作将算子的数据源拆分为多个独立的分区,然后通过并行计算来提升整体的吞吐,优化的过程就是寻找更合适的分解方式,追求更好的扩展性和计算性能,相关技术细节在正文中有详细介绍。另一方面,则是来自于我们的参赛体验。

    我们的队伍有两位成员一位顾问,分散在国内三座不同的城市,比赛从开始到结束,我们没有机会线下交流过,全都是以 slack 和文档的形式进行合作,我们开玩笑说,在做一个分布式的比赛的同时,我们也是一支分布式的队伍。在这样一种受限的条件下,有两个因素对队伍的高效合作起了关键作用:其一是我们的顾问对任务做了干净利落的拆分,同样的思路应用到两个不同类型的算子上,使得我们可以花开两朵,各表一枝;其二就是 TiDB 整体高内聚低耦合的设计,从纵向的角度讲,就是 TiDB 清晰的分层设计使得我们的优化可以只关注解析器和执行器层面,而不必深入更底层的 TiKV 存储,从横向的角度讲,就是 TiDB 对分治和多态的充分实践使得我们可以只关注被优化的算子,而不必担心对其它算子产生副作用。

    技术背景

    我们的优化思路是使用 Shuffle 算子来实现 MergeJoin 算子和 StreamAggregation 算子的并行化。Shuffle 算子最先在 PR https://github.com/pingcap/tidb/pull/14238中引入,用于并行化 Window 算子。图 1 展示的就是 Window 算子的并行化过程。

    图中左侧是串行的 Window 算子,因为 Window 算子要求输入数据有序,所以在数据源和 Window 之间通常有一个 Sort 算子。图中右侧展示的是对应的 Shuffle 算子,为了完成并行计算,Window 算子和 Sort 算子都被复制了多份,每一份与一 ShuffleWorker 相对应,从数据源流入的数据由 Splitter 按哈希值拆分为独立的数据分区,发往不同的 ShuffleWorker ,最终各个 Window 算子的结果汇总后输出。图中所画箭头就是数据流动的方向,其中数据的分发和结果的汇总是通过 go channel 来实现的,其它数据流动都是父节点通过调用子节点的 Next 方法来获取的。图中虚线表示启动的协程,每个 ShuffleWorker 都会启动一个协程来完成自身的运算,同时 Splitter 也会启动一个协程来完成数据的分发。

    题目链接:

    1. ShuffleMergeJoin:https://github.com/pingcap/tidb/issues/14441

    2. ShuffleStreamAgg:https://github.com/pingcap/tidb/issues/20651

    图 1 Window 算子并行化

    ShuffleMergeJoin

    扩展 Shuffle 算子

    若要对MergeJoin进行并行优化,直接套用ShuffleWindow的框架是否可行呢?答案是否定的。MergeJoin算子与前文的Window算子有所不同,它需要两个数据源。那么,现有的Shuffle实现能否让每个并行算子对应两个ShuffleWorker,从而对应两个数据源呢?答案同样是否定的。原因在于,前文提到的Shuffle实现将数据分区和计算并行这两个功能过度耦合了,这种过度耦合导致它无法支持两个数据源。接下来,我们具体说明一下这个问题。

    过度耦合指的是 ShuffleWorker 充当的角色太多,它既是数据流动的一环,同时也是计算并行的基本单元,于是带来了这两个问题:

    1. 因为 ShuffleWorker 时数据分区,所以并行后的每个 MergeJoin 需要两个 ShuffleWorker 来接收来自两个数据源的数据,但是 ShuffleWorker 同时又是计算并行的基本单元,于是有 n 个 MergeJoin 算子就会出现 2n 个协程,同一个 MergeJoin 算子的两个协程还会出现数据竞争。

    2. 控制逻辑复杂, ShuffleWorker 作为数据的一个分区,它必须作为 Sort 算子的子节点,而它作为计算并行的基本单元,又必须在协程中调用 Window 算子的 Next 方法来完成计算,所以在原来的实现往 ShuffleWorker 里放了个指向 Window 算子的指针,这样的设计一方面存在破坏执行树有向无环特性的隐患,另一方面也降低了代码的可读性。

    当然,第一个问题我们可以通过在 ShuffleWorker 中增加一个布尔变量来解决:同一个 MergeJoin 对应的两个 ShuffleWorker 一个为 true,一个为 false,只有为 true 的那个才会启动协程。可是这个方法无疑会使上面提到的复杂的逻辑更加复杂。

    **我们提出的解决方案是把数据分区和计算并行解耦。**如图 2 所示:计算并行还是由 ShuffleWorker 负责,但是它不再是数据流动过程中的一环,它原来在数据流动过程中的位置由 ShuffleReceiver 来代替。MergeJoin 是 ShuffleWorker 的一个成员,每个 ShuffleWorker 对应一个协程,在协程中调用 MergeJoin 的 Next 方法,并将结果发送给汇总算子,这样上文中提到的两个问题都得到了解决。

    图 2 拓展后的 Shuffle 算子

    相关 PR:https://github.com/pingcap/tidb/pull/20942

    实现与效果

    在实现中我们考虑两个场景:其一是数据源本身无序的情况,这种情况下数据进入 MergeJoin 之前要先经过 Sort 节点;另一是数据源本身有序的情况,这种情况下数据进入 MergeJoin 之前无需排序。

    图 3 数据源无序情况下的 ShuffleMergeJoin

    图 3 展示的就是数据源无序情况下 MergeJoin 的并行化过程,这种情况 MergeJoin 和 Sort 算子的计算开销都可以分摊到多个协程上。启动 2 个 worker 的优化效果如表 1 所示,我们在不同规模的数据源上都做了测试,表中前两列是两个数据源的行数,表中的后两列是串行和并行版本的运行性能,单位是 ns/op,越小性能越高。从表中可以看出, Shuffle 是可以明显加速 MergeJoin 运算的,并且数据量越大的情况下加速效果越好(因为并行化是会引入管道、协程等额外开销的,比较大的数据量才能保证并行化的收益大于开销)。在我们的几个测试案例中,效果最好的情况下 2 个 worker 的运算时间仅为串行版本的 56.5% 。

    表 1 ShuffleMergeJoin 优化效果

    图 4 展示的是数据源有序情况下 MergeJoin 的并行化过程,区别就是数据不再经过 Sort 算子。这种情况下计算的负载本身比较轻量,相比之下根据哈希值来分发数据的 Splitter 就成为了系统的性能瓶颈,并行化以后性能提升并不明显。

    图 4 数据源有序情况下的 ShuffleMergeJoin

    相关 PR:

    1. ShuffleMergeJoin 实现:https://github.com/pingcap/tidb/pull/21255

    2. 控制参数:https://github.com/pingcap/tidb/pull/21332

    3. 单元测试与性能测试:https://github.com/pingcap/tidb/pull/21360

    ShuffleStreamAggregation

    聚合运算是 SQL 语句必不可少的一部分,无论是 OLTP 还是 OLAP 场景,聚合都是经常被使用到的算子。

    从系统实现层面来看,聚合的实现一般有两种,第一种是基于 Hash 的方法,该方法通过构建 Hash table,维护每一个被聚合元素的值,计算得到最后的结果值。另外一种,则是基于有序数据流的方法,该方法要求输入数据源必须是有序的,然后通过遍历有序的数据流,并在同时维护相应的聚合值,即可得到最后的计算结果。

    一般来说,基于 Hash 的方法具有更快的计算速度,但是它需要维护一个 Hash table,内存空间使用成本较高,当被聚合 key 的可能取值个数非常大的时候,那么相应 Hash table 中的元素个数也会非常多,对内存是个不小的考验,存在爆内存的风险,这反而导致计算不能正常地被完成。而基于有序数据流方法的聚合运算实现方式,无需随时都在内存中维护所有的被聚合 key 的值,因此内存使用量相对较小,但是它的运行速度相对而言更慢一点,且更为严格地要求输入数据必须是有序的。如果可以提升基于有序数据流方法的聚合算子的运行速度,那么该方法将会更加适用于大数据量的情况。因此我们选择对基于有序数据流方法的聚合运算实现方法,即 Stream Aggregation 进行并行加速,以提升该算子的整体运行速度。

    实现与效果

    在具体的实现过程中,我们利用了之前由其他社区贡献者提供的 Shuffle 算子,在 StreamAggregation 算子外围,将输入数据分割成多个有序的输入数据流,分别输入到多个 StreamAggregation 算子当中,然后通过简单的整合,得到最后的计算结果。简单地说,初始输入数据源 DataSource,首先会经过 Shuffle 算子,被分割成多个 Partition,且每个 Partition 都是其内部有序的,然后每个 Partition 分别被作为一 StreamAggregation 算子的输入,生成部分结果,最后通过对相同 key 的元素进行整合,即可得到最后的整体计算结果。

    此处需要考虑 DataSource 是否有序的情况,如果 DataSource 在被聚合 key 上是无序的,比如普通的 PhysicalTableReader 算子,或其他算子的输出,那么需要在被分割之前,使得其有序,因此需要在其上添加一个 Sort 算子(如图 5 所示)。

    图 5 数据源无序情况下的 ShuffleStreamAggregation

    针对这种场景,我们的方法最后取得了非常明显的性能提升(如表 2 所示)。分析认为,非并行的情况下,Sort 作用在整个 DataSource 之上,而并行化的版本是作用在每个不同的 Partition 之上,输入相对较小,且并行执行,因此性能提升较大。

    表 2 ShuffleStreamAggregation 优化效果

    另外,还需要考虑 DataSource 在被聚合 key 上是有序的情况,比如下面的 SQL 语句,被聚合 key 为 b,且输入数据源 t 上刚好有由 b 创建的索引,因此在具体的计算过程中, DataSource 是基于 b 的 PhysicalIndexTableReader ,那么我们就无序引入 Sort 算子,直接将输入分割成多个 Partition,然后经过图 6 所示的计算过程即可得到结果。

    create table t(a int, b int, key b(b));
    select /*+ stream_agg() */ count(a) from t group by b;

    图 6 数据源有序情况下的 ShuffleStreamAggregation

    通过 Benchmark 的结果表明,在该情况下,目前的基于 Shuffle 的实现,运行速度并没有得到提升,反而有所下降,我们粗浅地认为,当前的 Shuffle 实现方式是瓶颈点,是后续需要被解决的重点。

    相关 PR:

    1. https://github.com/pingcap/tidb/pull/20658

    2. https://github.com/pingcap/tidb/pull/21095

    RangeSplitter

    上面提到,Shuffle算子会把数据输入划分成多个Partition。一开始,只有基于Hash方法的Splitter被实现了,这种实现对输入数据是否有序没有要求。要是数据源是有序的,虽然基于Hash的方法仍然能用,但用基于Range的方法来分割数据源会更自然。因为相同聚合key的多行数据肯定是挨在一起的,如果能直接找到这一块数据的起始点和结束点,一次性整体分割,那就不用构建HashTable,也不用调用开销更大的HashFunction,这样能让Partition过程的开销更小。基于这个想法,我们实现了PartitionRangeSplitter,它的计算原理是把紧挨在一起的相同聚合key的多行数据,批量地分配到一个worker上。和基于Hash方法的Partitioner相比,基于Range方法的实现方式开销更小。在处理有序输入数据源时,使用RangePartitioner比使用HashPartitioner能快一倍(见表3),这就证明了这个算子更适合数据源有序的情况。

    表 3 RangeSplitter 与 HashSplitter 性能比较

    相关 PR:

    1. RangeSplitter 实现:https://github.com/pingcap/tidb/pull/21306

    2. 相关性能测试:https://github.com/pingcap/tidb/pull/21363

    总结

    在本次性能挑战大赛中,我们使用 Shuffle 算子对 MergeJoin 算子和 Stream Aggregation 算子进行了加速,在数据源无序的场景下,取得了明显的性能提升。在优化 MergeJoin 的过程中,为了适配多个数据源的算子,我们对现有的 Shuffle 实现做了扩展,提高了可读性和可扩展性。在优化 StreamAggregation 的过程中,考虑到数据源有序的情况,提出了一个简单的基于 Range 方法的 Splitter 实现,也证明了其有效性。我们在后续将会考虑如何对现有的 Shuffle 算子进行改造,消除其中存在的性能瓶颈,以期进一步提升基于 Shuffle 的一系列并行算子的性能。

    本文内容来源于网友投稿,如有侵权请联系删除。
    作者最新文章
    系统应用 云计算
    相关文章 更多
    win10升级新版本后取消开机密码失效怎么修复
    win10升级新版本后取消开机密码失效怎么修复

    Windows 10更新后开机突然要求输入密码?本文解析自动登录失效的真实原因,提供通过netplwiz重新保存凭据、关闭Windows Hello干扰及排查账户策略的完整步骤,助你恢复免密进入桌面。

    win10更新没下载完反复重试该怎么停止任务
    win10更新没下载完反复重试该怎么停止任务

    Windows 10更新下载失败反复重试怎么办?不要直接停用服务。本文教你通过设置页面暂停更新、启用按流量计费连接以及调整活动时间,安全地暂时停止下载任务并避免意外重启。

    win10怎么取消系统准备强制升级新版本
    win10怎么取消系统准备强制升级新版本

    面对Win10强制升级新版本的提示,本文解析家庭版与专业版的应对差异。介绍如何通过暂停更新、设置活动时间临时避让,以及利用组策略控制功能版本。强调Windows 10支持终止后的安全风险,避免使用高风险的禁用服务手段。

    win10组策略找不到更新选项该怎么处理
    win10组策略找不到更新选项该怎么处理

    Win10组策略编辑器中找不到Windows Update选项怎么办?本文分析家庭版与专业版的权限差异,厘清本地策略与域策略的区别,提供设置页替代方案及企业环境排查思路,避免盲目修改注册表或安装非官方补丁。

    win10取消开机密码会影响电脑里的文件安全吗?
    win10取消开机密码会影响电脑里的文件安全吗?

    很多人为了便捷选择取消 Win10 开机密码,这会导致文件丢失吗?本文详解取消密码后文件访问权限的变化、BitLocker 加密的有效性以及更安全的替代方案,帮助用户在便利与数据安全之间做出正确选择。

    win10弹出立即重启更新怎么延后这个操作
    win10弹出立即重启更新怎么延后这个操作

    Win10弹出立即重启更新会打断工作?本文介绍如何通过Windows Update页面安排重启时间、设置活动时段以及暂停后续更新,有效延后重启操作,保护未保存的工作成果。

    win10自动更新在夜间偷偷运行怎么限制
    win10自动更新在夜间偷偷运行怎么限制

    Win10晚上自动下载或重启怎么办?不要直接禁用服务。本文教你通过设置活动时间、安排指定重启时间及短期暂停更新,精准控制Windows Update行为,避免夜间网络占用和意外重启,同时保留系统安全性。

    win10本地账户取消开机密码有哪些简单方法
    win10本地账户取消开机密码有哪些简单方法

    想跳过Win10开机输入密码?本文详解本地账户清空密码、netplwiz自动登录及PIN设置的步骤与区别,同时提醒Microsoft账户和域策略下的限制与风险。

    win10更新重启倒计时怎么取消不让电脑重启
    win10更新重启倒计时怎么取消不让电脑重启

    面对Win10更新重启倒计时,如何通过设置界面取消或延后?本文详解活动时间、安排重启及暂停更新的位置与作用,帮助你在不影响工作的前提下管理系统重启。

    win10取消开机密码重启后还要输密码怎么办?
    win10取消开机密码重启后还要输密码怎么办?

    Win10设置取消密码后重启依然要求输入?这通常不是系统故障,而是自动登录配置错误或睡眠唤醒验证未关闭。本文教你区分本地账户与微软账户,正确设置netplwiz自动登录,并检查锁屏策略,彻底解决开机免密问题。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    WINDOWS 更多
    3dmax(3ds max)
    3dmax(3ds max)
    Windows

    Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。