未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合已有Presto与Linux运维基础的读者,用作JVM内存、查询限制、Spill、资源组、OOM排查及性能测试的综合调优任务框架,不是可直接部署的生产配置或已验证的性能基准。本次仅作全文结构审阅,未作外部事实核验或执行验证;正文尚未界定发行分支、JDK版本、连接器和监控接口的兼容环境。大规模SEQUENCE数据生成未提供适配目标实现限制的方案,订单表每客户仅一行,无法支撑多订单筛选与多行分区窗口基准;监控SQL依赖未说明的系统表字段及对象创建能力,GC统计列、部分API指标映射和回归脚本仍有影响执行或判断的缺陷,不能据此认定GC表现、内存泄漏或调优收益。高内存标记仅供排查,终止查询应结合内存趋势、负载和业务影响判断;Spill是否有效仍受实现、操作符支持、磁盘资源及其他限制约束。
Presto内存配置与调优 - 企业级性能优化实战指南
abstract 📋 本节概览
- 🎯 学习目标:深度掌握Presto内存管理机制,实现企业级性能调优和资源优化
- 📚 前置知识:10-JOIN优化与子查询|JOIN优化与子查询 和 Linux系统管理基础
- ⏱️ 预计用时:150分钟
🧠 Presto内存架构
📊 内存管理体系架构
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TB
subgraph "Presto内存管理架构"
A[JVM堆内存<br/>🏠 Java Heap Memory]
subgraph "Presto内存池"
B[通用内存池<br/>🌊 General Pool]
C[保留内存池<br/>🔒 Reserved Pool]
D[系统内存池<br/>⚙️ System Pool]
end
subgraph "查询内存管理"
E[查询内存限制<br/>📊 Query Memory Limit]
F[节点内存限制<br/>🖥️ Node Memory Limit]
G[任务内存限制<br/>📋 Task Memory Limit]
end
subgraph "内存使用者"
H[JOIN操作<br/>🔗 Hash Tables]
I[排序操作<br/>📊 Sort Buffers]
J[聚合操作<br/>🗂️ Aggregation]
K[窗口函数<br/>🪟 Window Functions]
L[临时表<br/>📄 Spill Files]
end
subgraph "内存监控与调试"
M[内存使用监控<br/>📈 Memory Monitoring]
N[OOM检测<br/>⚠️ Out of Memory Detection]
O[Spill机制<br/>💾 Disk Spilling]
P[内存诊断<br/>🔍 Memory Diagnostics]
end
A --> B
A --> C
A --> D
B --> E
C --> F
D --> G
E --> H
F --> I
G --> J
E --> K
F --> L
H --> M
I --> N
J --> O
K --> P
style A fill:#e3f2fd
style B fill:#f3e5f5
style E fill:#e8f5e8
style H fill:#fff3e0
style M fill:#fce4ec
end
🎯 内存池类型详解
| 内存池类型 | 用途 | 大小配置 | 主要特征 |
|---|---|---|---|
| General Pool | 普通查询内存 | 依目标发行分支的节点内存池实现与配置确定,不能由query.max-memory乘固定比例推得 | 大部分查询使用 |
| Reserved Pool | 大查询专用内存 | 是否存在及大小取决于目标发行分支与配置,不能由query.max-memory乘固定比例推得 | 仅适用于具有该池机制的实现 |
| System Pool | 系统级操作内存 | resources.reserved-system-memory | 元数据、连接器等 |
⚙️ 核心内存配置
🔧 JVM内存配置
JVM启动参数配置 (etc/jvm.config):
# 堆内存配置
-Xms16G # 初始堆大小
-Xmx16G # 最大堆大小
-XX:+UseG1GC # 使用G1垃圾收集器
-XX:G1HeapRegionSize=32M # G1区域大小
-XX:+UseGCOverheadLimit # GC开销限制
-XX:+ExplicitGCInvokesConcurrent # 并发GC调用
# 非堆内存配置
-XX:MaxMetaspaceSize=512M # 元空间大小
-XX:ReservedCodeCacheSize=256M # 代码缓存
-XX:MaxDirectMemorySize=2G # 直接内存大小
# GC调优参数
-XX:+UnlockExperimentalVMOptions
-XX:+UseTransparentHugePages
-XX:+UseCompressedOops
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/presto/logs
-XX:OnOutOfMemoryError=kill -9 %p
# 性能监控
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-Xloggc:/opt/presto/logs/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10M
📊 Presto内存配置
核心内存配置 (etc/config.properties):
# 查询内存配置
query.max-memory=50GB # 单个查询最大内存
query.max-memory-per-node=8GB # 单节点查询最大内存
query.max-total-memory=100GB # 集群查询总内存限制
# 内存池配置
memory.heap-headroom-per-node=2GB # 每节点堆内存预留
resources.reserved-system-memory=4GB # 系统保留内存
# Spill配置
spill-enabled=true # 启用磁盘溢出
spiller-spill-path=/tmp/presto-spill # 溢出文件路径
spiller-max-used-space-threshold=0.8 # 磁盘使用阈值
spiller-threads=4 # 溢出线程数
# 任务并发配置
task.max-worker-threads=200 # 最大工作线程
task.http-response-threads=100 # HTTP响应线程
task.http-timeout-threads=3 # HTTP超时线程
# 连接器内存配置
connector.max-connections-per-node=50 # 每节点最大连接数
💡 内存使用优化策略
🔗 JOIN操作内存优化
-- 创建用于内存优化测试的大表
CREATE TABLE memory.default.large_table_a AS
SELECT
customer_id,
'Customer_' || CAST(customer_id AS VARCHAR) as customer_name,
CAST(random() * 100000 AS DECIMAL(10,2)) as total_spent,
CAST(random() * 1000 + 1 AS BIGINT) as order_count
FROM (
SELECT row_number() OVER () as customer_id
FROM UNNEST(SEQUENCE(1, 5000000)) t(x)
);
CREATE TABLE memory.default.large_table_b AS
SELECT
customer_id,
CAST(random() * 1000 + 1 AS BIGINT) as product_id,
CAST(random() * 100 + 10 AS DECIMAL(8,2)) as order_amount,
DATE '2024-01-01' + CAST(random() * 90 AS INTEGER) * INTERVAL '1' DAY as order_date
FROM (
SELECT row_number() OVER () as customer_id
FROM UNNEST(SEQUENCE(1, 10000000)) t(x)
);
-- 内存密集型JOIN查询示例
-- 配置JOIN分发策略减少内存使用
SET SESSION join_distribution_type = 'PARTITIONED';
SET SESSION task_concurrency = 8;
SET SESSION query_max_memory_per_node = '4GB';
-- 优化的大表JOIN
EXPLAIN ANALYZE
SELECT
a.customer_name,
a.total_spent,
COUNT(b.customer_id) as order_count,
SUM(b.order_amount) as recent_orders_amount
FROM memory.default.large_table_a a
JOIN memory.default.large_table_b b ON a.customer_id = b.customer_id
WHERE b.order_date >= DATE '2024-02-01'
GROUP BY a.customer_name, a.total_spent
HAVING COUNT(b.customer_id) > 10
ORDER BY recent_orders_amount DESC
LIMIT 1000;
📊 聚合操作内存管理
-- 高基数聚合内存优化
-- 启用部分聚合减少内存使用
SET SESSION use_partial_aggregation = true;
SET SESSION partial_aggregation_strategy = 'AUTOMATIC';
SET SESSION aggregation_operator_unspill_memory_limit = '100MB';
-- 内存友好的聚合查询
WITH customer_segments AS (
SELECT
customer_id,
total_spent,
order_count,
CASE
WHEN total_spent > 50000 THEN 'VIP'
WHEN total_spent > 10000 THEN 'Premium'
WHEN total_spent > 1000 THEN 'Standard'
ELSE 'Basic'
END AS segment
FROM memory.default.large_table_a
),
segment_stats AS (
SELECT
segment,
COUNT(*) as customer_count,
AVG(total_spent) as avg_spent,
MIN(total_spent) as min_spent,
MAX(total_spent) as max_spent,
APPROX_PERCENTILE(total_spent, 0.5) as median_spent,
APPROX_PERCENTILE(total_spent, 0.95) as p95_spent
FROM customer_segments
GROUP BY segment
)
SELECT
segment,
customer_count,
ROUND(avg_spent, 2) as avg_spent,
ROUND(min_spent, 2) as min_spent,
ROUND(max_spent, 2) as max_spent,
ROUND(median_spent, 2) as median_spent,
ROUND(p95_spent, 2) as p95_spent,
ROUND(customer_count * 100.0 / SUM(customer_count) OVER (), 2) as segment_percentage
FROM segment_stats
ORDER BY avg_spent DESC;
🪟 窗口函数内存优化
-- 窗口函数内存优化配置
SET SESSION window_operator_buffer_size = '100MB';
SET SESSION max_drivers_per_task = 4;
-- 内存高效的窗口函数查询
WITH ranked_customers AS (
SELECT
customer_id,
customer_name,
total_spent,
order_count,
-- 以下排名函数按排序计算;后续移动平均使用ROWS窗口
ROW_NUMBER() OVER (ORDER BY total_spent DESC) as spending_rank,
NTILE(10) OVER (ORDER BY total_spent) as spending_decile,
-- 限制窗口范围减少内存使用
AVG(total_spent) OVER (
ORDER BY total_spent
ROWS BETWEEN 1000 PRECEDING AND 1000 FOLLOWING
) as moving_avg_spent,
LAG(total_spent, 1) OVER (ORDER BY total_spent) as prev_spent
FROM memory.default.large_table_a
WHERE total_spent > 1000 -- 预过滤减少数据量
),
top_customers AS (
SELECT
customer_id,
customer_name,
total_spent,
spending_rank,
spending_decile,
ROUND(moving_avg_spent, 2) as moving_avg_spent,
ROUND(total_spent - COALESCE(prev_spent, 0), 2) as spent_diff
FROM ranked_customers
WHERE spending_rank <= 10000 -- 限制结果集大小
)
SELECT
spending_decile,
COUNT(*) as customer_count,
AVG(total_spent) as avg_spent,
MIN(total_spent) as min_spent,
MAX(total_spent) as max_spent
FROM top_customers
GROUP BY spending_decile
ORDER BY spending_decile;
🔧 Spill机制配置
💾 磁盘溢出设置
Spill配置详解:
# 启用Spill机制
spill-enabled=true
# Spill路径配置(可配置多个路径负载均衡)
spiller-spill-path=/data1/presto-spill,/data2/presto-spill,/data3/presto-spill
# Spill阈值配置
spiller-max-used-space-threshold=0.75 # 磁盘使用率阈值
memory-revocation-target=0.5 # 内存回收目标比例
memory-revocation-threshold=0.9 # 内存回收触发阈值
# Spill性能配置
spiller-threads=8 # Spill线程数
spiller-max-file-size=1GB # 单个Spill文件最大大小
spiller-compression-enabled=true # 启用Spill文件压缩
spiller-compression-codec=LZ4 # 压缩算法
# 操作符级别Spill配置
spill-operators=JOIN,ORDER_BY,WINDOW,AGGREGATION,DISTINCT
aggregation-operator-unspill-memory-limit=512MB
join-operator-unspill-memory-limit=1GB
order-by-operator-unspill-memory-limit=512MB
📊 Spill监控和诊断
-- 监控Spill使用情况
SELECT
query_id,
node_id,
stage_id,
task_id,
operator_type,
spilled_bytes,
spilled_rows,
spill_time_ms,
memory_revocation_count
FROM system.runtime.tasks
WHERE spilled_bytes > 0
ORDER BY spilled_bytes DESC;
-- 查看当前活跃查询的内存使用
SELECT
query_id,
state,
user_time,
total_memory_reservation,
peak_user_memory_reservation,
peak_total_memory_reservation,
spilled_bytes
FROM system.runtime.queries
WHERE state IN ('QUEUED', 'PLANNING', 'STARTING', 'RUNNING')
ORDER BY peak_total_memory_reservation DESC;
-- 内存相关的查询统计
SELECT
DATE_TRUNC('hour', created) as hour,
COUNT(*) as query_count,
AVG(peak_total_memory_reservation) as avg_peak_memory,
MAX(peak_total_memory_reservation) as max_peak_memory,
SUM(spilled_bytes) as total_spilled_bytes,
COUNT(CASE WHEN spilled_bytes > 0 THEN 1 END) as spilled_query_count
FROM system.runtime.queries
WHERE created >= CURRENT_TIMESTAMP - INTERVAL '24' HOUR
GROUP BY DATE_TRUNC('hour', created)
ORDER BY hour DESC;
📈 内存性能监控
🔍 系统级内存监控
内存监控脚本 (monitor_memory.sh):
#!/bin/bash
# Presto内存监控脚本
PRESTO_PID=$(pgrep -f "io.prestosql.server.PrestoServer")
LOG_FILE="/opt/presto/logs/memory_monitor.log"
while true; do
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
# JVM内存使用情况
heap_used=$(jstat -gc $PRESTO_PID | tail -1 | awk '{sum=$3+$4+$6+$8; print sum}')
heap_capacity=$(jstat -gc $PRESTO_PID | tail -1 | awk '{sum=$1+$2+$5+$7; print sum}')
# 系统内存使用
sys_mem=$(free -m | grep "Mem:" | awk '{printf "%.2f", ($3/$2)*100}')
# GC统计
gc_count=$(jstat -gc $PRESTO_PID | tail -1 | awk '{print $5+$7}')
gc_time=$(jstat -gc $PRESTO_PID | tail -1 | awk '{print $6+$8}')
echo "$timestamp,HEAP_USED:${heap_used}KB,HEAP_CAPACITY:${heap_capacity}KB,SYS_MEM:${sys_mem}%,GC_COUNT:$gc_count,GC_TIME:${gc_time}ms" >> $LOG_FILE
sleep 30
done
📊 Presto内存API监控
# 获取集群内存使用情况
curl -s http://coordinator:8080/v1/cluster | jq '.runningQueries, .blockedQueries, .queuedQueries'
# 获取节点内存统计
curl -s http://coordinator:8080/v1/node | jq '.[] | {nodeId: .nodeId, memoryInfo: .memoryInfo}'
# 获取查询内存详情
curl -s http://coordinator:8080/v1/query/{query-id} | jq '.memoryPool, .totalMemoryReservation'
# 获取系统内存池状态
curl -s http://coordinator:8080/v1/cluster/memory | jq '.'
🎯 自定义内存监控查询
-- 创建内存监控仪表板视图
CREATE VIEW system.monitoring.memory_dashboard AS
WITH current_queries AS (
SELECT
query_id,
state,
user_name,
query,
created,
total_memory_reservation / 1024.0 / 1024.0 / 1024.0 as memory_gb,
peak_total_memory_reservation / 1024.0 / 1024.0 / 1024.0 as peak_memory_gb,
spilled_bytes / 1024.0 / 1024.0 / 1024.0 as spilled_gb
FROM system.runtime.queries
WHERE state IN ('QUEUED', 'PLANNING', 'STARTING', 'RUNNING')
),
memory_stats AS (
SELECT
COUNT(*) as active_queries,
COALESCE(SUM(memory_gb), 0) as total_memory_gb,
COALESCE(MAX(peak_memory_gb), 0) as max_peak_memory_gb,
COALESCE(SUM(spilled_gb), 0) as total_spilled_gb,
COUNT(CASE WHEN spilled_gb > 0 THEN 1 END) as spilled_queries
FROM current_queries
)
SELECT
'CURRENT_STATUS' as metric_type,
active_queries,
ROUND(total_memory_gb, 2) as total_memory_gb,
ROUND(max_peak_memory_gb, 2) as max_peak_memory_gb,
ROUND(total_spilled_gb, 2) as total_spilled_gb,
spilled_queries,
ROUND(spilled_queries * 100.0 / NULLIF(active_queries, 0), 2) as spill_percentage
FROM memory_stats;
-- 内存使用趋势分析
WITH hourly_memory AS (
SELECT
DATE_TRUNC('hour', created) as hour,
COUNT(*) as queries,
AVG(peak_total_memory_reservation) / 1024.0 / 1024.0 / 1024.0 as avg_peak_memory_gb,
MAX(peak_total_memory_reservation) / 1024.0 / 1024.0 / 1024.0 as max_peak_memory_gb,
SUM(spilled_bytes) / 1024.0 / 1024.0 / 1024.0 as total_spilled_gb
FROM system.runtime.queries
WHERE created >= CURRENT_TIMESTAMP - INTERVAL '24' HOUR
GROUP BY DATE_TRUNC('hour', created)
)
SELECT
hour,
queries,
ROUND(avg_peak_memory_gb, 2) as avg_peak_memory_gb,
ROUND(max_peak_memory_gb, 2) as max_peak_memory_gb,
ROUND(total_spilled_gb, 2) as total_spilled_gb,
ROUND(LAG(avg_peak_memory_gb) OVER (ORDER BY hour), 2) as prev_avg_memory_gb,
ROUND(
(avg_peak_memory_gb - LAG(avg_peak_memory_gb) OVER (ORDER BY hour)) * 100.0 /
NULLIF(LAG(avg_peak_memory_gb) OVER (ORDER BY hour), 0), 2
) as memory_growth_percent
FROM hourly_memory
ORDER BY hour DESC;
🚀 高级内存调优技术
⚡ 动态内存管理
动态内存调整策略:
-- 基于查询类型的内存配置
-- 大型分析查询配置
SET SESSION query_max_memory = '20GB';
SET SESSION query_max_memory_per_node = '4GB';
SET SESSION task_concurrency = 4;
SET SESSION join_distribution_type = 'PARTITIONED';
-- 实时分析查询配置
SET SESSION query_max_memory = '5GB';
SET SESSION query_max_memory_per_node = '1GB';
SET SESSION task_concurrency = 16;
SET SESSION join_distribution_type = 'BROADCAST';
-- ETL批处理查询配置
SET SESSION query_max_memory = '50GB';
SET SESSION query_max_memory_per_node = '8GB';
SET SESSION task_concurrency = 2;
SET SESSION spill_enabled = true;
🎛️ 资源组内存管理
资源组配置 (etc/resource-groups.properties):
resource-groups.configuration-manager=file
resource-groups.config-file=etc/resource-groups.json
资源组JSON配置 (etc/resource-groups.json):
{
"rootGroups": [
{
"name": "adhoc",
"softMemoryLimit": "50%",
"hardConcurrencyLimit": 50,
"maxQueued": 100,
"schedulingPolicy": "fair",
"schedulingWeight": 1,
"jmxExport": true,
"subGroups": [
{
"name": "small",
"softMemoryLimit": "30%",
"hardConcurrencyLimit": 30,
"maxQueued": 50,
"schedulingPolicy": "fair"
},
{
"name": "large",
"softMemoryLimit": "70%",
"hardConcurrencyLimit": 5,
"maxQueued": 10,
"schedulingPolicy": "fair"
}
]
},
{
"name": "etl",
"softMemoryLimit": "40%",
"hardConcurrencyLimit": 10,
"maxQueued": 20,
"schedulingPolicy": "fifo",
"schedulingWeight": 2,
"jmxExport": true
},
{
"name": "reporting",
"softMemoryLimit": "30%",
"hardConcurrencyLimit": 20,
"maxQueued": 50,
"schedulingPolicy": "fair",
"schedulingWeight": 1,
"jmxExport": true
}
],
"selectors": [
{
"user": "analytics_team",
"group": "adhoc.large"
},
{
"user": "etl_service",
"group": "etl"
},
{
"source": "reporting_tool",
"group": "reporting"
},
{
"group": "adhoc.small"
}
]
}
🔄 内存回收策略
-- 创建内存回收监控存储过程
CREATE OR REPLACE FUNCTION system.monitoring.analyze_memory_pressure()
RETURNS TABLE(
recommendation VARCHAR,
current_usage DOUBLE,
threshold DOUBLE,
action_required BOOLEAN
)
LANGUAGE SQL
AS $$
WITH memory_analysis AS (
SELECT
SUM(total_memory_reservation) / 1024.0 / 1024.0 / 1024.0 as current_memory_gb,
COUNT(*) as active_queries,
COUNT(CASE WHEN spilled_bytes > 0 THEN 1 END) as spilled_queries,
AVG(total_memory_reservation) / 1024.0 / 1024.0 / 1024.0 as avg_query_memory_gb
FROM system.runtime.queries
WHERE state IN ('QUEUED', 'PLANNING', 'STARTING', 'RUNNING')
),
thresholds AS (
SELECT
50.0 as memory_warning_threshold_gb,
80.0 as memory_critical_threshold_gb,
0.3 as spill_ratio_warning,
10.0 as avg_memory_warning_gb
)
SELECT
CASE
WHEN ma.current_memory_gb > t.memory_critical_threshold_gb THEN
'CRITICAL: Consider killing large queries or adding nodes'
WHEN ma.current_memory_gb > t.memory_warning_threshold_gb THEN
'WARNING: Monitor memory usage closely'
WHEN ma.spilled_queries * 1.0 / NULLIF(ma.active_queries, 0) > t.spill_ratio_warning THEN
'INFO: High spill ratio, consider memory tuning'
WHEN ma.avg_query_memory_gb > t.avg_memory_warning_gb THEN
'INFO: Queries using high memory, optimize query patterns'
ELSE 'OK: Memory usage within normal range'
END as recommendation,
ma.current_memory_gb as current_usage,
CASE
WHEN ma.current_memory_gb > t.memory_critical_threshold_gb THEN t.memory_critical_threshold_gb
ELSE t.memory_warning_threshold_gb
END as threshold,
ma.current_memory_gb > t.memory_warning_threshold_gb as action_required
FROM memory_analysis ma, thresholds t
$$;
-- 使用内存分析函数
SELECT * FROM system.monitoring.analyze_memory_pressure();
🛡️ 内存故障处理
⚠️ OOM故障诊断
内存溢出诊断脚本:
#!/bin/bash
# oom_diagnosis.sh - OOM故障诊断脚本
PRESTO_LOG_DIR="/opt/presto/logs"
DIAGNOSIS_DIR="/opt/presto/diagnosis"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $DIAGNOSIS_DIR
echo "=== Presto OOM Diagnosis Report - $TIMESTAMP ===" > $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
# 检查HeapDump文件
echo "1. Checking for heap dumps..." >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
find $PRESTO_LOG_DIR -name "*.hprof" -mtime -1 >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
# 分析GC日志
echo -e "\n2. GC Analysis (last 1000 lines)..." >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
tail -1000 $PRESTO_LOG_DIR/gc.log | grep -E "(OutOfMemoryError|Full GC|concurrent mark-sweep|G1)" >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
# 系统内存状态
echo -e "\n3. System Memory Status..." >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
free -h >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
echo -e "\nMemory usage by process:" >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
ps aux --sort=-%mem | head -20 >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
# Presto日志中的内存错误
echo -e "\n4. Presto Memory Errors (last 24 hours)..." >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
find $PRESTO_LOG_DIR -name "server.log*" -mtime -1 -exec grep -l "OutOfMemoryError\|Memory\|exceeded" {} \; | \
while read file; do
echo "File: $file" >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
grep -E "(OutOfMemoryError|Memory.*exceeded|Query.*killed|Memory pool)" "$file" | tail -50 >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
done
# 获取当前查询状态
echo -e "\n5. Current Query Status..." >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt
curl -s http://localhost:8080/v1/query | jq '.[] | {queryId: .queryId, state: .state, memoryReservation: .memoryPool}' >> $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt 2>/dev/null
echo "Diagnosis complete. Report saved to: $DIAGNOSIS_DIR/oom_diagnosis_$TIMESTAMP.txt"
🔧 内存泄漏检测
-- 内存泄漏检测查询
WITH long_running_queries AS (
SELECT
query_id,
state,
user_name,
source,
created,
started,
"end",
elapsed_time,
execution_time,
total_memory_reservation / 1024.0 / 1024.0 / 1024.0 as memory_gb,
peak_total_memory_reservation / 1024.0 / 1024.0 / 1024.0 as peak_memory_gb,
DATE_DIFF('minute', created, COALESCE("end", CURRENT_TIMESTAMP)) as runtime_minutes
FROM system.runtime.queries
WHERE (state IN ('RUNNING', 'QUEUED') AND created < CURRENT_TIMESTAMP - INTERVAL '30' MINUTE)
OR (state = 'FINISHED' AND execution_time > INTERVAL '1' HOUR)
),
memory_growth_analysis AS (
SELECT
query_id,
state,
user_name,
runtime_minutes,
memory_gb,
peak_memory_gb,
memory_gb / NULLIF(runtime_minutes, 0) as memory_per_minute,
CASE
WHEN runtime_minutes > 60 AND memory_gb > 5 THEN 'POTENTIAL_LEAK'
WHEN runtime_minutes > 120 THEN 'LONG_RUNNING'
WHEN peak_memory_gb > 20 THEN 'HIGH_MEMORY'
ELSE 'NORMAL'
END as risk_level
FROM long_running_queries
)
SELECT
query_id,
state,
user_name,
runtime_minutes,
ROUND(memory_gb, 2) as current_memory_gb,
ROUND(peak_memory_gb, 2) as peak_memory_gb,
ROUND(memory_per_minute, 4) as current_memory_per_runtime_minute,
risk_level,
CASE risk_level
WHEN 'POTENTIAL_LEAK' THEN 'Investigate memory trends and workload before considering termination'
WHEN 'LONG_RUNNING' THEN 'Monitor closely for memory growth'
WHEN 'HIGH_MEMORY' THEN 'Check if memory usage is justified'
ELSE 'No immediate action required'
END as recommendation
FROM memory_growth_analysis
ORDER BY
CASE risk_level
WHEN 'POTENTIAL_LEAK' THEN 1
WHEN 'LONG_RUNNING' THEN 2
WHEN 'HIGH_MEMORY' THEN 3
ELSE 4
END,
memory_gb DESC;
🎯 性能基准测试
📊 内存性能基准
-- 内存性能基准测试套件
-- 1. JOIN内存基准测试
WITH benchmark_config AS (
SELECT
'JOIN_BENCHMARK' as test_name,
CURRENT_TIMESTAMP as start_time
)
SELECT
bc.test_name,
bc.start_time,
COUNT(*) as result_count,
-- 这里会消耗大量内存进行Hash JOIN
COUNT(DISTINCT a.customer_id) as unique_customers,
SUM(a.total_spent) as total_spent,
AVG(b.order_amount) as avg_order_amount
FROM benchmark_config bc
CROSS JOIN memory.default.large_table_a a
JOIN memory.default.large_table_b b ON a.customer_id = b.customer_id
WHERE a.total_spent > 1000
GROUP BY bc.test_name, bc.start_time;
-- 2. 聚合内存基准测试
WITH aggregation_benchmark AS (
SELECT
customer_id,
COUNT(*) as order_count,
SUM(order_amount) as total_amount,
AVG(order_amount) as avg_amount,
MIN(order_amount) as min_amount,
MAX(order_amount) as max_amount,
STDDEV(order_amount) as stddev_amount,
APPROX_PERCENTILE(order_amount, 0.5) as median_amount,
APPROX_PERCENTILE(order_amount, 0.95) as p95_amount,
APPROX_DISTINCT(product_id) as unique_products
FROM memory.default.large_table_b
GROUP BY customer_id
HAVING COUNT(*) > 5
)
SELECT
'AGGREGATION_BENCHMARK' as test_name,
CURRENT_TIMESTAMP as timestamp,
COUNT(*) as customer_count,
SUM(order_count) as total_orders,
AVG(total_amount) as avg_customer_value,
MAX(unique_products) as max_product_diversity
FROM aggregation_benchmark;
-- 3. 窗口函数内存基准测试
SELECT
'WINDOW_BENCHMARK' as test_name,
CURRENT_TIMESTAMP as timestamp,
COUNT(*) as processed_rows,
AVG(customer_rank) as avg_rank,
AVG(running_total) as avg_running_total
FROM (
SELECT
customer_id,
order_amount,
ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_amount DESC) as customer_rank,
SUM(order_amount) OVER (PARTITION BY customer_id ORDER BY order_date ROWS UNBOUNDED PRECEDING) as running_total,
AVG(order_amount) OVER (PARTITION BY customer_id ORDER BY order_date ROWS BETWEEN 10 PRECEDING AND 10 FOLLOWING) as moving_avg
FROM memory.default.large_table_b
WHERE order_date >= DATE '2024-02-01'
) windowed_data
WHERE customer_rank <= 100;
🏃♂️ 性能回归测试
#!/bin/bash
# memory_regression_test.sh - 内存性能回归测试
TEST_DIR="/opt/presto/tests"
RESULTS_DIR="/opt/presto/test_results"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p $RESULTS_DIR
# 测试配置
declare -A TEST_CONFIGS=(
["small_memory"]="query_max_memory=2GB,query_max_memory_per_node=512MB"
["medium_memory"]="query_max_memory=8GB,query_max_memory_per_node=2GB"
["large_memory"]="query_max_memory=32GB,query_max_memory_per_node=8GB"
)
# 运行内存基准测试
run_memory_test() {
local config_name=$1
local config_params=$2
local result_file="$RESULTS_DIR/memory_test_${config_name}_$TIMESTAMP.json"
echo "Running memory test with config: $config_name"
echo "Parameters: $config_params"
# 设置会话参数并运行测试查询
presto-cli --server localhost:8080 --execute "
SET SESSION $config_params;
WITH test_execution AS (
SELECT
'$config_name' as config_name,
CURRENT_TIMESTAMP as start_time,
COUNT(*) as row_count,
SUM(a.total_spent * b.order_amount) as computation_result
FROM memory.default.large_table_a a
JOIN memory.default.large_table_b b ON a.customer_id = b.customer_id
WHERE a.total_spent > 5000
)
SELECT
config_name,
start_time,
CURRENT_TIMESTAMP as end_time,
DATE_DIFF('second', start_time, CURRENT_TIMESTAMP) as execution_seconds,
row_count,
computation_result
FROM test_execution;
" --output-format JSON > $result_file
echo "Test completed. Results saved to: $result_file"
}
# 执行所有测试配置
for config_name in "${!TEST_CONFIGS[@]}"; do
run_memory_test "$config_name" "${TEST_CONFIGS[$config_name]}"
sleep 60 # 等待内存回收
done
# 生成汇总报告
echo "Generating summary report..."
python3 << EOF
import json
import glob
import pandas as pd
from datetime import datetime
# 读取所有测试结果
result_files = glob.glob('$RESULTS_DIR/memory_test_*_$TIMESTAMP.json')
results = []
for file in result_files:
with open(file, 'r') as f:
data = json.load(f)
if data:
results.extend(data)
# 创建DataFrame并分析
df = pd.DataFrame(results)
if not df.empty:
summary = df.groupby('config_name').agg({
'execution_seconds': ['mean', 'min', 'max'],
'row_count': 'mean',
'computation_result': 'mean'
}).round(2)
print("Memory Performance Test Summary:")
print("=" * 50)
print(summary)
# 保存详细报告
with open('$RESULTS_DIR/memory_test_summary_$TIMESTAMP.txt', 'w') as f:
f.write("Memory Performance Test Summary - $TIMESTAMP\n")
f.write("=" * 50 + "\n")
f.write(str(summary))
f.write("\n\nDetailed Results:\n")
f.write(str(df))
else:
print("No test results found.")
EOF
echo "Memory regression testing completed."
echo "Results available in: $RESULTS_DIR"
🔗 学习连接
📚 本节核心技能
- 内存架构理解:掌握Presto内存管理的完整架构和工作原理
- 配置优化:JVM配置、Presto内存配置的最佳实践
- 性能调优:JOIN、聚合、窗口函数的内存优化策略
- Spill机制:磁盘溢出的配置和监控技巧
- 故障诊断:OOM问题的诊断和解决方法
- 监控体系:建立完善的内存监控和告警机制
🎯 下一步学习
内存管理检查:
- ✅ 能够配置和优化Presto集群的内存使用?
- ✅ 掌握不同查询类型的内存调优策略?
- ✅ 理解Spill机制和故障处理方法?
继续学习路径:
tip 💡 内存管理精髓
🧠 内存优化核心原则:
- 📊 合理分配 - 根据工作负载特征配置内存池大小
- ⚡ 预防为主 - 通过监控和限制避免OOM问题
- 💾 优雅降级 - 在操作符支持、磁盘资源充足且未触及其他限制时,Spill机制可缓解内存压力,但不保证查询完成
- 🔍 持续监控 - 建立完善的内存使用监控和告警体系
🚀 安全加固:内存调优完成后,进入 12-安全配置与权限管理|安全配置与权限管理 学习企业级安全配置!