该论文详细介绍了opengauss内存库的工程实现,原理请参考《Speedy Transactions in Multicore In-Memory Databases》 https://ata.alibaba-inc.com/articles/216581
ABSTRACT
GaussDB和开源版本的openGauss是TP型数据库,本文展示了其面向main memory和多核的优化,在TaiShan服务器上TPCC有2.5倍的提升。
1. INTRODUCTION
2012年研发OLAP,2015年发布,FusionInsight MPPDB,GaussDB200
2017年研发下一代OLTP,为了满足来自高端用户的5个需求:
- 基于share-nothing架构的scale-out能力;
- 多核的scale-up能力;
- 大内存;
- SQL兼容;
- 高可用;
策略是基于GaussDB研发一款内存数据库,可以服用GaussDB的SQL兼容和HA能力。
基于PostgreSQL9.2,继承了MPPDB的线程模型。
本文要讨论GaussDB的内存数据库:Memory-Optimized Tables,下文简称为mot。
1.1 System Architecture
GaussDB的整体架构如下图所示,典型的sharenothing系统:单机DB+2PC和全局事务管理。mot做为单机的存储引擎,因此可以完整复用已有的SQL,执行器,分布式事务,HA等。
mot的内存管理:感知NUMA的内管管理,2MB chunk
1.2 Internal Architecture
mot底层使用无锁的masstree,并发控制协议使用Silo,并对进行了工程上的增强。Silo的论文参考《Speedy Transactions in Multicore In-Memory Databases》
mot在使用masstree时key的长度从29字节增加到了45字节。
mot并发控制协议的选择考虑如下因素:
- OCC:Silo和TicToc分3个阶段。并发事务无需等待,仅仅在validation阶段做检测,适当的abort;
- Encounter time locking (ETL):乐观读,写者上锁。因此,并发的写者能互相感知并做abort。ETC和相比OCC:一方面能提前检测冲突,减少无用功;另一方面能加速Read-after-writes;
- Pessimistic concurrency control (2PL):
结论:OCC更加适合多核(参考《Staring into the abyss: An evaluation of concurrency control with one thousand cores》)。mot选择了Silo的算法,相比于TicToc,Silo算法比较简洁而且大部分负载情况下性能表现差不多。
mot表大小受限于内容大小,当然可以进一步优化来提升表大小,比如:anti-caching和tiering。
2. ADJUSTING THE ENGINE
2.1 Indexing and Storage
引入类似HOT机制,在更新一个行时不用每次都更新索引树,而是跟新这一行的sentinels(类似BTree索引中的IndexTuple)。
2.1.1 Optimistic inserts优化
在Silo基础上,mot完善了基于OCC的insert优化。
2.1.1.1 Motivation.
OCC的3个阶段中,insert先在本地操作仅仅在commit时检测冲突。因此需要优化:
- 并发事务insert相同的key,如何保证唯一性;
- insert的事务最终abort;
- 同一个事务插入相同的key;
- 如何insert多个索引树;
2.1.1.2 Implementation Details.
Silo处理插入key为K流程:
- 如果K已经对应一个已提交record,本事务abort;
- 否则,生成record为R,状态为ABSENT,索引树中插入K->R;
- 如果发现已存在ABSENT的record,则直接复用(先成功插入者为准);
mot对Silo插入流程的改进:
- DoubleRow:同一行插入到在不同索引树上,导致部分索引树冲突;
- Self Inserted:事务需要识别索引树上哪些ABSENT状态的record是自己插入的;
2.1.2 Non-unique indexes
在key之后增加后缀:8个字节的row指针。
2.2 Concurrency Control
read after write hazard:occ实现的事务中,如何读取本事务刚刚写入的数据。
mot的优化:实现了一个access-set结构,记录本事务read,update,insert,deleted集合。
维护每个key的状态的状态转换。提交时从access-set上提取出write-set和insert-set。
和Silo不同的是,mot支持了RC隔离级别:不对read-set做校验。
read-set:如果高于RC级别,对read-set做冲突检测;
write-set:对row上锁;
insert-set:对sentinel上锁;
3. INTEGRATION WITH GAUSSDB
由于GaussDB是基于早起PostgreSQL做的开发,并没有storage engine的概念,因此mot是通过FDW机制来实现。同时,也需要对原有FDW做相应的扩展,比如:在create table/index时在本地触发对mot的DDL操作。
3.1 Tables and Indexes Created and Used
创建FDW函数:ValidateTableDef
fdwroutine->AddForeignUpdateTargets = MOTAddForeignUpdateTargets; fdwroutine->GetForeignRelSize = MOTGetForeignRelSize; fdwroutine->GetForeignPaths = MOTGetForeignPaths; fdwroutine->GetForeignPlan = MOTGetForeignPlan; fdwroutine->PlanForeignModify = MOTPlanForeignModify; fdwroutine->ExplainForeignScan = MOTExplainForeignScan; fdwroutine->BeginForeignScan = MOTBeginForeignScan; fdwroutine->IterateForeignScan = MOTIterateForeignScan; fdwroutine->ReScanForeignScan = MOTReScanForeignScan; fdwroutine->EndForeignScan = MOTEndForeignScan; fdwroutine->AnalyzeForeignTable = MOTAnalyzeForeignTable; fdwroutine->AcquireSampleRows = MOTAcquireSampleRowsFunc; fdwroutine->ValidateTableDef = MOTValidateTableDef; fdwroutine->PartitionTblProcess = NULL; fdwroutine->BuildRuntimePredicate = NULL; fdwroutine->BeginForeignModify = MOTBeginForeignModify; fdwroutine->ExecForeignInsert = MOTExecForeignInsert; fdwroutine->ExecForeignUpdate = MOTExecForeignUpdate; fdwroutine->ExecForeignDelete = MOTExecForeignDelete; fdwroutine->EndForeignModify = MOTEndForeignModify; fdwroutine->IsForeignRelUpdatable = MOTIsForeignRelationUpdatable; fdwroutine->GetFdwType = MOTGetFdwType; fdwroutine->TruncateForeignTable = MOTTruncateForeignTable; fdwroutine->VacuumForeignTable = MOTVacuumForeignTable; fdwroutine->GetForeignRelationMemSize = MOTGetForeignRelationMemSize; fdwroutine->GetForeignMemSize = MOTGetForeignMemSize; fdwroutine->GetForeignSessionMemSize = MOTGetForeignSessionMemSize; fdwroutine->NotifyForeignConfigChange = MOTNotifyForeignConfigChange;
3.1.1 Index Usage for Planning and Execution
优化器中新增GetForeignPaths。
3.2 Integrating MOT with HA
- Logging:复用GaussDB的XLOG来记录WAL日志,GaussDB并行写日志,多个并发backend进程插入WAL slot,在commit时接管WAL并flush;
- Checkpointing:提供checkpoint的callback函数;
- Recovery:提供recover的callback函数,并行recover,每个线程负责一个datasegment;
3.3 VACUUM and DROP
3.3.1 Deleting Pools
基于epoch的gc,新分配内存区域,将live的数据拷贝到新内存区域,老的内存区域直接回收掉。
3.3.2 GC and Pools Deletion
3.4 JIT for Query Acceleration
GaussDB已经支持了JIT:
- 表达式,比如WHERE子句;
- Inline函数;
上述两种JIT优化是通用的,但近支持了SQL的部分JIT优化,对加速CPU-bound查询效果显著,比如OLAP。
而mot选择对特定OLTP查询场景进行优化,一旦进入这种场景就是全JIT优化,面向OLTP常用负载:点查、简单的范围查询。
GaussDB的JIT仍然是火山模型的pull-model,mot对特性查询的sql进行了全JIT支持,因此一旦命中就不需要使用火山模型。
mot并不支持复杂agg函数,比如:groupby,count distinct
4. EXPERIMENTAL RESULTS
4.1 Hardware
环境1:
Huawei ARM64: TaiShan Kunpeng920-4826和6426 1TB内存 4TB NVMe 3.5GB/s – 2-sockets of 48 cores, adding to 96 cores. – 2-sockets of 64 cores, adding to 128 cores. – 4-sockets of 64 cores, adding to 256 cores.
环境2:
Intel x86: 2socket,每个socket上18物理核,2个超线程,总共72核
4.2 Benchmark and Configuration
client端和server之间10Gb网络
所有的测试中,原GaussDB的存储引擎的数据全部能在内存中buffer住,因此测试结果可以忽略IO的影响。
4.3 Results
使用TPCC测试性能和正确性。
结论:ARM单核性能是x86的0.75倍,和主频成比例,96个ARM核和72个x86核性能相当。
上图测试扩展性,结论:
- mot随着并发增加吞吐增加,大概250并发达到峰值;
- 而GaussDB原存储在并发100时达到峰值;
单条query的加速比
构建表且仅有2个整数,其中一个整数做为primary key。
结论:
- insert提升2倍多:原Disk-based的插入需要在page内定位行指针,插入B-Tree也需要定位node,而mot使用masstree;
- lookup提升2倍多:原因同上;
- delete提升1.5到2倍:mot执行了物理删除,而原Disk-based仅仅是标记,后续vacuum负担会更大;
- update提升3.5倍多:masstree查找更快。
- 所有测试中ARM平台提升比x86更加明显:ARM的relaxed memory对原Disk-based的2PL并发控制不优化,而MOT OCC在这种优化更明显;
JIT加速
结论:
- stocklevel无提升:包含mot不支持的复杂agg,同时sql中包含pipeline breaker需要物化;
- 其他场景均提升30%;
- 在并发300个连接时提升更高,OCC允许reader在本地并发执行,减少cache miss;
存在abort时
OCC中事务执行期间不会被阻塞,但是当有竞争时需要abort。
上图中,12个warehouse,120个并发来模拟高比例的竞争场景。
结论:
- 随着并发增加,abort比例上升;
- mot整体性能仍然有2倍提升;
Replication对性能的影响
开启replication时,结论:
- mot有7%损耗;
- Disk-based有20%损耗;
5. RELATED WORK
- Prototypes and Frameworks: DBX提供了对各种并发控制协议算法进行比较的框架:2PL,OCC,Silo。经过测试在华为的机器上Silo性能表现最好;
- Microsoft’s Hekaton: SQL的Hekaton是内存引擎,也是在commit是进行alidation,类似Silo。不同点是它使用Bw-tree作为索引,read-lock会阻塞写,虽然能减少abort,但是也降低了并发度,因为需要原子的对共享内存区域进行++来实现原子锁,虽然都是乐观,Silo更加乐观;
- PostgreSQL Storage Engine:也是用FDW来实现内存引擎,功能不完善,缺少二级索引,DDL等;
- Mot:兼容SQL,工业级内存引擎;
6. CONCLUSION
MOT是为众核和大内存设计的内存引擎,内存管理,并发控制协议,GC都进行了工程上的优化,通过FDW嵌入到GaussDB中,无缝兼容SQL引擎,方便用户使用。