本篇文章讲述了Greenplum如何把一个以AP为主的数据库打造成HTAP数据库:
- 全局死锁检测;
- 2PC+1PC优化;
- 资源隔离;
由Gartner提出的HTAP数据库是近年来数据库领域的一个热门方向,由HTAP数据库提供的实时分析能力已经成为企业的核心竞争力之一:
- 在TP系统里增加AP的能力;
- 从NewSQL转向HTAP;
- 而 Greenplum的论文则展示了如何从AP系统转向HTAP;
首先分析了对于当前MPP-OLAP在保证ACID时的两个开销:restrictive lock和两阶段提交;
Greenplum团队提出一种全新的全局死锁检测来减少独占锁的使用。可以极大的提高数据库在高并发状态下的性能。
此外对于只在单节点上的查询,Greenplum将两阶段提交优化为单阶段提交,这样可以大幅减少原本两阶段提交中的时间和网络开销。
为了进一步提高HTAP的性能,Greenplum引入了资源组的概念,使得数据库可以很好的对OLAP查询负载和OLTP查询负载进行对应的资源隔离,提高了整个系统的利用率。
论文中的优化均已应用在了最新版本的Greenplum 中,基于TPC-B和CH-benCHmark数据集进行的测试也显示出最新版本的Greenplum的OLTP性能有了极大的提升,与此同时之前的OLAP查询的性能没有受到任何影响。
摘要
Greenplum在传统意义上是一个OLAP的数据仓库,具有有限处理OLTP的能力:
- 瓶颈:restrictive lock和两阶段提交协议;
- TP与AP之间的资源竞争;
1. Introduction
Greenplum是数据仓库系统,有企业版和开源版本的实现。把数据分割成不相交的部分以存放在share nothing的segment server上。和其他大规模数据仓库系统比较类似,比如Oracle Exadata, Teradata, 以及Vertica,包括DWaaS系统(AWS Redshift, AnalyticDB和BigQuery)。这些数据仓库系统能够有效地管理并查询PB级的数据。
而分布式TP数据库(如CockroachDB, Amazon RDS)则主要专注于为TB级提供可扩展的存储和事务查询。
Greenplum的用户通过一个协调者跟数据仓库系统打交道,底层的分布式结构对用户来说是透明的。对于一个给定的查询,协调者将其优化以作并行查询,并将生成的查询计划分发给执行查询的segment server。每个segment server以并行的方式执行查询,并在需要时在segment server之间shuffles tuples。协调者聚集查询结果并返回给用户。
DML修改存放在segment server上的数据。原子性是通过二阶段提交协议来保障。分布式快照用来隔离并发事务。Greenplum通过一系列压缩算法支持列存(Column-oriented),这些列存表格很适合OLAP负载中的大范围读和读操作。
图1给出了一个典型的数据处理工作流,这个数据通过ETL模块周期性地被转换,并被加载到数据仓库中以作后期的分析使用。用户更希望有一个可以同时满足OLAP和OLTP查询负载的单一系统。换句话说,这样的系统需要支持对点查询和分析查询。这个需求在相关文献中被称为混合事务和分析处理(HTAP)系统。
为满足Greenplum企业用户的需求,提出了增强版的HTAP Greenplum,专注于以下问题:
- 改进并行数据加载,使其具备ACID;
- 降低执行OLTP点查询的响应时间;
- 资源组,隔离不同负载或用户组之间的资源。
Greenplum设计的初衷是将OLAP查询作为一等公民,而OLTP负载不是其主要焦点。两阶段提交会对仅更新几个元组的事务造成性能损失。协调者上执行的比较重量的锁(heavy locking)旨在防止分布式死锁,但过于严格,影响了点查。
如图2,一个耗时10s、占用少量连接的查询来讲,锁执行时间超过25%。当并发超过100时,锁机制消耗的时间将变得无法接受。
增强版的HTAP Greenplum:
- 分析并找出了将OLAP转换为HTAP面临的挑战;
- 提出全局锁检测器来降低锁引起的开销,提高OLTP的响应时间,与此同时不会牺牲OLAP的性能;
- 通过转换为一阶段提交协议,加速仅需在某个segment server上执行的事务;
- 一个新的资源隔离模块,用以管理OLTP和OLAP负载,避免资源竞争;
- 执行全面的性能评估。实验结果表明HTAP版本的Greenplum跟传统OLTP数据库性能相当;同时仍然能够为高并发和混合的负载提供实时计算的能力。
2. Related Work
- HTAP 系统:与纯粹的OLAP或OLTP系统相比,HTAP系统带来了=以下好处:1)HTAP能够减少数据分析任务的等待时间,因为没有ETL传输时延。无需额外的系统或外部组间即可进行实时数据分析。2)HTAP系统还可以降低硬件和管理方面的成本。许多商用OLTP DBMS转向类HTAP。但是,在商用OLAP中对OLTP负载的支持仍没有实现。随着HTAP概念的流行,越来越多的数据库系统正在支持HTAP功能。Ozcan等人将HTAP数据库分为两类:single systems for OLTP&OLAP 以及seperated OLTP&OLAP systems。将讨论HTAP数据库的不同演化路径。
- 从OLTP到HTAP:OLTP支持高并发性和低延时的事务型处理。Oracle Exadata可同时执行OLTP负载和分析处理。Exadata引入了智能扩展存储、RDMA和infiniBand网络以及NVMe闪存来提高HTAP性能。最近,它支持带有内存列存(column-level checksum with in-memory column cache)和智能OLTP缓存等功能,减少闪存磁盘故障或更换的影响。Aurora是一个基于AWS的云OLTP数据库,将繁重的日志处理卸载到存储层。为了支持OLAP负载,Aurora支持并行查询,将单个查询的计算下推到数千个CPU的存储层。使分析查询加快两个数量级。
- 从NewSQL到HTAP:自从Google Spanner以来,具有扩展性和强ACID保证的NewSQL数据库克服了NoSQL在扩展性和强ACID保证方面的缺陷。早期NewSQL数据库的实现,如CockroachDB、TiDB和F1,基于Paxos或Raft等共识协议,以支持分布式OLTP。最近,几个NewSQL数据库声称自己已变成了HTAP数据库。其中,TiDB引入了TiFlush来处理OLAP工作负载。它对Raft进行扩展,将数据以异步形式复制到Raft“Learner”中,并将行存转成列存以服务于OLAP工作负载。类似地,传统的数据库(例如Vertica)同样使用write-optimized stores(WOS, 写优化存储)来处理插入、更新和删除查询。然后将WOS异步转换为ROS,以处理OLAP工作负载。F1 lighting提供“HTAP即服务”。在谷歌,Lighting被用来从OLTP数据库(如SPanner和F1)中拷贝数据,并将这些数据转换成列形式,以处理OLAP负载。与TiFlash不同,Lighting提供了与OLTP数据库的快照隔离(此处应该是作者理解有误,TiFlash也是快照隔离)。
Greenplum提供了另一种进化成HTAP数据库的途径。其在传统的OLAP数据库上增加了OLTP功能,并且支持细粒度的资源隔离。
3. GreenPlum’s MPP Architecture
Greenplum基于MPP架构,构建了一个数据库集群。运行中的Greenplum集群由多个正在运行的worker segments组成,可以视为增强版的PostgreSQL,图3显示了整个架构。
接下来,介绍Greenplum MPP架构的几个重要模块和设计。这些概念对于理解Greenplum的HTAP改进至关重要。
3.1 Roles and Responsibility of Segments
一个Greenplum集群由很多跨主机的segments组成。有一个segment server称为协调者,其他的称为segment server。协调者直接连接到用户客户端。协调者接收来自客户的命令或查询,生成分布式查询计划,根据计划生成分布式进程,将其分配到每个进程,收集结果,并最终发送回客户端。
segment server存储数据,并从协调者处执行分布式计划的子树。为了高可用性,segment server可被配置为镜像节点。镜像不会直接参与计算。相反,他们会连续地从相应的主segment server接收WAL日志并回放日志。
Greenplum是无共享的架构,协调者和segment server都有它们自己的共享内存和数据目录。协调者仅通过网络与segment server进行通信。
3.2 分布式查询和分布式执行器
通常,对于分布式表来说,每个segment server仅存储整个数据的一小部分。当对两个表进行join时,通常需要检查来自不同的segment server的两个元组是否符合join的条件。这意味着Greenplum必须在segment server之间移动数据,以保证所有可能匹配的元组在同一个segment server中。Greenplum中引入一个算子来实现这样的数据移动,称作Motion。
Motion算子通过网络从不同的segment server发送和接收数据。Motion算子将计划切成slice,Motion下面或上面的每一个slice都被称为Greenplum切片。每个切片由一组分布式进程执行,这组进程称为gang。
有了上述的Motion算子和gang,Greenplum的查询计划以及执行器就变成了分布式的。计划会被分发到每一个进程,根据进程的上下文状态,每一个进程执行自己的计划切片以完成查询的执行。执行是单程序多数据技术:将相同的计划分配给跨集群的进程组,由不同segment server生成不同的进程,都有自己的本地上下文、状态和数据。
下面使用一个例子来说明上述概念。图4的顶部是一个join的分布式计划树,底部是在具有两个segment server的集群中的执行过程。顶部slice由协调者上的单个进程执行,其他slice在segment server上执行。一个slice扫描表,然后通过redistributed Motion将元组发送出去。另一个执行hash join的slice将从Motion节点接收元组,扫描学生表,构建hashtable,计算hashjoin,最后将元组发送到顶部slice。
3.3 分布式事务管理
在一个Greenplum集群中,每个segment server运行着一个增强版的PostgreSQL实例,并且事务在每个segment server中同步地提交或终止。为了确保ACID属性,Greenplum使用分布式快照和两阶段提交协议。分布式事务管理的性能对于增强Greenplum作为一个可行的HTAP系统至关重要。详情将在第5章进行讨论。
3.4 混合存储与优化
Greenplu支持PostgreSQL原生的heap表(heap tables),heap表是行存储,在segment server上共享bufferpool,以方便并发读写操作。
Greenplum中引入了两种新的表类型:
- Append Optimized的行存储(AO行);
- Append Optimized的列存储(AO列);
AO表支持批量I/O,而不是随机访问,这使得它们更适合AP的工作负载。在AO列表中,为每列分配一个单独的文件。进一步减少从一个大表中仅选择几列的I/O。AO表可用各种算法进行压缩,如zstd、quicklz和zlib。在AO列表中,每一个列都可以使用特定的算法进行压缩,包括带有增量压缩的运行长度编码(run-length-encoding, RLE)。Greenplum中的查询执行引擎不感知表的存储类型。AO行、AO列和heap表可以在同一查询中被join。
表可以按用户指定的关键字和分区策略(list、range)进行分区。这是通过在根表下面创建一个表的层次结构来实现的,其中只有叶子表中包含用户数据。层次结构中的每个分区都可以是堆、AO行、AO列或外部表。外部表用于读取/写存储在Greenplum以外的数据,例如存储在 Amazon S3中的数据。
图5显示了按销售日期划分的销售表,每个分区由日期范围定义。最近的分区为heap表(6月至8月)。AO列存储用于稍旧的销售数据(9月至12月),而前几年的销售数据则存档在外部表中。可以对销售表或其单独的分区进行查询,无需关心该表的存储性质。
与存储一样,Greenplum中的查询优化也是灵活的。查询优化取决于工作负载。分析类的工作负载由包含许多join和aggregates的复杂查询组成。查询性能主要由查询计划的效率决定。Greenplum的Orca查询优化器是一个cost-based的优化器,专为OLAP设计。另一方面,TP的工作负载由短查询组成,这些查询对查询计划延迟很敏感。在生成一个简单的计划时,它需要快速的优化器。Greenplum的PostgreSQL内置优化器适用于这类事务型查询。用户可以从SQL、会话或数据库级别,在两个优化器之间进行选择。选择最合适优化器,帮助Greenplum更有效地处理HTAP工作。
4. Object Lock Optimization
本节重点介绍锁优化,这是Greenplum优化OLTP性能的基石。其核心思想是通过检测器算法来解决分布式环境中的全局死锁问题。
4.1 Greenplum中的锁
锁在数据库中被广泛使用,以防止在不同粒度级别上的竞争情况。Greenplum中有三种锁:spin锁、LW锁和对象级别锁。spin锁和LW锁用于保护读取或写入共享内存的临界区,并遵循一些规则(例如,以相同的顺序取得锁),来避免死锁。在操作表、元组或事物等数据库对象时,对象锁定会直接影响进程的并发性。在本节中关注对象锁。
一些对象,比如表,可以由事务并发操作。在访问此类对象时,要以正确的模式上锁,以保护该对象。
Greenplum采用两阶段锁:上锁保持在第一阶段,并在提交或中止事务时释放。从PostgreSQL继承而来,Greenplum有8种不同级别的锁模式。更高级别的锁定模式支持更严格的并发控制粒度。所有的锁模式、冲突模式及对应的典型语句见表1。
作为一个MPP数据库,Greenplum中的锁逻辑和算法与PostgreSQL有所不同。PostgreSQL锁逻辑无法检测或解决在Greenplum等MPP数据库中经常遇到的全局死锁问题,必须增加DML操作的锁级别,以避免出现此类问题。
在基于PostgreSQL锁机制的Greenplum中,并发事务的性能非常糟糕:因为一次只能处理同个表上的一个事务更新或删除。
4.2 全局死锁问题
在像Greenplum这样的分布式系统中,在处理全局死锁时,DML语句(插入、删除和更新)的锁定级别非常重要。这些DML语句的锁定行为如下:
- 首先,在解析/分析阶段,事务会对表上锁;
- 其次,在执行过程中,事务会上tuplelock;
在单segment server数据库中,如PostgreSQL,第一阶段通常以RowExclusive模式锁定目标表,以便它们可以并发运行。只有当两个事务恰好写入(更新或删除)同一元组时,一个事务才会等待该元组的事务锁定,直到另一个事务被提交或中止。锁依赖关系存储在每个segment server实例的共享内存中。如果发生死锁,则很容易扫描共享内存中的锁信息。
这种方法在Greenplum的分布式架构中是不够的。即使Greenplum集群中的每个segment server都是具有本地死锁处理程序的增强PostgreSQL实例,如果等待行为跨越不同的segment server发生,无法避免全局死锁。
如图6所示:
- 事务A更新存储在segment server0中的一个元组,并持有segment server0上的一个tuplelock;
- 事务B更新存储在segment server1中的一个元组,它保持在segment server1上的一个tuplelock。直到现在,一切都很顺利,没有等待的情况发生;
- 事务B更新segment server0上事务A刚刚更新的相同元组,因为事务A尚未提交或中止,事务B必须等待。事务A工作正常,正在等待下一个语句;
- 事务A更新被事务B锁定的segment server1上的元组,因此它也必须等待;
- 现在,在segment server0上,事务B等待事务A,在segment server1上,事务A等待事务B。每个PostgreSQL实例都没有本地死锁。但导致全局死锁。
图7显示了一个更加复杂的情况,其中包括协调者在内的所有segment server都涉及全局死锁。下面的顺序号与图7中的顺序号一致。
- 事务A通过更新语句锁定segment server0上的c1=2的表t1;
- 事务B通过更新语句锁定segment server1上的c1=1的表t1;
- 事务C通过LOCK语句锁定协调者上的表t2,以及所有的segment server;
- 事务C尝试获取segment server0上c1=2的相关元组锁,但该元组已被事务A锁定,因此事务C等待;
- 事务A试图锁定segment server1上的c1=1的表,但segment server1已被事务B锁定,因此事务A等待;
- 事务D通过更新语句锁定segment server0上c1=3的表t1相关元组;
- 事务D继续尝试通过LOCK语句锁定协调者上的表t2,它将等待,因为事务C持有t2上的锁;
- 事务B继续尝试锁定与t1相关的c1=3上的元组,该segment server被事务D锁定,因此事务B也在等待;
- 现在,在segment server1上,事务A等待事务B,在segment server0上,事务B等待事务D,在协调者上,事务D等待事务C,在segment server0,事务C等待事务A,从而发生全局死锁。
在Greenplum5和以前的所有版本中,在协调者的parse/analyze阶段,使用X模式锁定目标表,因此执行更新或删除的事务实际上是以串行形式运行的;
并且事务锁定等待不是在segment server上的,而是在协调者节点上的(本地死锁检测算法),这就避免了全局死锁。但是,该方法会导致OLTP性能不佳,因为它不允许并发地写入同一个表,即使这两个事务实际上更新了不同的元组。
4.3 全局死锁检测算法
为了解决全局死锁问题,提出了一种检测机制,作为Greenplum 6的一部分。全局死锁检测算法(简称GDD)的工作流程如下:
- Greenplum在协调者上启动一个守护进程;
- 守护进程定期收集每个segment server上的Wait-for图;
- 守护进程检查是否发生全局死锁;
- 守护进程使用预定义的策略终止全局死锁,例如终止最年轻的事务;
GDD守护进程收集每个segment server的本地wait-for(包括协调者),并构建一个全局wait-for。每个顶点表示一个事务,并且边是从等待事务开始指向持有锁的事务。对于表示事务的每个顶点,其输出边的数量是顶点的出度,其输入边的数量是顶点的入度。
顶点的局部度:是其仅在某单个segment server的wait-for中计算的值;
顶点的全局度:是所有segment server的所有局部度的总和。
使用
来表示顶点V的全局出度,degi(V)表示顶点V在segment server-i上的局部出度。举个例子,在图7中,deg(G)(C) = 1,因为在事务C仅在segment server0上对C有依赖,并且deg-1(C)=0,因为在segment server1上事务C没有出度。
需要强调的是,从每个segment server收集的等待信息是异步的,并且在协调者中分析时,不能忽略延时。GDD算法中最重要的思想是贪婪规则。该算法不断删除等待边缘,当无法删除等待边时,如果存在剩余等待边,则会发生全局死锁。在这种情况下,检测器守护进程将锁定协调者中的所有进程,检查剩下的边是否仍然有效。如果某些事务已完成(已中止或已提交),GDD守护进程只需丢弃本轮检测所有信息,并在下次运行中继续执行全局死锁检测。请注意,运行作业的周期是Greenplum的可配置参数,以满足各种业务需求。
在全局wait-for中有两个不同的等待边符号:
- 实边:只有在持有锁的事务结束(已提交或中止)后,Wait-for才会消失。一个典型情况是在更新或删除语句中对目标表上锁,该锁只能在事务结束时才会被释放。只有当持有锁的事务不会在任何地方被阻塞时,这样的边才能被删除,因为基于贪婪规则,可以假设持有锁的事务会结束并释放它所持有的所有锁;
- 虚边:表示持有锁的事务,即使不结束事务,也可以释放其所持有的锁。例如,在修改元组的内容时持有的tuplelock。只有当特定segment server中的其他人没有阻塞持有锁的事务时,才能删除这样的边。这是基于贪婪的规则,可以假设持有锁的事务将释放阻塞等待事务的锁,而不提交或中止。
完整的GDD算法如算法1所示。在每轮循环中:
- 首先将全局出度为0的顶点对应的输入边删除掉(没有任何事务依赖,因此可以直接删除掉);
- 然后扫描每一个局部wait-for,并将局部出度为0的点所对应的输入虚边删除掉(虚变代表事务依赖本地tuple lock,而它锁依赖的事务并没有local出度,说明该依赖关系可以在事务执行完之前执行完,因此可以删除掉);
GDD是一个贪心算法,换句话说,如果有可能让持有锁的事务继续运行,总是假设它们最终会把其所持有的锁释放掉。
GDD in Action
下面使用GDD算法来分析几个实际例子。
对图6中提到的死锁而言,从它的wait-for中可以看出,没有全局出度为0的顶点,所以第一轮循环中没有边被删除掉;同时在每个segment server上都不存在虚边,所以第二轮也没有边被删除。原始wait-for即为最终状态,其中含有一个全局的环,意味着发生了全局死锁。
图8描述了另一种情况。语句的执行过程按时间顺序如下:
- 事务A通过Update语句,对segment server0上表t1中c1=3的元组上锁;
- 事务C通过Update语句,对segment server1上表t1中的c1=1的元组上锁;
- 事务B尝试给表t1上c1=1,或c2=3的元组上锁,它将被segment server0上的事务A和segment server1上的事务C阻塞();
- 事务A尝试给segment server1中t1上c1=1的元组上锁,它将被segment server1上的事务B所有持有的tupelock阻塞;
图8中,GDD算法的执行过程可通过图9进行描述,具体如下:
- 图9.a中,deg(G)(C) = 0(即源wait-for),基于GDD算法,可以把顶点C以及所有指向C的边删除,执行这一步操作后,可以得到图9.b;
- 图9.b中没有顶点满足deg(G)(V)=0了,接着检查本地wait-for中顶点的出度,可以发现deg1(B) = 0,基于GDD算法可以把segment server1上所有指向B的虚边都删除掉,执行完后可以得到图9.c;
- 接下来在图9.c中,发现deg(G)(A)=0,因此所有指向A的边都会被删除掉,这步操作之后可以得到图9.d;
- 最后没有边剩下,因此GDD算法将输出该情况下未产生死锁。
GDD Correctness
GDD依赖最终的状态属性,如下所示:
- 如果图中没有边能够被删除,那么图就不会再更新了;
- 所有的事务仍然存在;
根据性质1和性质2,可以得出结论:最终的等待边是最新的了。基于边删除策略,如果最终wait-for中包含环,那么会导致全局死锁。如果全局死锁发生了,基于定义,全局wait-for必须包含一个环,而且在图中的事务都不能更进一步地执行了。GDD算法不能移除任何边并且会汇报一个全局死锁。因此,证明GDD算法是完备的。
GDD Overhead
启动GDD后,UPDATE和INSERT操作的锁定级别会降级。因此,在同一个表上执行更新和删除的事务可以并行执行。
GDD守护进程仅仅周期性地执行一个简单的调用,从各个segment server上拉去wait-for,因此它并不会在集群上消耗太多的资源。
5. 分布式事务
Greenplum中的事务是由协调者创建并分发到segment server上。协调者为每一个事务分配一个单调递增的整数,作为分布式事务id。
而segment server给每个从协调者那里获得的分布式事务分配一个本地事务标id:使用原生的PG事务机制来生成本地事务标id。
分布式事务id惟一的标识了全局级别上的事务。而本地事务标id唯一标识某个segment server上的事务。
segment server使用PG原生快照机制来生成本地快照。
协调者:创建分布式事务、创建分布式快照以及在参与segment server之间协调两阶段提交协议。
5.1 分布式事务隔离
Greenplum增加了分布式快照,处理分布式环境下的事务隔离。元组的可见性是由本地和分布式快照共同决定的。
接下来,让考虑在segment server上对元组执行DML操作。对一个给定的事务,当修改一个元组时,为这个元组创建一个新的版本并且打上局部事务标id。对每个元组,也维护一个从局部事务标id至全局事务标id的映射。结合该分布式事务标id以及分布式快照(有协调者提供),扫描算子决定元组的可见性。
将本地事务标id映射到分布式事务标id所产生的开销非常大。为了减少这项开销,仅维护至最大的分布式事务标id,其可能被任何分布式快照认为正在执行。
segment server使用这个逻辑来频繁地截断映射的元数据。如果查找不到映射,一个segment server结合本地事务标id和本地快照来决定元组的可见性。
5.2 一阶段提交协议
协调者使用两阶段提交来保证事务要么在所有segment server上都被提交或都被中止。
协调者创建一个后端进程来处理用户的连接。后端进程参与segment server之间初始化两阶段提交协议,来接收用户的commit请求。
两阶段提交的优化就是缩短commit过程,使得锁释放得更快,从而提升并发度。
一阶段提交是针对仅在同一个segment server上更新数据的事务执行的优化。
图10展示了一阶段提交的好处。协调者能够决定一个写操作仅发生在一个segment server上。如果上述条件成立,协调者将跳过PREPARE阶段,把Commit命令分发至参与segment server上。一阶段提交节省了一个网络来回的PREPARE消息,和文件系统同步操作:
- segment server上的PREPARE;
- 协调者上的COMMIT;
让看看数据一致性是如何不受一阶段提交优化所影响的:
- 一阶段提交事务由协调者分配分布式事务标id和分布式快照;
- 在参与的segment server节点上,元组版本被一阶段提交事务创建出来,并打上本地事务标id;
- segment server记住本地和分布式事务标id之间的映射,就像两阶段提交事务那样;
- 当前一阶段提交事务和此时取到的分布式快照是并发的关系,直到协调者从参与segment server接收到“Commit OK”认可;
- 在这之后,一阶段提交事务在新创建的分布式快照中以Committed状态出现;
- 简单而言,一阶段提交优化使用与两阶段提交事务一样的机制来维护数据库的一致性;
- 因此,一阶段提交对OLTP负载中单实例的insert, update和delete操作来说是正确且有效的。
在上述例子中,所有10个元组都在列c1上具有相同分布键1。所有的元组都将被发送至hash值为1的segment server,一阶段提交优化。
5.3 其他优化
最后一个Query和Prepared/Commit消息合并。
6. Resource Isolation
如何在高并发、混合工作负载中减轻资源竞争造成的性能下降是一个具有挑战性的问题。在本节中,将讨论Greenplum如何为HTAP工作负载隔离和调度资源,如CPU和内存。
当分析型工作负载和事务型工作负载同时运行时,分析型的工作负载会对事务型工作负载产生重大影响。通常,分析型的工作负载会消耗大量的CPU、内存和IO带宽,这将占用事务型工作负载的资源,并导致事务型查询被延迟。
为了减少干扰,Greenplum引入了资源组,以在不同类型的工作负载或用户组之间隔离资源。目前,资源组支持使用不同的方式隔离计算资源和内存资源。
CPU隔离是基于cgroup技术实现的。Cgroup是一个Linux内核功能,它限制和隔离进程组的资源使用情况。从Cgroup树结构的角度来看,一个资源组被实现为一个内部节点,并且属于该资源组的所有进程都是子节点。
有两种方式为每个资源组的进程分配CPU的使用情况。其中一个是cpu.shares,它控制着CPU的使用百分比或者优先级;另一个是cpuset.cpus,指定了该资源组的CPU核数。
第一种配置是软控制:如果没有并发的工作负载,资源组中的进程可以使用超过指定限额的CPU资源;第二个配置则是硬控制,严格限制了一个资源组中最多能使用的CPU核数。
在Greenplum中,内存隔离是通过基于内存管理模块Vmemtracker实现的。Vmemtracker负责跟踪Greenplum数据库内核的内存使用情况。Greenplum使用该特性来控制不同资源组之间的内存使用。
不像CPU,内存是一个硬件资源,并且一旦分配就不能立马收回。当一个资源组的内存使用超过了限制,该组内的查询将被取消。
但是在实际的工作负载中,想要控制显式内存的使用情况,并不是一件容易的事。如:很难获得一个hash表的显式内存的大小。
为了使内存资源执行得更加健壮,资源组引入了三个层次来管理内存使用情况:
- slot memory:控制单个查询的内存使用情况,计算公式是组非共享内存除以并发数;
- shared memory:在同一资源组中的查询超过slot memory时使用该层,组共享内存可以由参数MEMORY_SHARED_QUOTA为每个资源组设置;
- global shared memory:不同组间内存使用量的最后守护者,只有当这三层都不能限制查询内存时,才会使用查询取消机制。
可以使用以下语法创建资源组:
要隔离不同用户组之间的资源,DBA可以使用“ALTER ROLE”或“CREATE ROLE”命令将资源组分配给一个角色。例如:
上面所示两个资源组:
- 一个专门用于分析型工作负载;
- 另一个专门用于事务型工作负载;
对于CPU资源,为事务型资源组分配更多的CPU速率限制,因为事务型查询很短,而且对查询延迟很敏感。通过将CPU资源优先分配给事务型资源组,希望当长时间执行的查询并发执行时,能够减轻对短时间运行query的延迟和TPS影响。
对于内存资源,为分析资源组分配更高的内存限制,以允许分析查询使用更多的内存,并避免过度的spill磁盘。相反,事务型查询的内存使用量通常很低。
并发性是资源组的另一参数,它控制最大连接数量。事务型工作负载通常涉及到更高的并发性。
另一方面,分析类的工作负载需要对并发性的细粒度控制。如前所述,内存不能立即回收,这将使每个查询使用的内存量变小,从而在并发限制设置为太大时,导致更频繁的spill磁盘。这是并发性和性能之间的权衡。在未来,计划引入一个工作负载预测模块,它允许负载不大时,即使当资源组的并发数设置为很大时,仍然可以使用更多的内存。
未来的工作。除了CPU和内存隔离外,磁盘IO和网络IO隔离对于混合工作负载也至关重要。与PG和其他数据库系统类似,Greenplum中的后端进程会更改共享内存缓冲池中的页面,脏页面通过后台进程异步刷到磁盘中。因此,很难将磁盘IO从greenplum集群中的不同进程中分离出来。然而,发现,对于许多工作负载,磁盘IO和网络IO的使用情况与CPU的使用情况有关。因此,限制CPU的使用也可以限制磁盘和网络IO的使用。计划在未来探索如何隔离磁盘和网络IO。
7. Performance Evaluation
7.1 测试环境
在8个主机的集群上进行了实验(其中每个主机分别有4个片segment server)。网络是10Gbps的以太网络。每个节点有32个IntelXeon CPU核心(2.2GHZ)、120GB RAM和500GB SSD。
本文讨论的HTAP优化在Greenplum 6.0中实现。为了验证方法的有效性,比较了GPDB6和Greenplum 5.0(实验基线)的性能。
7.2 OLTP性能
以前的Greenplum版本是传统的数据仓库,由于数据的批量处理,使得它的事务处理能力和数据分析延迟低。从Greenplum 6开始,其引入了全局死锁检测器、单阶段提交和资源组(本文的核心贡献)等许多特性,以提高整体的OLTP性能。Greenplum继续迎合大数据分析市场,这使得Greenplum能够将分析型、深度学习、报表和专门的查询(ad-hoc queries)视为一等的公民。相比之下,CockroachDB和TiDB等HTAP系统主要关注对TB级别数据的事务型查询,并试图减少执行分析的时间。虽然它们的分析查询得到了改进,但不能像Greenplum这样查询PB级别的数据。
为了更好地理解Greenplum 6中的事务处理优化,使用TPC-B基准测试来评估OLTP的性能。比较了GPDB6、GPDB5和PG的性能。
结果显示,与GPDB5相比,GPDB6每秒可以多处理80倍的事务,如图12所示。这些改进与第4节和第5节中讨论的优化直接相关。一开始,TPS中的吞吐量随着客户端的数量呈线性增长。超过100个客户端时,吞吐量的增长减少了,最后在600个客户端时,吞吐量开始下降。
的分析表明,这个下降是由于转向了LWLock的瓶颈引起的,这将在Greenplum的后续版本中得到解决。
还比较了GPDB6和PG 9.4的性能。使用pgbench来生成具有不同尺度因子的数据。比例因子1K对应于14GB数据,10K对应于143GB数据,100K对应于1.4TB数据。图13显示,当数据规模比较小时,PG在TPS中的吞吐量高于GPDB6。随着数据大小的增加,PG吞吐量急剧下降,而Greenplum吞吐量保持稳定。这是一个令人鼓舞的证据,表明Greenplum能够持续提高OLTP。
下面研究针对OLTP工作负载的单个优化对性能的影响。为了评估全局死锁检测器的影响,使用了update-only的工作负载。图14显示,GPDB6的TPS高于12000,大概是GPDB5的TPS的100倍。原因是GPDB5必须序列化在同一表上的更新操作,而全局死锁检测器允许GPDB6对同一张表进行并发更新。
为了更好地研究单阶段提交优化的影响,分析了insert-only工作负载的性能。此工作负载的每个插入语句涉及的值都映射到了同一个segment server。单阶段提交优化可以应用于此类事务。
图15中的结果显示,GPDB6每秒处理的事务量是GPDB5的5倍。原因是单阶段提交协议不仅能降低协调者和segment server之间的通信开销,也可以消除实际上没有插入任何元组的segment server上不必要的CPU成本。除了单阶段提交协议之外,Insert-only的查询也受益于事务管理器优化,这反过来又降低了LWLock的开销。
考虑到协调者经常成为Greenplum的OLTP性能瓶颈。在未来,计划增加具有分布式事务的hot standby和multi-master。
7.3 HTAP性能
为了评估Greenplum在HTAP工作负载上的性能,使用HTAP基准测试CH-benCHmark进行了一个实验。CH-benCHmark是一个混合基准,它由TPC-H OLAP基准和TPC-C OLTP基准组成。在的实验中,OLAP工作负载和OLTP工作负载同时运行,并且设置了不同的客户端数目和资源组配置。
接下来,比较了GPDB5和GPDB6之间的HTAP性能。图16显示了在不同OLAP客户端数目和两个固定的OLTP客户数目(0和100)的情况下OLAP工作负载的QPH(queries per hour)。本实验展示了OLTP工作负载对OLAP工作负载的影响。结果表明,在GPDB6中,OLAP的QPH性能下降了超过2倍,但在GPDB5中没有显著差异。这是因为GPDB5中的每分钟OLTP查询(QPM)太小,无法抢占OLAP工作负载的资源。
为了更好地评估OLAP工作负载对OLTP工作负载的影响,比较在不同OLTP客户端数和两个固定的OLAP客户端数目(0和20)情况下,OLTP工作负载的QPM数目。图17中显示,GPDB6上的OLTP性能降低了3倍,而GPDB5上没有差异,因为QPM数量受到锁冲突而不是系统资源的限制。上述两个实验也证明了HTAP能力的显著改进。GPDB6能够处理1万个OLTP查询,并同时执行复杂的专门查询。
上述实验表明,OLAP和OLTP工作负载在并发运行时将抢占系统资源。为了展示Greenplum的资源隔离能力,创建了两个资源组,一个用于OLAP工作负载,另一个用于OLTP工作负载。资源组的配置因CPU优先级而异。下面列出了三种不同的配置:
- 配置I均匀地分配了CPU,具有相同的CPU速度限制;
- 配置II将32个CPU中的4个分配给OLTP资源组;
- 配置III将32个CPU中的16个分配给OLTP资源组。
下面实验评估了当OLTP工作负载与固定数量的OLAP并发性(20)同时运行时对OLTP工作负载的性能影响,并使用上述不同的资源组配置。
图18中的结果显示,当通过CPU隔离将特定的CPU分配给OLTP资源组时,OLTP工作负载的延迟就减少了。资源组能够灵活地调整HTAP工作负载的资源分配和查询性能。
8. Conclusion
论述了如何将分布式数据仓库转换为一个可以同时满足OLAP和OLTP工作负载的HTAP系统。正如在两阶段提交和锁看到的那样,OLAP工作负载的设计对于OLTP工作负载的设是非常昂贵的。
对于短的OLTP查询来说,看似无关紧要的开销变得相当重要了。的新方法在不牺牲性能和ACID的情况下减少了这种开销。
详细的性能分析表明,全局死锁检测器和单阶段提交协议显著提高了Greenplum对OLTP工作负载的性能。
Greenplum通过资源组限制CPU和内存,能够在单个系统中同时运行OLTP和OLAP工作负载。
Greenplum后续会有更多功能使得分析查询系统能够与专门的OLTP(如PG)相媲美。