使用实践:OOM排查指南

简介: 本文将会介绍如何针对Hologres中出现的OOM问题进行排查并处理。

本文将会介绍如何针对Hologres中出现的OOM问题进行排查并处理。


OOM产生的基本原因

OOM(Out of Memory),描述的是Query的内存消耗超出了系统的当时供给,系统做出的一种异常提示。有的系统在内存资源不足时会采用磁盘缓存的方式进行算子降级(Spill to disk),Hologres为了保障查询的效率,默认所有算子都采用内存资源进行计算,因此会存在内存不足OOM的问题。


1、内存的分配和上限

一个Hologres实例由多个节点组成的分布式系统,不同的实例规格对应不同的节点数,详情可以见文档


在Hologres中,一个节点的规格是16Core 64G,即内存上限是64G,一个Query运行时涉及的任意节点的内存不足,都会产生OOM异常。内存会分为三部分,三分之一分配给计算运行时分配,三分之一分配给缓存,三分之一分配给元数据及常驻执行进程。在早期版本中,计算节点(Worker Node)的内存上限是20G,但从V1.1.24版本开始,计算节点运行时内存取消单节点20GB限制,采用动态调整节点内存,定期检查内存水位,如果元数据较少时,会尽量将剩余可用内存都分配给查询运行时,尽量保证运行时内存最大化分配,保障Query获得足够内存分配。


2、识别OOM

当计算内存超上限时(大于等于20G),就会出现OOM的情况。常见的报错如下:

Total memory used by all existing queries exceeded memory limitation.

memory usage for existing queries=(2031xxxx,184yy)(2021yyyy,85yy)(1021121xxxx,6yy)(2021xxx,18yy)(202xxxx,14yy); Used/Limit: xy1/xy2 quota/sum_quota: zz/100.


报错解读:

  • Used/Limit: xy1/xy2 指单个节点使用的计算内存/单个节点的计算内存上限,单位B。单个节点使用内存是指当前时刻所有query运行时在该节点使用的计算内存总和。例如Used/Limit: 33288093696/33114697728,代表所有query在该节点运行时的内存使用了33.2G,但是单个计算节点的弹性内存只能配33.1G,因此出现OOM。

说明:实例虽然由多个节点组成,只要一个节点上的计算总内存超限制,就会出现OOM。

  • queries=(2031xxxx,1844yyy),指queries=(query_id,query使用的内存),例如queries=(2031xxxx,18441803528),代表query_id=2031xxxx的query,在运行时单个节点使用了18G的内存。异常信息里面会列出消耗内存的Top 5 query,可以通过报错找到内存消耗最大的query,并在慢query日志中查看详细的query信息。
  • quota/sum_quota: zz/100:quota代表资源组,其中zz对应资源组分配的资源。例如quota/sum_quota: 50/100,代表设置了资源组,其分配的资源是实例总资源的50%


3、查看内存消耗

1)管控台提供整个实例的内存消耗情况,即多个节点的内存汇总值,详情可以见文档

2)慢query日志中memory_bytes字段提供单个query的内存消耗情况,是非精确的值,存在一定的误差,详情见文档


为什么内存水位高

可以通过Hologres管控台内存指标了解实例的内存综合使用率。当内存水位长期超过80%,可以认为属于内存水位高的情况。Hologres的内存资源采用预留模式,即使没有执行查询操作,也会有部分Meta、Index元数据和Cache加载到内存中,该类元数据用于提升计算速度,无任务运行时内存使用率可能会到达30%-50%左右,属于正常现象。内存使用率持续升高,甚至接近100%,通常会影响系统的运行,影响实例的稳定性和性能。关于该问题产生的原因、主要影响和解决方法具体如下:

产生原因

  • 元数据占用内存多:表数据量增加,数据总量也随之增加,元数据占用内存多,当没有任务运行时,内存水位也会高,通常不建议一个Table Group超过3000张表(包括分区子表),Table Group的Shard数高,也会造成更多的文件碎片和积累的元数据,消耗元数据内存。
  • 索引设置不合理:例如,表的字段大多数是text类型,设置了过多的Bitmap或Dictionary索引。
  • 计算内存高:运行任务时扫描大数据量或者计算逻辑非常复杂,例如有非常多的count distinct,复杂的Join操作,多字段Group By,窗口操作等。

主要影响

  • 影响稳定性。当元数据等过大时,会超额占据正常Query可用的内存空间,导致在查询过程中,可能会偶发SERVER_INTERNAL_ERRORERPC_ERROR_CONNECTION_CLOSEDTotal memory used by all existing queries exceeded memory limitation等报错。
  • 影响性能。当元数据等过大时,会超额占据正常Query可用的缓存空间,从而导致缓存命中减少,Query延迟增加。

解决方法

  • 若是元数据过多导致内存较高,建议删除不再查询的数据或者表,减少不必要的分区表设计,以释放占用的内存。
  • 若是索引导致,建议设置合理的索引,可以根据业务场景具体分析,删除不涉及的Bitmap和Dictionary,需要注意的是,修改Bitmap和Dictionary时数据会进行compation,将会耗费资源,建议业务低峰期进行。
  • 若是计算导致内存较高,建议区分写入或者查询场景,进行SQL优化,详情可以见下面查询OOM和导入OOM解法
  • 扩容,对实例的计算和存储资源进行升配。


如何解决查询时OOM

当出现查询OOM时,其原因通常有以下几类:

  1. Plan错误:统计信息不正确、join oder不正确等
  2. Query并发度大,且每个Query消耗的内存很大
  3. Query本身复杂或者扫描的数据量巨大
  4. Query中包含union all,增加执行器的并行度
  5. 设置了资源组,且给资源组分配的资源较少
  6. 数据倾斜或者shard pruning导致负载不均衡,个别节点的内存压力较大

下面将会具体分析以下原因以及给出相应的解法

1、检查执行计划是否合理

类型1:统计信息不准确

通过explain sql可以查询执行计划,row=1000表示缺少统计信息,缺少统计信息或者统计信息不准确,会导致生成不准确的执行计划,从而使用更多的资源进行计算,造成OOM。

解决办法:

  • 执行analyze表,更新表的统计信息,执行analyze tablename
  • 设置auto analyze自动更新统计信息


类型2:Join Order不正确

当两个表通过Hash算子执行连接时,合理的连接方式是较小的表构建HashTable。通过explain sql查看执行计划,如果数据量更大的表在下方,小表在上方时,表示会使用更大的表构建hash表,这种Join Order不正确,容易导致OOM。Join Order不正确的原因通常如下:

  • 表未及时更新统计信息,例如下图中,上面的table没有更新统计信息,导致rows=1000
  • 优化器未能生成更好的plan

解决办法如下:

  • 对参与join的表都执行analyze tablename,及时更新统计信息,使其生成正确的join order
  • 若是analyze之后,join order还是不正确,可以通过修改GUC进行人工干预。设置optimizer_join_order=query,使优化器按照写SQL的书写顺序确定join order,适用于复杂query。
set optimizer_join_order = query;select*from a join b on a.id= b.id;-- 会将b作为HashTable的build side

同时也可以根据业务情况,调整join order 策略:

参数

说明

set optimizer_join_order = <value>;

优化器Join Order算法,values有如下三种。

  • query:不进行Join Order转换,按照SQL书写的连接顺序执行,优化器开销最低。
  • greedy:通过贪心算法进行Join Order的探索,优化器开销适中。
  • exhaustive(默认):通过动态规划算法进行Join Order转换,会生成最优的执行计划,但优化器开销最高。


类型3:hash table预估错误

当有join操作时,通常是会把小表或者数据量小的子查询作为build side,构建hash表,这样既能优化性能,又能节省内存。但是有时候因为查询过于复杂,或者统计信息的问题,数据量会估错,就导致把数据量大的表或者子查询做了build side,这样一来,构建hash表会消耗大量的内存,导致OOM。

如下图所示,通过执行计划查看,Hash (cost=727353.45..627353.35 , rows=970902134 witdh=94) 即为build side,rows=970902134就是构建hash表的数据量,若是实际表数据量比这个少,说明hash表预估不准确。

解决办法:

  • 查看子查询的表是否更新统计信息或者统计信息是否准确,若是不准确,需要执行analyze tablename
  • 通过以下参数关闭执行引擎对hash表的预估

说明:该参数默认关闭,但是可能在某些调优场景被打开过,若是查看时打开的,可以进行关闭

set hg_experimental_enable_estimate_hash_table_size =off


类型4:大表被Broadcast

Broadcast是指将数据复制至所有Shard。仅在Shard数量与广播表的数量均较少时,Broadcast Motion的优势较大。

若是如下图所示,在join场景中,执行计划先进行broadcast,即将build side的数据广播完再构建hash表,这就意味着每个shard内构建hash表的数据都是build side的全量数据,若是shard多或者数据量较大,则会消耗很多内存,造成OOM。

假如表数据量是8000万,如下图plan,但是执行计划中,预估出来表只有1行,参与broadcast只有80行,与真实情况不符合,真实执行时需要8000万行数据参与broadcast,导致消耗过多内存从而出现oom。

解决办法:

  • 检查执行计划中预估行数是否正确,不正确的话更新统计信息,执行analyze tablename
  • 通过以下GUC关闭broadcast,直接改写为reditribution算子
set optimizer_enable_motion_broadcast = off;

2、Query并发大

监控指标上明显QPS增加,或者OOM中报错HGERR_detl memory usage for existing queries=(2031xxxx,184yy)(2021yyyy,85yy)(1021121xxxx,6yy)(2021xxx,18yy)(202xxxx,14yy); 且每个query使用的内存较少,说明当前query并发较大,可以通过以下方式解决:


3、复杂Query

若是query本身比较复杂或者扫描数据量较多,一个query就出现OOM,可以通过以下方法解决:

  • 计算前置,将清洗好的数据写入Hologres,避免在Hologres进行大型ETL操作
  • 增加过滤条件
  • SQL优化:例如使用fixed plan,count distinct优化等,详情见文档调优-SQL优化

4、UnionAll

subquery1 union all subquery2 union all subquery3 ...

当SQL中含有大量union all subquery时,执行器会并行处理每个subquery,导致内存压力变大,从而出现OOM,可以通过下面的参数强制执行器串行执行,减少OOM情况,但性能会变慢一点。

set hg_experimental_hqe_union_all_type=1;set hg_experimental_enable_fragment_instance_delay_open=on;

5、资源组配置不合理

OOM时出现报错memory usage for existing queries=(3019xxx,37yy)(3022xxx,37yy)(3023xxx,35yy)(4015xxx,30yy)(2004xxx,2yy); Used/Limit: xy1/xy2 quota/sum_quota: zz/100。其中zz的取值较小,如下图所示,为10,说明资源组只拥有实例10%的资源。

解决办法:重新合理的设置资源组,每个资源组都不应该小于30%。


6、数据倾斜或Shard Pruning

当实例整体内存水位不高,但仍然出现OOM的情况,一般原因为数据倾斜或者Shard Pruning导致某个/某几个节点的内存水位较高,从而出现OOM。

说明:shard pruning是指通过查询剪枝技术,只扫描部分shard

  • 通过以下sql排查数据倾斜,hg_shard_id是每个表的内置字段,表示数据所在的Shard:
selectcount(1)from t1 groupby hg_shard_id;
  • 从plan查看shard pruning,例如如下的执行计划中,shard selector为10(1),说明只选中了一个shard数据进行查询

解决方法:

  • 合理的设计distribution key,避免数据倾斜
  • 若是业务有数据倾斜,需要业务进行改造


如何解决导入导出时OOM

导入导出OOM是指数据在Hologres表之间导入导出,也包括内表和外表之间导入导出,尤其是MaxCompute导入到出Hologres出现OOM。

1、大宽表或者宽列+高scan并行度;

通常在MaxCompute导入场景会出现大宽表或者比较宽的列有比较大的scan并行度,导致写入出现OOM。

可以通过以下参数控制导入并行度以减少OOM:

  • 大宽表导入(常用场景):

说明:以下参数与SQL一起执行(优先选择前2个参数,若是仍然出现oom,可以适当调低参数取值)

--设置访问外表时的最大并发度,默认为实例的Core数,最大为128,不建议设置大,避免外表query(特别是数据导入场景)影响其它query,导致系统繁忙导致报错。该参数在Hologres 1.1中生效。

set hg_foreign_table_executor_max_dop =32;--调整每次读取MaxCompute表batch的大小,默认8192。set hg_experimental_query_batch_size =4096;--设置访问外表时执行DML语句的最大并发度,默认值为32,针对数据导入导出场景专门优化的参数,避免导入操作占用过多系统资源,该参数在Hologres 1.1中生效。set hg_foreign_table_executor_dml_max_dop =16;--设置MaxCompute表访问切分split的数目,可以调节并发数目,默认64MB,当表很大时需要调大,避免过多的split影响性能。该参数在Hologres 1.1中生效。set hg_foreign_table_split_size =128;
  • 比较宽的列有比较大的scan并行度

若是已经调整过大表的导入参数,但是仍然出现OOM,业务可以排查是否有比较宽的列,若有,可以通过以下参数解决:

说明:以下参数取值为经验值,适用于大多数场景,也可以根据业务进行调整

--调整宽列的shuffle并行度,减少宽列数据量的堆积set hg_experimental_max_num_record_batches_in_buffer =32;--调整每次读取MaxCompute表batch的大小,默认8192。set hg_experimental_query_batch_size=128;


2、外表重复数多

若是外表的重复数据较多,导致导入速度慢或者出现oom,而预先去重之后再导入则速度很快。

说明:重复数是相对而言,并没有统一标准,例如有1亿行数据,有8000万数据都是重复的,则认为重复数据较多,实际需要业务上判断。

解决办法:导入之前先对数据进行去重再进行导入,或者分批次导入,避免一次性导入大量重复


了解Hologres:https://www.aliyun.com/product/bigdata/hologram

合集.png

相关实践学习
基于Hologres+PAI+计算巢,5分钟搭建企业级AI问答知识库
本场景采用阿里云人工智能平台PAI、Hologres向量计算和计算巢,搭建企业级AI问答知识库。通过本教程的操作,5分钟即可拉起大模型(PAI)、向量计算(Hologres)与WebUI资源,可直接进行对话问答。
相关文章
|
6月前
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
117 0
|
8月前
|
运维 监控 Java
内存溢出+CPU占用过高:问题排查+解决方案+复盘(超详细分析教程)
全网最全的内存溢出CPU占用过高排查文章,包含:问题出现现象+临时解决方案+复现问题+定位问题发生原因+优化代码+优化后进行压测,上线+复盘
1394 5
|
6月前
|
消息中间件 存储 缓存
【10个OOM异常的场景以及对应的排查经验】
【10个OOM异常的场景以及对应的排查经验】
|
6月前
|
监控 Java
jvm异常排查
jvm异常排查
28 0
|
7月前
|
缓存 算法 Java
JVM问题排查
JVM问题排查
112 0
|
8月前
|
Java Linux
|
8月前
|
Arthas Java 测试技术
JVM学习笔记(5)——JVM线上问题排查
JVM学习笔记(5)——JVM线上问题排查
66 0
|
8月前
|
监控 Java
【线上问题排查】CPU100%和内存100%排查
【线上问题排查】CPU100%和内存100%排查
119 1
|
9月前
|
SQL 存储 Java
【问题处理】—— 一次内存溢出(OutOfMemoryError)实战排查
【问题处理】—— 一次内存溢出(OutOfMemoryError)实战排查
183 0
|
Java
OOM排查小案例
写作目的 排查过某OOM问题吗?额。。。没有
154 0
OOM排查小案例