Trino 内存管理及保护机制

640 阅读4分钟

Trino Memory Pool:

general memory pool: user memory(join 时 build 侧的hash table / sort)+ system pool(算子之间的buffer)。每个 worker 上有自己独立的 memory pool,计算方式参考 io.prestosql.memory.LocalMemoryManager#configureMemoryPools,公式为:Runtime.getRuntime().maxMemory() - config.getHeapHeadroom().toBytes(),即 -Xmx - memory.heap-headroom-per-nodequery.max-memory-per-nodequery.max-total-memory-per-node分别表示单个查询 user memory 和 total memory(user memory + system memory) 在单个 worker 上的最大可用内存,query.max-memory为单个 query 在整个集群中的最大可用内存。

reserved memory pool: 和 general memory pool 大小相等,只有 worker 内存使用量超过配置的最大内存之后才会把查询放在这个 pool 中执行,正常情况下用不到,而且启动时就分配,所以是一种内存浪费,低版本 Presto 生产环境中一般关掉,高版本的 Trino 已经移除了这个 pool。

coordinator: master 节点,虽然查询执行在各个 worker 上,但是 coordinator 也可能会出现 OOM。比如使用 hive connector 时,查询的分片太多,分片元信息存储在 coordinator;或者使用到 Dynamic Filter 时,满足条件的 filter 是通过 coordinator 分发到其它 worker 上去的。

Trino 如何跟踪内存

每个算子(ScanFilterAndProjectOperator/HashAggregationOperator)都有一个OperatorContext,包含算子的各种信息,同样的还有TaskContext/QueryContext,其中QueryContext中有MemoryTrackingContext,各个 Operator 通过 setBytes来统计当前算子已申请的内存,同时update父节点已申请的内存以及 worker memory pool 内存。Context 通过树结构来组织 cluster → query → stage → task → driver之间的关系,这样就可以统计出根节点RootAggregatedMemoryContext即可知道当前这个查询使用了多少内存,每个 worker 和 coordinator 之间通信,从而知道集群目前整体的内存使用量。

OOM Killer 维持集群的稳定性

相关的配置有:query.low-memory-killer.delay 以及 query.low-memory-killer.policy。其中 delay 指的是 当前 OOM 时间和上次 OOM 的 interval。policy 有两种:total-reservation表示杀掉集群中占有内存最大的SQL,total-reservation-on-blocked-nodes表示杀死在内存不足(阻塞)的节点上使用最多内存的查询。

真的稳定了吗?

答案是否定的。即使有oom-killer,trino worker 仍会重启(oom killer 有 delay:query.low-memory-killer.delay),而且堆外内存使用会持续升高,直到超过机器可用内存,最终被 linux 系统的 oom killer 杀掉 trino 进程(系统oom killer杀掉占用内存最大的进程)。

打开 -XX:NativeMemoryTracking=detail 之后,使用 jcmd 跟踪 JVM 内存使用。发现 reserved 为108GB 左右,但是top中占用为118(刚重启不久的时候top RES是比jcmd reserved/commited小一点的)。因为 jcmd 不包含C语言类库申请的内存,因此多出的 10GB 应该是这部分内存,对比 jcmd detail 和 pmap 详情,确实有一些地址在 pmap 中存在,在 jcmd detail 中不存在。这部分内存是随着时间逐渐增加的,不是在启动的时候就有。从 jcmd 中也可以看到G1垃圾回收器占用内存也有 5GB,不过是空间换时间了。jvm.config中有配置-Djdk.nio.maxCachedBufferSize=2000000,单位是字节,针对具体线程,jcmd中Other部分为这部分Direct Memory,分配给NIO使用,也没有很多。


Native Memory Tracking:

Total: reserved=110538MB, committed=109475MB
-                 Java Heap (reserved=102400MB, committed=102400MB)
                            (mmap: reserved=102400MB, committed=102400MB)

-                     Class (reserved=928MB, committed=928MB)
                            (classes #82696)
                            (  instance classes #79763, array classes #2933)
                            (malloc=60MB #742359)
                            (mmap: reserved=868MB, committed=867MB)
                            (  Metadata:   )
                            (    reserved=868MB, committed=867MB)
                            (    used=553MB)
                            (    free=314MB)
                            (    waste=0MB =0.00%)

-                    Thread (reserved=1084MB, committed=127MB)
                            (thread #1071)
                            (stack: reserved=1075MB, committed=118MB)
                            (malloc=2MB #5458)
                            (arena=7MB #2140)

-                      Code (reserved=539MB, committed=433MB)
                            (malloc=23MB #80172)
                            (mmap: reserved=516MB, committed=409MB)

-                        GC (reserved=5068MB, committed=5068MB)
                            (malloc=1232MB #512363)
                            (mmap: reserved=3836MB, committed=3836MB)

-                  Compiler (reserved=4MB, committed=4MB)
                            (malloc=10MB #9509)
                            (arena=17592186044410MB #5)

-                  Internal (reserved=38MB, committed=38MB)
                            (malloc=38MB #59237)

-                     Other (reserved=372MB, committed=372MB)
                            (malloc=372MB #238563)

-                    Symbol (reserved=42MB, committed=42MB)
                            (malloc=39MB #555804)
                            (arena=4MB #1)

-    Native Memory Tracking (reserved=36MB, committed=36MB)
                            (tracking overhead=35MB)

-                    Module (reserved=27MB, committed=27MB)
                            (malloc=27MB #107047)

Linux系统默认使用glibc分配内存,分配的大小和MALLOC_ARENA_MAX有关,arena 的数量在 32 位系统上最多是2 * CPU核心数, 64 位系统上最多是 8 * CPU 核心数,每个大小64MB,这一部分最多可以占到86464MB=32GB。尝试通过减小MALLOC_ARENA_MAX,调整MALLOC_ARENA_MAX = 4之后并没有生效,还是有很多64MB的内存块。最后把 glibc 替换为 jemalloc 发现有效。

参考:

  1. github.com/prestodb/pr…
  2. heapdump.cn/article/492…
  3. stackoverflow.com/questions/5…