3 数据模型设计优化
数据模型设计优化的七个原则
第一,各个节点运算本地化充分发挥并行优势。
MPP数据库的基本思想是支持在x86服务器上处理big data,因为海量数据终会超过单个x86服务器的处理能力,因此将big data采用分布算法(一般采用hash分布)分布在多个x86服务器节点上,每个服务器仅处理big data的一部分数据,即实现最重要的两个分布式算子--分布式join和聚合算子(group by order
by distinct)。
由于节点处理的数据必须在本地,因此对于需要跨节点进行数据访问的场景就需要节点间进行数据交换,从而增加了集群节点间的处理开销。节点运算本地化就是尽力避免这种跨节点访问数据的场景,避免SQL运算的额外开销。
第二,提高数据自身质量以提高处理效率。
l 数值类型的处理性能比字符串要好,建议选择数值类型。
l 对于表间关联常用的字段,各表应该设计成同样的字段类型。
l 在表关联运算中,如果对各表的Hash分布列加rtrim、ltrim函数会改变hash join的执行计划,必须去掉。为回避此类情形,必须保证加载入库前的数据文件内的数据质量,以保证字段数据的正确性。
第三,使用临时表和汇总模型提升处理效率。
数据加工处理,应该尽量在小表内进行,对于局部的数据加工处理为了不影响基础大表,应建立临时表作进行处理。
对于年汇总、月汇总等粗粒度类数据汇总处理,应该在基于事先建立的日汇总等低粒度结果(包括用户、产品等维度上汇总)基础上进行,这种处理可减少上级统计对明细层数据的重复性读取。
第四,分离基于Hadoop的数据存储中心集群和基于MPP的数据分析中心集群。
为了限制MPP内数据处理数据量和减轻深度分析处理的系统压力,保存在MPP数据库中的最基础(底层)数据,应该是经Hadoop处理过的信令级数据低度汇总之后的数据,而作为更为明细的信令数据应该保持在Hadoop中。
作为与数据仓库独立的数据平台,应建立独立历史库,以分离以简单查询为主的历史库和以深度分析为主的数据仓库的两项大数据平台功能。
第五,设计库时选择合适的数据模型。
一般来说,在数据模型设计过程中,大表要建成分布表,小表要建成复制表,频繁跟其他表做等值join连接的表如维度表也要建成复制表,经常进行等值连接的列要建成hash分布列等。
下面给出一个星形模型的示意图:

图:星形模型示意图
下面重点介绍一下Hash分布列的选择原则,这是MPP数据库数据模型优化的重中之重。具体选择原则如下:
l 尽量选择count(distinct)值大的列做Hash分布列,让数据均匀分布,避免数据倾斜产生木桶效应。
l 优先考虑大表间的JOIN,尽量让大表JOIN条件的列为Hash分布列(相关子查询的相关JOIN也可以参考此原则),以使得大表间的JOIN可以直接分布式执行。
l 其次考虑GROUP BY,尽量让GROUP BY带有Hash分布列,让分组聚合一步完成。
l 通常是等值查询的列,并且使用的频率很高的应考虑建立为hash分布列。
第六,善用索引,提升SQL语句处理性能。
l Hash
Index。通常可以用来解决等值查询的定位效率,特别是对以单表精确查询为主的应用场景尤为适合,如金融业务中的并发账号查询等(特别是内存基本充足的场景)。
l 对有实时数据加载的场景,可以先建立无索引的临时表加载数据,再将临时表内数据插入到带索引的同结构目标表中或在临时表上创建索引。一次性处理索引建立,可较大幅度的降低索引带来的维护成本。
l 索引是一种有损的优化手段,使用索引通常会带来维护的成本,会影响数据加载及DML操作的性能,实际使用时需根据具体需求而定
第七,使用压缩,有效减少I/O的时间,提升性能。
压缩可减少I/O的时间,提升性能。由于并行执行器已经能并行调度解压,因此使解压的性能得到了很大的提升,很多场景下(尤其是针对超大数据量的场景),默认使用压缩数据的方式都可以获得比不压缩更好的性能。
l 压缩方式:
•
列级 int型压缩方式选项:0,1,5
•
列级 char型压缩方式选项:0,3,5
•
表级组合压缩方式为:(0,0)、(1,3)、(5,5),第一位为int型压缩方式,第2列为char型压缩方式
l 适用场景:
•
(1,3)压缩优势是压缩比高,比(5,5)压缩高一倍压缩比,但是执行效率一般
•
如果对存储空间要求高,对性能不太要求时,建议使用(1,3)压缩。
•
如果对存储空间要求不高,对性能要求高时,建议使用(5,5)压缩。
常用数据模型优化策略
数据分布
GBase8a集群性能取决于各个节点整体的性能,每个节点存储的数据量对于集群性能有很大影响,为了尽可能达到最好的性能,所有的数据节点应该尽量存储等量的数据,因此在数据库表规划定义阶段要考虑表是复制表还是分布表,以及对分布表上的某一些列设置为分布列进行hash分布。
例如根据数据的分布特性设计,可以把字典表或者维度表建成复制表的方式将数据存储到各个节点上,不须对其数据进行分片存储,因为字典表的数据量相对较小,虽然在各个节点进行存储有一定的数据冗余,但和事实表的JOIN 运算就可在本地进行,避免节点间搬动数据。对于事实表(大表)可将数据分片到不同的节点上存储,分片方法可采用(range, round robin, hash)等不同方法,SQL执行的查询条件满足只在其中部分节点时,查询优化可决定SQL的执行仅在这些节点执行即可。
建Hash分布列的原则基本如下:
l 尽量选择count(distinct)值大的列做Hash分布列,让数据均匀分布。
l 优先考虑大表间的JOIN,尽量让大表JOIN条件的列为Hash分布列(相关子查询的相关JOIN也可以参考此原则),以使得大表间的JOIN可以直接分布式执行。
l 其次考虑GROUP BY,尽量让GROUP BY带有Hash分布列,让分组聚合一步完成。
l 通常是等值查询的列,并且使用的频率很高的应考虑建立为hash分布列
数据排序
数据在按某查询列进行排序后,则相同数据取值会集中存放在有限的数据包中,因此在以该列进行过滤时,利用智能索引命中的数据包会很少,不仅能降低IO量而且会提高压缩比。这样做最大的好处是可以将智能索引的过滤效果发挥到最优,从而使整体查询性能大幅提升。
建议在实际应用场景许可的前提下,将数据按照查询常用条件列进行排序。如在金融行业中,通常按照银行账号或保单号进行查询,因此可按一定的时间间隔对数据按照银行账号或保单号进行排序,则在此时间范围内的银行账号或保单号有序,在进行查询时,便可通过智能索引特性提高查询性能。
投影列
GBase 8a MPP数据库在编写select语句时应遵循尽量选择有效投影列,对于无关的投影列应避免写入到select语句中,这样可以将需要物化的列有效缩减,进而降低io成本,因此能有效提升查询性能。
压缩策略
大部分应用中性能的瓶颈是磁盘IO,所以新型数据库的设计都已降低磁盘IO为主要设计目标,压缩可减少I/O的时间,提升性能,GBase 8a MPP数据库也不例外,压缩也是提高性能的主要技术之一,GBase 8a
MPP数据库并行执行器已经能够从上层并行调度解压,因此使解压的适用性得到了很大的提升,很多场景下(尤其是针对超大数据量的场景),使用压缩数据的方式都可以获得比不压缩更好的性能。
压缩方式:
l 列级 int型压缩方式选项:0,1,5
l 列级 varchar型压缩方式选项:0,3,5
l 表级组合压缩方式为:00、31、55
选取原则:
l 31压缩优势是压缩比高,比55压缩高一倍压缩比,但是执行效率一般。
l 如果对存储空间要求高,对性能不太要求时,建议使用31压缩。
l 如果对存储空间要求不高,对性能要求高时,建议使用55压缩。
nocopies 表
以nocopies方式创建的表,由于不带副本,因此维护效率会更高。该类型表适用于存储中间临时结果或对高可用需求不高的数据,即使没有备份造成数据损失也可以基于基础数据重新生成,该功能提供了一种在处理性能和高可用需求之间进行权衡和选取的方式。
Hash索引
Hash Index 通常可以用来解决等值查询的定位效率,特别是对以单表精确查询为主的应用场景尤为适合。GBase 8a MPP数据库中的hash索引分为 Global Hash与Local Hash,两者主要的区别是应对的场景不同和计算Hash值的范围不同。
在应对的场景上,Global Hash
Index用于列中数据较分散的情况,而Local Hash Index则用于列中数据较集中的情况,因此,如果一个列中的数值的重复度较高,则应该使用Local Hash Index,否则需要使用Global HashIndex。
在计算范围上,Global Hash
Index是在整个列的全部数值范围上进行计算和维护,而Local Hash Index则是在各个DC上进行计算和维护。现场中,使用Global Hash Index的场景较多。
在使用上,GBase 8a MPP数据库一定是首先进行智能索引过滤的,之后,如果发现查询条件中的等值查询条件列上建立了Hash Index,则使用Hash Index,否则进行全DC扫描。
对有实时数据加载的场景,可以先建立无索引的临时表加载数据,再将临时表内数据插入到带索引的同结构目标表中或在临时表上创建索引。一次性处理索引建立,可较大幅度的降低索引带来的维护成本。
gnode参数
此项优化没有很精确的评估标准,主要还是根据实际应用sql和使用规则进行调整,通过查看gnode层的trace日志分析确定,本次只给出通用的设置原则如下:
l Heap参数(global):
Ø gbase_heap_data 主要设计用途是缓存数据(DC),应分配最多的内存
Ø gbase_heap_large 用于管理不频繁申请、释放的大块儿内存
Ø gbase_heap_temp 用来分配较为琐碎的和小块儿的临时内存,较少使用
Ø gbase_memory_pct_target 设置内存的可用比例,默认0.8
参数下限
Ø gbase_heap_data >= 512MB
Ø gbase_heap_large >= 256MB
Ø gbase_heap_temp >= 256MB
参数上限
Ø (gbase_heap_data + gbase_heap_large + gbase_heap_temp ) <= total
memory * gbase_memory_pct_target
注:total memory =
physical memory + swap(可忽略)
l 算子buffer(session): 基本都是从large堆上分配的。
算子buffer均为session级别,即如果设置gbase_buffer_result=1G,且并发数为30,则在执行并发的过程中,30并发总共占用的gbase_buffer_result就为1G x 30 = 30G,对于一般机器来说已经算很大了,这还不算其他的算子buffer。所以如果在高并发环境中将某一个算子buffer设置很大的话,很有可能就会出现内存不足无法分配的情况。
常用的buffer如下:
Ø gbase_buffer_distgrby:用于保存distinct操作的中间结果;
Ø gbase_buffer_hgrby: 用于保存hash group by操作的中间结果;
Ø gbase_buffer_hj: 用于保存hash join操作的中间结果;
Ø gbase_buffer_insert: 用于保存insert values的中
Ø gbase_buffer_result: 用于保存物化的中间结果;
Ø gbase_buffer_rowset: 用于保存join的行号;
Ø gbase_buffer_sj: 用于保存sort merge join的中间结果,当join条件是a>=b或者a<=b时,可能会使用sort merge join;
Ø gbase_buffer_sort: 用于保存sort操作的中间结果;
算子buffer的设置原则:
Ø 一般情况下(非高并发场景),根据系统内存大小,算子buffer可以按照如下方法设置:
u gbase_buffer_hgrby和gbase_buffer_hj最大不超过4G;
u gbase_buffer_result最大不超过2G;
u gbase_buffer_rowset最大不超过1G;
u 其他算子使用系统估算即可。
u 如果在高并发场景下,则不需要设置过大的算子buffer,一般以系统自动评估为准。但如果并发数过大,不排除需要人为将算子buffer设置更小的情况。具体标准参考上面提到的“算子buffer的特点”,即并发数x总算子buffer大小不超过gbase_heap_large为宜,但最大也不能超过系统总内存大小。
Ø 修改算子buffer的其它场景:
在进行poc时,如果某条sql由于某个算子执行过慢(瓶颈点可参考单机trace),可以适当调大与之对应算子buffer。如根据trace发现join较慢,可以适当调大gbase_buffer_hj的值。但是需要注意,调整该值时必须不能影响其他sql的执行,且必须是现场允许修改系统参数时才可以。如整个测试只允许在开始时设置参数,开始测试后就不允许再修改,如果开始时就把算子buffer设置的很大,而后面又有高并发测试,就很容易会出问题。
SQL改写
任何数据库的优化器都不是完美的,当通过性能分析发现GBase 8a MPP数据库优化器执行方式存在问题时,很多场合下可以通过人为改写SQL。
SQL 的方式避免一些性能问题(相当于人工干预,帮助优化器按最有效率的方式来执行SQL),这个是我们项目中偶尔会采取的优化手段。
其他优化建议
尽量不用游标
游标的操作类似将每行的值取出来,做一系列处理。如果可以去掉游标,改成一条包含多个相关子查询的sql,性能将大大提升。
能用varchar不用char
Char的空格可能影响性能。
Char和varchar的关联可能会影响关联性能。
能用union all尽量不用union
由于union操作需要进行一次去重,去重对于性能影响很大,尽量保证相同数据只入库一次,不同表间无重复数据,进行union all性能会很大提升。
避免超大结果集的直接返回
对于查询结果集达到1万以上,尤其是百万、千万的结果集,应避免结果集的直接返回,将原始select修改为insert select,即将查询结果插入到一个结果表中或者在客户端输出时要加-q参数。
高精度decimal可能使性能变慢
如果系统升级前使用的是低精度decimal,则升级后的高精度decimal可能使得性能变慢,这是由于高精度decimal的关联,取值等操作均要耗费更多的资源,但是这种性能变慢是正常的,只要在一个合理可接受的范围内,就不需要考虑这个问题。
insert into ... select ...
group by串行
现象:insert into ...
select ... group by ...并行按hash 划分,并且是多趟聚集,导致insert 部分串行执行。
原因:按hash划分数据,一趟执行不完的时候,group by操作占着线程,导致线程池中没有空闲线程,做insert时只能串行操作。
解决办法:
1)如果机器核数较多(>=32),可以将并行度调低到与核数相同,线程池使用缺省值(核数)即可;
2)如果核数<32,可以将并行度调大到核数的2倍。




