OOM常见问题排查指南

OOM(Out of Memory)描述的是Query的内存消耗超出了系统当前的供给,系统做出的一种异常提示。本文将为您介绍如何在Hologres中查看内存消耗、分析内存水位高情况,识别OOM(内存溢出)现象及其产生原因,并提供相应的处理方法。

内存消耗分析

  • 查看内存消耗

    • 管理控制台提供整个实例的内存消耗情况,即多个节点的内存汇总值,详情请参见Hologres管控台的监控指标

    • 慢Query日志中memory_bytes字段提供单个Query的内存消耗情况,是非精确的值,存在一定的误差,详情请参见慢Query日志查看与分析

  • 内存水位高

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

    • 产生原因

      • 元数据占用内存多

        表现为Meta内存使用率高:表数据量增加,数据总量也随之增加,元数据占用内存多,当没有任务运行时,内存水位也会高,通常建议一个Table Group下不要超过10000张表(包括分区子表,不包含外部表),Table Group的Shard数高,也会造成更多的文件碎片和积累更多的元数据,占用元数据内存。

      • 计算内存高

        表现为Query内存使用率高:运行任务时扫描大数据量或者计算逻辑非常复杂,例如有非常多的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延迟增加。

    • 解决方法

      • 元数据过多导致内存较高时,建议通过hg_table_info表对数据表进行治理,详情请参见表统计信息查看与分析。建议删除不再查询的数据或者表和减少不必要的分区表设计,以释放占用的内存。

      • 计算导致内存水位较高时,建议区分写入和查询场景,进行SQL优化,详情请参见如何解决查询时OOM如何解决导入导出时OOM

      • 扩容,对实例的计算和存储资源进行升配,详情请参见实例列表

识别OOM报错

当计算内存超出上限时(大于等于20 GB),就会出现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

报错解读如下。

  • queries=(2031xxxx,184yy)

    queries=(query_id,query使用的内存),例如queries=(2031xxxx,18441803528),代表query_id=2031xxxx的Query,在运行时单个节点使用了18 GB的内存。异常信息里面会列出消耗内存的Top 5的Query,可以通过报错找到内存消耗最大的Query,并在慢Query日志查看与分析中查看详细的Query信息。

  • Used/Limit: xy1/xy2

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

  • quota/sum_quota: zz/100

    quota代表资源组,其中zz对应资源组分配的资源。例如quota/sum_quota: 50/100代表设置了资源组,其分配的资源是实例总资源的50%。

产生OOM的基本原因

有的系统在内存资源不足时会采用磁盘缓存的方式进行算子降级(Spill to Disk),Hologres为了保障查询的效率,默认所有算子都采用内存资源进行计算,因此会存在内存不足导致OOM的问题。

内存的分配和上限

一个Hologres实例是由多个节点组成的分布式系统,不同的实例规格对应不同的节点数,详情请参见实例规格概述

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

如何解决查询时OOM

  • 当出现查询OOM时,其原因通常有如下几类。

    • 执行计划错误:统计信息不正确、Join Order不正确等。

    • Query并发度高,且每个Query消耗的内存很大。

    • Query本身复杂或者扫描的数据量很大。

    • Query中包含union all,增加执行器的并行度。

    • 设置了资源组,但是给资源组分配的资源较少。

    • 数据倾斜或者Shard Pruning导致负载不均衡,个别节点的内存压力较大。

  • 以上原因的具体分析以及相应的解决方法如下。

    • 资源组分配的资源较少

      使用Serverless Computing功能执行查询。Serverless Computing功能可以实现在实例独享资源之外,使用Serverless资源执行相关查询。相比实例独享资源,Serverless Computing提供更丰富的计算资源,并且查询之间不会相互争抢资源,因此非常适合解决内存溢出(OOM)问题。Serverless Computing介绍,详情请参见Serverless Computing概述,其使用方式详情,请参见Serverless Computing使用指南

    • 检查执行计划是否合理

      • 类型1:统计信息不准确

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

        解决方法如下。

        • 执行analyze <tablename>命令,更新表的统计信息。

        • 设置AUTO ANALYZE自动更新统计信息,详情请参见ANALYZE和AUTO ANALYZE

      • 类型2:Join Order不正确

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

        • 表未及时更新统计信息,例如下图中上面的表没有更新统计信息,导致rows=1000joinorder不正确

        • 优化器未能生成更好的执行计划。

        解决方法如下。

        • 对参与Join的表都执行analyze <tablename>命令,及时更新统计信息,使其生成正确的Join Order。

        • 执行analyze tablename命令之后,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表预估错误

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

        如下图所示,执行计划中Hash (cost=727353.45..627353.35 , rows=970902134 width=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万行,如下图执行计划所示,预估表只有1行,参与Broadcast只有80行,与真实情况不符合,真实执行时需要8000万行数据参与Broadcast,导致消耗过多内存从而出现OOM。类型4

        解决方法如下。

        • 检查执行计划中预估行数是否正确,不正确的话,请执行analyze tablename命令更新统计信息。

        • 通过以下GUC关闭Broadcast,直接改写为redistribution算子。

          SET optimizer_enable_motion_broadcast = off;
    • Query并发大

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

    • 复杂Query

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

      • 计算前置,将清洗好的数据写入Hologres,避免在Hologres进行大型ETL操作。

      • 增加过滤条件。

      • SQL优化:例如使用Fixed Plan、Count Distinct优化等,详情请参见优化查询性能

    • UNION ALL

      如下所示,当SQL中含有大量UNION ALL subquery时,执行器会并行处理每个subquery,导致内存压力变大,从而出现OOM。

      subquery1 UNION ALL subquery2 UNION ALL subquery3 ...

      可以通过如下参数强制执行器串行,减少OOM情况,但查询速度会变慢。

      SET hg_experimental_hqe_union_all_type=1;
      SET hg_experimental_enable_fragment_instance_delay_open=on;
    • 资源组配置不合理

      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%

    • 数据倾斜或Shard Pruning

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

      说明

      Shard Pruning是指通过查询剪枝技术,只扫描部分Shard。

      • 通过以下SQL排查数据倾斜,hg_shard_id是每个表的内置隐藏字段,表示数据所在的Shard。

        SELECT hg_shard_id, count(1) FROM t1 GROUP BY hg_shard_id;
      • 从执行计划查看Shard Pruning,例如执行计划中Shard Selectorl0[1],说明只选中了一个Shard数据进行查询。

        -- 这里distribution_key为x, 基于x=1过滤条件,可以快速定位所在Shard
        SELECT count(1) FROM bbb WHERE x=1 GROUP BY y;

        数据倾斜执行计划

      解决方法如下。

      • 设计合理的Distribution Key避免数据倾斜。

      • 若是业务有数据倾斜,需要对业务进行改造。

    • 大基数多阶段GROUP BY

      从Hologres V3.0版本开始,对于大基数的多阶段Agg,当GROUP BY的列与数据分布不匹配(Distribution Key不是GROUP BY Key的子集)时,低阶段Agg的每个并发实例都会维护一张很大的Hash Table做GROUP BY Key聚合,导致内存压力很大,容易OOM。可以通过设置如下参数分阶段去进行Agg操作。

      -- 通过guc设置Agg HashTable行数的上限,如下SQL表示partial_agg_hash_table最多时8192行。默认值为0,表示不限制。
      SET hg_experimental_partial_agg_hash_table_size = 8192;

如何解决导入导出时OOM

导入导出OOM是指数据在Hologres表之间导入导出,也包括内部表和外部表之间导入导出,常见于MaxCompute导入到Hologres时出现OOM。

  • 使用Serverless Computing功能执行导入导出

    Serverless Computing功能可以实现在实例独享资源之外,使用Serverless资源执行相关导入导出任务。相比实例独享资源,Serverless Computing提供更多的计算资源,并避免了任务之间的资源争抢,非常适合解决内存溢出(OOM)问题。Serverless Computing概述请参见Serverless Computing概述,其详细使用方法请参见Serverless Computing使用指南

  • 大宽表或者宽列且有高Scan并行度

    通常在MaxCompute导入场景会出现大宽表或者比较宽的列有比较大的Scan并行度,导致写入出现OOM。可以通过以下参数控制导入并行度减少OOM。

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

      说明

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

      -- 设置访问外部表时的最大并发度,默认为实例的Core数,最大为128,不建议设置大,避免外部表Query(特别是数据导入场景)影响其它Query,导致系统繁忙导致报错。该参数在Hologres V1.1及以上版本中生效。
      SET hg_foreign_table_executor_max_dop = 32;
      
      -- 调整每次读取MaxCompute表batch的大小,默认为8192。
      SET hg_experimental_query_batch_size = 4096;
      
      -- 设置访问外部表时执行DML语句的最大并发度,默认值为32,针对数据导入导出场景专门优化的参数,避免导入操作占用过多系统资源,该参数在Hologres V1.1及以上版本中生效。
      SET hg_foreign_table_executor_dml_max_dop = 16;
      
      -- 设置MaxCompute表访问切分split的数目,可以调节并发数目,默认64MB,当表很大时需要调大,避免过多的split影响性能。该参数在Hologres V1.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;
  • 外部表数据重复

    若是外部表包含大量重复数据,会导致导入速度变慢或出现OOM。重复数据是相对而言,并没有统一标准,例如有1亿行数据,有8000万行数据都是重复的,则认为重复数据较多,请根据实际业务情况进行判断。

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