Vertica 内存压力诊断与调优¶
作者:JiangChong | 发布时间:2026-04-07
适用场景:集群频繁出现 RESOURCE_REJECTED、查询排队严重、query_budget 不足导致 Spill,或内存使用率持续 > 90%。
关联:Vertica 资源池最佳实践 | Vertica 监控最佳实践 | Vertica 性能调优 - 2 使用系统表排除 Vertica 查询性能故障 | Vertica CPU 持续高负载诊断与优化
理解全文脉络¶
本文按照「理解内存模型 → 发现内存问题 → 定位内存消耗者 → 解决问题」的路径组织。如果你刚收到 RESOURCE_REJECTED 告警,可以从第 2 节开始排查;如果你想理解 Vertica 内存分配机制以便做容量规划,从第 1 节读起。
1. 理解 Vertica 的内存模型¶
1.1 Vertica 如何使用内存¶
Vertica 的内存消耗与传统数据库有本质区别。理解这几点是诊断内存问题的基础:
- 没有 shared buffer pool:PostgreSQL 有
shared_buffers,MySQL 有 InnoDB buffer pool,而 Vertica 不使用全局缓存。每次查询都直接从磁盘读取数据到查询专属的内存空间中处理。 - 内存按查询分配:每条查询在开始执行时,Resource Manager 会根据资源池的
query_budget为它预留一块内存。如果查询实际需要更多内存,可以在执行中申请额外内存(AcquireAdditional请求)。 - 内存换磁盘 I/O:Vertica 的算子(如
GroupByHash、JoinHash)倾向将数据尽量保留在内存中处理。如果内存不够,数据会溢出(Spill)到磁盘临时文件——Spill 不仅增加 I/O,还会因额外的排序操作增加 CPU 消耗。 - 内存是硬限制:当资源池的
MAXMEMORYSIZE被打满,新的查询请求会被拒绝(RESOURCE_REJECTED)或排队等待(RESOURCE_QUEUES)。
关键结论:Vertica 的内存消耗模式是「爆发式」的——平时可能很空闲,但一旦有复杂查询,瞬间申请几十 GB。这与传统 OLTP 数据库的「稳定水位」完全不同。
1.2 内存压力的三种表现¶
当系统出现内存压力时,通常表现为以下三种模式之一或组合:
| 表现 | 症状 | 对应的系统表 |
|---|---|---|
| 内存资源拒绝(RESOURCE_REJECTED) | 查询提交后被直接拒绝,报错 RESOURCE_REJECTED |
v_monitor.resource_rejections |
| 查询排队(Queuing) | 查询长时间处于 Queue 状态,position_in_queue 不断增长 |
v_monitor.resource_queues |
| 查询溢出磁盘(Spill) | 查询执行正常但极慢,执行计划中出现 GROUP_BY_SPILLED、JOIN_SPILLED |
v_monitor.query_events、execution_engine_profiles |
1.3 内存来源分类¶
在 Vertica 内部,内存消耗可以分解为以下几个维度:
| 来源 | 说明 | 典型特征 |
|---|---|---|
| 查询执行内存 | 每个算子的内存申请(哈希表、排序缓冲区、网络缓冲区) | 占比最大,集中在 GroupByHash、JoinHash、Sort 算子 |
| Tuple Mover 内存 | mergeout/moveout 过程中的排序和压缩 | TM 资源池独立配置,默认 MAXCONCURRENCY=7、PLANNEDCONCURRENCY=4 且 query_budget 很小 |
| Recovery 内存 | 节点恢复时的数据同步和校验 | 恢复完成即释放 |
| Catalog / Metadata 内存 | 表定义、投影定义、统计信息等元数据加载 | Catalog 过大(每节点 > 20GB)时消耗显著 |
| 操作系统开销 | 文件系统缓存(page cache)、网络缓冲区、进程开销 | Linux free -h 中 buff/cache 占用 |
详见 Vertica 监控最佳实践。
2. 系统级内存监控:先看清全局¶
2.1 使用 system_resource_usage 查看历史内存¶
v_monitor.system_resource_usage 表以固定间隔(默认每 10 秒)采集每个节点的内存使用百分比。
SELECT node_name,
end_time,
average_memory_usage_percent,
average_cpu_usage_percent
FROM v_monitor.system_resource_usage
WHERE end_time > sysdate - INTERVAL '1 hour'
ORDER BY end_time DESC;
如何解读结果:
average_memory_usage_percent > 90%持续超过 10 分钟:内存已成为瓶颈,新查询大概率被拒绝或排队。- 各节点内存使用差异 > 30%:说明某些节点承担了不成比例的数据或查询负载(数据倾斜或查询路由不均)。
- 内存高但 CPU 低:典型的内存溢出(Spill)场景——查询在等磁盘 I/O,CPU 在等数据。优化方向是增大
query_budget或优化 SQL。 - 内存和 CPU 同时高:正常的大计算场景,系统在全力工作。如果查询响应时间可以接受,无需干预。
2.2 使用 Linux 命令辅助定位¶
# 查看系统内存使用(重点关注 available 列)
free -h
# 查看 Vertica 进程的内存占用(RSS)
ps aux | grep vertica | awk '{sum+=$6} END {print "Total RSS: " sum/1024/1024 " GB"}'
# 查看内存使用 Top 10 的进程
ps aux --sort=-%mem | head -11
如何解读:
free -h中available< 10% 总内存:操作系统层面内存紧张,可能触发 OOM Killer。buff/cache占用过高:Linux 文件系统缓存挤占了可用内存。可通过echo 3 > /proc/sys/vm/drop_caches释放(需谨慎,会影响 I/O 性能)。- 单个 Vertica 进程 RSS 超过节点总内存的 80%:可能有内存泄漏查询或资源池配置不当。
3. 定位内存消耗来源¶
3.1 第一步:查看资源拒绝事件¶
RESOURCE_REJECTED 是内存不足的最直接信号。每当 Resource Manager 拒绝了某条查询的内存申请,就会在 v_monitor.resource_rejections 中记录一条。
SELECT node_name,
pool_name,
reason,
resource_type,
rejection_count,
first_rejected_timestamp,
last_rejected_timestamp,
last_rejected_value
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 hour'
ORDER BY last_rejected_timestamp DESC
LIMIT 20;
如何解读:
reason字段直接告诉你是谁被拒绝了:Memory(KB) Exceeded:池的MAXMEMORYSIZE已被打满,无法接受新请求Request exceeds limits:单条查询申请的额外内存超过了池上限rejection_count:该资源在该时间段内被拒绝的次数。如果该值很大,说明大量查询重复申请失败。last_rejected_value:最后一次被拒绝时请求的值(与reason配合定位具体缺口大小)。- 集中在某个
pool_name:说明该资源池的并发或内存配置需要调整,其他池可能正常。 resource_type:被拒绝的资源类型(如Memory(KB)、FileHandles),帮助区分是内存不足还是文件句柄不足。
3.2 第二步:查看资源队列¶
当资源池的 MAXCONCURRENCY 被打满时,新查询进入队列等待。队列越深,说明资源越紧张。
SELECT node_name,
pool_name,
transaction_id,
statement_id,
position_in_queue,
queue_entry_timestamp
FROM v_monitor.resource_queues
WHERE pool_name NOT IN ('sysquery', 'sysdata')
ORDER BY queue_entry_timestamp;
如何解读:
position_in_queue> 5:严重排队,说明资源池并发限制太低或查询执行太慢。- 同一
pool_name下有大量排队记录:该池的MAXCONCURRENCY或MAXMEMORYSIZE需要调整。 queue_entry_timestamp很久远:查询在队列中等待了很长时间,可能永远拿不到资源。考虑设置QUEUETIMEOUT让超时查询自动取消。
3.3 第三步:查看资源池内存状态¶
resource_pool_status 是监控内存最核心的表。它显示每个资源池在每个节点上的实时内存和并发状态。
SELECT pool_name,
memory_inuse_kb,
memory_size_actual_kb,
general_memory_borrowed_kb,
max_memory_size_kb,
running_query_count,
max_concurrency,
query_budget_kb
FROM v_monitor.resource_pool_status
WHERE node_name = (SELECT local_node_name())
AND pool_name NOT IN ('sysquery', 'sysdata')
ORDER BY running_query_count DESC;
如何解读:
memory_inuse_kb接近max_memory_size_kb:该池内存即将打满。新查询要么排队,要么被拒绝。general_memory_borrowed_kb > 0:该池从 general 池借了内存。少量借入正常,但如果借入量持续 > 池自身的max_memory_size_kb的 20%,说明该池配置的内存不够用。query_budget_kb只有几百 MB:每查询平均只能拿到几百 MB 内存,几乎必然发生 Spill。公式:query_budget = MAXMEMORYSIZE / PLANNEDCONCURRENCY。memory_size_actual_kb远小于max_memory_size_kb:该池配置了较大的内存上限但实际未使用。可能是被其他池借走,或者该池的查询确实不消耗内存。
3.4 第四步:通过 execution_engine_profiles 定位内存大户¶
这是定位单条查询内部内存消耗的最精确方法。使用 PROFILE 关键字后,查询每个算子的内存指标。
-- 找出内存消耗最大的算子和路径
SELECT operator_name,
path_id,
SUM(DECODE(counter_name, 'memory reserved (bytes)', counter_value, NULL)) AS mem_reserved_bytes,
SUM(DECODE(counter_name, 'execution time (us)', counter_value, NULL)) AS exec_time_us,
SUM(DECODE(counter_name, 'cumulative size of raw temp data (bytes)', counter_value, NULL)) AS spill_bytes
FROM v_monitor.execution_engine_profiles
WHERE transaction_id = :t_id
AND statement_id = :s_id
AND counter_name IN ('memory reserved (bytes)', 'execution time (us)', 'cumulative size of raw temp data (bytes)')
GROUP BY operator_name, path_id
ORDER BY mem_reserved_bytes DESC
LIMIT 20;
关键计数器解读:
memory reserved (bytes):优化器在查询规划时为该算子预留的内存。这个值由query_budget和统计信息共同决定。如果reserved很大但查询依然 Spill,说明统计信息严重低估了数据量。peak memory requested (bytes):算子在执行中实际申请的内存峰值。如果peak远大于memory reserved,说明优化器低估了内存需求——通常是统计信息不准确导致的。注意此计数器在v_monitor.execution_engine_profiles中也可能表现为current memory requested (bytes)。cumulative size of raw temp data (bytes):如果该值 > 0,说明算子发生了 Spill,数据溢出到了磁盘。这是内存不足的最直接证据。rows produced:算子实际处理的数据行数。与memory reserved对比,如果行数很大但预留内存很小,优化器明显低估了数据规模。
3.5 第五步:检查 Spill 事件¶
Spill 是内存不足的直接后果。当 GroupByHash 或 JoinHash 算子无法在内存中完成计算时,数据被写入磁盘临时文件,然后通过外部排序算法处理。
SELECT event_type,
operator_name,
path_id,
event_description,
suggested_action
FROM v_monitor.query_events
WHERE transaction_id = :t_id
AND statement_id = :s_id
AND event_type IN ('GROUP_BY_SPILLED', 'JOIN_SPILLED');
Spill 的连锁反应:
- 算子需要的内存不够 → 溢出到磁盘
- 磁盘 I/O 增加 → 算子执行时间变长
- 算子变慢 → 下游算子等待更久(
input queue wait增加) - 查询总体执行时间增加 → 其他查询排队更久
- 排队查询堆积 → 并发内存占用增加 → 更频繁的
RESOURCE_REJECTED
3.6 第六步:识别内存泄漏查询¶
「内存泄漏查询」指执行过程中不断申请新内存却从不释放的异常查询。特征是在执行时间增长的同时,memory_inuse_kb 持续线性增长。
-- 通过 resource_acquisitions 找出频繁申请额外内存的查询
SELECT transaction_id,
statement_id,
pool_name,
COUNT(*) AS acquisition_count,
SUM(memory_inuse_kb) AS total_memory_kb,
MAX(duration_ms) AS max_duration_ms
FROM v_monitor.resource_acquisitions
WHERE request_type = 'AcquireAdditional'
AND acquisition_timestamp > sysdate - INTERVAL '1 hour'
GROUP BY transaction_id, statement_id, pool_name
HAVING COUNT(*) > 5 -- 申请了超过 5 次额外内存
ORDER BY total_memory_kb DESC
LIMIT 10;
如何解读:
acquisition_count > 10:该查询频繁申请额外内存,SQL 复杂度可能超出优化器预估,或者存在统计信息严重缺失。total_memory_kb巨大(几十 GB):典型的「贪吃查询」,应检查该 SQL 的 WHERE 条件和 JOIN 逻辑。- 结合
dc_query_executions的ExecutePlan阶段耗时:如果ExecutePlan阶段耗时线性增长,说明查询在执行中不断等待资源,而非纯粹的 CPU 计算。
4. 使用资源池控制内存¶
4.1 资源池内存参数详解¶
| 参数 | 作用 | 默认值 | 内存影响 |
|---|---|---|---|
MEMORYSIZE |
给该池预留的独占内存,其他池不能借用 | 0%(不预留) |
谨慎使用——预留后即使空闲也不会释放给其他池 |
MAXMEMORYSIZE |
该池中所有查询总共能用的内存上限 | 95%(general 池) |
最常调整的参数。太小→频繁拒绝,太大→一个池挤占全局 |
PLANNEDCONCURRENCY |
预期该池同时运行的查询数,决定 query_budget |
AUTO(=CPU 核心数) |
与实际并发越接近,优化器估算越准 |
MAXCONCURRENCY |
该池最多同时运行多少条查询 | 无硬限制(general 池) | 硬刹车——超过后查询排队,但不会消耗内存 |
QUERY_BUDGET_KB |
每条查询的初始内存预算(只读计算列) | = MAXMEMORYSIZE / PLANNEDCONCURRENCY |
太小→频繁 Spill,太大→并发降低 |
EXECUTIONPARALLELISM |
单查询最大线程数 | AUTO(=CPU 核心数) |
线程越多,内存中同时活跃的算子越多,总内存占用越高 |
完整参数列表详见 Vertica 资源池最佳实践 中的参数表。
4.2 query_budget:内存调优的核心杠杆¶
query_budget 是 Vertica 内存管理的核心概念。它决定了优化器为每条查询分配多少「初始内存」,直接影响查询是否会发生 Spill。
计算公式:
例如在 256GB 节点上:
- general 池
MAXMEMORYSIZE = 95%,PLANNEDCONCURRENCY = 48(AUTO = 48 核) query_budget = 256 × 95% / 48 ≈ 5.1 GB
但这只是初始预算。如果查询实际需要更多内存,可以发起 AcquireAdditional 请求。优化器根据表的统计信息估算内存需求:
- 统计信息准确 → 优化器估算准确 → 不需要或少需要额外申请
- 统计信息缺失或过时 → 优化器低估 → 频繁
AcquireAdditional→ 可能触发RESOURCE_REJECTED
4.3 策略一:增大 query_budget,减少 Spill¶
当查询普遍出现 GROUP_BY_SPILLED 或 JOIN_SPILLED 时,说明 query_budget 太小。
调整方法(二选一):
- 降低
PLANNEDCONCURRENCY:减少预期并发数,每条查询分到更多内存。适合「查询少但每条查询复杂」的场景。 - 增大
MAXMEMORYSIZE:扩大池的总内存。适合「节点内存有余量但池配置偏保守」的场景。
-- 方案 A:降低 PLANNEDCONCURRENCY(节点 256GB,目标预算 8GB)
-- query_budget = 256 × 95% / 30 ≈ 8.1 GB
ALTER RESOURCE POOL general PLANNEDCONCURRENCY 30;
-- 方案 B:创建专用报表池(大预算、低并发)
CREATE RESOURCE POOL report_pool
MAXMEMORYSIZE '40%'
PLANNEDCONCURRENCY 6
MAXCONCURRENCY 4;
-- query_budget = 256 × 40% / 6 ≈ 17 GB
取值建议:
- 短查询(< 5 秒):1-3 GB 预算通常够用
- 中等分析查询(10-60 秒):4-8 GB
- 大报表/ETL 查询(> 2 分钟):8-20 GB
- 注意:
query_budget只是初始预留,不是硬上限。查询可以通过AcquireAdditional申请更多。
4.4 策略二:设置 MAXMEMORYSIZE 防止单池挤占全局¶
默认的 general 池拥有 MAXMEMORYSIZE = 95%,意味着它可以用掉几乎所有节点内存。如果用户自定义池也没有设置限制,多个池可能超分内存。
-- 检查各池的最大内存分配
SELECT pool_name, max_memory_size_kb,
pool_name, max_memory_size_kb / (1024*1024) AS max_gb
FROM v_monitor.resource_pool_status
WHERE node_name ILIKE '%0001%'
AND pool_name NOT IN ('sysquery', 'sysdata');
分配原则:
- 所有用户池的
MAXMEMORYSIZE之和 ≤ 80%(留 20% 给系统开销和峰值缓冲) - General 池降到 50-60%,留出空间给专用池
- 独立池(
MEMORYSIZE = MAXMEMORYSIZE)不参与共享内存池,需严格控制占比
4.5 策略三:TM 资源池内存调优¶
Tuple Mover 有自己的内置资源池 tm,默认配置非常保守。当 TM 的 mergeout 操作处理大表时,内存不足会导致 mergeout 极慢,间接让 ROS container 数量失控,最终影响查询性能。
-- 查看 TM 池当前配置
SELECT pool_name, max_memory_size_kb, planned_concurrency,
max_concurrency, query_budget_kb
FROM v_monitor.resource_pool_status
WHERE pool_name = 'tm'
AND node_name ILIKE '%0001%';
调优建议(来自 ROS Pushback 故障排查):
- 将 TM 池的
MEMORYSIZE增加到 2GB × MAXCONCURRENCY PLANNEDCONCURRENCY设为与MAXCONCURRENCY相等
5. 深入案例¶
5.1 虚构案例一:query_budget 过小导致普遍 Spill¶
📝 虚构案例
场景:3 节点集群(每节点 256GB / 48 核),general 池默认配置。用户反馈 BI 报表查询近期越来越慢,但数据量并未明显增长。
诊断过程:
-
Step 1: 查看 query_events
- → 几乎所有超过 30 秒的查询都出现了 GROUP_BY_SPILLED
-
Step 2: 查看 query_budget
- → general 池 query_budget ≈ 5 GB(默认值)
-
Step 3: 查看 execution_engine_profiles 内存计数器
- → GroupByHash 算子:memory_allocated = 8.2 GB,但 memory_reserved = 5 GB
- → 每次查询都有 3+ GB 的 AcquireAdditional 请求
- → cumulative size of raw temp data = 12 GB(大量 Spill)
-
Step 4: 查看 resource_acquisitions
- → 频繁的 AcquireAdditional 请求,成功但耗时
根因分析:默认 query_budget = 5GB 不足以容纳 GroupByHash 的哈希表,导致每次查询都需要额外申请内存 + Spill。不是单条查询的问题,而是所有查询都在「吃不饱」的状态。
修复:降低 PLANNEDCONCURRENCY 从 48 到 24 → query_budget 从 5GB 翻倍到 10GB。同样查询的 Spill 事件从 80% 降到 5%,平均执行时间从 3 分钟降到 45 秒。
5.2 虚构案例二:独占池挤占全局内存¶
📝 虚构案例
场景:某集群设置了 management_pool 作为独立池(MEMORYSIZE = MAXMEMORYSIZE = 32GB),供高管报表使用。平时没有问题,但某天高管跑了 3 条并发大查询后,general 池的所有查询都开始报 RESOURCE_REJECTED。
诊断过程:
-
Step 1: 查看 resource_rejections
- → general 池大量 Memory(KB) Exceeded
-
Step 2: 查看 resource_pool_status
- → management_pool: memory_inuse_kb = 32GB (100%)
- → general_pool: general_memory_borrowed_kb = 0 (借不到内存!)
-
Step 3: 确认原因
- → management_pool 是独立池,32GB 被独占
- → 即使这 3 条查询只用了一半,剩余 16GB 也不会释放给 general 池
根因分析:独立池(MEMORYSIZE = MAXMEMORYSIZE)的内存是独占的。即使空闲,其他池也无法使用。32GB 看起来合理,但在 256GB 节点上占用了 12.5%,且不能再分配给其他池。
修复:
- 将
management_pool从独立池改为共享池(移除MEMORYSIZE,保留MAXMEMORYSIZE) - 设置
MAXCONCURRENCY = 2防止高管同时跑太多查询 - 或保留独立池但降低
MEMORYSIZE到 16GB
5.3 真实案例一:query_budget 过小导致大量排队¶
📋 真实案例
背景:某运营商经分系统,138 节点集群,Vertica v11.1.1。每天早上 9-12 点业务高峰期,app_pool 和 hz_pool 出现大量排队,数据库性能恶化超过 7 倍(平均响应时间从 13 秒升到 94 秒)。
诊断过程:
- 检查 query_budget 与实际内存消耗的差距
- 发现 app_pool 的 query_budget = 1GB
- 但 40% 的查询任务实际消耗 > 1GB
关键发现:
| 资源池 | 任务消耗 > 1GB 比例 | query_budget | 后果 |
|---|---|---|---|
| app_pool | 40% | 1 GB | 40% 任务需要在执行中重新申请内存 |
| hz_pool | 10% | - | 相对较轻 |
在业务高峰期,执行过程中重新申请内存(AcquireAdditional)可能因内存资源紧张长时间申请不到,导致执行时间延长,连锁引发更多排队。同时发现某大查询因统计信息过期,错误选择了驱动表(将 14.5 亿行大表作为内表),消耗了 28GB 内存。
修复措施:
- 收集过期表的统计信息:
SELECT ANALYZE_STATISTICS('user_dtal'); - 增大
app_pool的query_budget(降低PLANNEDCONCURRENCY)
效果:统计信息更新后,大查询执行计划恢复正常;query_budget 调大后,AcquireAdditional 频率大幅下降,高峰期排队缓解。
关键教训:query_budget 小 + 统计信息过期 = 双重放大。优化器低估内存需求(因为统计信息不准),而实际分配的内存又太少(因为 query_budget 小),两者叠加使得查询几乎必然 Spill 或排队。
6. 完整诊断流程实战¶
📝 虚构场景 · 完整演练
场景¶
- 5 节点集群(每节点 512GB / 64 核),Vertica v24.x
- 用户反馈:每天下午 3 点后新提交的查询频繁报错
RESOURCE_REJECTED - 检查发现:内存使用率 98%,
general_memory_borrowed_kb异常高
诊断过程¶
Step 1:确认内存压力来源
SELECT pool_name, memory_inuse_kb, max_memory_size_kb,
general_memory_borrowed_kb, running_query_count
FROM v_monitor.resource_pool_status
WHERE node_name ILIKE '%0001%'
AND pool_name NOT IN ('sysquery', 'sysdata');
→ general 池 memory_inuse_kb 接近上限。3 个用户自定义池(etl_pool、report_pool、adhoc_pool)全部从 general 借了大量内存。
Step 2:找出谁在借内存
SELECT pool_name, memory_inuse_kb, general_memory_borrowed_kb
FROM v_monitor.resource_pool_status
WHERE node_name ILIKE '%0001%'
AND general_memory_borrowed_kb > 0
ORDER BY general_memory_borrowed_kb DESC;
→ etl_pool 借了 85GB!远超其自身 MAXMEMORYSIZE = 40GB。
Step 3:定位 etl_pool 中的大查询
SELECT transaction_id, statement_id, request_duration_ms, memory_acquired_mb
FROM v_monitor.query_requests
WHERE is_executing = 't'
AND request LIKE '%etl%'
ORDER BY memory_acquired_mb DESC
LIMIT 5;
→ 发现一条 ETL 查询已运行 45 分钟,内存占用 72GB。这条查询在做 8 张大表的全量 JOIN。
Step 4:PROFILE 分析大查询
PROFILE <etl_query>;
-- 结果:JOIN_SPILLED × 12,GROUP_BY_SPILLED × 4
-- cumulative size of raw temp data = 180GB
→ 不是一条查询的问题,是这条查询触发了连锁反应:它大量 spill 到磁盘 → 执行极慢 → 长时间占用 etl_pool 内存 → etl_pool 借 general → general 池枯竭 → 其他所有查询被拒绝。
修复措施¶
立即措施:终止失控 ETL 查询,释放内存。
短期优化:
- 为 etl_pool 设置
RUNTIMECAP = '00:30:00',防止单条查询无限执行 - 设置
MAXCONCURRENCY = 2,限制 etl_pool 同时运行的查询数 - 将 etl_pool 的
MAXMEMORYSIZE从 40% 提升到 60%,减少向 general 池借内存的频率
- 为大 JOIN 表创建预连接投影(pre-join projection),避免运行时全量 JOIN
效果:修复后 RESOURCE_REJECTED 降为零。Etl_pool 不再向 general 借内存。下午高峰期恢复正常。
7. 快速诊断 SQL 工具箱¶
| 诊断目标 | SQL |
|---|---|
| 系统内存历史 | SELECT * FROM system_resource_usage ORDER BY end_time DESC; |
| 资源拒绝事件 | SELECT node_name, pool_name, reason, rejection_count, last_rejected_timestamp, last_rejected_value FROM resource_rejections WHERE last_rejected_timestamp > sysdate - INTERVAL '1 hour' ORDER BY last_rejected_timestamp DESC; |
| 资源队列深度 | SELECT pool_name, position_in_queue, queue_entry_timestamp FROM resource_queues WHERE pool_name NOT IN ('sysquery', 'sysdata') ORDER BY queue_entry_timestamp; |
| 资源池实时内存 | SELECT pool_name, memory_inuse_kb, max_memory_size_kb, general_memory_borrowed_kb, running_query_count, query_budget_kb FROM resource_pool_status WHERE pool_name NOT IN ('sysquery', 'sysdata'); |
| 算子内存消耗 | SELECT operator_name, path_id, SUM(DECODE(counter_name, 'memory allocated (bytes)', counter_value, NULL)) FROM execution_engine_profiles WHERE transaction_id=:t_id AND statement_id=:s_id GROUP BY 1,2 ORDER BY 3 DESC; |
| Spill 事件 | SELECT event_type, operator_name, suggested_action FROM query_events WHERE transaction_id=:t_id AND statement_id=:s_id AND event_type IN ('GROUP_BY_SPILLED', 'JOIN_SPILLED'); |
| 查询内存大户 | SELECT transaction_id, statement_id, memory_acquired_mb, request_duration_ms FROM query_requests WHERE is_executing='t' ORDER BY memory_acquired_mb DESC LIMIT 10; |
| TM 池状态 | SELECT pool_name, max_memory_size_kb, planned_concurrency, query_budget_kb FROM resource_pool_status WHERE pool_name = 'tm' AND node_name ILIKE '%0001%'; |
| 额外内存申请 | SELECT transaction_id, statement_id, COUNT(*) AS cnt, SUM(memory_inuse_kb) FROM resource_acquisitions WHERE request_type = 'AcquireAdditional' GROUP BY 1,2 HAVING COUNT(*) > 5 ORDER BY 3 DESC; |
8. 最佳实践清单¶
- 定期监控 RESOURCE_REJECTED:这是内存压力最早期的信号。设置告警在拒绝次数 > 10/小时时触发。
- query_budget 宁大勿小:原则是让 80% 以上的查询用初始预算就能完成,不需要
AcquireAdditional。额外申请虽然有兜底机制,但每次申请都增加延迟和不确定性。 - 避免使用独立池(MEMORYSIZE ≠ 0%):独立池的独占内存在空闲时也不会释放给其他池,整体内存利用率低。除非确有 SLA 要求(如高管报表必须秒级响应),否则用
MAXMEMORYSIZE软限制即可。 - 设置 RUNTIMECAP 防止内存泄漏:失控查询不仅浪费 CPU,还会持续占用内存不释放。设置合理的时间上限(报表 30 分钟、ETL 2 小时),到期自动终止。
- 保持统计信息准确:统计信息过期 → 优化器低估内存需求 → 初始
query_budget不够 → 额外申请 → 可能被拒绝。每次大批量加载数据后运行ANALYZE_STATISTICS。 - TM 池也需要关注:默认 TM 池的
query_budget通常只有几十 MB,大表 mergeout 会极其缓慢。适当增大 TM 池的内存配置。 - 用 Spill 作为内存充足的度量指标:如果 90% 以上的查询没有 Spill 事件,说明内存配置基本合理;如果 Spill 普遍存在,优先调大
query_budget。 - general 池不要给 95%:降低到 50-60%,将余量分配给专用池。所有查询挤在一个池里,一条大查询就能拖垮全局。
- 大表 JOIN 考虑预连接投影:运行时 JOIN 需要将内表全部加载到内存构建哈希表,如果内表有几亿行,内存消耗巨大。预连接投影将 JOIN 提前在数据写入时完成,查询时直接读取结果。
- 善用 close_session 应急:当发现某条查询正在吞噬内存并拖慢整个集群时,果断用
SELECT close_session('session_id')终止它。这比让集群慢慢恢复有效得多。
扩展阅读¶
- Vertica 错误日志解读与常见错误处理 — 错误日志分析的系统方法论与工具箱
- Vertica 查询 Spill 到磁盘的原因与优化 — Spill 是内存不足的直接后果
- Vertica 资源池最佳实践 — 资源池参数详解与配置策略
- Vertica CPU 持续高负载诊断与优化 — CPU 与内存问题往往相互关联
- Vertica 监控最佳实践 — 建立内存、CPU、查询的监控告警体系
- Vertica 性能调优 - 2 使用系统表排查查询故障 — 系统表排查方法论
- Vertica 统计信息管理与查询性能 — 统计信息缺失导致内存预算估算偏差