导航

Greenplum优化总结

Posted on 2020-06-15 23:27  水木山川  阅读(5275)  评论(0编辑  收藏  举报

Greenplum优化总结

GP优化需要了解清理缓存、性能监控、执行计划分析等知识。优化主要包含以下四方面:
  表、字段,SQL,GP配置、服务器配置,硬件及节点资源。

一、 清理缓存:

#!/usr/bin/sudo bash
gpstop -r   #快速停止GP数据库
sync     #清空高速缓存前尝试将数据刷新至磁盘

#释放linux内存
echo 1 > /proc/sys/vm/drop_caches
echo 2 > /proc/sys/vm/drop_caches
echo 3 > /proc/sys/vm/drop_caches

gpstart   #启动GP数据库

二、 性能监控Performance Monitor

  Greenplum监控管理平台Pivotal Greenplum Command Center (GPCC)和Pivotal Greenplum (GPDB)。实际使用过程中发现对于6-8秒的查询(单表亿级数据),GPCC反应比较慢,CPU、IO等信息为0,可以采用其他方式实时监控CPU、内存、IO、网络等信息。

三、 执行计划分析
  EXPLAIN 会为查询显示其查询计划和估算的代价,但是不执行该查询。
  EXPLAIN ANALYZE除了显示查询的查询计划之外,还会执行该查询。EXPLAIN ANALYZE会丢掉任何来自SELECT语句的输出,但是该语句中的其他操作会被执行(例如INSERT、UPDATE或者DELETE)。

  slice、motion
  GPDB 有一个特有的算子:移动( motion )。移动操作涉及到查询处理期间在 Segment 之间移动数据。motion 分为广播( broadcast )、重分布( redistribute motion )、Gather motion。正是 motion 算子将查询计划分割为一个个 slice ,上一层 slice 对应的进程会读取下一层各个 slice 进程广播或重分布的数据,然后进行计算。每一个广播或重分布或gather会产生一个slice。每一个切片在每个数据节点会对应发起一个进程来处理该slice负责的数据。SQL中要控制切片的数量,如果太多,应适当将sql拆分,避免由于进程太多,给数据库、机器带来太多的负担,也容易导致sql失效。

  Gather motion的作用就在于将每个节点上面的中间结果集中到主节点上面。GP中的数据迁移方式为数据广播和数据重分布。

  OLAP的基本多维分析操作有钻取(Drill-up和Drill-down)、切片(Slice)和切块(Dice)、以及旋转(Pivot)

四、优化Greenplum连接

  1.分解查询,去除join或者减小join数据量
  2.缓存映射关系
  3.建立索引,分部键
  4.使用官方驱动包

  数据查询去重
  连接查询:建立两个临时表,通过关键字段筛选
  连接查询:建立两个临时表,通过获取最大关键字段,再比较
  连接查询:分组排序,添加序号,获取序号最大值

五、优化表结构

  1、表字段设计

    表字段选择恰当的字段类型,例如:数字类型选择int4或int8,浮点数选择float8,字符串选择varchar(32)等。

  2、表存储方式

    Heap 或 Append-Only存储:GP默认使用堆表。堆表最好用在小表,如:维表(初始化后经常更新)。Append-Only表不能update和delete。一般用来做批量数据导入。 不建议单行插入。
    多列查询请求
    行存储 => 在select或where子句中,查询所有列或大部分列
    列存储 => 在where或having子句中,查询单列的值汇总或单行过滤

    若数据需要频繁地更新或者插入,则使用行存储。
    若需要同时访问一个表的很多字段,则使用行存储。
    对于通用或者混合型业务,建议使用行存储。

    若查询访问的字段数目较少,或者仅在少量字段上进行聚合操作,则使用列存储。
    若仅常常修改表的某一字段而不修改其他字段,则使用列存储。

  3、压缩

    对于大AO表和分区表使用压缩,以提高系统I/O。在字段级别配置压缩。考虑压缩比和压缩性能之间的平衡。压缩的性能取决于硬件、查询调优设置、其它因素。

    QuickLZ - 低压缩率、低cpu消耗、压缩数据块
    zlib - 高压缩率、低速

  4、列存储

    列存里面可以启动压缩。只适合append-only表。

  5、索引

    高基数的列(唯一值多)一般来说,在Greenplum数据库中索引不是必需的。对于高基数的列存储表,如果需要遍历且查询选择性较高,则创建单列索引。频繁更新的列不要建立索引。
在加载大量数据之前删除索引,加载结束后再重新创建索引。优先使用 B 树索引。不要为需要频繁更新的字段创建位图索引。不要为唯一性字段、基数非常高或者非常低的字段创建位图索引。不要为事务性负载创建位图索引。一般来说不要索引分区表。如果需要建立索引,则选择与分区键不同的字段。可优化部分小结果集查询。

  6、 分组扩展

  Greenplum数据库的GROUP BY扩展可以执行某些常用的计算,且比应用程序或者存储过程效率高。

    GROUP BY ROLLUP(col1, col2, col3)
    GROUP BY CUBE(col1, col2, col3)
    GROUP BY GROUPING SETS((col1, col2), (col1, col3))

    ROLLUP 对分组字段(或者表达式)从最详细级别到最顶级别计算聚合计数。ROLLUP的参数是一个有序分组字段列表,它计算从右向左各个级别的聚合。例如 ROLLUP(c1, c2, c3) 会为下列分组条件计算聚集:

      (c1, c2, c3)
      (c1, c2)
      (c1)
      ()

    CUBE 为分组字段的所有组合计算聚合。例如 CUBE(c1, c2, c3) 会计算一下聚合:

(c1, c2, c3)
(c1, c2)
(c2, c3)
(c1, c3)
(c1)
(c2)
(c3)
()

    GROUPING SETS 指定对那些字段计算聚合,它可以比ROLLUP和CUBE更精确地控制分区条件。

  7、分区

  黄金法则

  目前Greenplum支持LIST和RANGE两种分区类型。分区的目的是尽可能的缩小QUERY需要扫描的数据量,因此必须和查询条件相关联。只为大表设置分区,不要为小表设置分区。仅在根据查询条件可以实现分区裁剪时使用分区表。建议优先使用范围 (Range) 分区,否则使用列表 (List) 分区。根据查询特点合理设置分区。不要使用相同的字段既做分区键又做分布键。不要使用默认分区。避免使用多级分区;尽量少地创建分区,每个分区的数据会多些。通过查询计划的 EXPLAIN 结果来确保对分区表执行的查询是选择性扫描(分区裁剪)。对于列存储的表,不要创建过多的分区,否则会造成物理文件过多:
    Physical files = Segments * Columns * Partitions。

  8、根据监控定位资源占用较多的情况:

  CPU、内存、IO、网络

  #检查磁盘空间使用,GP里面就可以查看到对应分区的使用情况

  # SELECT dfsegment,dfhostname,dfdevice,dfspace FROM gp_toolkit.gp_disk_free ORDER BY dfsegment;
  # SELECT sodddatname,pg_size_pretty(sodddatsize) FROM gp_toolkit.gp_size_of_database ORDER BY sodddatname;

  #查看现有配置值

  # gpconfig -s work_mem;
  Greenplum配置参数优化:5.10.2

  GP数据库参数配置,配置文件 postgresql.conf参数
    shared_buffers:刚开始可以设置一个较小的值,比如总内存的15%,然后逐渐增加,过程中监控性能提升和swap的情况。
    effective_cache_size : 这个参数告诉PostgreSQL的优化器有多少内存可以被用来缓存数据,以及帮助决定是否应该使用索引。这个数值越大,优化器使用索引的可能性也越大。 因此这个数值应该设置成shared_buffers加上可用操作系统缓存两者的总量。通常这个数值会超过系统内存总量的50%。
    work_mem: 当PostgreSQL对大表进行排序时,数据库会按照此参数指定大小进行分片排序,将中间结果存放在临时文件中,这些中间结果的临时文件最终会再次合并排序,所以增加此参数可以减少临时文件个数进而提升排序效率。当然如果设置过大,会导致swap的发生,所以设置此参数时仍需谨慎,刚开始可设定为总内存的5%。
    temp_buffers: 即临时缓冲区,拥有数据库访问临时数据,GP中默认值为1M,在访问比较到大的临时表时,对性能提升有很大帮助。
    gp_fts_probe_threadcount: 设置ftsprobe线程数,此参数建议大于等于每台服务器segments的数目。
    gp_hashjoin_tuples_per_bucket: 此参数越小,hash_tables越大,可提升join性能。
    gp_interconnect_setup_timeout: 此参数在负载较大的集群中,应该设置较大的值。
    gp_vmem_protect_limit: 控制了每个段数据库为所有运行的查询分配的内存总量。如果查询需要的内存超过此值,则会失败。使用下面公式确定合适的值:

      (swap + (RAM * vm.overcommit_ratio)) * .9 / number_of_Segments_per_server
    例如:8GB 交换空间,128GB 内存,vm.overcommit_ratio = 50,8 个段数据库
      (8 + (128 * .5)) * .9 / 8 = 8 GB, 则设置gp_vmem_protect_limit为 8GB

    gp_statement_mem: 服务器配置参数 gp_statement_mem 控制段数据库上单个查询可以使用的内存总量。如果语句需要更多内存,则会溢出数据到磁盘。用下面公式确定合适的值
      (gp_vmem_protect_limit * .9) / max_expected_concurrent_queries
    例如,如果并发度为40, gp_vmeme_protect_limit为8GB,则 gp_statement_mem 为:
      (8192MB * .9) / 40 = 184MB,每个查询最多可以使用 184MB 内存,之后将溢出到磁盘。
    gp_workfile_limit_files_per_query
    如果为SQL查询分配的内存不足,Greenplum数据库会创建溢出文件(也叫工作文件)。在默认情况下,一个SQL查询最多可以创建 100000 个溢出文件,这足以满足大多数查询。 该参数决定了一个查询最多可以创建多少个溢出文件。0 意味着没有限制。限制溢出文件数据可以防止失控查询破坏整个系统。 如果分配内存不足或者出现数据倾斜,则一个SQL查询可能产生大量溢出文件。如果超过溢出文件上限,Greenplum数据库报告如下错误:
    ERROR: number of workfiles per query limit exceeded
    在尝试增大gp_workfile_limit_files_per_query前,先尝试通过修改 SQL、数据分布策略或者内存配置以降低溢出文件个数。
    max_connections: 最大连接数,Segment建议设置成Master的5-10倍。

六、数据库查询分析

  1. VACUUM

  vacuum只是简单的回收空间且令其可以再次使用,没有请求排它锁,仍旧可以对表读写
  vacuum full执行更广泛的处理,包括跨块移动行,以便把表压缩至使用最少的磁盘块数目存储。相对vacuum要慢,而且会请求排它锁。
  定期执行:在日常维护中,需要对数据字典定期执行vacuum,可以每天在数据库空闲的时候进行。然后每隔一段较长时间(两三个月)对系统表执行一次vacuum full,这个操作需要停机,比较耗时,大表可能耗时几个小时。
  reindex:执行vacuum之后,最好对表上的索引进行重建

  2. ANALYZE

  命令:analyze [talbe [(column,..)]]
  收集表内容的统计信息,以优化执行计划。如创建索引后,执行此命令,对于随即查询将会利用索引。
  自动统计信息收集,在postgresql.conf中有控制自动收集的参数gp_autostats_mode设置,gp_autostats_mode三个值:none、no_change、on_no_stats(默认)
    none:禁止收集统计信息
    on change:当一条DML执行后影响的行数超过gp_autostats_on_change_threshold参数指定的值时,会执行完这条DML后再自动执行一个analyze 的操作来收集表的统计信息。
    no_no_stats:当使用create talbe as select 、insert 、copy时,如果在目标表中没有收集过统计信息,那么会自动执行analyze 来收集这张表的信息。gp默认使用on_no_stats,对数据库的消耗比较小,但是对于不断变更的表,数据库在第一次收集统计信息之后就不会再收集了。需要人为定时执行analyze.
如果有大量的运行时间在1分钟以下的SQL,你会发现大量的时间消耗在收集统计信息上。为了降低这一部分的消耗,可以指定对某些列不收集统计信息,如下所示:

  1. create table test(id int, name text,note text);
  上面是已知道表列note不需出现在join列上,也不会出现在where语句的过滤条件下,因为可以把这个列设置为不收集统计信息:

  1. alter table test alter note SET STATISTICS 0;

  3. EXPLAIN执行计划

  显示规划器为所提供的语句生成的执行规划。

    cost:返回第一行记录前的启动时间, 和返回所有记录的总时间(以磁盘页面存取为单位计量)
    rows:根据统计信息估计SQL返回结果集的行数
    width:返回的结果集的每一行的长度,这个长度值是根据pg_statistic表中的统计信息来计算的。

  EXPLAIN执行计划字段说明

TYPE:
ALL:Full Table Scan,全表扫描
index: Full Index Scan,索引扫描
range:范围扫描
ref:表示连接匹配条件
eq_ref:类似ref,区别就在使用的索引是唯一索引
const:常量查询,比如pk等值
system:是const类型的特例当查询的表只有一行的情况下,使用system
Extra:
Using filesort:排序
Using index:使用索引可以返回请求列
Using index condition:通过索引初步过滤
Using temporary:回表再过滤其它条件临时表
Using where:单独出现时一般代表表上出现全表扫描过滤
Using index & Using where:使用索引返回数据同时通过索引过滤

 

  4. 两种聚合方式

  hashaggregate 根据group by字段后面的值算出hash值,并根据前面使用的聚合函数在内存中维护对应的列表,几个聚合函数就有几个数组。相同数据量的情况下,聚合字段的重复度越小,使用的内存越大。
  groupaggregate 先将表中的数据按照group by的字段排序,在对排好序的数据进行全扫描,并进行聚合函数计算。消耗内存基本是恒定的。
  选择方式,在SQL中有大量的聚合函数,group by的字段重复值比较少的时候,应该用groupaggregate

  5. 关联

  分为三类:hash join、nestloop join、merge join,在保证sql执行正确的前提下,规划器优先采用hash join。

  hash join: 先对其中一张关联的表计算hash值,在内存中用一个散列表保存,然后对另外一张表进行全表扫描,之后将每一行与这个散列表进行关联。
  nestedloop:关联的两张表中的数据量比较小的表进行广播,如笛卡尔积:select * fromtest1,test2
  merge join:将两张表按照关联键进行排序,然后按照归并排序的方式将数据进行关联,效率比hash join差。full outer join只能采用merge join来实现。
  关联的广播与重分布解析P133,一般规划器会自动选择最优执行计划。有时会导致重分布和广播,比较耗时的操作。

  6. 重分布

  一些sql查询中,需要数据在各节点重新分布,受制于网络传输、磁盘I/O,重分布的速度比较慢。

  关联键强制类型转换
    一般,表按照指定的分布键作hash分部。如果两个表按照id:intege、id:numericr分布,关联时,需要有一个表id作强制类型转化,因为不同类型的hash值不一样,因而导致数据重分布。
  关联键与分部键不一致
    group by、开窗函数、grouping sets会引发重分布

  查询优化
    通过explain观察执行计划,从而确定如果优化SQL。

  7. 选择合适分布键

  分布键选择不当会导致重分布、数据分布不均等,而数据分布不均会使SQL集中在一个segment节点的执行,限制了gp整体的速度。使所有节点数据存放是均匀的,数据分布均匀才能充分利用多台机器查询,发挥分布式的优势。join、开窗函数等尽量以分布键作为关联键、分区键。尤其需要注意的是join、开窗函数会依据关联键、分区键做重分布或者广播操作,因而若分布键和关联键不一致,不论如何修改分布键,也是需要再次重分布的。尽量保证where条件产生的结果集的存储也尽量是均匀的。

  查看某表是否分布不均: select gp_segment_id,count(*) from fact_tablegroup by gp_segment_id;
  在segment一级,可以通过 select gp_segment_id,count(*) from fact_table group by gp_segment_id; 的方式检查每张表的数据是否均匀存放在系统级,可以直接用 df -h 或 du -h检查磁盘或者目录数据是否均匀
  查看数据库中数据倾斜的表
  首先定义数据倾斜率为:最大子节点数据量/平均节点数据量。为避免整张表的数据量为空,同时对结果的影响很小,在平均节点数据量基础上加上一个很小的值,SQL如下:

SELECT tabname,
max(SIZE)/(avg(SIZE)+0.001) AS max_div_avg,
sum(SIZE) total_size
FROM
(SELECT gp_segment_id,
oid::regclass tabname,
pg_relation_size(oid) SIZE
FROM gp_dist_random('pg_class')
WHERE relkind='r'
AND relstorage IN ('a','h')) t
GROUP BY tabname
ORDER BY 2 DESC;

  8. 分区表

  按照某字段进行分区,不影响数据在数据节点上的分布,但是,仅在单个数据节点上,对数据进行分区存储。可以加快分区字段的查询速度。

  9. 压缩表

  对于大AO表和分区表使用压缩,以节省存储空间并提高系统I/O,也可以在字段级别配置压缩。应用场景:

  不需要对表进行更新和删除操作、访问表的时候基本上是全表扫描,不需要建立索引、不能经常对表添加字段或者修改字段类型。

  9. 窗口函数

  窗口函数可以实现在结果集的分组子集上的聚合或者排名函数,例如 sum(population) over (partition by city)。窗口函数功能强大,性能优异。因为它在数据库内部进行计算,避免了数据传输。

  窗口函数row_number()计算一行在分组子集中的行号,例如 row_number() over (order by id)。如果查询计划显示某个表被扫描多次,那么通过窗口函数可能可以降低扫描次数。窗口函数通常可以避免使用自关联。

  10. 列存储和行存储

  列存储亦即同一列的数据都连续保存在一个物理文件中,有更高的压缩率,适合在宽表中对部分字段进行筛选的场景。需要注意的是:若集群中节点较多,而且表的列也较多,每个节点的每一列将会至少产生一个文件,那么总体上将会产生比较多的文件,对表的DDL操作就会比较慢。在和分区表使用时,将会产生更多文件,甚至可能超过linux的文件句柄限制,要尤其注意。

  行存储:如果记录需要 update/delete,那么只能选择非压缩的行存方式。对于查询,如果选择的列的数量经常超过30个以上的列,那么也应该选择行存方式。

  列存储:如果选择列的数量非常有限,并且希望通过较高的压缩比换取海量数据查询时的较好的 IO性能,那么就应该选择列存模式。其中,列存分区表,每个分区的每个列都会有一个对应的物理文件,所以要注意避免文件过多,导致可能超越linux上允许同时打开文件数量的上限以及DDL命令的效率很差。

  11. 函数和存储过程

  虽然支持游标但是,尽量不要使用游标方式处理数据,而是应该把数据作为一个整体进行操作。

  12. 索引使用

  如果是从超大结果集合中返回非常小的结果集(不超过5%),建议使用BTREE索引(非典型数据仓库操作),表记录的存储顺序最好与索引一致,可以进一步减少IO(好的index cluster)
where条件中的列用or的方式进行join,可以考虑使用索引。键值大量重复时,比较适合使用bitmap索引。

 

参考资料:
  Greenplum 的分布式框架结构:https://www.cnblogs.com/arthurqin/p/6243277.html
  Greenplum函数参考:https://gp-docs-cn.github.io/docs/ref_guide/function-summary.html#top