内存溢出的几种情况
内存溢出的几种情况
前言
Greenplum是一个重计算和重资源的MPP数据库,可以说是有多少资源就能消耗多少资源,带来的好处就是处理速度变快了,坏处就是容易用超。
CPU、网络、硬盘用超的话,关系不大,大不了就是硬件瓶颈,但是内存用超的话就会带来较大的负面影响,例如操作系统OOM用户进程,导致数据库崩溃等。
如果要达到非常强壮的稳定性,GREENPLUM内存配置非常的关键
那些情况会导致OOM
- 1.数据库节点内存不足
- 2.操作系统内存相关的内核参数配置不当
- 3.数据倾斜,导致某些查询时,某个segment需要申请的内存超大
- 4.查询倾斜,例如某些聚合、窗口函数的分组不是分布键,那么需要对数据进行重分布,重分布之后导致某个segment的数据出现倾斜,导致某些查询时,某个segment需要申请的内存超大
- 5.数据膨胀或者统计信息不准备,索引问题等
如何避免OOM
- 1.优化sql
- 2.使用资源队列,限制并发查询数,降低集群内同时运行的查询数,从而降低整个系统的内存资源的使用。
- 3.减少单个主机部署的segment数量,例如有128G内存的主机,部署16和和部署8个SEGMENT节点,每个节点能使用的内存相差了一倍
- 4,增加单台主机的内存数
- 5.设置数据库参数gp_vmem_protect_limit,限制单个segment可以使用的VMEM上限,单个主机的内存以及部署多少个SEGMENT决定了平均单个SEGMENT最多可以使用多少内存
- 6.也可以在库级别设置statemnt_men参数,对这个数据库的所有会话生效
- 7.对于某些对内存使用量不可预知的sql,通过在会话级别设置statemnt_men,限制单个sql对内存的使用,避免单个sql吃光内存的情况
如何配置内存相关参数
正确的配置(操作系统、数据库参数、资源队列管理)可以有效的降低OOM发生的概率。
- 1、加内存并不是最有效的方法,因为加内存还有成本问题,而且无法避免所有问题。
- 2、在计算单主机内单个SEGMENT的平均可使用内存时,不能只考虑primary segment,还需要考虑mirror segment,因为当集群出现主机故障时,会将SEGMENT切换到对应的MIRROR,此时,主机上跑的SEGMENT数就比平时更多了。
-3、因此我们必须考虑到failover时,mirror需要占用的资源。
操作系统内核参数
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_percentgp_vmem_protect_limit/100)时,主动terminate QUERY。防止OOM。
terminate 的顺序从使用最多内存的QUERY依次开始,直到内存降低到(runaway_detector_activation_percentgp_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执行计划中的总成本。
5、MIN_COST float,(资源超限时,是需要排队的)但是,当QUERY的成本低于min_cost时,不需要排队,直接运行。(也就是说小查询,就让他跑吧。)
6、PRIORITY={MIN|LOW|MEDIUM|HIGH|MAX},指当前资源队列的优先级,当资源紧张时,优先将CPU资源分配给高优先级的资源队列。(处于高优先级的资源队列中的SQL,可以获得更高优先级的CPU资源)。建议将实时性要求高的查询对应的用户分配到高优先级的资源队列中。
类似LINUX CGROUP中的CPU资源组,real time task和普通task的时间片策略。
第一种:查询需要的内存达到gp_vmem_protect_limit限制的90%时,被gp取消
报错现象
ERROR: Canceling query because of high VMEM usage. Used: 7296MB, available 816MB, red zone: 7372MB (runaway_cleaner.c:135) (seg15 slice6 sdw2:1032 pid=745469) (cdbdisp.c:1528)(File cdbdisp.c;Line 1528;Routine cdbdisp_finishCommand;)].
原因
This error is occurs when queries running on the cluster use 90% of the gp_vmem_protect_limit value. This helps in preventing segments crashing because of out-of-memory (OOM) issues which would cause all running queries to fail. The runaway detector will cancel the query that is using the largest amount of memory to free up RAM and allow all the other queries to run to completion.
As indicated in the sample output above, the query that was executed using 7296MB out of 8GB (90%) of the gp_vmem_protect_limit.
Exceeding the memory limits may also be caused by poor plans due to out-of-date statistics for the tables.
当在集群上运行的查询使用gp_vmem_protect_limit值的90%时,会发生此错误。这有助于防止由于内存不足(OOM)问题导致的段崩溃,这将导致所有正在运行的查询失败。失控检测器将取消使用最大内存量释放RAM的查询,并允许所有其他查询运行完成。
如上面的示例输出所示,使用gp_vmem_protect_limit的8GB(90%)中的7296MB执行的查询。
超过内存限制也可能是由于表的统计数据过时导致计划不周造成的。
解决方案:
1.Verify the gp_vmem_protect_limit
设置合适的gp_vmem_protect_limit
Use https://greenplum.org/calculator/ to set appropriate value for gp_vmem_protect_limit
2.Ensure all tables involve in the query are VACUUM'ed and ANALYZE'd
确保所有的表都执行过表分析和索引重建
Find all the tables involved in the query and run the following against each table:
VACUUM
ANALYZE
REINDEX table
For more information: Routine System Maintenance Tasks
3.Ensure the main catalog tables are VACUUM'ed and ANALYZE'd
确保关键的系统表进行统计信息分析和空间优化
VACUUM pg_catalog.pg_class
VACUUM pg_catalog.pg_attribute
VACUUM pg_catalog.pg_type
ANALYZE pg_catalog.pg_class
ANALYZE pg_catalog.pg_attribute
ANALYZE pg_catalog.pg_type
REINDEX TABLE pg_catalog.pg_class
REINDEX TABLE pg_catalog.pg_attribute
REINDEX TABLE pg_catalog.pg_type
4.Check the guidelines
通过资源队列管理内存
Memory and Resource Management with Resource Queues
To monitor the usage of memory by the session, you may check the view session_state.session_level_memory_consumption, which lists the memory consumption for sessions that are running SQL queries. For more information on this view, please refer to Monitoring a Greenplum System
第二种
ERROR: insufficient memory reserved forstatement (memquota.c:228)
当扫描一张分区特别多的表时,会出现该错误,此时需要将默认的125MB的配置提高,建议在500MB左右或者更高一些。
不过,如果需要做系统级别的修改需要谨慎对待,后面会结合几个参数说明。
解决方案:
调整参数statement_mem
第三种
"ERROR","53200","Outof memory.Failed on request of size 156bytes.(context 'CacheMemoryContext') (aset.c:840)"
"ERROR","53400","Outof memory (seg13 slice13 sdw1-1:40001 pid=10183)","VM Protectfailed to allocate 8388608 bytes, 6 MB available"
该错误是Greenplum系统无法从OS申请到所需要的内存导致的错误,因为gp_vmem_protect_limit配置高于OS所能提供的能力,当Greenplum
申请超过OS能力时,会得到该异常信息。解决办法是确保内存消耗不超过gp_vmem_protect_limit配置,官方建议不超过其90%。
针对最常见的内存错误OOM,需要说明(针对4.1之后版本,早起版本请前去查询相关文档):
第四种
ERROR:number of workfiles per query limit exceeded
这种是内存严重不足,或者语句计算出现严重的倾斜,则查询可能会呆滞溢出文件的数量超出限制,则数据库就会返回上面的错误
注意:最好不要尝试增加gp_workfile_limit_files_per_query参数,如果遇到了溢出文件数量超过了限制的报错,首先应该考虑如果优化sql,检查数据倾斜和膨胀,以及统计信息,可能修改内存参数也有助于缓解,然而100000个文件数都超过的话,可能需要非常大的内存尺寸才能满足要求。
如果分配给语句的内存不足以保证期完全在内存中完成计算,则需要将数据文件溢出到文件系统已缓存中间计算结果,确实情况下,一个查询在primary上溢出的文件数量不能超过10万个,
gp_workfile_limit_files_per_query默认值100000对于绝大部分的查询来说是足够的,如果超出了这个限制,那么通常是查询的问题。
gp_workfile_limit_files_per_query为0时,表示不限制溢出文件,不过溢出文件的数量,可以用来防止劣质的查询对系统造成破坏性的影响。
总结:
1.单语句的内存消耗受3个参数控制:gp_resqueue_memory_policy、statement_mem、max_statement_mem
A、缺省gp_resqueue_memory_policy配置为eager_free,在此情况下,内存将物尽其用(我从词面理解的,官方并未给出详细说明),但
不能超过max_statement_mem的限制以及resource queue的memory_limit限制,此时如果resource queue未做限制(缺省资源队列
均无内存限制),既存在OOM缺口,而此时statement_mem将不具备严格限制功能,同时max_statement_mem的缺省值为2000MB。
B、当gp_resqueue_memory_policy配置为auto时,内存消耗将受statement_mem和resourcequeue的memory_limit限制,同样,
缺省资源队列无内存限制,不过,statement_mem的缺省值为125MB。
综合考虑内存限制参数,由于资源队列的memory_limit缺省是没有限制的,我们假设有10个需要消耗1000MB的语句同时提交。
对于A情况来说,需要向OS申请1000MB×10≈10GB的内存,这已经大于gp_vmem_protect_limit的缺省限制,此时,如果
gp_vmem_protect_limit×节点节点实例数>>节点内存,则极有可能出现OOM错误,此时通过放大gp_vmem_protect_limit是可能带来
缓解的,但并非良措,因为超过物理内存部分的内存申请需要SWAP来响应。而且,很容易超过物理内存+SWAP的总和,将继续出现OOM且
将进入无解状态。
对于B情况来说,需要向OS申请125MB×10≈1.25GB<<10GB的内存,通常的硬件环境,每个Instance不至于少于2GB的内存。
如果按照gp_vmem_protect_limit缺省为8192来计算,对于A情况,每个Instance需要向系统申请8GB内存,其余的2GB需要pgsql_tmp目录来响应。而对于B情况,每个Instance需要向系统申请2GB的内存,其余的8GB需要pgsql_tmp目录来响应。由于pgsql_tmp位于性能高于SWAP的数据盘(通常的环境是如此,不排除高福帅配置),性能有一定保障,B情况通常不会出现OOM,且可承受的并发数远大于A情况。
以上比较详细的说明了GP内存控制的几个关键参数,若要避免OOM,最好还是综合考虑,GP从4.1版本之后设置缺省的A情况也有其合理性,因为,
如果按照B情况设置,多数情况下内存不能物尽其用,且需要性能较低的pgsql_tmp来承担内存的角色,实在不划算。可以尝试设置资源队列的
memory_limit参数,但此方式也有一些小问题,比如超限SQL或被自动cancel掉等等。最好的办法还是充分优化那些低劣的高内存消耗的SQL,合理
安排高内存消耗SQL的并发度。数据库的智能程度是有限的。因为Greenplum定位在数据仓库上,对任何SQL都会尽量调动所有可调动的资源去完成
计算,其资源控制上必然不能做到完美,关键还是深入的去理解数据库工作机制,让驴拉磨,牛犁地,物尽人用。
常见的内存溢出优化思路
“One or more query execution processes ran out of memory on this segment. Logging memory usage” 这个错误信息通常出现在 Greenplum 或 PostgreSQL 数据库中,表示在执行查询时,一个或多个查询执行进程超出了分配的内存限制,导致内存不足。具体来说,Greenplum 集群中的一个或多个分片(segment)发生了内存溢出。
可能的原因:
- 1.查询数据量过大:如果查询涉及大量数据(例如复杂的连接、聚合等操作),可能会超出可用内存。
- 2.内存配置不足:如果数据库的内存配置过低,或者系统中可用的内存不足,可能会导致查询超出内存限制。
- 3.查询优化不佳:查询本身没有得到有效优化(例如缺少索引、连接方式不合理等),可能会消耗更多的内存。
- 4.并发量过高:当多个查询同时执行时,可能会导致内存资源被多个查询竞争,从而发生内存不足的情况。
解决步骤:
-
1.检查内存配置:
在 Greenplum 中,调整以下参数可以优化内存使用: -
work_mem:控制每个查询用于排序、哈希等操作的内存大小。可以适当增大这个值,避免数据溢出到磁盘。
-
maintenance_work_mem:控制用于维护操作(如创建索引)的内存大小。
-
shared_buffers:数据库服务器用于缓存数据的内存总量。
-
temp_buffers:临时表所使用的内存。
-
如果你在云环境中,确保实例的内存足够支持当前的工作负载。
-
分析查询执行计划:
-
使用 EXPLAIN 或 EXPLAIN ANALYZE 查看查询的执行计划,检查是否有高内存消耗的操作(如大规模的哈希连接、排序等)。
-
如果查询涉及到大量的数据连接或聚合,可以考虑分解查询,或使用更高效的连接类型(如 MERGE JOIN 或 HASH JOIN)。
-
优化查询:
-
如果查询涉及大量的数据处理,可以考虑将其分解为更小的查询,或者使用批量处理的方式来减少内存的使用。
-
确保在查询条件中使用索引来加速数据检索,减少内存负担。
-
增加分片内存:
-
如果你使用的是多节点的 Greenplum 集群,可以考虑增加每个分片的内存配置。这可以通过修改每个分片上的 segment_memory 配置来实现。
-
数据溢出到磁盘:
-
确保系统有足够的磁盘空间,以便 Greenplum 在内存不足时能够将数据溢出到磁盘。在 Greenplum 中,当内存超限时,查询会将部分数据溢出到磁盘,这虽然能避免内存错误,但会降低性能。
-
监控系统资源:
-
使用监控工具(如 vmstat、top、htop 或 gp_toolkit)来监控查询执行时的内存和磁盘使用情况。
-
查看 Greenplum 日志(通常位于 pg_log 目录下)以获取更详细的错误信息,帮助分析内存不足的具体原因。
-
重启和数据平衡:
-
如果内存使用异常,可以尝试重启 Greenplum 集群,并重新平衡数据(rebalance),以确保数据在各个分片间的分布均匀,从而避免某个分片负载过高。
示例调整:
如果你认为查询因内存不足而溢出到磁盘,可以增加 work_mem 设置。例如:
SET work_mem = '512MB';
这会为每个查询的操作分配 512MB 的内存。你也可以将该设置永久写入 postgresql.conf 文件中。
总结:
针对 内存溢出 的问题,可以通过调整数据库内存配置、优化查询、增加系统资源等方式来解决。重点是对查询进行优化,确保内存资源得到了合理利用,从而避免因内存不足导致的查询失败。

浙公网安备 33010602011771号