当谈起Greenplum 7时,我们在谈什么?之内核篇

在上篇文章中,我们为大家介绍了Greenplum商业版组件的最新近况,下面来具体介绍一下Greenplum 7中内核方面都将带来哪些惊喜。由于7版本中的新特性较多,本文将就一些重点特性进行简单介绍,具体细节可以关注《Greenplum 7新版本大剧透》的后续直播,原厂工程师将为大家详细讲解内核新特性和原理实现。相关PPT可以前往Greenplum中文社区cn.greenplum.org获取。

 1、 基于PostgreSQL 12

Greenplum一直紧密拥抱PostgreSQL社区,内核版本的升级也非常快,目前master分支上已经是12版本,Greenplum团队正在进行一系列的代码清理和测试,希望为大家带来更加稳定的产品体验。

 2 、Just-In-Time 编译

了解Java或者Python的同学可能知道,这类语言都是解释执行的。解释执行是跨平台的,但是解析执行需要时间,执行效率低。

在Greenplum里也存在这样的问题,SQL本质上也是一种解释执行语言。例如SELECT a+b,在SQL里,第一步会先把a的数据取出来,放在左操作符里,再把b的值取出来放在右操作符里,最后再做加法运算。接着会将值做形式转换返回。整个过程效率不高,需要多条机器指令而不是一条加法指令。

于是我们想到是否可以用即时编译的技术来提升效率,即JIT。在Greenplum 7版本里将支持JIT编译的功能,从而可以用JIT来支持SQL的表达式、投影等运算。

但JIT编译并不一定是更好的选择。在编译的过程中,会把原生表达式编译成内部的格式。然而,编译是需要时间的,因此如果在查询语句本身很快,此时JIT编译的时间就不可忽略。

JIT编译默认使用LLVM来执行,可以被用来加速以下表达式。

  • WHERE 语句
  • Target list
  • 聚合(aggregate)
  • 投影运算(projections)

大家在大学里应该都学过过程语言,过程语言中,编译时可以使用内联函数,即inline。函数本来的实现是一种跳转,可以用call的方式进行调用。而内联函数,在调用函数的地方,会把代码直接嵌到源代码里,而不是进行调用。函数的执行效率和局部性将大为提高。

但如果全部函数都是内联函数,会导致编译出来的结果较大,因为每个地方都会被替换成函数体,而非调用函数命令本身。因此inline是个双刃剑,需要斟酌使用场景。生成JIT时可以选择以何种优化粒度来生成代码。

用户需要通过GUC值来控制什么时候使用JIT。

  • jit:默认是“开”的状态,如果这里的状态为“关”,则后面的都关闭状态
  • jit_above_cost:前面我们提到,不是每个查询语句都需要用JIT,当查询计划的cost高于用户设定的阈值时,才会试着用JIT;
  • jit_inline_above_cost:当高于该阈值时,会用内联的方式来处理;
  • jit_optimize_above_cost:当高于该阈值时,就会用optimize的方式来生成代码
  • jit_provider:默认llvmjit,很少会用到

下面是一个例子是一个TPC-H query1进行测试,对两个列进行group by,有聚集,也有Where条件和投影。上文中我们提到,JIT对于这类语句的支持较好。

下面是JIT 的option,这里的option是实验中的option,而非GUC值,0标识打开,-1标识不打开。

第一行的JIT值把jit_above_cost 打开了,第二行的JIT_OPT是指会用optimize的方式生成代码,第三行的JIT_INLINE是指不打开optimize生成代码,而打开内联方式;第四行的JIT_ALL是指把所有的选项均打开,NOT JIT是不使用JIT。

由于有JIT,输出结果里会多出现一些信息。具体可以看下图。

针对q1的option,进行了测试,针对执行时间进行了对比,结果如下图。其中NO JIT时间最长,而JIT_ALL的时间最短。

 3、 Block Range Index -BRIN

我们再来看看BRIN。Greenplum 7版本对Index进行了增强。

Brin Index记录了每个块里最大值和最小值的信息。当在使用WHERE 语句时,通过索引的最大值与最小值与过滤条件的对比,判断是否有交集,来决定是否可以跳过该块的扫描,从而节省扫描成本。和B树索引不同,Brin Index记录的信息很小,效率很高。

然而,有时会存在Brin Index无效的情况。比如在数据比较散,每个块里的数据范围交叠比较多等情况下,就很难过滤太多数据的数据块,而不得不将每个数据块进行扫描,此时Brin Index就失效了。

现在我们来通过下面这个例子来帮助大家理解。例子中的ao表里生成了1亿条数据,并在里面建了两个索引,一个是B树索引,一个是Brin索引。这里大家可以看到,在Brin 索引里,有个option叫pages_per_range。这个值的如果设定的特别大,可以很快的跳过一些块,可能会降低过滤效果。如果太小,则扫描速度太慢。因此这个值不能太大也不能太小,和数据的特性紧密相关。

关于创建时间,大家可以看到创建时间相差较大。B树索引的创建时间约231秒钟,而Brin索引只有18秒钟,相当于B树索引的十分之一的创建时间。

在建立完索引,插入数据时,对于B树,索引的数由于会不停的发生分裂,会动态调整。分裂的过程中会形成新的page,这个过程会造成开销(cost),当数量很大时,开销也会较大。而对于Brin索引,虽然也会有结构体进行维护,但维护成本并不大。设想当插入数据时,只要不违反该块最小值最大值的范围,则不需要更新块的统计信息。除非出现更新数据修改了最小值最大值的情况,才会更新索引。图中最下面的蓝色柱形图是没有索引的情况,没有索引,就不用维护结构,所以非常快。

现在我们来看一下Brin 索引的磁盘空间使用量有多大。本身的数据大小是796M,B树用了2125M比源数据更大,而Brin 索引很小,只有13M。

Brin 索引的使用需要根据具体的使用场景来决定。下面我们来看一下Brin 索引的执行时间。其中最下面一条是没有索引的情况,会比B树和Brin索引的执行慢很多。而B树和Brin索引的时间非常接近。

 4、 优化器的增强

讲完Brin 索引,下面我们来看一下Greenplum 7版本中对优化器的增强。

在Greenplum的较早版本里,多个列是独立分布的,但在现实生活中,数据库中创建的列都是有意义的值,互相会存在依赖关系,例如,国家名、城市名等,在这种情况下,就无法认为这两个列是两个独立事件了。此时,用户需要告诉优化器依赖关系的存在,优化器在生成优化计划时,会将依赖关系考虑在内,从而生成更加准确的查询计划。

 5、 UPSERT

在做数据导入时,会出现数据重复的情况,很难判断哪些列用UPDATE,哪些列用INSERT,而且UPDATE和INSERT的语法是不同的。

针对这种情况,Greenplum 7将支持UPSERT功能,可以把没有的数据INSERT进去,把已有的数据进行UPDATE。

用户可以控制UPSERT的行为。例如下面的例子中,{1,2},{3,3}冲突,用户可以控制是保留原值还是更新为新值,如果保留原值则do nothing。

如果保留新值,则do update。

 6、 Partitioned Table

除了UPSERT之外,Greenplum 7还将增加对Partitioned Table的支持,与PostgreSQL的语法更加接近。Greenplum之前的版本中,创建Partitioned Table时,在指定Partitioned by的语句后,系统会生成很多Partition表。而在Greenplum 7中,会接近PostgreSQL的语法,可以让一个表变成另一个表的partition 子表,在使用中会更加方便、易懂。下图例子显示了两种语法的区别。

我们还可以将一个表附到(attach)另一个表上,作为它的子表,或者从另一个表中拆分(detach)出来。大表和子表的值范围将相同,从而在查询计划中,可以利用这个特性,例如在WHERE语句中,就可以利用该特性,扫描时将特定行过滤掉。

此外,还支持Partition的一些实用函数,可以将某表的父表,直至根部信息均打印出来。此外还提供了pg_partitioned_table系统表,这个表记录了对应到partition表列的范围的相关信息。这样通过与pg_class表做关联就可以得到partition表的相关信息。

Greenplum 7还将支持hash partitioning,按某个列的值哈希后,通过求模数,得到0、1、2、3… 根据分区策略,分布到不同的partition表。

在Greenplum 7中,对heap表和ao表的访问是用Table Access Method来完成。Greenplum之前的版本中,索引的访问是使用access method,现在把heap 表和ao表的访问也使用了access method,用表的方式来记录,这样会更加统一和灵活。

Greenplum 7中,一些已有的语法会发生一些变动。过去的Greenplum中,如下图所示,创建ao表的语法比较简单,Greenplum新版本中,由于使用access method的方式,会用using的语法。此外,Greenplum 7也会为表增加一个新的属性叫Table Access Method。

 7、 VACUUM的提升

除此之外,Greenplum 7版本对于VACUUM也会有不少的提升:

  • 避免扫描和回收仅包含事务frozen元组的页面
  • 避免VACUUM没有必要的索引扫描
  • 避免无用的堆截断尝试并在VACUUM期间采取独占锁定
  • 提高VACUUM删除尾随空堆页面的速度
  • 如果需要的话,可以选择使用INDEX_CLEANUP来跳过INDEX清除
  • 可以选择使用SKIP_LOCKED选项来跳过对无法立刻被锁的表进行VACUUM和ANALYZE
  • 可以使用–jobs选项并行进行VACUUMDB和VACUUM;也支持—skip-locked

 8、 Multi Site Replication

讲完Greenplum内核代码层面的更新,我们再来讲一讲集群之间的灾备相关的特性。众所周知,Greenplum有primary和mirror节点,一旦主节点出现故障,会用从节点来替代,但这仅限于一个数据中心内部。整个事务提交是同步的,当主集群提交事务时,需要等备集群把日志传到主集群才能提交,对于跨区域或者是两地三中心数据中心来说,保证完全同步的开销是很难接受的,针对这种情况,Greenplum 7版本采用了多个集群间异步的方式来处理,来确保数据同步。这样备份集群和主集群之间可能会存在延迟,延迟的大小是由传输管道的带宽决定的,但在大部分使用场景下,这样的延迟是可以接受的。

 9、 Automated Master Failover

在第8节中,我们提到,在Greenplum中,当primary出现故障,mirror会进行替代。但对于Coordinator(Master)来说,一旦出现故障,在7之前的版本都是人工进行备用节点的替换。

Greenplum 7 将支持把这一流程自动化,减少人工的干预,如果Coordinator(Master)出现故障,将自动把备用节点进行替换。这一功能的代码已经完成,正在测试,相信这一功能将解决很多用户的使用痛点。

10、 Greenplum 数据联邦

Greenplum与FDW(Foreign Data Wrapper)进行了很多集成,对各种各样的外围数据进行了封装。对于Greenplum来说,集群1和集群2互为外围数据。Greenplum希望做到让集群1和集群2之间能够高效的,n对n的交换数据,同时也会保证MVCC一致性以及相关性能。这就是gp2gp的一个正在开发的功能。除了对接Greenplum,也可以对接FDW支持的外部资源,例如PostgreSQL、ElasticSearch、Solr…等。

11、 Greenplum 数据加密

Greenplum团队还正在进行Greenplum数据加密的开发,希望能做到一个较为透明的加密。通过全方位的加密,可以提升Greenplum安全级别,保护客户的信息。除了数据的加密,还有对日志、Operator Data、Wal log等信息进行加密保护。


这就是《Greenplum 7 新版本大剧透》系列直播的第一场活动的全部精华内容,下一场直播(4月28日),原厂内核研发工程师陈金豹将为大家介绍Greenplum 7中Brin Index的理论原理与实现细节,记得关注哦!

关注微信公众号

VMware 中国研发中心

Greenplum官方技术交流群

扫码添加小助手即可入群,添加时请备注 “GP网站”