Greenplum数据库是一种大规模并行处理(MPP)数据库服务器,其架构特别针对管理大规模分析型数据仓库以及商业智能工作负载而设计。
MPP(也被称为shared nothing架构)指有两个或者更多个处理器协同执行一个操作的系统,每一个处理器都有其自己的内存、操作系统和磁盘。 Greenplum使用这种高性能系统架构来分布数T字节数据仓库的负载并且能够使用系统的所有资源并行处理一个查询。
Greenplum数据库是基于PostgreSQL开源技术的。它本质上是多个PostgreSQL面向磁盘的数据库实例一起工作形成的一个紧密结合的数据库管理系统(DBMS)。 它基于PostgreSQL 9.4开发,其SQL支持、特性、配置选项和最终用户功能在大部分情况下和PostgreSQL非常相似。 与Greenplum数据库交互的数据库用户会感觉在使用一个常规的PostgreSQL DBMS。
Greenplum数据库可以使用追加优化(append-optimized,AO)的存储格式来批量装载和读取数据,并且能提供HEAP表上的性能优势。 追加优化的存储为数据保护、压缩和行/列方向提供了校验和。行式或者列式追加优化的表都可以被压缩。
Greenplum数据库和PostgreSQL的主要区别在于:
- 在基于Postgres查询规划器的常规查询规划器之外,可以利用GPORCA进行查询规划。
- Greenplum数据库可以使用追加优化的存储。
- Greenplum数据库可以选用列式存储,数据在逻辑上还是组织成一个表,但其中的行和列在物理上是存储在一种面向列的格式中,而不是存储成行。 列式存储只能和追加优化表一起使用。列式存储是可压缩的。当用户只需要返回感兴趣的列时,列式存储可以提供更好的性能。 所有的压缩算法都可以用在行式或者列式存储的表上,但是行程编码(RLE)压缩只能用于列式存储的表。Greenplum数据库在所有使用列式存储的追加优化表上都提供了压缩。
为了支持Greenplum数据库的并行结构,PostgreSQL的内部已经被修改或者增补。 例如,系统目录、优化器、查询执行器以及事务管理器组件都已经被修改或者增强,以便能够在所有的并行PostgreSQL数据库实例之上同时执行查询。 Greenplum的interconnect(网络层)允许在不同的PostgreSQL实例之间通讯,让系统表现为一个逻辑数据库。
Greenplum数据库也可以使用声明式分区和子分区来隐式地生成分区约束。
Greenplum数据库也包括为针对商业智能(BI)负载优化PostgreSQL而设计的特性。 例如,Greenplum增加了并行数据装载(外部表)、资源管理、查询优化以及存储增强,这些在PostgreSQL中都是无法找到的。 很多Greenplum开发的特性和优化都在PostgreSQL社区中找到了一席之地。例如,表分区最初是由Greenplum开发的一个特性,现在已经出现在了标准的PostgreSQL中。
Greenplum数据库的查询使用一种火山式查询引擎模型,其中的执行引擎拿到一个执行计划并且用它产生一棵物理操作符树,然后通过物理操作符计算表,最后返回结果作为查询响应。
Greenplum数据库通过将数据和处理负载分布在多个服务器或者主机上来存储和处理大量的数据。 Greenplum数据库是一个由基于PostgreSQL 8.3的数据库组成的阵列,阵列中的数据库工作在一起呈现了一个单一数据库的景象。 Master是Greenplum数据库系统的入口。客户端会连接到这个数据库实例并且提交SQL语句。 Master会协调与系统中其他称为Segment的数据库实例一起工作,Segment负责存储和处理数据。
Figure 1. 高层的Greenplum数据库架构
image
下面的主题描述了组成一个Greenplum数据库系统的组件以及它们如何一起工作。
- 关于Greenplum的Master
- 关于Greenplum的Segment
- 关于Greenplum的Interconnect
Parent topic: Greenplum数据库概念
关于Greenplum的Master
Greenplum数据库的Master是整个Greenplum数据库系统的入口,它接受连接和SQL查询并且把工作分布到Segment实例上。
Greenplum数据库的最终用户与Greenplum数据库(通过Master)交互时,会觉得他们是在与一个典型的PostgreSQL数据库交互。 他们使用诸如<samp class="ph codeph" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">psql</samp>之类的客户端或者JDBC、ODBC、 libpq(PostgreSQL的C语言API)等应用编程接口(API)连接到数据库。
Master是全局系统目录的所在地。全局系统目录是一组包含了有关Greenplum数据库系统本身的元数据的系统表。 Master上不包含任何用户数据,数据只存在于Segment之上。 Master会认证客户端连接、处理到来的SQL命令、在Segment之间分布工作负载、协调每一个Segment返回的结果以及把最终结果呈现给客户端程序。
Greenplum数据库使用预写式日志(WAL)来实现主/备镜像。 在基于WAL的日志中,所有的修改都会在应用之前被写入日志,以确保对于任何正在处理的操作的数据完整性。
Note: Segment镜像还不能使用WAL日志。
关于Greenplum的Segment
Greenplum数据库的Segment实例是独立的PostgreSQL数据库,每一个都存储了数据的一部分并且执行查询处理的主要部分。
当一个用户通过Greenplum的Master连接到数据库并且发出一个查询时,在每一个Segment数据库上都会创建一些进程来处理该查询的工作。 更多有关查询处理的内容,请见关于Greenplum的查询处理。
用户定义的表及其索引会分布在Greenplum数据库系统中可用的Segment上,每一个Segment都包含数据的不同部分。 服务于Segment数据的数据库服务器进程运行在相应的Segment实例之下。 用户通过Master与一个Greenplum数据库系统中的Segment交互。
Segment运行在被称作Segment主机的服务器上。 一台Segment主机通常运行2至8个Greenplum的Segment,这取决于CPU核数、RAM、存储、网络接口和工作负载。 Segment主机预期都以相同的方式配置。 从Greenplum数据库获得最佳性能的关键在于在大量能力相同的Segment之间平均地分布数据和工作负载,这样所有的Segment可以同时开始为一个任务工作并且同时完成它们的工作。
关于Greenplum的Interconnect
Interconect是Greenplum数据库架构中的网络层。
Interconnect指的是Segment之间的进程间通信以及这种通信所依赖的网络基础设施。 Greenplum的Interconnect采用了一种标准的以太交换网络。出于性能原因,推荐使用万兆网或者更快的系统。
B默认情况下,Interconnect使用带流控制的用户数据包协议(UDPIFC)在网络上发送消息。 Greenplum软件在UDP之上执行包验证。这意味着其可靠性等效于传输控制协议(TCP)且性能和可扩展性要超过TCP。 如果Interconnect被改为TCP,Greenplum数据库会有1000个Segment实例的可扩展性限制。对于Interconnect的默认协议UDPIFC则不存在这种限制。
Greenplum 的查询处理
这个主题给出了Greenplum数据库如何处理查询的概述。理解这一处理有助于编写和调优查询。
用户像对任何数据库管理系统那样将查询发送到Greenplum数据库。它们使用psql之类的客户端应用连接到Greenplum的Master主机上的数据库实例并且提交SQL语句。
Master接收、解析并且优化查询。作为结果的查询计划可能是并行的或者定向的。如图 Figure 1所示,Master会把并行查询计划分发到所有的Segment。而如图 2Figure 2所示,Master会把定向查询计划分发到单一的一个segment实例。每个segment实例负责在其自己的数据集上执行本地数据库操作。
大部分的数据库操作(例如表扫描、连接、聚集和排序)都会以并行的方式在所有segment实例上执行。在一个segment实例的数据库上执行的每个操作都独立于存储在其他segment实例数据库中的数据。
Figure 1. 分发并行查询计划
image
某些查询可能只访问单个Segment上的数据,例如单行的INSERT, UPDATE, DELETE, 或者 SELECT操作或者以表分布键列过滤的查询。在这些查询中,segment实例,而是定向给到包含受影响或者相关行的segment实例。
Figure 2. 分发定向查询计划
image
理解Greenplum的查询计划
查询计划是Greenplum数据库将要执行以产生查询答案的操作集合。计划中的每个节点或者步骤表示一个数据库操作,例如表扫描、连接、聚集或者排序。计划的读取和执行按照从底向上的顺序进行。
除通常的数据库操作(例如表扫描、连接等等)之外,Greenplum数据库还有一种额外的被称为移动的操作类型。移动操作涉及到在查询处理期间在segment实例之间移动元组。注意并非每一个查询都需要移动操作。例如,定向查询计划就不需要通过Interconnect移动数据。
为了在查询执行期间达到最大并行度,Greenplum将查询计划的工作划分成切片。切片是Segment能够在其上独立工作的计划片段。只要有一个移动操作出现在计划中,该查询计划就会被切片,在移动的两端分别有一个切片。
例如,下面涉及两个表之间连接的简单查询:
代码语言:javascript复制SELECT customer, amount
FROM sales JOIN customer USING (cust_id)
WHERE dateCol = '04-30-2016';
Figure 3 展示了这个查询计划。每个segment实例接收一份查询计划的拷贝并且并行地根据计划工作。
这个例子的查询计划有一个重分布移动,它在segment实例之间移动元组以完成连接。重分布移动是必要的,因为customer表在Segment上按照cust_id分布,而sales表是按照sale_id分布。为了执行该连接,sales元组必须按照cust_id重新分布。该计划在重分布移动操作的两边被切换,形成了slice 1和slice 2。
这个查询计划由另一种称为收集移动的移动操作。收集操作表示segment实例何时将结果发回给Master,Master再将结果呈现给客户端。由于只要有移动产生查询计划就会被切片,这个计划在其最顶层也有一个隐式的切片(slice 3)。不是所有的查询计划都涉及收集移动。例如,一个CREATE TABLE x AS SELECT...语句不会有收集移动,因为元组都被发送到新创建的表而不是发给Master。
Figure 3. 查询切片计划
image.png
理解并行查询执行
术语说明: 查询分发器(QD) 查询执行器(QE)
Greenplum会创建若干数据库进程来处理查询的工作。在Master上,查询工作者进程被称作查询分发器(QD)。QD负责创建并且分发查询计划。它也收集并且表达最终的结果。在Segment上,查询工作者进程被称为查询执行器(QE)。
QE负责完成它那一部分的工作并且与其他工作者进程交流它的中间结果。
对查询计划的每一个切片至少要分配一个工作者进程。工作者进程独立地工作在分配给它的那部分查询计划上。在查询执行期间,每个Segment将有若干进程并行地为该查询工作。
为查询计划的同一个切片工作但位于不同Segment上的相关进程被称作团伙。随着部分工作的完成,元组会从一个进程团伙流向查询计划中的下一个团伙。这种Segment之间的进程间通信被称作Greenplum数据库的Interconnect组件。
Figure 4 所示查询计划在Master和两个Segment实例上的查询工作者进行。
Figure 4. 查询工作者进程
image.png
Tips: hash join
hash join是一种数据库在进行多表连接时的处理算法,对于多表连接还有两种比较常用的方式:sort merge-join 和 nested loop。 多表连接的查询方式又分为以下几种:内连接,外连接和交叉连接。外连接又分为:左外连接,右外连接和全外连接。对于不同的查询方式,使用相同的join算法也会有不同的代价产生,这个是跟其实现方式紧密相关的,需要考虑不同的查询方式如何实现,对于具体使用哪一种连接方式是由优化器通过代价的衡量来决定的,后面会简单介绍一下几种连接方式代价的计算。 hashjoin其实还有很多需要考虑和实现的地方,比如数据倾斜严重如何处理、内存放不下怎木办,hash如何处理冲突等。
nested loop join
嵌套循环连接,是比较通用的连接方式,分为内外表,每扫描外表的一行数据都要在内表中查找与之相匹配的行,没有索引的复杂度是O(N*M),这样的复杂度对于大数据集是非常劣势的,一般来讲会通过索引来提升性能。
sort merge-join
merge join需要首先对两个表按照关联的字段进行排序,分别从两个表中取出一行数据进行匹配,如果合适放入结果集;不匹配将较小的那行丢掉继续匹配另一个表的下一行,依次处理直到将两表的数据取完。merge join的很大一部分开销花在排序上,也是同等条件下差于hash join的一个主要原因。
原理和实现
简单的对于两个表来讲,hash-join就算讲两表中的小表(称S)作为hash表,然后去扫描另一个表(称M)的每一行数据,用得出来的行数据根据连接条件去映射建立的hash表,hash表是放在内存中的,这样可以很快的得到对应的S表与M表相匹配的行。
对于结果集很大的情况,merge-join需要对其排序效率并不会很高,而nested loop join是一种嵌套循环的查询方式无疑更不适合大数据集的连接,而hash-join正是为处理这种棘手的查询方式而生,尤其是对于一个大表和一个小表的情况,基本上只需要将大小表扫描一遍就可以得出最终的结果集。
不过hash-join只适用于等值连接,对于>, <, <=, >=这样的查询连接还是需要nested loop这种通用的连接算法来处理。如果连接key本来就是有序的或者需要排序,那么可能用merge-join的代价会比hash-join更小,此时merge-join会更有优势。
好了,废话说了不少了,来讲讲实现,拿一条简单的多表sql查询语句来举个栗子:select * from t1 join t2 on t1.c1 = t2.c1 where t1.c2 > t2.c2 and t1.c1 > 1。这样一条sql进入数据库系统中,它是如何被处理和解剖的呢?sql:鬼知道我都经历了些什么。。。
1.背景知识
1.第一步呢,它需要经历词法以及语法的解析,这部分的输出是一颗带有token结点的语法树。
image
语法分析,顾名思义这部分只是语法层面的剖析,将一个string的sql语句处理成为一颗有着雏形结构的node tree,每个结点有它们自身的特殊标识,但是并没有分析和处理这个结点的具体含义和值。
2. 第二步是语义分析和重写处理。
重写的过程不同的数据库可能有不同的处理,有些可能是跟逻辑执行过程放在一起,有的则分开。
image
这一步做完树的形状大体上是与语法分析树保持一致的,但是此时的结点都携带了一些具体的信息,以where后面的表达式为例,这颗中缀表达式每一个结点都有了自身的类型和特定的信息,并不关心值是什么,这步做完后进入改写过程,改写是一种逻辑优化方式,使得一些复杂的sql语句变得更简单或者更符合数据库的处理流程。
3.优化器处理
优化器的处理是比较复杂的,也是sql模块最难的地方,优化无止境,所以优化器没有最优只有更优。优化器需要考虑方方面面的因素既要做的通用型很强又要保证很强的优化能力和正确性。
优化器最重要的作用莫过于路径选择了,对于多表连接如何确定表连接的顺序和连接方式,不同的数据库有着不同的处理方式,pg支持动态规划算法,表数量过多的时候使用遗传算法。路径的确定又依赖于代价模型的实现,代价模型会维护一些统计信息,像列的最大值、最小值、NDV和DISTINCT值等,通过这些信息可以计算选择率从而进一步计算代价。
image
回归到正文,使用哪一种连接方式就是在这里决定的,hash join 对大小表是有要求的,所以这里形成的计划是t1-t2还是t2-t1是不一样的,每种连接方式有着自身的代价计算方式。
hash join的代价估算:
COST = BUILD_COST M_SCAN_COST JOIN_CONDITION_COST FILTER_COST
简单来看,hash join的代价主要在于建立hash表、扫描M表、join条件连接和filter过滤,对于S表和M表都是只需要扫描一次即可,filter过滤是指t1.c2>t2.c2这样的条件过滤,对于t1.c1>1这样只涉及单表的条件会被下压,在做连接之前就被过滤了。
优化器处理过后,会生成一颗执行计划树,真正的实现过程根据执行计划的流程操作数据,由低向上地递归处理并返回数据。
image
2.hash join的实现
hash join的实现分为build table也就是被用来建立hash map的小表和probe table,首先依次读取小表的数据,对于每一行数据根据连接条件生成一个hash map中的一个元組,数据缓存在内存中,如果内存放不下需要dump到外存。依次扫描探测表拿到每一行数据根据join condition生成hash key映射hash map中对应的元組,元組对应的行和探测表的这一行有着同样的hash key, 这时并不能确定这两行就是满足条件的数据,需要再次过一遍join condition和filter,满足条件的数据集返回需要的投影列。
image
hash join实现的几个细节
1.hash join本身的实现不要去判断哪个是小表,优化器生成执行计划时就已经确定了表的连接顺序,以左表为小表建立hash table,那对应的代价模型就会以左表作为小表来得出代价,这样根据代价生成的路径就是符合实现要求的。
2.hash table的大小、需要分配多少个桶这个是需要在一开始就做好的,那分配多少是一个问题,分配太大会造成内存浪费,分配太小会导致桶数过小开链过长性能变差,一旦超过这里的内存限制,会考虑dump到外存,不同数据库有它们自身的实现方式。
3.如何对数据hash,不同数据库有着自己的方式,不同的哈希方法也会对性能造成一定的影响。
参考资料
https://www.cnblogs.com/shangyu/p/6055181.html http://docs-cn.greenplum.org/v6/admin_guide/query/topics/parallel-proc.html#topic1