1. 内存池初始化
初始化代码在LocalMemoryManager中,启动时将内存分为3个内存池,分别是:
- RESERVED_POOL:预留内存池,用于执行最耗费内存资源的查询。
- GENERAL_POOL:普通内存池,用于执行除最耗费内存查询以外的查询。
- SYSTEM_POOL:系统内存池,预留的用于Presto内部数据结构和临时的分配需求使用的内存。
long maxHeap = Runtime.getRuntime().maxMemory();
//堆内存最大值,由Xmx指定
maxMemory = new DataSize(maxHeap - systemMemoryConfig.getReservedSystemMemory().toBytes(), BYTE);
//扣除系统内存池的剩余最大内存
builder.put(RESERVED_POOL, new MemoryPool(RESERVED_POOL, config.getMaxQueryMemoryPerNode()));
//RESERVED_POOL的大小由MaxQueryMemoryPerNode指定
DataSize generalPoolSize = new DataSize(Math.max(0, maxMemory.toBytes() - config.getMaxQueryMemoryPerNode().toBytes()), BYTE);
builder.put(GENERAL_POOL, new MemoryPool(GENERAL_POOL, generalPoolSize));
//扣除SYSTEM_POOL,再扣除RESERVED_POOL,剩余内存作为GENERAL_POOL
builder.put(SYSTEM_POOL, new MemoryPool(SYSTEM_POOL, systemMemoryConfig.getReservedSystemMemory()));
2. 定时调整内存池分配策略
首先看SqlQueryManager的代码
queryManagementExecutor.scheduleWithFixedDelay(() -> {
...
enforceMemoryLimits();
...
}, 1, 1, TimeUnit.SECONDS); //每隔1S触发一次
其调用的是ClusterMemoryManager的process方法,其中有一行:
updateNodes(updateAssignments(queries));
我们看updateAssignments方法,其作用是找出最耗费内存的查询,并放入RESERVED_POOL:
if (reservedPool.getAssignedQueries() == 0 && generalPool.getBlockedNodes() > 0) {
//要求reservedPool当前没有分配查询,并且generalPool中有阻塞的节点
QueryExecution biggestQuery = null;
long maxMemory = -1;
for (QueryExecution queryExecution : queries) {
if (resourceOvercommit(queryExecution.getSession())) {
// 不要提升那些请求资源过量使用的查询到reserved pool,因为他们的内存使用是没有限制的。
continue;
}
long bytesUsed = queryExecution.getTotalMemoryReservation();
if (bytesUsed > maxMemory) {
biggestQuery = queryExecution;
maxMemory = bytesUsed;
}
}
if (biggestQuery != null) {
biggestQuery.setMemoryPool(new VersionedMemoryPoolId(RESERVED_POOL, version));
}
}
再看外层的updateNodes方法,可以发现RESERVED POOL的这种分配策略会应用到每个节点。也就是说这个查询在每个节点都会独占RESERVED POOL的空间。
// Schedule refresh
for (RemoteNodeMemory node : nodes.values()) {
node.asyncRefresh(assignments);
}
3. 配置优化
以下是调整前的Presto的配置
query.max-memory=14GB
query.max-memory-per-node=7GB
resources.reserved-system-memory=2GB
-Xmx10G
计算可得:
- 节点总内存:10GB
- RESERVED_POOL(预留内存池):7GB
- GENERAL_POOL(普通内存池):1GB
- SYSTEM_POOL(系统内存池):2GB
分析:
- 实际执行查询发现SYSTEM_POOL最大使用量大概在1.3GB,基本符合要求。
- 当只提交一个查询时,该查询会进入GENERAL POOL,并出现了可用量为负数的情况(即可用量已无法完成查询),而此时RESERVED POOL的可用量却为7GB,一点都没使用。
- 可以得出目前RESERVED_POOL设置的不太合理,可改为2GB。
保持总内存不变进行修改,修改后配置:
query.max-memory=14GB
query.max-memory-per-node=2GB
resources.reserved-system-memory=2GB
-Xmx10G
经测试,该配置可以较好的利用资源。