OpenGauss MOT 实现技术研究

news2025/2/25 21:34:09

目录

一、概述

二、技术细节和OpenGauss中的实现

1、内存表在内存中的组织

2、事务并发控制算法

3、检查点算法


这篇文档写于2022年6月份,今天打算发到网上,重读时发现可能opengauss mot现在的代码已经有所改变,文中有些代码分支可能已经过时,但是许多思想的出发点仍然很有参考价值,很有助与理解opengauss mot和mot表。

一、概述

OpenGauss的MOT引擎,目的在于将表数据存储到内存,降低IO对性能的影响,且保证可持久性(即已提交的事务,不因断电而丢失)。同时,MOT引擎对表的内存结构、事务的并发控制算法、日志算法、检查点算法使用个更加优化的模型。这些优化针对的是高并发的OLAP业务,事务并发控制使用的是基于有效性检查的乐观算法,这种算法更适于并发时,读写或写写冲突较少,读占大多数的场景。

MOT的设计上参考了下面几篇论文:

1、《Cache Craftiness for Fast Multicore Key-Value Storage》

2、《Speedy Transactions in Multicore In-Memory Databases》

3、《Low-Overhead Asynchronous Checkpointing in Main-Memory Database Systems》

论文1主要描述了一种称为masstree的B+树,创新点在于通过减少对全局变量的争用,提高多核服务器的读写并发性能。一个B+树可以看做一个key-value数据库,以树结构组织的数据库,增删改查时,访问记录,要经过树的内部节点,且内部节点可能改变,当多个线程并发操作时,会对树的中间节点加锁,以防止树结构改变导致读写单个记录时的不一致性,然而这又引入了性能问题。学术界和工业界一直在努力解决一致性和性能的矛盾。论文1中的Masstree数据结构及其操作算法,就是最新的解决方案。

论文2主要描述了基于有效性检查的事务并发控制算法(OCC),创新点也在于通过减少锁和对全局变量的争用,提高多核服务器的读写并发性能。论文1保证了单个记录(key-value)的原子性读写,论文2中保证了包含多个读写操作的事务的原子性。论文2中的事务底层,是单条记录的读写,要调用论文1中的接口。论文1还给出了写redo日志的方法,以保证恢复时的一致性。

论文3描述了对内存数据库做检查点和恢复的算法(CALC)。内存数据库仍然要求崩溃时保证持久性。这是通过磁盘对内存数据库(表)的完整备份和wal日志实现的。内存数据库(表)在磁盘中的完整备份,就是内存数据库所谓的检查点,可见内存数据库的检查点概念和磁盘数据库的检查点概念是完全不同的。对内存数据库做检查点,就是在磁盘中保存一份内存数据库(表)的完整备份,但是,内存中的数据量通常很大,保存到磁盘需要一段时间,这段时间内,内存数据库内容可能改变,如何保证保存到磁盘的备份是一致的呢?

这就是论文3要解决的问题,其算法的特点有:

  1. 创建检查点时不需要数据库停止运行修改操作,仍然能得到一致性的备份。
  2. 不需要数据库支持完整的多版本,不会带来多版本导致的内存膨胀,仍然能得到一致性的备份。
  3. 不需要借助日志,仍然能得到一致性的备份。

其关键就是创建检查点的算法流程。

总的来说,OpenGauss实现了三篇论文中的算法,有些算法没有完全实现,可能是出于工程上的考虑,OpenGauss的代码实现已经很好,但还有改进空间。这次对论文和OpenGauss代码的研究,让我感到内存数据库是未来OLTP数据库的发展方向,未来可能还会有更好的设计出现。

二、技术细节和OpenGauss中的实现

这一章还是以三篇论文为纲,将OpenGauss MOT所有的技术分为三大块:

1、内存表在内存中的组织

OpenGauss中,MOT表数据及其元数据所在的内存,称为全局内存,每个事务(线程)还有私有内存,计算时事务从全局内存读取数据到私有内存,中间结果先写到私有内存,提交时,经有效性检查,再写到全局内存。全局内存中,一个MOT::Table对象存储一个表的元数据,做检查点时,会把所有MOT::Table对象保存到磁盘文件,启动重建时从磁盘文件重建MOT::Table对象。

全局内存中,表数据的组织结构就是论文1所说的masstree,它是一种B+树变种,每个表就是一个masstree,叶子节点就是一个记录,对记录的增删改查,就是对叶子节点的增删改查,操作要使用masstree提供的接口,接口内部自动会处理树结构变化和多线程并发操作的冲突,保证单个记录(key-value)读写的原子性。

 上图就是masstree数据结构的示意图,把记录的主键(primay key)看做一个字符串,取前8个字节作为键值形成一个B+Tree(红色框),这是masstree的第一层,如果主键字符串长度超过8字节(例如14个字节),那么后6个字节作为键值再形成一个子B+Tree(蓝色框),前8个字节作为键值的叶子节点(红色圈),有一个指针指向子B+Tree,这个子B+Tree作为masstree的第二层,对于字节数更长主键以此类推,可以形成多层子树。

例如,主键值为‘abcde’的记录,它会落到masstree的第一层B+Tree的叶子节点(黄色圈),主键值为’abcdefgh’的记录,它也会落到masstree的第一层B+Tree的叶子节点(绿色圈),而主键值为‘abcdefgh123456’,它会落到masstree的第二层B+Tree的叶子节点(蓝色圈)。

Masstree提供了put(basic_table::insert)、get(basic_table::find)、remove(basic_table::remove)操作接口,这些操作保证原子性,通过提供尽量高的并发性能(吞吐量),事务读写单个记录时会调用这些接口。

在OpenGauss中,masstree既是内存表的组织结构,又是表的索引结构。如果不为表创建索引,也默认创建masstree索引,以rowid为key,行记录为value。

OpenGauss代码中的masstree数据结构和操作,并不是从头自己写的,而是引用了第三方masstree库。

2、事务并发控制算法

论文1里的并发处理,指的是masstree保证单个记录(key-value)多个线程读写(删除、插入)时的原子性,而多个key-value的操作组成事务,事务的并发控制算法,解决的是多个事务在并发执行时的原子性,和次前提下如何尽量提高并发性,以及冲突时的处理办法。

OpenGauss MOT引擎的事务并发控制机制,与磁盘表不同,与OpengGauss中已有的磁盘表引擎互相独立,所以目前不支持将对磁盘表的操作和MOT表的操作放在同一个事务中(更新,OpengGauss5.0.0已经支持)。MOT引擎使用的是论文2中描述的基于有效性校验的事务并发控制算法(OCC)。

算法描述如下:

事务计算时,把需要读写的数据从全局内存(masstree表),复制到事务本地内存,在commit以前,insert、delete、update结果都缓存在本地内存。(每个事务在commit前的计算过程中,只会读共享内存,不会写共享内存。)

当Commit命令发出,提交分三个阶段:

第一阶段:事务写记录集合,在全局内存中逐条加锁,使用有超时的自旋锁,如果超时(各种原因,例如正被其它事务锁住修改、删除),事务终止。所有记录成功获得锁则进入第二阶段。

如过后面的几步有效性检验通过,成功commit,可以认为,整个事务是在写集合成功加锁这一瞬间完成的。

第二阶段:对事务读记录集合进行校验——事务运行中,读取的记录包含TID(OpenGauss中是一个全局唯一的64位无符号整数),是这条记录的状态标志,所谓校验,就是比较每条记录的TID和此时的全局内存中,对应记录的TID是否相同,如果不同,说明在这个事务运行过程中,其它事务修改了并提交了这条记录,则本事务终止,如果相同,说明整个事务执行过程到第一阶段为止,没有其它事务修改这条记录,则进入第三阶段。

第三阶段:到此为止,写记录集合已经加锁,其它事务不可能修改写记录集合,且本事务整个运行过程中,都没有其它事务修改本事务所涉及的读集合,则此时如果直接修改写集合在全局内存对应的所有记录,是原子性的操作(一致的)。因此,就将写集合修改到全局内存对应的记录,当然还要写redo日志,然后释放锁,commit命令返回。

为了便于理解,这里忽略了论文中的一些细节,例如论文在第一步会读取全局的时间戳变量Epoch,第二步使用它生成事务ID(TID),第三步,如果有修改,将新生成的TID写到被修改记录的全局内存记录中。但是OpenGauss的代码没有这个设计,而是用全局变量CSNManager::m_csn表示事务ID。当通过有效性确认,将修改写到全局内存时,调用CSNManager::GetNextCSN()获得最新的事务ID,这个操作原子性地对全局变量CSNManager::m_csn加1(如果失败则不断尝试直到成功),多个并发事务可以同时调用CSNManager::GetNextCSN()修改CSNManager::m_csn,虽然修改是原子性的,不会导致不一致,但是这导致了争用,并没有达到silo中使用Epoch以避免争用的目的。但是OpenGauss开发人员认为这样并不会带了来争用,见这个关于CSNManager::m_csn的回复:

多事务并发调用CSNManager::GetNextCSN(),导致争用 - Community - mailweb.opengauss.org

MOT对应的日志为redo日志,不支持多版本(更新,opengauss5.0.0支持多版本了)、不支持undo log。有三种可选redo策略:异步日志、同步日志、同步组提交日志。异步日志:将日志记录加到日志缓冲区,然后客户端commit返回,不保证已提交事务不丢失。同步日志:等的日志记录落盘,然后commit返回,保证已提交事务不丢失。同步组提交日志:若干个事务作为一个组,它们的日志记录写到这个组的缓冲区(包括了commit日志记录),但是客户端commit不返回,而是等待,当超时或者缓冲区满,由一个线程将缓冲区记录落盘,然后同一组的、已提交的事务,继续执行,客户端commit返回。

虽然OpenGauss也实现了组提交日志,也不是完全遵循论文2中的组提交设计,而是有较大差别:

1、提交时生成事务ID(TID),OpenGauss的事务通过竞争设置一个全局变量,获得本事务的全局唯一CSN(代码在CSNManager::GetNextCSN),这个操作会带来争用,而论文2的方案,是用全局变化的Epoch,加本事务自己生成的数字,生成局部的TID,虽然不保证全局唯一,但是避免了同时写共享变量,然后以其算法保证提交和恢复的正确性。

2、OpenGauss中,写日志是在通过了commit协议第二阶段的有效性确认,在第三阶段修改已提交记录之前(OccTransactionManager::WriteChanges)之前做的。代码主要可以看GroupSyncRedoLogHandler::WriteToLog、CommitGroup::WaitLeader、CommitGroup::WaitMember、CommitGroup::CommitInternal这几个函数。多个需要写盘的事务,按照先来后到的顺序,加入事务组,一般第一个成为Leader(GroupSyncRedoLogHandler::WriteToLog),加入组时要将自己的m_redoBuffer加入组(CommitGroup::AddToGroup),然后并不立即返回,而是等待返回条件,条件有两个a、等待写盘的事务数CommitGroup::m_groupSize达到阈值。b、超时。条件的检查由leader事务来做,达到条件后也由leader事务线程通知其它事务,leader负责将缓冲区写盘(CommitGroup::WaitLeader)。这个通知的机制使用C++11标准库的条件变量实现(std::condition_variable)。这一点与论文2使用Epoch实现组提交以避免写共享变量的设计又不一样了。

3、检查点算法

OpenGauss选择了论文3描述算法,作为MOT的检查点算法。所谓内存数据库的检查点,就是所有表记录在磁盘中的一个备份,数据库崩溃时,可以从这个备份加redo日志恢复,达到与磁盘数据库相同的效果——已提交事务不丢失。

论文3创建检查点的算法如下:

1、有一个专门的检查点线程,执行检查点流程。

2、事务线程需要配合检查点线程,做一些额外的操作。

3、检查点流程分为5个阶段,分别称为Rest、Prepare、Resovle、Capture和Complete,检查点线程控制检查点管理器的状态(CheckpointManager)在这五个阶段循环往复,在不同的阶段,事务提交时要做不同的额外操作。OpenGauss对算法的实现,与论文稍有不同,下面的描述以OpenGauss的实现为准。

4、内存中,每个活跃事务对应一个TxnManager对象,其中的成员变量m_checkpointPhase,记录了commit开始时这个事务所处的检查点阶段,称为事务开始阶段。

5、Rest阶段,此时检查点尚未开始,事务提交除了记录自己所在的阶段不做任何额外操作。当有信号通知检查点线程开始做检查点时,检查点线程会确认前一阶段(Complete)开始的事务都结束了,然后进入下一阶段(Prepare)。

6、Prepare阶段,如果事务在这个阶段开始commit,那么它写记录时(通过了有效性检查)需要将旧值保留,称为stable version,如果这个事务在Prepare阶段结束,那么就删除这个stable version,否则保留。检查点线程确认前一阶(Rest)段开始的事务都结束了,然后进入下一阶段(Resolve)。

7、Resolve阶段,这一阶段会创建保存检查点的目录,准备将文件写到这个目录里,这个阶段不允许事务开始提交,但允许事务结束提交。检查点线程确认前一阶(Prepare)段开始的事务都结束了,然后进入下一阶段(Capture)。

8、前三个阶段切换的代码在CheckpointManager::CreateSnapShot中。

9、Capture阶段,这个阶段开启一个线程遍历所有表的记录,将记录写盘,如果发现记录有对应的stable version则将stable version写盘,并且删除完成写盘的stable version,当所有表完成写盘,Capture阶段结束进入Complete阶段。在这个阶段,事务提交可以开始也可以结束,写记录时,需要将旧值保留(stable version)。

10、Complete阶段,这个阶段开始的事务行为与Rest相同,即不用创建stable version。只有等到Capture阶段开始的事务结束,Complete阶段才能结束,结束前还要做一些收尾工作。

11、当事务和检查点按照上面的流程操作时,实际上相当于做检查点时,使用了一个局部的快照,这个快照比完整实现多版本的快照占用内存要少的多,而且不依赖redo日志。

12、OpenGauss没有实现CALC论文中的增量检查点,即每次做检查点时,按照上面的步骤,是把整个表保存到磁盘的,而不是相对于前一个检查点变化的记录。这每次做检查点时实际IO是挺大的,因此不能频繁做检查点,这种设计必然要求redo日志会比较多。检查点的创建和内存表重建虽然不依赖redo日志,但是数据库要恢复到最新状态还是需要redo日志的。例如,恢复时使用这个检查点恢复数据库到较早的一个状态,然后使用redo日志追到最新的状态。华为的开发人员认为这种全量的检查点不会有太大问题:OpenGauss没有实现pCALC吗?(OpenGauss does not implement pCALC?) - Community - mailweb.opengauss.org

13、OpenGauss的redo日志,是一种“物理日志”,即是对某个表的增删改操作加上要增删改的数据,但不是SQL形式的,而是二进制形式的,恢复时以非事务方式执行这些操作,直接调用masstree的接口。

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

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

相关文章

高效远程控制另一台电脑的3种方法,提升工作效率!

如何从我的电脑控制另一台电脑? “我妈妈的电脑出了问题,我需要帮她修理下,但是我不能亲自去进行故障排除。我应该如何从我的电脑远程控制另一台电脑,并提供远程支持?” 如何远程控制另一台电脑?&a…

基于SpringBoot+Vue的学生综合测评系统设计与实现

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

http缓存详解

为什么要缓存 通过http协议在客户端和服务端建立连接需要消耗时间,重复访问同一个资源,增加访问服务器数据资源的成本,因此,利用浏览器的缓存机制重用以前获取的数据来优化性能 1、 减少了网络延迟,加快了页面响应速度…

分析全志Tina打包流程,并在buildroot上来实现打包生成tina镜像,支持PhoenixSuit烧写

本文转载自全志在线:https://bbs.aw-ol.com/topic/1532/ 步骤简述 首先记录下官方 tina-sdk 打包的log输出。 bookvirtual-machine:~/D1s-Core/tina-d1-h$ pack ---- PACK_CHIP sun20iw1p1 PACK_PLATFORM tina PACK_BOARD d1-h-nezha PACK_KE…

Vue.js 中的指令和组件详解

Vue.js 中的指令和组件详解 在 Vue.js 中,指令和组件是两个非常重要的概念,它们都可以用来扩展 Vue.js 的功能,但它们之间有一些不同之处。本文将对 Vue.js 中的指令和组件进行详细的介绍,并附上相关的代码示例。 指令 在 Vue.j…

华为OD机试真题 Java 实现【找最小数】【2023 B卷 100分】,附详细解题思路

一、题目描述 给一个正整数num1,计算出新正整数num2,num2为num1中移除N位数字后的结果,需要使得num2的值最小。 二、输入描述 输入的第一行为一个字符串,字符串由0~9字符组成,记录正整数num1,num1的长度…

2.1 网络io、io多路复用select/poll/epoll、基于事件驱动的reactor

目录 一、网络IO请求二、一请求一线程三、IO多路复用——select的通俗理解1、select函数2、accpet函数3、recv函数 四、IO多路复用——poll五、IO多路复用——epoll1、epoll_create2、epoll_ctl3、epoll_wait4、epoll_event5、边缘触发和水平触发) 五、区别对比1、s…

axios、跨域与JSONP、防抖和节流

文章目录 一、axios1、什么是axios2、axios发起GET请求3、axios发起POST请求4、直接使用axios发起请求 二、跨域与JSONP1、了解同源策略和跨域2、JSONP(1)实现一个简单的JSONP(2)JSONP的缺点(3)jQuery中的J…

Wwise内存问题

1)Wwise内存问题 ​2)安卓平台特效显示不一致的问题 3)多个矩形小方块组成的地形接缝处有黑线问题 这是第339篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社区帖子等技术知识点,助力大…

nginx系列第七篇:结合nginx讨论“惊群”问题

目录 1.什么是惊群 2.linux下socket通信之accept"惊群"现象 3.select/poll/epoll"惊群"现象 4.nginx中的惊群处理 1.什么是惊群 "惊群"是多个进程(线程)阻塞在某个系统调用上等待事件触发,当事件触发后,这些睡眠的进程…

数位dp训练笔记

依稀还记得去年寒假的时候对数位dp的恐惧达到了顶峰,打死也不想做一题,也是怎么学都学不会,甚至板子也只是真的去网上copy了一份,自己也都不理解。(羞愧) 这个状态持续了一年多(羞愧羞愧&#…

Windows操作/文件/设置/DOS 记录

目录 1.系统操作 1.环境变量 2.文件夹操作 1.显示隐藏文件夹 3.DOS窗口 1.DOS窗口中docker切换管理员root /]#身份: docker run -it centos​编辑 4.文件操作 1.图片分辨率无损修改尺寸(例1280x800) 2.图片修改png/jpg文件后缀类型 1.系统操作 1.…

Python 语句

文章目录 一、条件语句1、顺序语句2、条件语句3、缩进和代码块4、条件语句练习5、空语句 二、循环语句1、while2、for3、break和continue 一、条件语句 1、顺序语句 从上到下依次执行 2、条件语句 Python中使用if else关键字表示条件语句. ①if if expression:do_somethi…

PCB板的Mark点设计对SMT重要性

Mark点也称光学点、基准点,是电路板元器件组装中,PCBA应用于自动贴片机上的位置识别点。 Mark点的选用,直接影响到自动贴片机的贴片效率,因此在设计时,需要设计好Mark点以及其在板内的位置。 Mark点的设计 1、布局位…

String s = new String(“xyz“) 创建了几个对象?

这个问题相信每个学习 java 的同学都不陌生,作为一个经典的面试题,到现在工作这么多年了我真是认为挺操蛋的一个问题,在网上到现在你仍然可以看见很多讨论这个问题的人,其中不乏工作很多年的人都有争论,我认为还是有必…

GreatSQL删除分区慢的跟踪

GreatSQL删除分区慢的跟踪 背景 某业务系统,每天凌晨会删除分区表的一个分区(按天分区),耗时较久,从最开始的30秒,慢慢变为1分钟,影响到交易业务的正常进行。 在测试环境进行了模拟,复现了删除分区慢的情…

市场火爆!三大发展优势加速汽车零部件行业布局

当前,中国新能源汽车自主品牌崛起,为汽车零部件发展带来新机遇;有别于传统汽车零部件市场,新能源领域,主机厂标准提升,对数字化要求逐渐提高,汽车零部件企业的智能制造异常重要,企业…

二分类结局变量Logistic回归临床模型预测(二)——3. 单因素多因素logistic回归分析及三线表(三)

本节讲的是二分类结局变量的临床模型预测,与之前讲的Cox回归不同,https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/124088364https://lijingxian19961016.blog.csdn.net/article/details/1300…

1929-2022年全球站点的逐月最低气温(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标,其中又以气温指标最为常用!说到气温数据,最详细的气温数据是具体到气象监测站点的气温数据! 之前我们分享过1929-2022年全球气象站…

Qt学习之旅 -信号与槽

文章目录 点击关闭窗口自定义信号和槽自定义信号和槽解决重载问题信号和连接信号断开连接Qt4版本信号槽连接Lambda表达式 点击关闭窗口 connect(信号发送者,发送的具体信号,信号接收者,型号的处理(槽slot));这里自定义的MyPushButton与QPushButton别无二…