Presto内存管理相关参数设置

时间:2024-04-06 16:30:46

背景

之前介绍过Presto内存管理和分配策略,但是那个是0.192版本,详细见:Presto内存管理原理和调优 ,0.201之后内存管理作了新的修改,所以重新简单分析下,然后给出一个配置模板,希望对使用Presto的同学有帮助。

两种内存

Presto里面内存只有2种内存,一种是user memory,另一种是system memory。system memory用于input/output/exchange buffers等,user memory 用于hash join、agg这些。

内存池

0.201之前有3种内存POOL,分别是GENERAL_POOL、RESERVED_POOL及SYSTEM_POOL。但是0.201之后,默认SYSTEM_POOL是不开启的,以下参数控制,默认值为false

 
  1. deprecated.legacy-system-pool-enabled

那SYSTEM_POOL不使用了,这块内存怎么控制呢,去代码里确认了下:

SqlTaskManager#createQueryContext

 
  1. private QueryContext createQueryContext(

  2. QueryId queryId,

  3. LocalMemoryManager localMemoryManager,

  4. NodeMemoryConfig nodeMemoryConfig,

  5. LocalSpillManager localSpillManager,

  6. GcMonitor gcMonitor,

  7. DataSize maxQueryUserMemoryPerNode,

  8. DataSize maxQueryTotalMemoryPerNode,

  9. DataSize maxQuerySpillPerNode)

  10. {

  11. if (nodeMemoryConfig.isLegacySystemPoolEnabled()) {

  12. Optional<MemoryPool> systemPool = localMemoryManager.getSystemPool();

  13. verify(systemPool.isPresent(), "systemPool must be present");

  14. return new LegacyQueryContext(

  15. queryId,

  16. maxQueryUserMemoryPerNode,

  17. localMemoryManager.getGeneralPool(),

  18. systemPool.get(),

  19. gcMonitor,

  20. taskNotificationExecutor,

  21. driverYieldExecutor,

  22. maxQuerySpillPerNode,

  23. localSpillManager.getSpillSpaceTracker());

  24. }

  25.  

  26. return new DefaultQueryContext(

  27. queryId,

  28. maxQueryUserMemoryPerNode,

  29. maxQueryTotalMemoryPerNode,

  30. localMemoryManager.getGeneralPool(),

  31. gcMonitor,

  32. taskNotificationExecutor,

  33. driverYieldExecutor,

  34. maxQuerySpillPerNode,

  35. localSpillManager.getSpillSpaceTracker());

  36. }

  37. }

可以看到systemPool.get()被替换为了localMemoryManager.getGeneralPool(),所以GENERAL_POOL扮演了之前GENERAL_POOL及SYSTEM_POOL的作用,提供user memory和system memory。

说完SYSTEM_POOL,再说下RESERVED_POOL,RESERVED_POOL作用就是当GENERAL_POOL满的时候,将最占用内存的SQL分配到这块内存上来,但是实际使用时,这块内存很少会被使用,原因是, 一般使用Presto的业务是用来SQL提速的,不会使用spill disk功能,第二为了服务的稳定性,会限制最大内存和Kill内存策略,所以会出现查询没有被分配到RESERVED_POOL之前, SQL将会被系统Kill掉。第三是大查询一般会用Spark/Hive替代,所以这块RESERVED_POOL就会浪费掉了,如下图所示:

Presto内存管理相关参数设置

目前有9个SQL正在查询,都在使用GENERAL_POOL,RESERVED_POOL将会浪费掉。所以对于绝大部分Presto使用者,禁止掉RESERVED_POOL是正确的选择。

新的参数:

 
  1. memory.heap-headroom-per-node

这个内存主要是第三方库的内存分配,无法被统计跟踪,默认值是XMX*0.3。

所以,Presto内存分配只需要考虑GENERAL_POOL及heap-headroom-per-node。

分配方法

既然Presto是一个纯内存OLAP引擎,那肯定不能让查询占用的内存无限大,需要参数控制SQL占用的最大内存,Presto内存管理包括了单机和集群粒度的内存,分别是:

  • query.max-memory-per-node: 单个Query在单个Worker上允许的最大user memory

  • query.max-total-memory-per-node: 单个Query在单个Worker上允许的最大user memory + system memory

  • query.max-memory: 单个Query在整个集群上允许的最大user memory

  • query.max-total-memory: 单个Query在整个集群上允许占用的最大user + system memory

而集群query.max-memory由query.initial-hash-partitions控制,默认值为8,那query.max-memroy 一般稍小于query.max-memory-per-node*8即可(考虑到数据倾斜)。

根据代码和上面分析知道:

RESERVED_POOL = query.max-total-memory-per-node

所以新的 GENERAL_POOL = TotalMemory - ReservedPool - memory.heap-headroom-per-node

禁止掉RESERVED_POOL时:

GENERAL_POOL= TotalMemory - memory.heap-headroom-per-node

配置

综上,Presto合适的内存分配配置为如下,假如-Xmx80G,Worker数大于8台:

 
  1. query.max-memory=120GB # 默认为20GB,query.max-memory-per-node * 8 * 0.8 ,倾斜按照0.8算即可

  2. query.max-memory-per-node=20GB # 默认值0.1 * XMX,一般线上按照0.25*XMX算,这个最好结合自己并发,如果并发大,大查询多,值最好小一点,稳定性考虑

  3. query.max-total-memory-per-node=32GB # 默认值0.3*XMX,一般设置为0.4*XMX

  4. experimental.reserved-pool-enabled=false # 不使用RESERVED_POOL

  5. memory.heap-headroom-per-node=16GB # 默认0.3*XMX,一般使用的是0.2*XMX

  6. query.low-memory-killer.policy=total-reservation-on-blocked-nodes # Kill策略,干掉Blocked的最大查询,GENERAL_POOL满了,防止OOM

比如上面分配后,GENERAL_POOL为80GB - 16GB = 64GB,如下图所示,RESERVED_POOL不再存在。query.max-total-memory默认值为2*query.max-memory,这个参数可以保持默认值即可。

Presto内存管理相关参数设置