第一届POLARDB数据库性能大赛-亚军0xCC☣☢比赛攻略

news2024/9/25 16:58:32

关联比赛:  第一届POLARDB数据库性能大赛

1 赛题分析

        本次大赛的初赛和复赛的赛题内容是一脉相传的,主要内容都是实现一个KV数据库存储引擎,实现随机插入,随机查询,区间查询这三个功能。赛题的难点主要有两个:1、实现在kill -9时的数据可靠性;2、实现高效的区间查询算法。

        程序运行的硬件环境是64个核的志强处理器,从型号核数推测应该为双CPU系统,磁盘为Intel的最新的傲腾SSD,推测型号为P4800X,但是在内存方面限制较为严格,仅允许使用约2G的大小。在开放语言上限定为Java和C++,虽然是开发IO密集型程序,但对内存和数据结构的高要求,C++还是相对占有优势的,从最后结果上也可以看出,C++的最好成绩比Java快了约10s。

        相对于比较吃紧的IO和内存,CPU运算还是比较富裕的,如何设计合适的高并发算法,减少线程间冲突,将运算和IO实现尽可能的重叠是高分段的主要优化点。

2 算法及程序结构设计

2.1 缓冲设计

        虽然傲腾磁盘拥有极其优秀的随机IO性能,但想要性能进入第一梯队,合适的读写缓冲是必不可少的。

        传统的存储模块不需要考虑程序被杀的情况,使用一块内存作为环形缓冲队列是常用的一种方法,但在赛题需求中,需要随时对抗程序被kill-9,由于没有任何事先预警,可能存在在任何情况下程序被终止(虽然在测评中,这个行为比较温和,仅在所以请求返回后调用kill-9),只有至少写入page cache的内容才能被保存下来。

        故对传统结构的缓冲结构进行改进,不难想到将传统的内存缓冲移到page cache上,具体实现如图1所示。

图1 基本缓冲结构

        由于将缓冲及相应的offset等元数据写入到page cache上,即使kill -9发生,缓冲中的数据也不会丢失。通过合理设计key、value数据和offset等元数据的输入顺序,配合原子写入(lock前缀原子指令),可以应对任意时刻发生的kill -9,不会对数据的一致性造成破坏,对于未返回的请求,虽然是否被保存是不确定的,但整体数据存储结构不会出现冲突。

        在具体实现方式上,使用mmap方式将page cache映射到程序内存空间中,提升操作效率,也为原子操作提供了可能。额外需要注意的是:1、这块page cache并不是kv存储文件,而是另一个定长的单独文件,用于做环形缓冲和元数据的实时存储,单独的文件也有利于整体映射及大小控制。2、page cache使用大小的细节,因为操作系统存在10%的脏写入阈值和20%的阻塞线程阈值,在cgroup限制下,该阈值的计算基数是当前所剩空闲内存的大小,这里需要精心规划内存使用,避免系统后台落盘线程的频繁刷盘。

2.2 存储结构设计

        存储结构的设计对写入和读取的性能起至关重要的作用。从数据分布采样统计上来看,具体的正确性测试和性能测试的数据分布略有不同,正确性测试进行了大量重复key及大量hash冲突的key值插入,如果使用hash表方式实现,需要有很好的扩展性。性能测试的数据较为均匀,比较适合使用hash方式进行分片存储。

        由于KV数据插入是随机的,第二阶段的测试也是随机读取,所以简单的hash分片存储是较为合适的数据存储方式。为了提升写入速率,采用每个分片内顺序写入的方式进行。考虑到后续区间读取,需要尽量将范围内的数据尽可能聚集,故可以使用key的升序前若干个bit作为key的hash(具体实现为bswap key的64bits然后无符号右移),这样不仅可以动态调整分片粒度,区间读取时也便于对整个分片进行缓存。

        结合上一节的缓冲设计,不难得出整体的存储结构,如图2所示。

图2 数据存储及缓冲结构(1k分片)

        由于数据的总量为6400w X 4KB约为245GB,虽然ext4文件系统支持大文件,但是小于1G的文件对于减小冲突和文件系统负载是比较合适的,故分片数选择256/512/1024/2048这几个2的幂次,对应key的前8-11个bit作为分片的序号。

2.3 随机写入实现

        如图3所示为写入缓冲及刷盘控制。

图3 写入部分代码

        通过key的hash到具体分片上,针对该分片的key和value的缓冲进行分别填充,当缓冲满则刷入磁盘(这里应该将offset++放在最后,保证任意时刻的数据完整性)。每个分片通过轻量级的自旋锁进行保护,由于分片的数目远大于线程数,冲突的概率很低,且由于CPU性能的富裕,使用自旋+yield的方式就能以很低的系统切换数实现线程的同步。

        由图2和3也可以发现,程序将key和value同时存储在一个文件中,由于锁的控制,KV的顺序完全一致,不需要存储额外的offset信息,仅需要顺序读取即可读入所有key建立索引,由于分片的原因,这个过程也可以并行进行。Key和value的存储区域为事先划分的,可以通过分为两个独立文件或设置合理的安全余量保证两者不会相交,同时,代码中也可以进行强制检查。

        在文件写入时有很对小的细节技巧:1、O_DIRECT的合理使用,由于自己实现的存储管理系统比操作系统更了解我们数据的读写需求,自己控制缓存能减少系统层面的延迟和额外操作,最大化压榨IO性能。2、fallocate可以对文件完成预分配工作,使得后续数据写入能更加稳定。3、合理设计每个分片的预偏移,在很少空间浪费的情况下,能够将每个缓冲的爆发写入均匀化,平衡写入的冲突。

      通过这些细节处理,写入性能能达到55.7w OPS左右,最高性能能达到56w OPS。

2.4 索引设计

        对于数据读取,良好的索引设计对于高速随机和区间读取都有极大的帮助。对于随机查询,性能最高的便是hash table,由于数据分布较为均匀,使用key的升序前几个bit作为hash的冲突率较低。由于之前存储结构的设计也是遵循hash来分片的,可以使用多线程并发加载若干分片的key部分,并且由于文件内部有序,直接将key的大块读入内存,以更细粒度的hash slot加载到索引结构中。

索引中的内容是key和对于value在存储文件中的offset,在大于1024的分片中,offset小于256MB,由于value是定长4KB的,故offset可以压缩到2bytes,故一个索引单元为10bytes,整体索引大小为610MB,算上分配余量,能限制在700MB左右。

        图4所示为索引存储结构。为了更好的完成随机查找,索引设计为64k个slot的hash table,每个slot中为自动伸缩的数组,参考deque的实现,通过尾链定长数组的增长方式,最大限度减少内存碎片,也提升了分配效率。数组内数据查找采用排序+二分查找实现。由于存储的分片粒度比索引大,在并行读取存储的key时,hash bucket间不会存在交叉,能够最大化并行效率,同时排序操作会紧接着读取操作完成后进行,充分利用cpu cache,也实现了IO和排序的重叠,整个key存储大小为8bytes*64*1000000=488.2MB,所有读取及排序操作时间在200ms左右。

图4 索引

2.5 随机读取实现

        如上一节介绍的,索引在加载完成后即完成了排序操作,后续所有查找即为hash+二分查找,都是只读操作,所有不需要加锁即可完成。在具体读取时使用先hash到slot,再二分查找的方式进行,读取时为了避免多线程在读同一个文件时竞争的问题,用spinlock控制单文件读取,使得性能稳定在60.5W OPS左右,最高可以到60.57W。value读取代码如图5所示。由于采用了写入缓冲,数据会存在于page cache中和具体存储文件中,程序会根据写入offset判断去mmap还是去文件读取。

图5 读取实现代码

2.6 区间读取实现

        作为复赛新增加的功能,区间读取下的IO性能压榨是个很富有挑战性的问题。性能测试阶段,64个线程分别进行2次全量的顺序读取。基础的思路是将这64个线程融合为2次全盘读取。由于全量顺序读也是创新运行实例,第一步同样是加载索引,将所有key和offset读入内存并排序。为了实现多个线程共享读取结果,以每个存储文件分片作为一个基础共享单元,以引用计数控制其生命周期,使用异步线程加载value数据,实现类似电梯算法的缓存算法。具体流程如图6所示。

图6 区间读缓存结构

图7 缓存控制部分变量

        因为分片算法将一个升序区间内的所有KV都放在一个存储文件中,同时hash table的分片也是有序和区间聚合的,所以一个hash table slot中的存储的所有KV都能在同一个存储文件中找到,且当遍历超出区间后,无论是存储文件还是hash table slot中的内容都不会再需要。因此遍历操作有很强的局部聚集性,不仅便于缓存的读取也更有利于CPU cache的命中。

        为了提升遍历操作和IO的重叠率,提升速度,在进行遍历时,请求线程会在所有需要遍历的分片上加引用计数,而服务线程则根据这些标记,在有空余内存块时,完成下一个块的预读。而对于遍历线程和读取线程间内存块的交换则通过一个无锁的栈结构实现,使用栈结构而不是队列,因为刚用完的内存相对于之前用完的内存具有更好的CPU cache命中率。

2.7 一些细节的优化

        由于本次比赛的数据量巨大,为了提升内存间数据拷贝的效率,可以使用SIMD指令进行数据拷贝加速,比如针对多种对齐和非对齐等内存拷贝可以使用AVX2指令集进行优化。具体实现可以使用asmjit或xbyak等动态汇编生成,当然使用gcc的immintrin.h头文件中的函数是一种更快捷的方式,但在调用前需要对CPU指令集支持情况进行判断。

        另外从榜上可以看到414s和415s之间存在一个分界,很大一部分原因是在区间读取时的差距,根据时间统计结果不难发现,在预读线程读取和遍历线程调用遍历回调之间存在预读线程干等的情况,在我的实现中是由于遍历线程都等待一个condition_variable上,在唤醒时由于同一个mutex造成了线程竞争,总共1k次等待造成了约1s的延迟,导致预读线程等待遍历线程的情况。通过改进唤醒机制使64个遍历线程等待在不同condition_variable上,解决了这个问题,但多个condition_variable也是个不小的负担。最后通过使用标志位+spin+yield解决了该问题,合理设置自旋阈值不会对CPU造成太大负担,也能使预读线程不会出现等待交出执行时间片。这个优化能将速度优化到414.23s。

        最后,两次range之间的预读也是一个优化点,即第一次遍历完成后立刻开始下一次预读,这也能提升约100ms,同时在内存中缓存上次range的部分块,也能提升些许的性能。

3 总结

        这是我第三次参加天池程序设计类竞赛,虽然这次比赛的题目看似简单,没有过于复杂的业务逻辑,但往往越是简单基础的功能,越考验基本功。KV数据存储引擎看似简单一个map就能实现的功能,在遇上大规模数据情况下变得异常复杂。特别是这次比赛中用到了新的硬件设备——傲腾SSD,这块神奇的磁盘刷新我对存储设备的认识,也让我学习到了对新设备进行评估测试及在上面进行开发的完整过程。

        由于这次比赛时间段比较特殊,复赛阶段没有太多的空余时间进行研究测试新的方案,有很多优化想法没有付诸实践,只能在初赛的测试结果和框架上修修改改,但这次比赛也让我学习到了很多知识,通过在竞赛交流群中的讨论,了解到了很多新思路新想法,是我受益匪浅。

查看更多内容,欢迎访问天池技术圈官方地址:第一届POLARDB数据库性能大赛-亚军0xCC☣☢比赛攻略_天池技术圈-阿里云天池

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2164213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C#常用数据结构栈的介绍

定义 在C#中&#xff0c;Stack<T> 是一个后进先出&#xff08;LIFO&#xff0c;Last-In-First-Out&#xff09;集合类&#xff0c;位于System.Collections.Generic 命名空间中。Stack<T> 允许你将元素压入栈顶&#xff0c;并从栈顶弹出元素。 不难看出&#xff0c;…

图片如何转化为pdf格式?这几种方法超好用!

图片如何转化为pdf格式&#xff1f;在日常工作与学习中&#xff0c;图片与PDF文件作为两种截然不同的文档格式&#xff0c;各自扮演着重要角色&#xff0c;图片以其直观性著称&#xff0c;能够瞬间捕捉并展示视觉信息&#xff0c;无需额外软件即可快速浏览&#xff0c;但其内容…

免费制作证件照的小程序源码

1、效果展示 证件照制作&#xff0c;证件照免费制作&#xff0c;证件照调用api源码&#xff0c;解析代码。证件照制作小程序包&#xff0c;可以下载程序包&#xff0c;最初级版本免费下载。以上是高级版本。如果你有开发能力的话可以自己写前端&#xff0c;然后以下调用以下api…

粉丝精准!小红书卖儿童绘本项目,单月变现近2w(附详细教程)

AI绘本故事以其创新性、个性化、互动性和教育意义&#xff0c;迎合了宝爸宝妈对高质量儿童读物的需求&#xff0c;同时融合科技与教育&#xff0c;满足了他们对孩子全面发展的期待&#xff0c;因此在小红书上备受追捧。 今天给大家分享一个【小红书卖儿童绘本】项目&#xff0…

Spring Boot使用注解方式整合MyBatis

文章目录 实战讲稿&#xff1a;Spring Boot使用注解方式整合MyBatis课程目标课程内容1. 创建员工映射器接口1.1 创建子包1.2 创建接口 2. 测试员工映射器接口2.1 自动装配员工映射器2.2 测试按标识符查询员工方法2.3 测试查询全部员工方法2.4 测试插入员工方法2.5 测试更新员工…

如何保持测试环境的稳定性?

日常自动化测试中最担心的就是环境不稳定问题。不稳定的测试环境&#xff0c;经常可能导致测试失败。 解决方法&#xff1a;尽量保持测试环境的稳定性&#xff0c;包括硬件、软件和网络等方面。 如何保持测试环境的稳定性&#xff1f; 要保持测试环境的稳定性&#xff0c;可…

从零开始,Docker进阶之路(二):Docker安装

Docker 要求 CentOS7 系统的内核版本在 3.10以上 1.通过 uname -r 命令查看你当前的内核版本 uname -r2. 使用 root 权限登录 Centos。确保 yum 包更新到最新。 yum -y update 慢慢等&#xff0c;小编也是等了十分钟之久 3.卸载旧版本(如果安装过旧版本的话) yum remove do…

元宇宙的未来趋势:Web3的潜在影响

元宇宙&#xff0c;一个日益受到关注的概念&#xff0c;代表着一个沉浸式的虚拟世界&#xff0c;其中用户可以进行社交、商业和娱乐活动。随着技术的发展&#xff0c;Web3逐渐成为推动元宇宙演变的重要力量。Web3以去中心化为核心&#xff0c;利用区块链、智能合约和其他创新技…

加密软件巅峰对决:Ping32 vs 天锐绿盾,谁是企业数据安全的守护者之王?

在信息安全日益重要的今天&#xff0c;企业加密软件已成为保护敏感数据的关键工具。在众多加密产品中&#xff0c;Ping32与天锐绿盾&#xff08;简称绿盾&#xff09;凭借其卓越的性能和丰富的功能&#xff0c;成为了企业用户关注的焦点。那么&#xff0c;在这场加密软件的巅峰…

干货分享 | TSMaster—LIN 唤醒与休眠机制

在汽车总线中常见的唤醒方式有硬线唤醒、网络唤醒和特定信号唤醒&#xff0c;而LIN总线则是通过休眠帧与唤醒电平来实现的&#xff0c;本文将介绍LIN的唤醒与休眠机制。 本文关键词&#xff1a;LIN 网络管理&#xff0c;休眠&#xff0c;唤醒 目录 Catalog 1. 网络管理 2. …

vue3开发中易遗漏的常见知识点

文章目录 组件样式的特性Scoped CSS之局部样式的泄露Scoped CSS之深度选择器CSS Modules在CSS中使用v-bind 非props属性继承组件通信父子组件的相互通信props/$emit父组件传递数据给子组件子组件传递数据给父组件 非父子组件的相互通信Provide/inject全局事件总线 组件插槽作用…

用Python与OpenCV的实践:实时面部对称性分析

目录 思路分析 整体代码 效果展示 总结 在当今计算机视觉领域&#xff0c;人脸识别和分析技术得到了广泛应用。无论是安全验证、社交媒体应用&#xff0c;还是美学研究&#xff0c;人脸特征的提取和分析都是关键技术之一。在这篇博客中&#xff0c;我们将深入探讨一个有趣的…

Arco HomeMenu - 无入侵式的个性化菜单配置插件

关于 Arco HomeMenu Arco HomeMenu 插件是一款对 odoo 菜单功能的增强工具&#xff0c;它的主要功能是允许用户个性化菜单收藏。主要通过分类文件夹及布局功能实现。 Arco HomeMenu 插件主要用于优化用户在 odoo 系统中的操作体验。通过插件功能&#xff0c;用户可以根据自己的…

innovus:如何报告SI

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 报告SI首先要设置si aware,报

79、Python之鸭子类型:没有听过鸭子类型?关键在于认知的转变

引言 不同于Java等静态类型的语言&#xff0c;Python基于动态类型系统的设计理念&#xff0c;使得Python在很多应用场景中&#xff0c;显得更急灵活、高效。而在动态类型系统中&#xff0c;有一个很重要的概念&#xff0c;就是“鸭子类型”。鸭子类型的背后&#xff0c;代表的…

一地通过率高达46.43%!为什么都说软考难?

从2023年上半年到2024年上半年&#xff0c;近三次考试&#xff0c;几个考区的软考通过率基本不超过13%。 然而根据近日陕西省科技资源统筹中心公布的数据&#xff0c;从1987年到2024年&#xff0c;陕西软考的总拿证率竟然高达46.43%。软考真的有大家认为的那么难吗&#xff1f;…

Unity场景内画车道线(根据五阶曲线系数)

之前做过使用Dreamteck Splines插件构建车道线之前需求是给定车道线的点位&#xff0c;根据点位来进行构建。 由于AI识别出来的点位不线性&#xff0c;画出来的车道线经常是歪七扭八&#xff0c;所以使用五阶曲线系数进行构建。 使用在线图形计算器进行测试构建&#xff0c;公式…

DATEDIF 函数用不了?DATEDIF函数怎么用我来告诉你

大家好&#xff0c;这里是效率办公指南&#xff01; &#x1f4c5; 在处理与时间相关的数据时&#xff0c;DATEDIF函数是Excel中一个非常有用的工具。DATEDIF函数可以计算两个日期之间的差异&#xff0c;无论是天数、月数还是年数。这在处理年龄计算、工龄统计或任何需要日期差…

大健康管理系统|健康综合咨询问诊平台设计与实现(源码+数据库+文档)

大健康管理系统目录 目录 健康综合咨询问诊平台设计与实现 一、前言 二、系统功能设计 三、系统实现 5.1用户信息管理 5.2 医生信息管理 5.3科室信息管理 5.1新闻信息管理 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文…

docker笔记_数据卷、挂载

docker数据存储 概述数据卷&#xff08;Volumes&#xff09;特点操作 绑定挂载&#xff08;Bind Mounts&#xff09;内存挂载&#xff08;tmpfs&#xff09;总结 概述 镜像构建过程中&#xff0c;所产生的layer都是只读层&#xff0c;只有在创建容器时才会生成一个可写的容器层…