查询性能显著提升,Apache Doris 向量化版本在小米A/B实验场景的调优实践|最佳实践
导读:长期以来,Apache Doris在小米集团都有着广泛的应用。随着小米互联网业务的快速发展,用户对Apache Doris的查询性能提出了更高的要求,Doris 向量化版本在小米内部上线已经迫在眉睫。在 SelectDB 公司和 Apache Doris 社区的鼎力支持下,我们在小米 A/B实验场景对 Doris 1.1.2 向量化版本进行了一系列的调优操作,使得查询性能和稳定性有了显著地提升。
作者|魏祚、赵立伟
业务背景
2019 年 9 月,为了满足小米互联网增长分析业务中近实时、多维分析查询的需求,小米集团首次引入了Apache Doris。在过去的三年时间里,Apache Doris 已经在小米内部得到了广泛的应用,支持了集团数据看板、广告投放、广告BI、新零售、用户行为分析、A/B 实验平台、天星数科、小米有品、用户画像、小米造车等小米内部数十个业务,并且在小米内部形成了一套以 Apache Doris 为核心的数据生态。小米集团作为 Apache Doris 最早期的用户之一,一直深度参与社区建设,参与 Apache Doris 的稳定性打磨。
为了保证线上服务的稳定性,小米内部基于 Apache Doris 社区的 0.13 版本进行迭代,为小米的业务提供稳定的报表分析和 BI看板服务,经过业务的长时间打磨,内部 Doris 0.13 版本已经非常稳定。但是,随着小米互联网业务的发展,用户对 Doris 的查询性能提出了更高的要求,Doris 0.13 版本在某些场景下逐渐难以满足业务需求了。与此同时,Apache Doris 社区在快速发展,社区发布的 1.1 版本已经在计算层和存储层全面支持了向量化,查询性能相比非向量化版本有了明显地提升,基于此,小米内部的 Apache Doris 集群进行向量化版本升级势在必行。
场景介绍
小米的 A/B 实验平台对 Apache Doris 查询性能的提升有着迫切的需求,因此我们选择优先在小米的 A/B 实验平台上线 Apache Doris 向量化版本,也就是 1.1.2 版本。
小米的 A/B 实验平台有几类典型的查询应用:用户去重、指标求和、实验协方差计算等,查询类型会涉及较多的 Count(distinct)、Bitmap计算、Like语句等。
上线前验证
我们基于 Apache Doris 1.1.2 版本搭建了一个和小米线上 Apache Doris 0.13 版本在机器配置和机器规模上完全相同的测试集群,用于向量化版本上线前的验证。验证测试分为两个方面:单 SQL 串行查询测试和批量 SQL 并发查询测试。在这两种测试中,我们在保证两个集群数据完全相同的条件下,分别在 Doris 1.1.2 测试集群和小米线上 Doris 0.13 集群执行相同的查询 SQL 来做性能对比。我们的目标是,Doris 1.1.2 版本在小米线上 Doris 0.13 版本的基础上有 1 倍的查询性能提升。
集群规模:3 FE + 89 BE
BE节点CPU: Intel(R) Xeon(R) Silver 4216 CPU @ 2.10GHz 16核 32线程 × 2
BE节点内存:256GB
BE节点磁盘:7.3TB × 12 HDD
单 SQL 串行查询测试
在该测试场景中,我们选取了小米A/B 实验场景中 7 个典型的查询 Case,针对每一个查询 Case,我们将扫描的数据时间范围分别限制为 1 天、7 天和 20 天进行查询测试,其中单日分区数据量级大约为 31 亿(数据量大约 2 TB),测试结果如图所示:
批量 SQL 并发查询测试
在并发测试中,我们将小米A/B 实验场景的查询 SQL 按照正常的业务并发分别提交到 Doris 1.1.2 测试集群和小米线上 Doris 0.13 集群,对比观察两个集群的状态和查询延迟。测试结果为,在完全相同的机器规模、机器配置和查询场景下,Doris 1.1.2 版本的查询延迟相比线上 Doris 0.13 版本整体上升了 1 倍,查询性能下降非常明显,另外,Doris 1.1.2 版本稳定性方面也存在比较严重的问题,查询过程中会有大量的查询报错。Doris 1.1.2 版本在小米A/B 实验场景并发查询测试的结果与我们的预期差别较大。并发查询测试过程中,我们遇到了几个比较严重的问题:
1. CPU使用率上不去
RpcException, msg: timeout when waiting for send fragments RPC. Wait(sec): 5, host: 10.142.86.26
detailMessage = failed to initialize storage reader. tablet=440712.1030396814.29476aaa20a4795e-b4dbf9ac52ee56be, res=-214, backend=10.118.49.24
图6-CPU火焰图
调优实践
为了解决 Apache Doris 1.1.2 版本在小米 A/B实验场景并发测试过程中暴露出的性能和稳定性问题,推动 Doris 向量化版本尽快在小米 A/B实验平台上线,我们和 SelectDB 公司以及 Apache Doris 社区一起对 Doris 1.1.2 版本进行了一系列的调优工作。
提升 CPU 使用率
函数调用栈
#0 sys_futex (v3=0, a2=0x0, t=0x7f786c9e7a00, v=<optimized out>, o=128, a=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at /root/doris/doris/be/src/gutil/linux_syscall_support.h:2419
#1 SpinLockDelay (loop=1822369984, value=2, w=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at /root/doris/doris/be/src/gutil/spinlock_linux-inl.h:80
#2 base::internal::SpinLockDelay (w=w@entry=0x560451827c48 <tcmalloc::Static::pageheap_lock_>, value=2, loop=loop@entry=20) at /root/doris/doris/be/src/gutil/spinlock_linux-inl.h:68
#3 0x000056044cfd825d in SpinLock::SlowLock (this=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at src/base/spinlock.cc:118
#4 0x000056044f013a25 in Lock (this=<optimized out>) at src/base/spinlock.h:69
#5 SpinLockHolder (l=<optimized out>, this=0x7f786c9e7a90) at src/base/spinlock.h:124
#6 (anonymous namespace)::do_malloc_pages(tcmalloc::ThreadCache*, unsigned long) () at src/tcmalloc.cc:1360
...
#0 sys_futex (v3=0, a2=0x0, t=0x7f7494858b20, v=<optimized out>, o=128, a=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at /root/doris/doris/be/src/gutil/linux_syscall_support.h:2419
#1 SpinLockDelay (loop=-1803179840, value=2, w=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at /root/doris/doris/be/src/gutil/spinlock_linux-inl.h:80
#2 base::internal::SpinLockDelay (w=w@entry=0x560451827c48 <tcmalloc::Static::pageheap_lock_>, value=2, loop=loop@entry=2) at /root/doris/doris/be/src/gutil/spinlock_linux-inl.h:68
#3 0x000056044cfd825d in SpinLock::SlowLock (this=0x560451827c48 <tcmalloc::Static::pageheap_lock_>) at src/base/spinlock.cc:118
#4 0x000056044f01480d in Lock (this=<optimized out>) at src/base/spinlock.h:69
#5 SpinLockHolder (l=<optimized out>, this=0x7f7494858bb0) at src/base/spinlock.h:124
#6 (anonymous namespace)::do_free_pages(tcmalloc::Span*, void*) [clone .constprop.0] () at src/tcmalloc.cc:1435
...
Doris 内存管理机制
aggressive_memory_decommit
参数用来配置是否会积极释放内存给操作系统。当设置为true
时,PageHeap 会积极地将空闲内存释放给操作系统,节约系统内存;当该配置设置为false
时,PageHeap 会更多地将空闲内存进行缓存,可以提升内存分配效率,不过会占用更多的系统内存;在 Doris 中该参数默认为true
aggressive_memory_decommit
参数设为false
,让 PageHeap 对空闲内存进行更多的缓存。果然,调整完成之后,CPU 使用率可以打到几乎 100%。在 Doris 1.1.2 版本,数据在内存中采用列式存储,因此,会相比于 Doris 0.13 版本行存的方式有更大的内存管理开销。社区相关的 PR:
缓解 FE 下发 Fragment 超时的问题
Two Phase Execution
)策略。在第一阶段,FE 会下发所有的 Fragment 到 BE 节点,在 BE 上对 Fragment 执行相应的准备工作,确保 Fragment 已经准备好处理数据;当 Fragment 完成准备工作,线程就会进入休眠状态。在第二阶段,FE 会再次通过 RPC 向 BE 下发执行 Fragment 的指令,BE 收到执行 Fragment 的指令后,会唤醒正在休眠的的线程,正式执行查询计划。RpcException, msg: timeout when waiting for send fragments RPC. Wait(sec): 5, host: 10.142.86.26
社区相关的 PR 如下:
https://github.com/apache/doris/pull/12392
https://github.com/apache/doris/pull/12495
https://github.com/apache/doris/pull/12459
修复 Tablet 元数据汇报的 Bug
detailMessage = failed to initialize storage reader. tablet=440712.1030396814.29476aaa20a4795e-b4dbf9ac52ee56be, res=-214, backend=10.118.49.24
res=-214
(OLAP_ERR_VERSION_NOT_EXIST)表示查询计划执行过程中在 BE 上初始化 Rowset Reader 的时候出现异常,对应的数据版本不存在。在正常情况下,如果 Tablet 的某一个副本存在版本缺失,FE 生成执行计划的时候就不会让查询落在该副本上,然而,查询计划在 BE 上执行的过程中却发现版本不存在,则说明 FE 并没有检测到该副本存在版本缺失。res=-214
的报错。社区相关的 PR 如下:
https://github.com/apache/doris/pull/12415
优化 Like 语句性能
std::search()
函数
对存储层读出的数据进行逐行匹配,过滤掉不满足要求的数据行,完成 Like 语句的模糊匹配。通过调研和对比测试发现,GLIBC 库中的std::strstr()
函数
针对字符串匹配比std::search()
函数
有 1 倍以上的性能提升。最终我们使用std::strstr()
函数
作为 Doris 底层的字符串匹配算法,将 Doris 底层字符串匹配的性能可以提升 1 倍。优化内存拷贝
在小米的场景中有很多字符串类型的查询字段,Apache Doris 1.1.2 版本使用 ColumnString 对象来存储内存中的一列字符串数据,底层使用了 PODArray 结构来实际存储字符串。执行查询时,需要从存储层逐行读取字符串数据,在这个过程中需要多次对 PODArray 执行 Resize 操作来为列数据申请更大的存储空间,执行 Resize 操作会引起对已经读取的字符串数据执行内存拷贝,而查询过程中的内存拷贝非常耗时,对查询性能影响极大。
所需PODArray总大小 = (当前PODArray总大小 / m)* n
调优测试结果
我们基于小米的 A/B实验场景对 Apache Doris 1.1.2 版本进行了一系列调优,并将调优后的 Doris 1.1.2 版本与小米线上 Doris 0.13 版本分别进行了并发查询测试。测试情况如下:
测试 1
测试 2
我们选择了 A/B实验场景下的 7 份 A/B 实验报告对两个版本进行测试,每份 A/B 实验报告对应小米 A/B实验平台页面的两个模块,每个模块对应数百或数千条查询 SQL。每一份实验报告都以相同的并发向两个版本所在的集群提交查询任务。测试结果如图所示,Doris 1.1.2 版本相比 Doris 0.13 版本,总体的平均延迟降低了大约 52%。在该测试中,Doris 1.1.2 版本相比 Doris 0.13 版本的查询性能提升了超过 1 倍。
测试 3
结束语
最后,欢迎更多的开源技术爱好者加入 Apache Doris 社区,携手成长,共建社区生态。Apache Doris 社区当前已容纳了上万名开发者和使用者,承载了 30+ 交流社群,如果你也是 Apache Doris 的爱好者,扫码加入 Apache Doris 社区用户交流群,在这里你可以获得:
专业全职团队技术支持 直接和社区专家交流,获取免费且专业回复 认识不同行业的开发者,收获知识以及合作机会 Apache Doris 最新版本优先体验权 获取一手干货和资讯以及活动优先参与权
▶ 打造自助对话式分析场景,Apache Doris在思必驰的应用
▶ 人群圈选效率提升30倍,Apache Doris云积分的最佳实践