【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(7)

目录
openGauss数据库SQL引擎

openGauss数据库执行器技术

openGauss存储技术

一、openGauss存储概览

二、openGauss行存储引擎

三、openGauss列存储引擎

四、openGauss内存引擎

Ⅰ.内存引擎的兼容性设计

Ⅱ.内存引擎索引

Ⅲ.内存引擎的并发控制

Ⅳ.内存引擎的内存管控

Ⅴ.内存引擎的持久化

openGauss事务机制

openGauss数据库安全

openGauss存储技术

四.openGauss内存引擎

内存引擎的并发控制03
内存引擎的并发控制机制采用OCC,在操作数据冲突少的场景下,并发性能很好。

内存引擎的事务周期以及并发管控组件结构,如图42所示。

【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(7)_第1张图片

图42 内存引擎的事务周期以及并发管控组件结构

这里需要解释一下,内存引擎的数据组织为什么整体是一个接近无锁化的设计。

除去以上提到的Masstree本身的无锁化机制外,内存引擎的流程机制也进一步最小化了并发冲突的存在。

每个工作线程会将事务处理过程中所有需要读取的记录,复制一份至本地内存,保存在read-set(读数据集)中,并在事务全程基于这些本地数据进行相应计算。相应的运算结果保存在工作线程本地的write set(写数据集)中。直至事务运行完毕,工作线程会进入尝试提交流程,对read set(读数据集)和write set进行validate(检查验证)操作并在允许的情况下对write set中数据对应的全局版本进行更新。

这样的流程,会把事务流程中对于全局版本的影响,缩小到validation的过程,而在事务进行其他任何操作的过程中都不会影响到其他的并发事务。并且,在仅有的validation(检查验证)过程中,所需要的也并不是传统意义上的锁,而仅是记录头部信息中的代表锁的数位(lock bit)。相应的这些考虑,都是为了最小化并发中可能出现的资源争抢以及冲突,并更有效地使用CPU缓存。

同时read set(读数据集)和write set(写数据集)的存在,可以良好地支持各个隔离级别,不同隔离级别可以通过在validation(检查验证)阶段对read set(读数据集)和write set(写数据集)进行不同的审查机制来获得。通过检查两个set(数据集)中行记录在全局版本中对应的lock bit(锁定位)以及行头中的TID结构,可以判断自己的读、写与其他事务的冲突情况,进而判断自己在不同隔离级别下是否可以commit(提交)、或是需要abort(终止)。同时由于Masstree中Trie node中存在版本记录,Masstree的结构性改动(insert/delete,插入/删除)会更改相关Trie node(节点)上面的版本号。因此维护一个Range query(范围查询)涉及的node set(节点集),并在validation(检查验证)阶段对其进行对比校验,可以比较容易地在事务提交阶段检查此Range query所涉及的子集是否有过变化,从而能够检测到Phantom(幻读)的存在,是一个时间复杂度很低的操作。

内存引擎的内存管控04
由于内存引擎的数据是全内存态的,因此可以按照记录来组织数据,不需要遵从页面的数据组织形式,从而从数据操作的冲突粒度这一点上有着很大优势。摆脱了段页式的限制,不再需要共享缓存区进行缓存以及与磁盘间的交互淘汰,设计上不需要考虑IO以及磁盘性能的优化(比如索引B+ 树的高度以及HDD(Hard Disk Driv,磁盘)对应的随机读写问题),数据读取和运算就可以进行大量的优化和并发改良。

由于是全内存的数据形态,内存资源的管控就显得尤为重要,内存分配机制及实现会很大程度上影响到内存引擎的计算吞吐能力。内存引擎的内存管理主要分为3层,如图43所示。

【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(7)_第2张图片

图43 内存引擎的内存管理示意图

下面分别对3层设计进行介绍:

(1) 第一层为内存引擎自身,包含了临时的内存使用以及长期的内存使用(数据存储)。

(2) 第二层为对象的内存池,主要负责为第一层对象如表、索引、行记录、Key值、以及Sentinel(行指针)提供内存。该层从底层索取大块内存,再进行细粒度的分配。

(3) 第三层为资源管理层,主要负责与操作系统之间的交互,以及实际的内存申请。为降低内存申请的调用开销,交互单位一般在2 MB左右。此层同时也有内存预取和预占用的功能。

第三层实际上是非常重要的,主要因为:

(1) 内存预取可以非常有效的降低内存分配开销,提高吞吐量。
(2) 与NUMA库进行交互的性能成本非常高,如果直接放在交互层会对性能产生很大影响。
内存引擎对短期与长期的内存使用针对NUMA结构适配的角度也是不同的。短期使用,一般为事务或session(会话)本身,那么此时一般需要在处理该session的CPU核对应的NUMA节点上获取本地内存,使得transaction(交易)本身的内存使用有着较小的开销;而长期的内存使用,如表、索引、记录的存储,则需要NUMA概念中interleaved内存,并且要尽量平均分配在各个NUMA节点上,来防止单个NUMA节点内存消耗过多带来的性能下降。

短期的内存使用,也就是NUMA角度的本地内存,也有一个很重要的特性,就是这部分内存仅供本事务自身使用(比如复制的读取数据以及做出的更新数据),因此也就避免了这部分内存上的并发管控。

内存引擎的持久化05
内存引擎基于同步的WAL机制以及Checkpoint(检查点)来保证数据的持久化,并且此处通过兼容openGauss的WAL机制(即Transaction log,事务日志),在数据持久化的同时,也可以保证数据能够在主备节点之间进行同步,从而提供RPO=0的高可靠以及较小RTO的高可用能力。

内存引擎的持久化机制如图44所示。

【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(7)_第3张图片

图44 内存引擎的持久化机制

可以看到,openGauss的Xlog模块被内存引擎对应的manager(管理器)所调用,持久化日志通过WAL的writer线程(刷新磁盘线程)写至磁盘,同时被wal_sender(事务日志发送线程)调起发往备机,并在备机wal_receiver(事务日志接收线程)处接收、落盘与恢复。

内存引擎的Checkpoint也是根据openGauss自身的Checkpointer机制被调起。

openGauss中的Checkpoint机制是通过在做Checkpoint时进行shared_buffer(共享缓冲区)中脏页的刷盘,以及一条特殊checkpoint日志来实现的。内存引擎由于是全内存存储,没有脏页的概念,因此实现了基于CALC的Checkpoint机制。

这里主要涉及一个部分多版本(partial multi-versioning)的概念:当一个Checkpoint指令被下发,使用两个版本来追踪一个记录:活跃(live)版本,也就是该记录的最新版本;稳定(stable)版本,也就是在Checkpoint被下发、形成虚拟一致性点时此记录对应的版本。在一致性点之前提交的事务需要更新活跃(live)和稳定(stable)两个版本,而在一致性点之后的事务仅更新活跃(live)版本本保持stable版本不变。在无Checkpoint状态的时候,实际上稳定(stable)版本是空的,代表着stable与live版本在此时实际是相同的值;仅有在Checkpoint过程中,在一致性点后有事务对记录进行更新,此时才会需要根据双版本来保证checkpoint与其他正常事务流程的并行运作。

CALC(Checkpointing Asynchronously using Logical Consistency,逻辑一致性异步检查点)的实现有5个阶段:

(1) rest(休息)阶段:这个阶段内,没有Checkpoint(检查点)的流程,每个记录仅存储live版本。
(2) prepare(准备)阶段:整个系统触发Checkpoint后,会马上进入这个阶段。在这个阶段中事务对读写的更改,也会更新live版本;但是在更新前,如果stable版本不存在,那么在更新live版本前,live版本的数据会被存入stable版本。在此事务的更新结束,在放锁前,会进行检查:如果此时系统仍然处于prepare阶段,那么刚刚生成的stable版本可以被移除;反之,如果整个系统已经脱离prepare阶段进入下一阶段,那么stable版本就会被保留下来。
(3) resolve(解析)阶段:在进入prepare阶段前发生的所有事务都已提交或回滚后,系统就会进入resolve阶段,进入这个阶段也就代表着一个虚拟一致性点已经产生,在此阶段前提交的事务相关的改动都会被反映到此次Checkpoint中。
(4) capture(捕获)阶段:在prepare阶段所有事务都结束后,系统就会进入capture阶段。此时后台线程会开始将Checkpoint对应的版本(如果没有stable版本的记录即则为live版本)写入磁盘,并删除stable版本。
(5) complete(完成)阶段:在Checkpoint写入过程结束后,并且capture阶段中进行的所有事务都结束后,系统进入complete阶段,系统事务的写操作的表现会恢复和rest阶段相同的默认状态。
CALC有着以下优点:

(1) 低内存消耗:每个记录至多在Checkpoint时形成两份数据。在Checkpoint进行中如果该记录stable版本和live版本相同,或在没有checkpoint的情况下,内存中只会有数据自身的物理存储。
(2) 较低的实现代价:相对其他内存库Checkpoint机制,对整个系统的影响较小。
(3) 使用虚拟一致性点:不需要阻断整个数据库的业务以及处理流程来达到一份物理一致性点,而是通过部分多版本来达到一个虚拟一致性点。
至此,“openGauss存储技术”章节全部结束,下一篇文章将开始对“openGauss事务机制”章节进行讲解,敬请期待......

你可能感兴趣的