本文将会介绍如何针对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_ERROR、ERPC_ERROR_CONNECTION_CLOSED、Total memory used by all existing queries exceeded memory limitation等报错。
- 影响性能。当元数据等过大时,会超额占据正常Query可用的缓存空间,从而导致缓存命中减少,Query延迟增加。
解决方法
- 若是元数据过多导致内存较高,建议删除不再查询的数据或者表,减少不必要的分区表设计,以释放占用的内存。
- 若是索引导致,建议设置合理的索引,可以根据业务场景具体分析,删除不涉及的Bitmap和Dictionary,需要注意的是,修改Bitmap和Dictionary时数据会进行compation,将会耗费资源,建议业务低峰期进行。
- 若是计算导致内存较高,建议区分写入或者查询场景,进行SQL优化,详情可以见下面查询OOM和导入OOM解法
- 扩容,对实例的计算和存储资源进行升配。
如何解决查询时OOM
当出现查询OOM时,其原因通常有以下几类:
- Plan错误:统计信息不正确、join oder不正确等
- Query并发度大,且每个Query消耗的内存很大
- Query本身复杂或者扫描的数据量巨大
- Query中包含union all,增加执行器的并行度
- 设置了资源组,且给资源组分配的资源较少
- 数据倾斜或者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有如下三种。
|
类型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并发较大,可以通过以下方式解决:
- 若是有写入,可以降低写入并发,见下文写入OOM
- 采用读写分离多子实例
- 扩容实例计算规格
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