一.背景
随着云计算的普及和数据分析需求的扩大,数据湖+数据仓库的湖仓一体分析能力成为下一代数据分析系统的核心能力。相对于数据仓库,数据湖在成本、灵活性、多源数据分析等多方面,都有着非常明显的优势。IDC发布的十项年中国云计算市场趋势预测中,有三项和数据湖分析有关。可以预见,跨系统集成能力、数据控制能力和更加全面的数据驱动能力,将会是未来数据分析系统重要的竞争领域。
AnalyticDBPostgreSQL版(简称ADBPG)是阿里云数据库团队基于PostgreSQL内核(简称PG)打造的一款云原生数据仓库产品。在PB级数据实时交互式分析、HTAP、ETL、BI报表生成等业务场景,ADBPG都有着独特的技术优势。作为一个数据仓库产品,ADBPG是如何具备湖仓一体分析能力呢?本文将会介绍ADBPG如何基于PG外表、打造数据湖分析能力。
ADBPG继承了PG的外表(ForeignTable)功能,目前ADBPG的湖仓一体能力主要是基于外表打造的。基于PG外表,ADBPG可以对其他数据分析系统的数据进行查询和写入,在兼容多种数据源的同时,复用ADBPG原有的优化器和执行引擎优势。ADBPG的湖仓一体分析能力目前已经支持OSS、MaxCompute、Hadoop、RDSPG、Oracle、RDSMySQL等多种数据源的分析或者写入。用户可以灵活地将ADBPG应用于数据存储、交互式分析、ETL等不同领域,可以在单个实例中实现多种数据分析功能。即可以用ADBPG完成数据分析的核心流程,也可以作为众多环节中的一环去搭建数据链路。
不过,外表数据的分析依赖于外部SDK和网络IO来实现数据读写,由于网络本身的特性与本地磁盘有巨大差异,因此需要在技术层面与本地存储不同、需要不同的性能优化方案。本文以OSS外表数据读写为例,介绍ADBPG在构建湖仓一体分析能力时,所遇到的一些重要问题和解决方案。
二.问题分析
ADBPG内核可以分为优化器、执行引擎和存储引擎。外表数据分析可以复用ADBPG原有的优化器和执行引擎的核心部分,仅需少量修改。主要扩展是存储引擎层的改造,也就是通过外表接口对外表数据进行读写。外表数据是存储在另一个分布式系统当中,需要通过网络与ADBPG进行连接,这是和读取本地文件的最核心的区别。一方面,不同的外表数据会提供不同的远程访问接口,需要在工程上进行兼容,比如OSS、MaxCompute的数据读取接口都不相同。另一方面,通过网络访问远程机器上的数据有一定的共性,比如网络的延迟、网络放大、带宽限制、网络稳定性问题等。
本文将会围绕上述核心挑战,介绍ADBPG外表分析项目在支持OSS数据分析过程中的一些重要技术点。OSS是一种阿里云推出的一种低成本分布式存储系统,存储了大量的冷热数据,有较大的数据分析需求。为了方便开发者进行扩展,OSS提供了基于Java、Go、C/C++、Python等主流开发语言的SDK。ADBPG采用了OSSCSDK进行开发。目前ADBPG已经完美支持OSS外表分析的各项功能,除建表语句不同外,用户可以像访问本地表一样访问OSS外表。支持并发读取和写入,支持CSV、ORC、Parquet等常见数据格式。
三.外表分析技术优化
接下来,我们介绍ADBPG在基于OSSCSDK开发OSS外表分析过程中,解决的一些核心技术问题。
3.1网络碎片请求问题
在分析型数据库场景,业界普遍认为列式存储在IO性能上强于行式存储。因为列式存储在扫描数据时,只需要扫描特定列,而行式存储毕竟扫描全量数据,因此列式存储可以节约一些IO资源。但是在开发过程中,团队发现在一些场景下,如字段较多的大宽表扫描,扫描性能较高的列存格式竟然比扫描CSV行存文本格式性能还要差。后经过定位发现一方面扫描ORC/PARQUET格式时,客户端与OSS服务端交互次数过于频繁,另一方面ADBPG单次向OSS请求的数据量比较小。这两个原因带来了很大的性能问题。
我们知道,相比于本地磁盘IO,网络IO所产生的往返时延往往可以放大几个量级。因此,如果解析一些列存格式(如ORC/PARQUET)时,如果将网络请求当作本地磁盘请求处理,高压缩比所带来的网络带宽占用的减少不足以抵消碎片化请求带来的往返时延放大,因此性能测试结果低于预期。问题的解决方案,就是通过缓存来减少碎片化的网络请求。ADBPG每次扫描OSS数据都会“预加载”足够的数据并缓存,请求时,判定是否命中缓存,如果命中,则直接返回缓存;否则,继续下一轮次的“预加载”,从而降低网络请求次数,提高单次请求效率。“预加载”的缓存大小开放配置,默认大小为1MB。
3.2列过滤与谓词下推
由于网络本身的IO性能往往是低于本地存储的IO性能的,因此在扫描外表数据时,要尽量减少IO的带宽资源消耗。ADBPG在处理ORC、Parquet格式的文件时,采用了列过滤和谓词下推技术,来达到这一目的。
列过滤,即外表只请求SQL查询所需的数据列、忽略不需要的数据列。因为ORC、Parquet都是列式存储格式,所以外表在发起网络请求时,只需请求所需列所在的数据范围即可,从而大幅减小网络I/O。同时,ORC、Parquet会对列数据进行压缩处理,进一步减小I/O。
谓词下推,是将执行计划里的上层的过滤条件(如WHERE子句中的条件),移动到下层的外表扫描节点,使外表扫描进行网络请求时,过滤掉不符合查询条件的数据块,从而减少网络I/O。在ORC/Parquet格式文件中,会在每一个block头部保存该block中每一列数据的min/max/sum等统计信息,当外表扫描时,会先读取该block的头部统计信息,与下推的查询条件进行比较,如果该列的统计信息不符合查询条件,则可以直接跳过该列数据。
这里简单介绍ORC格式的外表的谓词下推的实现方案。一个ORC文件按数据行分成若干个Stripe组成,Stripe中数据按列式存储。每个Stripe又分为若干个RowGroup,所有列的每行组成一个RowGroup。如下图所示。
ORC文件保存3个层次的统计信息,文件级别与Stripe级别的统计信息存储在ORC文件末尾,RowGroup级别的统计信息在每个Stripe块头部存放。使用这3个层次的统计信息,ORC外表可以实现文件级过滤,Stripe级过滤以及RowGroup级别过滤。具体做法是,每当扫描一个新的ORC文件,会先读取文件末尾的文件级统计信息,若不符合查询条件,则直接跳过整个文件的扫描;接着读取文件末尾所有Stripe级别的统计信息,过滤掉不符合条件的Stripe块;对于每个符合条件的Stripe块,读取块头部的RowGroup级别的统计信息,过滤掉不必要的数据。
3.3“”问题
OSSCSDK定义了一类错误代码,用于表示异常情况,这里的是OSSCSDK中定义的错误码-。类似的还有错误码-、-、-等。这一类错误,通常都是网络异常导致的OSS外表导入导出失败。-是最为常见的一种。
OSSCSDK内部使用CURL与OSS服务端进行网络交互,相应的CURL错误码,常见CURL56(Connectionresetbypeer)、52等。这些网络异常,通常是由于OSS服务端在负载较高情况下,服务端主动剔除其认为“不活跃”的客户端连接所致。当需要导入或导出较大规模OSS数据时,由于客户端处于执行计划的不同阶段,不能长时间持有连接进行连续通信,从而被OSS服务端当作“不活跃”的客户端连接而关闭。
通常对于这种情况,客户端需要尝试重试解决。实际开发过程中发现,即使客户端接口增加了自动异常重试机制,这种异常依然得不到改善。后经过定位发现,OSSCSDK为提高连接效率,增加了CURL句柄的连接池,但这些网络异常的CURL句柄,也会存放到池中,因此,即使重试,还是会使用异常的CURL句柄进行通信,所以异常的问题得不到改善。
既然知道了根本原因,解决的方法也很直观。我们在CURL句柄的回收接口中,增加对CURL句柄状态检查,对于异常的CURL句柄进行销毁,而不是加回连接池中。这样避免了连接池中存在无效的CURL句柄。客户端接口重试时,选择有效的或者创建新的CURL连接再次通信即可。当然,自动异常重试机制只能针对那些可以重试解决的情况。
①ADBPG访问OSS外表时,先从CURL连接池中获取连接,若不存在则新建。
②ADBPG使用CURL连接句柄与OSSServer请求通信。
③OSSServer通过CURL连接句柄返回通信结果。
④正常返回的CURL连接句柄使用完毕后加回连接池待下次使用。
⑤异常状态的CURL连接句柄销毁。
3.4内存管理方案的兼容问题
ADBPG基于PostgreSQL内核打造,也继承了PostgreSQL的内存管理机制。PostgreSQL的内存管理采用了进程安全的内存上下文MemoryContext,而OSSCSDK是线程安全的内存上下文APRPool。在MemoryContext内存环境下,每个已经分配的内存,都可以显式的调用free释放,由MemoryContext进行内存碎片的整理,但在APRPool中,我们只看到内存池的创建、内存的申请和内存池的销毁等操作,却没有内存的显式释放接口。
这种情况意味着,我们需要对于OSSCSDK接口所持有的内存的生命周期有明确的了解,否则极易出现内存泄漏和访问已经释放的内存等问题。通常我们会按照如下两种方式申请APRPool的内存。
方式一适用于重入低频的操作接口,如获取OSS文件清单列表。方式二适用于多次重入的操作接口,如周期性向OSS请求指定文件指定范围的数据。通过这种方法,可以很好地解决ADBPG与OSSCSDK在内存管理方面的不兼容问题。
3.5数据格式的兼容和优化
OSS上的数据,大部分采用CSV、ORC、Parquet等格式。由于ORC/Parquet等格式对数据的底层存储编码,与ADBPG的数据编码并不一致,所以当进行外表扫描时,数据类型转换是必不可少的步骤。类型转换,本质上是将数据从一种编码,改变成另一种编码方式。例如ORC对于Decimal类型的表示方式和ADBPG不相同,在ORC中Decimal64类型由一个int64存放数据的数字值,再由precision和scale表示数字个数和小数点位数,而在ADBPG中,Decimal类型由int16数组来存放数据的数字值。格式转换算法需要对每个数据进行循环的除法与取模操作,这是非常耗费CPU的。
为了减少类型转换带来的CPU消耗,进一步优化外表查询性能,ADBPG在使用外表进行导出数据时,跳过类型转换步骤,直接将ADBPG的数据,以二进制形式写入到外表文件中,这样在查询外表时,也无需进行任何数据类型转换。例如,在导出ORC外表时,外表可以将任意的数据类型,都直接写入为ORC的Binary类型,在ORC中存储的二进制数据,都是按照对应ADBPG的数据类型来编码,于是在查询该ORC外表时,可以直接省略类型转换步骤,减少了CPU消耗。根据TPCH查询测试结果,整体查询性能可以提升15%-20%左右。
四.性能测试
关于在ADBPG中如何使用外表分析功能,请参考阿里云产品手册(