Greenplum 内存与负载管理(resource queue)最佳实践

2 minute read

背景

Greenplum是一个重计算和重资源的MPP数据库,可谓有多少资源就能消耗多少资源,带来的好处是处理速度变快了,坏处就是容易用超。

CPU、网络、硬盘用超的话,关系不大,因为大不了就是到硬件瓶颈,但是内存用超的话会带来较大的负面影响,例如操作系统OOM用户进程,导致数据库崩溃等。

如果要达到非常强壮的稳定性,Greenplum内存的配置非常的关键。(当然代码本身的质量也很关键)

如何避免OOM

OOM指遇到了进程申请内存不足的错误。例如

Out of memory (seg27 host.example.com pid=47093)  
VM Protect failed to allocate 4096 bytes, 0 MB available  

哪些情况会引发OOM

导致数据库OOM报错的原因可能有:

1、数据库节点的内存不足。

2、操作系统内存相关的内核参数配置不当。

3、数据倾斜,导致某些查询时,某个SEGMENT需要申请的内存超大。

4、查询倾斜,例如某些聚合、窗口函数的分组不是分布键,那么需要对数据进行重分布,重分布后导致某个SEGMENT的数据出现倾斜,导致某些查询是,某个SEGMENT需要申请的内存超大。

如何避免OOM

1、调整QUERY,使之需要更少的内存。

2、使用资源队列(Greenplum控制资源的一种手段),限制并发QUERY数。降低集群内同时运行的QUERY数,从而减少系统整体的内存资源的使用。

3、减少单个主机部署的SEGMENT数量,例如有128G内存的主机,部署16个和部署8个SEGMENT节点,每个节点能使用的内存相差了一倍。

4、增加单台主机的内存数。

5、设置数据库参数gp_vmem_protect_limit,限制单个SEGMENT可以使用的VMEM上限。单个主机的内存以及部署多少个SEGMENT决定了平均单个SEGMENT最多可以使用多少内存。

6、对于某些对内存使用量不可预知的SQL,通过在会话中设置statement_mem参数,限制单条SQL对内存的使用,从而避免单条SQL把内存吃光的情况。

7、也可以在库级别设置statement_mem参数。对这个数据库的所有会话生效。

8、使用资源队列(Greenplum控制资源的一种手段),限制这个资源组的内存使用上限,将数据库用户加入资源组,控制这些用户共同使用内存的上限。

如何配置内存相关参数

正确的配置(操作系统、数据库参数、资源队列管理)可以有效的降低OOM发生的概率。

1、加内存并不是最有效的方法,因为加内存还有成本问题,而且无法避免所有问题。

2、在计算单主机内单个SEGMENT的平均可使用内存时,不能只考虑primary segment,还需要考虑mirror segment,因为当集群出现主机故障时,会将SEGMENT切换到对应的MIRROR,此时,主机上跑的SEGMENT数就比平时更多了。

因此我们必须考虑到failover时,mirror需要占用的资源。

接下来我们分析一下操作系统内核配置、数据库配置。如何让数据库尽量的避免OOM。

操作系统内核参数

1、不要配置系统的huge page,因为Greenplum的PG版本较老,还没有支持huge page。而操作系统的huge page会锁定内存的分配,导致这部分内存不能被数据库节点使用。

2、vm.overcommit_memory,如果使用SWAP建议设置为2,如果不使用SWAP建议设置为0。

==============================================================      
.    
overcommit_memory:      
.    
This value contains a flag that enables memory overcommitment.      
.    
When this flag is 0,       // 比较友好,允许申请的内存空间通常不能超过"总内存-不可释放内存(RSS部分)"的部分。超过时申请内存才会报错。      
the kernel attempts to estimate the amount      
of free memory left when userspace requests more memory.      
.    
When this flag is 1,       // 比较暴力,因为大多数进程不管三七二十一,先使用malloc申请一打开空间,但是不使用它或者是只使用部分。所以设置为2时,不管什么情况都允许malloc申请成功,除非遇到真的内存不足的情况。  
the kernel pretends there is always enough memory until it actually runs out.      
.    
When this flag is 2,       // 最友好,在计算允许申请的内存空间时,将SWAP也算进去,也就是说申请大量内存时,可能触发SWAP但是允许你申请成功。  
                           // 在开启SWAP的时候,建议设置为2。GPDB官网也推荐设置为2。主要是防止OOM。  
the kernel uses a "never overcommit"      
policy that attempts to prevent any overcommit of memory.      
Note that user_reserve_kbytes affects this policy.      
.    
This feature can be very useful because there are a lot of      
programs that malloc() huge amounts of memory "just-in-case"      
and don't use much of it.      
.    
The default value is 0.      
.    
See Documentation/vm/overcommit-accounting and      
security/commoncap.c::cap_vm_enough_memory() for more information.      
.    
==============================================================      
.    
overcommit_ratio:      
.    
When overcommit_memory is set to 2,   // 当设置为2时,允许申请的内存地址范围不能超过“swap+内存大小*overcommit_ratio”  
the committed address space is not permitted to exceed     
      swap + this percentage of physical RAM.      
See above.      
.    
==============================================================     

3、overcommit_ratio,越大允许用户进程申请的内存空间越大,但是给操作系统保留的空间就越小。需要一个公式来计算。具体参考后面的例子。

数据库参数

1、gp_vmem_protect_limit

控制每个segment上,所有进程可以申请到的最大内存。如果这个值太高,可能触发系统的OOM或者更严重的问题。如果设置太低,则可能导致系统有足够内存的情况下,SQL确无法运行。

后面有设置公式。

2、runaway_detector_activation_percent

这个参数默认为90,是一个百分比值。当任一SEGMENT使用的内存超过(runaway_detector_activation_percent*gp_vmem_protect_limit/100)时,主动terminate QUERY。防止OOM。

terminate 的顺序从使用最多内存的QUERY依次开始,直到内存降低到(runaway_detector_activation_percent*gp_vmem_protect_limit/100)以下。

通过 gp_toolkit.session_level_memory_consumption 视图可以观察每个会话的内存使用情况,以及runaway的信息。

3、statement_mem

默认为125MB。设置单条SQL最多可以申请的内存,当超过这个内存时,写spill file文件。

建议的设置为单个SEGMENT的保护内存乘以0.9除以期望的最大SQL并行运行的值。

(gp_vmem_protect_limit * 0.9) / max_expected_concurrent_queries  

注意1,statement_mem在会话中设置,如果当前并行度很低,某个会话需要RUN一条需要大量内存的QUERY,可以在会话中设置一下。

注意2,statement_mem比较适合低并发的环境对内存的使用控制。对于高并发的环境,如果使用statement_mem来控制内存,你会发现每条QUERY可以使用的内存极少,不利于高并发情况下少量对内存需求多的QUERY的性能。建议高并发的情况下,使用资源队列(resource queue)来控制内存的使用上限。

4、gp_workfile_limit_files_per_query

限制每个QUERY可以使用的最大spill文件数(当QUERY申请的内存超过statement_mem的限制时,使用spill file(workfiles),类似操作系统的swap空间)。当使用的spill file超过限制时,QUERY会被terminate。

默认为0,表示无限制。

5、gp_workfile_compress_algorithm

设置spill file的压缩算法。Valid values are “NONE”, “ZLIB”.

设置压缩,CPU换空间,或CPU换IO能力。当磁盘紧张、磁盘spill file有写入瓶颈时可以设置压缩。

内存参数计算例子

gp_vmem,gp_vmem_protect_limit,vm.overcommit_ratio,设置举例:

环境如下:

主机配置:  
  
Total RAM = 256GB  
  
SWAP = 64GB  
  
部署配置:  
8 primary segments and 8 mirror segments per host, in blocks of 4 hosts (4台主机)  
  
当挂掉一台主机时,8个PRIMARY要分摊到剩余的3台主机,最多单台额外承担3个PRIMARY。所以是8+3=11。  
Maximum number of primaries per host during failure is 11  

1、首先计算给gpdb的总内存

(给操作系统保留 “7.5G + 5%内存” 的余量,算出整个系统给应用软件的实际可用内存。),然后(实际可用内存 除以 1.7的经验系数)

gp_vmem = ((SWAP + RAM) – (7.5GB + 0.05 * RAM)) / 1.7  
        = ((64 + 256) - (7.5 + 0.05 * 256)) / 1.7   
        = 176  

2、计算overcommit_ratio,用到了一个经验系数0.026。

vm.overcommit_ratio = (RAM - (0.026 * gp_vmem)) / RAM   
                    = (256 - (0.026 * 176)) / 256   
                    = .982  
  
Set vm.overcommit_ratio to 98.  

3、计算每个segment的内存使用上线保护参数:gp_vmem_protect_limit,除以挂掉一台节点后单台节点需要运行的primary数。

gp_vmem_protect_limit calculation  
gp_vmem_protect_limit = gp_vmem / maximum_acting_primary_segments  
                      = 176 / 11   
                      = 16GB  
                      = 16384MB  

资源队列的使用

Greenplum resource queue可以用来限制“并发的QUERY数、总的内存使用”。当QUERY运行时,会添加到对应的队列中,使用的资源将记录到对应的队列中,对应队列的资源控制限制对该队列内的所有会话起作用。

Greenplum资源队列控制资源的思想和Linux 的CGROUP非常类似。

一、创建资源队列的语法:

Command:     CREATE RESOURCE QUEUE  
Description: create a new resource queue for workload management  
Syntax:  
CREATE RESOURCE QUEUE name WITH (queue_attribute=value [, ... ])   
where queue_attribute is:  
   ACTIVE_STATEMENTS=integer  
        [ MAX_COST=float [COST_OVERCOMMIT={TRUE|FALSE}] ]  
        [ MIN_COST=float ]  
        [ PRIORITY={MIN|LOW|MEDIUM|HIGH|MAX} ]  
        [ MEMORY_LIMIT='memory_units' ]  
|  MAX_COST=float [ COST_OVERCOMMIT={TRUE|FALSE} ]   
        [ ACTIVE_STATEMENTS=integer ]  
        [ MIN_COST=float ]  
        [ PRIORITY={MIN|LOW|MEDIUM|HIGH|MAX} ]  
        [ MEMORY_LIMIT='memory_units' ]  

解释

1、ACTIVE_STATEMENTS ,允许同时运行(active状态)的SQL数。 -1不限。

2、MEMORY_LIMIT ‘memory_units kB, MB or GB’ , 设置资源队列中所有SQL允许的最大内存使用量。 -1不限(但是受前面提到的数据库或系统参数限制,触发OOM错误。)。

SQL的内存使用限制不仅受资源队列限制,同时还受到参数限制:

2.1 参数gp_resqueue_memory_policy=none时,限制同Greenplum Database releases prior to 4.1。

2.2 参数gp_resqueue_memory_policy=auto时,如果设置了会话的statement_mem参数,或者设置了statement_mem参数时,单条QUERY允许申请的内存将突破资源队列的MEMORY_LIMIT限制。

例子

=> SET statement_mem='2GB';  
=> SELECT * FROM my_big_table WHERE column='value' ORDER BY id;  
=> RESET statement_mem;  

注意,还有一个系统参数max_statement_mem,这个可以理解为SEGMENT级别的内存使用安全控制阀,单个QUERY申请的memory不能超过max_statement_mem。

意思是你可以随便改会话级的statement_mem参数,但是不要随便改max_statement_mem参数。

建议的max_statement_mem设置:

(seghost_physical_memory) / (average_number_concurrent_queries)  

2.3 参数gp_resqueue_memory_policy=eager_free时,表示数据库在评估SQL对内存的申请渴望时,分阶段统计,也就是说一个SQL可能总共需要申请1G内存,但是每个阶段只申请100MB,所以需要的内存实际上是100MB。

使用eager_free策略,可以降低QUERY报内存不足的可能性。

3、MAX_COST float,设置为浮点或指数((for example 100.0),(for example 1e+2)),-1不限制。

表示资源组允许同时执行的QUERY加起来的COST上限。COST是SQL执行计划中的总成本。

4、COST_OVERCOMMIT boolean,当系统空闲时,是否允许(TRUE)超过max_cost的限制。

5、MIN_COST float,(资源超限时,是需要排队的)但是,当QUERY的成本低于min_cost时,不需要排队,直接运行。(也就是说小查询,就让他跑吧。)

6、PRIORITY={MIN LOW MEDIUM HIGH MAX},指当前资源队列的优先级,当资源紧张时,优先将CPU资源分配给高优先级的资源队列。(处于高优先级的资源队列中的SQL,可以获得更高优先级的CPU资源)。建议将实时性要求高的查询对应的用户分配到高优先级的资源队列中。

类似LINUX CGROUP中的CPU资源组,real time task和普通task的时间片策略。

二、修改资源队列限制举例:

ALTER RESOURCE QUEUE myqueue WITH (MAX_COST=-1.0, MIN_COST= -1.0);  

三、如何将用户放到资源队列中,举例:

ALTER ROLE sammy RESOURCE QUEUE poweruser;  

四、资源队列相关参数

1、gp_resqueue_memory_policy,资源队列的内存管理策略,前面讲了用法。

2、gp_resqueue_priority,是否使用资源队列的优先级。ON使用,OFF不使用。不使用资源队列优先级时,所有队列公平对待。

3、gp_resqueue_priority_cpucores_per_segment,每个SEGMENT可以使用的CPU核数,例如8核的机器,跑了2个PRIMARY SEGMENT,则配置为4。master 上面如果没有其他节点,配置为8。

当发生CPU抢占时,优先级高的资源组中运行的SQL,优先分配CPU资源。

4、gp_resqueue_priority_sweeper_interval,CPU时间片统计间隔,SQL执行时,计算它的share值(根据优先级以及计算gp_resqueue_priority_cpucores_per_segment出来)。

越小越频繁,优先级设置带来的效果越好。但是overhead越大。

五、建议的资源队列使用方法:

1、GPDB默认的资源队列为pg_default,如果不创建队列,那么所有的用户都会被指定给pg_default。这是非常不建议的。

建议的做法是为每个用户创建一个资源队列。(因为通常一个数据库用户对应一个业务。不同的数据库用户可能对应不同的业务或者使用者(例如业务用户、分析师用户、开发者、DBA等)。)

2、超级用户发起的SQL请求不受资源队列的限制,仅仅受前面讲到的参数的限制。因此如果要使用resource queue来限制资源的使用,那么就不建议业务使用超级用户来执行QUERY。

3、ACTIVE_STATEMENTS表示资源队列中,允许同时执行的SQL。(注意当QUERY的成本低于min_cost时,不需要排队,直接运行。)

4、MEMORY_LIMIT,设置资源队列中所有SQL允许的最大内存使用量。前面讲了突破方法,statement_mem设置的优先级更高,可以突破resource queue的限制。

注意所有资源队列的内存加起来不要超过gp_vmem_protect_limit的限制。

5、通过配置资源队列的优先级,可以区分不同的业务。例如出报表的业务优先级最高,其次是普通业务,其次是分析师。这样的情况,我们可以创建3个资源队列,分别使用MEDIUM HIGH MAX的优先级。

6、如果每个时间段的资源需求不一样,可以写一个CRONTAB任务,定时的调整资源队列的限制。

例如白天分析师的优先级更高,晚上处理报表的队列优先级更高。

目前Greenplum还不支持按时间段来设置资源限制,所以只能外部部署任务,alter resource queue来实现。

7、通过gp_toolkit提供的视图,可以观察资源队列的资源使用。

gp_toolkit.gp_resq_activity              
  
gp_toolkit.gp_resq_activity_by_queue     
  
gp_toolkit.gp_resq_priority_backend      
  
gp_toolkit.gp_resq_priority_statement    
  
gp_toolkit.gp_resq_role                  
  
gp_toolkit.gp_resqueue_status  

六、限制了资源队列但是并发执行的QUERY依旧很高,原因分析

当我们创建了资源队列,但是你发现并没有限制住并发时,可能是什么原因呢?

因为在资源队列中有一个开关,如果SQL的COST低于设置的值时,SQL依旧被放行,实际上就是简单的小SQL,不会和大SQL一样排队。

就好像银行的窗口,取钱业务通常可以很快办理,但是开卡的业务需要很久。那么在排队时,我们可以让取钱的业务不排队,直接去窗口办理。

但是,这里也可能存在问题,你怎么就知道他一定是来取钱的呢?万一他也来办卡呢?

在数据库中,有COST来标识,但是记住,他需要依赖准确的统计信息,否则可能COST不准确,例如一张1亿的表,我们没有收集统计信息的话,记录数就是0,那么对这个表的查询操作COST算出来就会很低。

这样的话,大SQL也被混入执行中。

所以,此时请关注,你的统计信息是否准确。

《Greenplum 统计信息收集参数 - 暨统计信息不准引入的broadcast motion一例》

参考

http://greenplum.org/docs/best_practices/workloads.html

Flag Counter

digoal’s 大量PostgreSQL文章入口