Trino 内存管理解析

625 阅读4分钟

内存构成

首先看一下Presto的堆内存构成图: image.png 可以看到Presto堆内存主要有heap headroom和user memory, system memory构成

image.png

  • 根据Trino代码中的MR记录,已经删除System memory,Trino堆内存只剩下heap headroom和user memory

Trino里面几个内存相关的参数:

参数默认值描述
query.max-memory-per-nodeJVM max memory * 0.3一个查询在一个 worker 节点上最大使用的内存
query.max-memory20GB一个查询在整个集群上最大使用的内存
query.max-total-memoryquery.max-memory*2一个查询在集群上最大使用的内存,包括可撤销的内存
memory.heap-headroom-per-nodeJVM max memory * 0.3Trino无法跟踪的内存分配,用于第三方依赖项、执行期间的本地/栈内存分配等

虽然配置了heap headroom的大小,但在代码里面却并没有对他的使用加以控制,因为Trino自己都无法跟踪他的内存分配,headroom的配置值只是在生成memory pool的时候起作用,保证memory pool的大小加上headroom小于 jvm的最大值。

内存池解析

Trino内存管理中最重要的一个类:ClusterMemoryManager

该类中包含上面提到的maxQueryMemory, maxQueryTotalMemory等参数,以及ClusterMemoryPool

ClusterMemoryPool中比较重要的几个属性:

参数描述
blockedNodes当前阻塞的节点个数,判断条件为该节点的可使用内存大小 + 可撤销的已预留内存大小 <= 0
totalDistributedBytes集群内存池最大值
reservedDistributedBytes集群内存池已预留值
reservedRevocableDistributedBytes集群内存池可撤销的已预留值,该参数为溢出到磁盘的大小
freeDistributedBytes集群内存池可用大小 = totalDistributedBytes - reservedDistributedBytes - reservedRevocableDistributedBytes;

ClusterMemoryPool中的属性是收集每个节点的NodeMemoryPool中的属性来进行汇总。

NodeMemoryPool的构造方法如下:

image.png image.png image.png 可以看出来节点的MemoryPool的最大值就是JVM的XMX减去heap headroom

所以可以简单的理解为JVM分为heap headroom 和 memory pool两个部分

内存池使用量统计

节点内存池中的reservedBytes是如何统计的呢,哪些内存使用会被统计到内存池里面?

Trino用MemoryTrackingContext 这个类来跟踪内存的使用情况

image.png

MemoryTrackingContext以树形层次结构组织,反映了opetator->driver->pipeline->task->query的层次结构. 对于一个Query, 所有的Operator占用的内存以树结构从下到上一路求和最终汇总计入到内存池中。通过这种机制,内存池可以统计每一个Operator使用的内存以及正在运行的Query所用的内存。(具体解释可阅读上图绿色文字部分)

在创建Operator时,每个Operator都有一个OperatorContext,OperatorContext中的MemoryTrackingContext来追踪内存。 以TableScanOperator为例,从下图中可以看到,在读取Page文件后,将source使用的内存大小更新到MemoryTrackingContext中.

image.png

memoryContext调用setBytes方法,最终调用父节点的updateBytes方法, 一步步以树形结构向上更新,

image.png 到根节点QueryContext时,则是将上报来的内存使用量更新到memory pool中,完成了一次内存分配的更新上报

image.png

内存不足Kill策略

kill策略相关的几个配置参数:

  1. query-low-memory-killer-policy
  • none :不会杀死任何查询
  • total-reservation :终止当前使用最多总内存的查询。
  • total-reservation-on-blocked-nodes :终止当前在内存不足的节点上使用最多内存的查询(默认)
  1. task.low-memory-killer.policy
  • none :不会杀死任何task.
  • total-reservation-on-blocked-nodes : 终止当前在内存不足的节点上使用最多内存的task(默认)
  • least-waste : 终止当前在内存不足的节点上使用大量内存的task. 这个策略会避免终止已经执行很久的task, 所以大量的工作不会被浪费掉.
  1. query.low-memory-killer.delay
  • 在发生OOM和query被终止掉中间的时间, 只有上面两个设为none以外的值才有作用

在sqlQueryManager类中,会启用一个定时线程每隔一秒去检查内存使用情况 image.png

判断是否发生OOM的情况是看blockedNodes是否大于0,而判断节点是否blocked是看该节点的可用内存大小加上可撤销内存大小是否小于等于0

image.png image.png

resource_overcommit 这个session property如果设置为True, 可以允许该查询使用整个memory pool的内存,不受query.max-memory的限制。但该查询即使没有到query.max-memory, 也有可能因为集群内存不足被kill掉。

从下图可以看到,Trino会遍历所有正在执行的Query, 以下三种情况会终止这个Query

  • resourceOvercommit 设置为True, 且当前集群OOM
  • resourceOvercommit 设置为false,该查询的userMemory使用量大于userMemory的限制值
  • resourceOvercommit 设置为false,该查询的totalMemory使用量大于totalMemory的限制值

image.png

再根据你配置的不同kill策略,对应不同的lowMemoryKiller对象,会有不同的chooseTargetToKill实现方法,来选择应该kill的Query或者Task。 image.png

在节点级别,每次更新预留内存大小时,都会去检查是否超过每个节点允许的最大使用内存,如果超过,这个查询则会抛出异常终止掉

image.png

image.png