Vertica 资源拒绝排查与资源池调优¶
适用场景:查询提交后直接被拒绝并报错
RESOURCE_REJECTED,或v_monitor.resource_rejections表中出现拒绝记录,资源池内存/并发达到上限导致新查询无法获取资源。关联:Vertica 资源池配置的最佳实践 | Vertica 内存压力诊断与调优 | Vertica 查询 Spill 到磁盘的原因与优化 | Vertica CPU 持续高负载诊断与优化 | Vertica 监控最佳实践
理解全文脉络¶
本文按照「理解拒绝机制 → 系统级监控发现拒绝 → 逐步定位拒绝根因 → 解决与优化 → 案例验证」的路径组织。如果你刚收到 RESOURCE_REJECTED 告警,可以从第 2 节开始快速确认拒绝规模和类型,再跳转到第 3 节定位具体原因;如果你想从架构层面预防拒绝,建议从第 1 节读起,理解 Vertica 资源管理的底层逻辑后再看第 4 节的参数调优和第 8 节的最佳实践清单。
1. 原理理解¶
1.1 什么是 RESOURCE_REJECTED¶
RESOURCE_REJECTED 是 Vertica Resource Manager 在查询请求资源时,因资源不足而直接拒绝请求的机制。它不是报错或 Bug——它是 Vertica 资源管理的保护性设计。当资源池无法满足查询的资源需求时,Resource Manager 有两个选择:
- 排队(Queue):查询进入等待队列,等资源释放后执行。前提是资源池启用了排队且查询未超时。
- 拒绝(Reject):直接拒绝查询请求,返回
RESOURCE_REJECTED错误。发生在排队不可用或资源缺口大到无法排队时。
这个机制的作用是防止单个查询或一组查询耗尽系统资源导致节点 OOM 或集群宕机。没有这个机制,Vertica 进程可能被 Linux OOM Killer 杀掉——那将是更严重的故障。
1.2 与传统数据库的对比¶
| 特性 | 传统 OLTP(如 PostgreSQL) | Vertica |
|---|---|---|
| 内存管理 | 全局 shared_buffers + 按连接分配 work_mem | 资源池模型,按查询分配 query_budget |
| 资源限制 | 通常只限制连接数 | 限制内存、并发、线程数、执行时间 |
| 资源不足时 | 查询变慢、使用 swap、最终 OOM | 排队或主动拒绝(RESOURCE_REJECTED) |
| 资源隔离 | 无(所有查询共享) | 资源池之间可隔离(MEMORYSIZE 独占、MAXMEMORYSIZE 上限) |
关键差异:传统数据库「尽力而为」,撑不住才崩;Vertica「主动拒绝」,宁可拒绝也不让系统崩溃。理解这个设计哲学是排查 RESOURCE_REJECTED 的前提——拒绝不是故障,是资源不足的信号。
1.3 RESOURCE_REJECTED 的触发机制¶
每条查询在提交到资源池时,Resource Manager 会做以下检查:
- 并发检查:当前资源池的
running_query_count是否已达到MAXCONCURRENCY? - 内存检查:查询申请的
query_budget+ 当前池已用内存是否超过MAXMEMORYSIZE?如果池有MEMORYSIZE(独占内存),则先检查独占内存是否足够。 - 磁盘检查:数据目录(
/data)的剩余空间是否足够创建临时文件? - 单查询内存检查:查询所需内存是否超过
MAXQUERYMEMORYSIZE?
如果任何一项不满足,查询就会被拒绝(或排队,取决于配置)。被拒绝的查询会写入 v_monitor.resource_rejection_details,汇总信息写入 v_monitor.resource_rejections。
1.4 拒绝原因分类总结¶
| 拒绝类型 | RESOURCE_TYPE | REASON 示例 | 根本原因 | 影响范围 |
|---|---|---|---|---|
| 内存拒绝 | Memory |
Insufficient resources to allocate memory |
MAXMEMORYSIZE 打满 / query_budget 过大 | 该资源池下所有查询 |
| 并发拒绝 | Concurrency |
Too many concurrent queries |
MAXCONCURRENCY 达上限 | 该资源池下新查询 |
| 磁盘拒绝 | Disk |
Insufficient disk space |
数据目录空间不足 | 该节点所有查询 |
| 单查询内存拒绝 | Memory |
Request exceeds MAXQUERYMEMORYSIZE |
单条查询内存需求超过 MAXQUERYMEMORYSIZE | 该条查询 |
2. 系统级监控:先看清全局¶
收到 RESOURCE_REJECTED 告警后,第一件事不是调整参数,而是量化问题的规模和范围——是偶发还是持续?是单节点还是全集群?是内存还是并发?
2.1 查看资源拒绝的总体情况¶
v_monitor.resource_rejections 是按节点、资源池、资源类型、原因的聚合视图。它告诉你哪里、什么类型、多少次拒绝。
SELECT node_name,
pool_name,
resource_type,
reason,
rejection_count,
first_rejected_timestamp,
last_rejected_timestamp,
last_rejected_value
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day'
ORDER BY rejection_count DESC;
如何解读结果:
rejection_count很大(> 100)且集中在某个资源池:该资源池配置不足,需要重点关注。resource_type = 'Memory':内存是瓶颈。看last_rejected_value,它是最近一次拒绝时的内存缺口(KB)。如果这个值持续增大,说明内存压力在恶化。resource_type = 'Concurrency':并发数不够。检查该池的MAXCONCURRENCY和PLANNEDCONCURRENCY。- 多个节点都出现拒绝:不是单节点问题,是集群级别的资源池配置偏小。
- 只有单个节点出现拒绝:可能是数据倾斜导致该节点负载更高,或该节点有其他进程消耗内存。
2.2 查看每次拒绝的详细信息¶
v_monitor.resource_rejection_details 保留了每次拒绝的逐条记录,可以关联到具体的 session、transaction 和 statement,帮助你定位是哪个用户、哪条 SQL 被拒绝了。
SELECT rejected_timestamp,
node_name,
pool_name,
resource_type,
reason,
rejected_value,
user_name,
session_id,
transaction_id,
statement_id,
request_id
FROM v_monitor.resource_rejection_details
WHERE rejected_timestamp > sysdate - INTERVAL '1 hour'
ORDER BY rejected_timestamp DESC
LIMIT 50;
如何解读结果:
user_name:哪个用户的查询被拒绝。如果集中在某个用户,可能是该用户的查询模式有问题(如大量高内存 SQL)。rejected_value:被拒绝时请求的资源量。Memory 类型时单位为 KB,可对比资源池的query_budget_kb来判断是否需要调整。session_id+statement_id:关联到v_monitor.query_requests可以获取被拒绝的 SQL 原文,用于后续 SQL 优化。
2.3 查看磁盘相关的拒绝¶
磁盘拒绝记录在独立的 v_monitor.disk_resource_rejections 表中,因为它的监控维度(存储路径、磁盘使用百分比)和内存/并发拒绝不同。
SELECT node_name,
rejected_reason,
rejected_count,
resource_type,
first_rejected_timestamp,
last_rejected_timestamp,
last_rejected_value
FROM v_monitor.disk_resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '7 day'
ORDER BY rejected_count DESC;
如何解读结果:
rejected_reason通常包含具体的存储路径和磁盘使用百分比,如Disk /data is 95% full。last_rejected_value通常表示磁盘剩余字节数。如果值很小(< 10 GB),需要立即清理或扩容。- 磁盘拒绝通常伴随
vertica.log中的DiskSpaceCheck告警。
2.4 查看资源池当前状态¶
v_monitor.resource_pool_status 是排查时的核心表——它告诉你每个资源池正在用多少内存、跑了多少查询、还有多少预算。
SELECT node_name,
pool_name,
memory_inuse_kb / 1024 / 1024 AS memory_inuse_gb,
memory_size_actual_kb / 1024 / 1024 AS memory_size_actual_gb,
max_memory_size_kb / 1024 / 1024 AS max_memory_size_gb,
query_budget_kb / 1024 AS query_budget_mb,
running_query_count,
planned_concurrency,
max_concurrency,
ROUND(100.0 * memory_inuse_kb / NULLIF(max_memory_size_kb, 0), 1) AS memory_pct,
ROUND(100.0 * running_query_count / NULLIF(max_concurrency, 0), 1) AS concurrency_pct
FROM v_monitor.resource_pool_status
WHERE pool_name NOT IN ('sysquery', 'sysdata', 'tm', 'refresh', 'recovery')
ORDER BY memory_inuse_kb DESC;
如何解读结果:
memory_pct> 90%:该资源池内存已接近上限,新查询很可能被拒绝。这是最需要关注的指标。concurrency_pct= 100% 且running_query_count = max_concurrency:并发打满,新查询即使内存足够也要排队或被拒绝。memory_inuse_gb远小于memory_size_actual_gb:说明资源池的实际分配内存大于用量,但MAXMEMORYSIZE才是硬限制——如果memory_inuse_gb接近max_memory_size_gb,就会拒绝。query_budget_mb很小(< 512 MB)但running_query_count很大:每条查询预算太小,导致频繁申请额外内存(AcquireAdditional),增加资源管理开销。
2.5 查看系统级资源使用历史¶
v_monitor.system_resource_usage 以固定间隔采样每个节点的 CPU 和内存使用百分比,可以帮助你判断拒绝是突发还是持续性问题。
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持续 > 85%:系统级内存接近饱和。如果同时出现 RESOURCE_REJECTED,根本原因是集群整体内存不足。average_cpu_usage_percent高但内存不高:可能是并发数太多(线程竞争 CPU)而非内存不足导致的拒绝。- 指标突然飙升随后恢复正常:可能是某个批处理/ETL 任务在集中执行。这类场景需要考虑错峰调度或扩大资源池。
2.6 Linux 层面快速检查¶
有些 RESOURCE_REJECTED 的根本原因不在 Vertica 内部,而在操作系统层面。以下命令帮助你快速排除系统级问题。
查看系统内存状态:
如何解读:关注 available 列。如果 available < 物理内存的 10%,操作系统层面内存不足,Vertica 的 MAXMEMORYSIZE 即使设置为 90% 也可能不够——操作系统自身需要内存做 page cache、网络缓冲等。
查看 OOM Killer 历史记录:
如何解读:如果有 Vertica 进程被杀掉(vertica 或 vertica-udx),说明问题已升级到 OOM 级别——这比 RESOURCE_REJECTED 严重得多。OOM 意味着 Resource Manager 未能阻止内存超额使用。参见 20251009某运营商标签库宕机排查报告 了解 11.0.2 版本中预取缓冲区绕过 maxmemorysize 的真实案例。
查看磁盘使用:
如何解读:Use% > 85% 时应警惕。如果 > 95% 且伴随 disk_resource_rejections 中的记录,需要立即清理。Vertica 的 mergeout、查询 spill、临时表都会消耗磁盘空间。
3. 逐步定位根因¶
第 2 节帮你确认了有拒绝发生以及拒绝的规模。本节按拒绝原因分类,逐步缩小范围找到根因。
3.1 步骤一:判断拒绝类型(内存 vs 并发 vs 磁盘)¶
做什么:首先确定当前的主要拒绝类型,避免在错误的方向上浪费时间。
SELECT resource_type,
reason,
SUM(rejection_count) AS total_rejections,
MAX(last_rejected_timestamp) AS latest_rejection
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day'
GROUP BY resource_type, reason
ORDER BY total_rejections DESC;
如何解读:
resource_type = 'Memory'占绝大多数 → 转到 3.2 节(内存拒绝排查)。resource_type = 'Concurrency'占绝大多数 → 转到 3.3 节(并发拒绝排查)。- 两种都有且数量相近 → 通常根因是内存不足导致查询堆积,间接造成并发打满。先解决内存问题。
如果不是则进入下一步:如果各类型拒绝都很均匀且数量少(< 10/天),可能是偶发性问题,收集更多监控数据后再判断。
3.2 步骤二:内存拒绝排查¶
内存拒绝是最常见的 RESOURCE_REJECTED 类型。排查的核心思路是:先确认 query_budget 是否合理,再找内存消耗最大的查询,最后检查是否有额外的内存申请压力。
3.2.1 检查 query_budget 是否合理
query_budget 是每条查询启动时分配的内存预算。如果预算太小,查询执行中需要频繁申请 AcquireAdditional,增加排队和拒绝概率。如果预算太大,则并发能力下降。
SELECT pool_name,
query_budget_kb / 1024 AS query_budget_mb,
memory_inuse_kb / 1024 / 1024 AS memory_inuse_gb,
max_memory_size_kb / 1024 / 1024 AS max_memory_gb,
running_query_count,
planned_concurrency,
ROUND(100.0 * memory_inuse_kb / NULLIF(max_memory_size_kb, 0), 1) AS memory_pct
FROM v_monitor.resource_pool_status
WHERE pool_name NOT IN ('sysquery', 'sysdata', 'tm', 'refresh', 'recovery')
AND max_memory_size_kb > 0
ORDER BY memory_pct DESC;
如何解读:
memory_pct> 90% 且query_budget_mb较大:每条查询的预算充足,但池总内存已达上限。解决方案:增大MAXMEMORYSIZE(场景 B),或将大查询拆分到独立资源池。memory_pct> 90% 且query_budget_mb很小:池内存总量不够且单条预算偏小,需要增大MAXMEMORYSIZE;如果实际并发数远低于PLANNEDCONCURRENCY,也可适当降低PLANNEDCONCURRENCY增大预算(场景 A)。running_query_count远小于planned_concurrency:实际并发远低于预期,query_budget 计算公式失效。query_budget = [MAX]MEMORYSIZE / PLANNEDCONCURRENCY,如果实际并发低,每条查询分到的内存更多但也造成了浪费。
3.2.2 检查是否有单条查询占用过大内存
SELECT qr.node_name,
qr.session_id,
qr.statement_id,
qr.request_id,
qr.user_name,
qr.memory_acquired_mb,
qr.request_duration_ms / 1000 AS duration_sec,
LEFT(qr.request, 200) AS request_snippet,
qr.start_timestamp
FROM v_monitor.query_requests qr
WHERE qr.is_executing = true
AND qr.memory_acquired_mb > 2 * 1024
ORDER BY qr.memory_acquired_mb DESC
LIMIT 20;
如何解读:
memory_acquired_mb> 20 GB 的单条查询:这是内存大户。如果这样的查询正在执行,它会占用大量资源池内存,导致其他查询被拒绝。- 同一
user_name有多条高内存查询:该用户/应用可能需要通过MAXQUERYMEMORYSIZE限制单条查询内存,或者做 SQL 优化。 duration_sec很大(> 600 秒):长时间运行的查询持续占用内存,加剧资源竞争。
3.2.3 检查是否有大量 AcquireAdditional 请求
查询在执行中如果初始内存不够,会向 Resource Manager 申请额外内存(AcquireAdditional)。频繁的额外申请不仅增加管理开销,还可能在池内存紧张时被拒绝。
SELECT node_name,
pool_name,
request_type,
COUNT(*) AS acquisition_count,
SUM(memory_inuse_kb) / 1024 / 1024 AS total_memory_gb,
AVG(duration_ms) AS avg_duration_ms,
MAX(duration_ms) AS max_duration_ms
FROM v_monitor.resource_acquisitions
WHERE is_executing = true
AND request_type = 'AcquireAdditional'
GROUP BY node_name, pool_name, request_type
ORDER BY total_memory_gb DESC;
如何解读:
acquisition_count很大(> 50):说明大量查询在初始内存不足后请求额外内存。根本原因是query_budget设置偏小。avg_duration_ms> 5000(5 秒):申请额外内存的等待时间过长,查询实际在执行中等待资源。- 某个资源池的
total_memory_gb接近MAXMEMORYSIZE:该池内存趋近饱和,需要扩容或分流。
3.2.4 检查资源池内存是否被 TM(Tuple Mover)操作挤占
TM 操作(mergeout、moveout)在 tm 资源池中执行。默认 PLANNEDCONCURRENCY=4、query_budget 较小,但如果大量 mergeout 同时发生,会消耗系统总内存(虽然 TM 池独立于 general 池,但总物理内存是共享的)。
3.3 步骤三:并发拒绝排查¶
并发拒绝的典型特征是 resource_type = 'Concurrency' 且查询排队集中在某个资源池。
3.3.1 检查排队和并发情况
SELECT rq.node_name,
rq.pool_name,
rq.position_in_queue,
rq.priority,
rq.memory_requested_kb / 1024 AS memory_requested_mb,
rq.queue_entry_timestamp,
rps.running_query_count,
rps.max_concurrency,
rps.planned_concurrency
FROM v_monitor.resource_queues rq
JOIN v_monitor.resource_pool_status rps
ON rq.node_name = rps.node_name AND rq.pool_name = rps.pool_name
ORDER BY rq.position_in_queue ASC;
如何解读:
position_in_queue> 0 的查询很多:说明池中并发已满,新查询在排队。running_query_count = max_concurrency:并发打到硬上限。如果planned_concurrency远小于max_concurrency,可能是瞬时并发远高于预期。- 排队时间长(
queue_entry_timestamp距离当前时间 > 5 分钟):业务感知会非常明显。考虑增大MAXCONCURRENCY或优化查询执行时间。
3.3.2 区分并发拒绝 vs 资源池级联(Cascade)
如果配置了资源池级联(CASCADE TO),查询在超时后会被移送到下一级资源池。移送失败也会产生拒绝。
SELECT move_timestamp,
node_name,
source_pool_name,
target_pool_name,
move_cause,
result_reason,
success,
user_name,
session_id,
statement_id
FROM v_monitor.resource_pool_move
WHERE move_timestamp > sysdate - INTERVAL '1 hour'
AND success = false
ORDER BY move_timestamp DESC
LIMIT 30;
如何解读:
success = false且result_reason包含 "not enough resources":目标资源池也满了。需要增大目标池的MAXMEMORYSIZE或MAXCONCURRENCY。- 移送频繁发生:说明
RUNTIMECAP设置偏小,太多查询超时需要移送。可以考虑增大源池的预算,减少移送。
3.4 步骤四:磁盘拒绝排查¶
磁盘拒绝通常比内存拒绝更紧迫——磁盘满了不仅导致拒绝,还会影响 mergeout 和 ROS 管理。
SELECT node_name,
rejected_reason,
rejected_count,
first_rejected_timestamp,
last_rejected_timestamp
FROM v_monitor.disk_resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day'
ORDER BY rejected_count DESC;
磁盘拒绝的常见根因:
| 原因 | 症状 | 解决思路 |
|---|---|---|
| ROS 文件堆积 | 大量 ROS container 未合并,数据目录使用率高 | 检查 TM 是否正常运行,手动执行 DO_TM_TASK('mergeout') |
| 临时文件未释放 | 查询异常终止后 spill 临时文件残留 | 检查 /scratch_b/VMart/v_vmart_node0001_data/ 目录,重启后自动清理SELECT node_name, storage_path, storage_usage FROM disk_storage WHERE storage_usage IN ('TEMP', 'DATA,TEMP'); |
| 数据量增长超预期 | 存储容量规划不足 | 扩容磁盘、清理历史数据、导出冷数据 |
| Catalog 过大 | /catalog 分区使用率高 |
检查 v_monitor.catalog_storage |
4. 解决方案¶
4.1 立即措施(当天可执行)¶
4.1.1 终止有问题的查询
当资源池被打满导致大面积拒绝时,第一步是识别并终止占用资源最多或运行时间最长的查询:
-- 先查看:找到内存/时间最大的查询
SELECT node_name,
session_id,
statement_id,
user_name,
memory_acquired_mb,
request_duration_ms / 1000 AS duration_sec,
LEFT(request, 100) AS request_snippet
FROM v_monitor.query_requests
WHERE is_executing = true
ORDER BY memory_acquired_mb DESC
LIMIT 10;
-- 终止指定查询(替换 :session_id 和 :statement_id)
SELECT CLOSE_SESSION(:session_id);
注意:CLOSE_SESSION 会终止整个 session,如果只想终止单条语句,使用 INTERRUPT_STATEMENT(:session_id, :statement_id)。
4.1.2 将卡住的查询移到其他资源池
如果查询因资源池满而无法执行,可以使用 MOVE_STATEMENT_TO_RESOURCE_POOL 临时移到有资源的池:
-- 语法:MOVE_STATEMENT_TO_RESOURCE_POOL (session_id , transaction_id, statement_id, target_resource_pool_name)
SELECT MOVE_STATEMENT_TO_RESOURCE_POOL(:session_id, :transaction_id, :stmt_id, 'general');
此方法在 20230221某银行 Vertica 数据库 Session 夯死问题排查报告 中有实际使用案例。
4.2 资源池参数调整(短期优化,当周执行)¶
资源池参数调整是解决 RESOURCE_REJECTED 最直接的手段。以下逐一解释关键参数的含义、默认值和调整建议。
核心参数速览:
| 参数 | 作用 | 默认值 | 调大时的效果 | 调大时的风险 |
|---|---|---|---|---|
MAXMEMORYSIZE |
资源池可用内存硬上限 | general 池默认为 Special: 95%(总内存的 95%) |
更多查询可同时运行 | 可能挤占操作系统内存,触发 OOM |
MEMORYSIZE |
独占内存(不会被其他池借用);设置后 query_budget 改用此值计算而非 MAXMEMORYSIZE | 无(0 = 非独占) | 保证该池一定有内存可用 | 独占内存闲置时其他池无法使用;若设得太小,query_budget 骤降可能导致 AcquireAdditional 风暴 |
PLANNEDCONCURRENCY |
预期并发数,用于计算 query_budget | general 默认 = CPU 核数 | 降低后可增大每条查询的 query_budget | 降低后并发能力下降 |
MAXCONCURRENCY |
最大并发数硬上限 | 无限制 | 更多查询可同时执行 | 过多查询共享有限资源,每条都变慢 |
EXECUTIONPARALLELISM |
单条查询的最大并行线程数 | AUTO(= CPU 核数) | 单条查询执行更快 | 并发场景下 CPU 竞争加剧 |
MAXQUERYMEMORYSIZE |
单条查询的内存上限 | 无限制 | 允许更复杂的查询 | 单条查询可能耗尽池内存 |
QUEUETIMEOUT |
排队超时时间 | 无限制 | 避免查询永久排队 | 超时后查询报错 |
重要计算关系:
这里的 [MAX] 是选择逻辑,不是固定值:
- 如果设置了
MEMORYSIZE(非 0 的独占内存):query_budget = MEMORYSIZE / PLANNEDCONCURRENCY - 如果
MEMORYSIZE为 0 或未设置:query_budget = MAXMEMORYSIZE / PLANNEDCONCURRENCY
例如:MAXMEMORYSIZE = 200 GB、MEMORYSIZE 未设置、PLANNEDCONCURRENCY = 10,则 query_budget = 200 GB / 10 = 20 GB。如果后来设置了 MEMORYSIZE = 60 GB,query_budget 会立刻变成 60 GB / 10 = 6 GB,减少到原来的三分之一——这可能导致原本够用的查询突然频繁 AcquireAdditional。
4.2.1 内存拒绝的调整策略¶
场景 A:query_budget 太小导致频繁 AcquireAdditional
判断标准:v_monitor.resource_acquisitions 中 AcquireAdditional 请求频繁,且 running_query_count 远小于 PLANNEDCONCURRENCY。
调整方向:
-- 减小 PLANNEDCONCURRENCY,增大每条查询的 query_budget
ALTER RESOURCE POOL app_pool PLANNEDCONCURRENCY 8;
-- 之前:200GB / 16 = 12.5GB/query,调整后:200GB / 8 = 25GB/query
为什么这个值:PLANNEDCONCURRENCY 不直接控制并发数——它只用于计算 query_budget = [MAX]MEMORYSIZE / PLANNEDCONCURRENCY。PLANNEDCONCURRENCY 从 16 降到 8,query_budget 翻倍,每条查询获得更大的初始内存,减少 AcquireAdditional。副作用是预算变大意味着能同时跑的查询数自然减少。
注意:如果该池已设置了 MEMORYSIZE,则 query_budget 的分子是 MEMORYSIZE 而非 MAXMEMORYSIZE——调小 PLANNEDCONCURRENCY 仍然有效,但基础值不同。另外不要在调小 PLANNEDCONCURRENCY 的同时调大 MEMORYSIZE/MAXMEMORYSIZE,否则 query_budget 双重膨胀(分子增大 + 分母缩小)。
场景 B:MAXMEMORYSIZE 总量不够
判断标准:memory_pct 持续 > 90% 但 running_query_count 已经很低(如 < 5),说明并发不是瓶颈,纯粹是池的内存配额不够支撑当前查询。
调整方向:
为什么这个值:调整前需要先算清楚内存账。假设节点物理内存 256 GB,之前 app_pool 的 MAXMEMORYSIZE = 40%(= 102 GB),高峰期 memory_pct 持续 95%,说明需要扩容。上调到 70%(= 179 GB)后该池有更多空间。
general 池默认 95% 是非常激进的值,操作系统也需要内存做 page cache 和网络缓冲。当物理内存较小时,建议下调到 ≤ 85%,避免因为操作系统内存不足触发OOM-Killer。
场景 C:GENERAL_MEMORY_BORROWED_KB 很大
GENERAL_MEMORY_BORROWED_KB 表示该池从 general 池借用了多少内存。如果 borrow 很大,说明该池自身内存不够,在消耗 general 池的配额。
调整方向:
为什么这个值:MEMORYSIZE 设置后该池获得独占内存,不再依赖 general 池外借。20% 是一个较保守的起点,后续根据实际用量调整。当然也要评估这个资源池的业务是否值得为其分配独占内存。
4.2.2 并发拒绝的调整策略¶
场景 D:MAXCONCURRENCY 硬限制过小
判断标准:running_query_count = max_concurrency 且 memory_pct < 70%(说明并发是瓶颈而非内存)。
调整方向:
为什么这个值:之前 MAXCONCURRENCY 被设成了一个偏小的固定值(如 16),导致并发数轻易打满而内存还有大量余量。从 16 调整到 30 既能匹配业务峰值,又不会让并发过高导致 CPU 争抢。保守公式:MAXCONCURRENCY ≈ CPU 核数 × 0.5 ~ 1.0。对于 48 核 CPU,30 条并发意味着每条查询平均分到约 1.6 个核。
4.2.3 单查询内存限制¶
场景 E:少数大查询耗尽池内存
判断标准:v_monitor.query_requests 中 1-2 条查询占用了池大部分内存。
调整方向:
为什么这个值:50 GB 源自实际案例(20210624某运营商 Vertica 数据库性能问题处理报告)。设置后,超过 50 GB 内存需求的查询会被直接拒绝,倒逼应用方优化 SQL 或拆分查询。这个值应该基于池中 80% 查询的实际内存用量来设定。
4.3 SQL 优化(根本治理,长期投入)¶
资源池参数调整是「治标」,SQL 优化是「治本」。以下是最常见的导致高内存消耗的 SQL 问题:
缺少统计信息导致执行计划低效:优化器缺少统计信息时会低估数据量,选择错误的 Join 顺序和方式(如将大表选为 Inner 表做 Broadcast),导致内存消耗远超预期。这是 RESOURCE_REJECTED 最常见的根因之一。
详见 Vertica 统计信息管理与查询性能 了解统计信息收集策略。
Group By Hash Spill:当 GROUP BY 的 distinct value 数量很大时,哈希表超出 query_budget 会 spill 到磁盘。虽然 spill 本身不直接导致 RESOURCE_REJECTED,但 spill 期间的额外排序和 I/O 会延长查询执行时间,间接增加并发压力。
Join Re-segmentation:当 Join 键和表的 segmentation 键不匹配时,Vertica 需要在节点间重分布数据(resegment),这个过程消耗大量内存和网络。详见 Vertica Join 重分段倾斜诊断与修复。
宽表扫描:扫描 300+ 列的表会增加内存中物化的数据量。如果不需要全部列,务必显式列名而非 SELECT *。
5. 深入案例¶
5.1 内存拒绝案例¶
📝 虚构案例
场景描述:某金融公司的 12 节点集群(每节点 256 GB 内存、48 核),业务高峰期(上午 9:00-11:00)频繁出现 RESOURCE_REJECTED,每天拒绝约 500-800 次,集中在 app_pool。应用端报错 RESOURCE_REJECTED: Insufficient resources to allocate memory。
诊断过程:
第一步,确认拒绝规模:
SELECT resource_type, reason,
SUM(rejection_count) AS total,
MAX(last_rejected_timestamp)
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day'
AND pool_name = 'app_pool'
GROUP BY resource_type, reason;
输出:
resource_type | reason | total | max
---------------+-------------------------------------------+-------+---------------------
Memory | Insufficient resources to allocate memory | 623 | 2026-05-31 10:45:00
确认全部是内存拒绝。
第二步,看资源池状态:
SELECT memory_inuse_kb / 1024 / 1024 AS mem_gb,
max_memory_size_kb / 1024 / 1024 AS max_gb,
query_budget_kb / 1024 AS budget_mb,
running_query_count, planned_concurrency
FROM v_monitor.resource_pool_status
WHERE pool_name = 'app_pool';
输出:
mem_gb | max_gb | budget_mb | running_query_count | planned_concurrency
--------+--------+-----------+---------------------+---------------------
198 | 205 | 4270 | 48 | 48
关键发现:mem_gb = 198 GB 接近 max_gb = 205 GB(使用率 97%),running_query_count = 48 = planned_concurrency。pool 内存几乎打满,并发也到了上限。
第三步,找出内存消耗最大的查询:
SELECT user_name, memory_acquired_mb,
request_duration_ms / 1000 AS sec,
LEFT(request, 150) AS sql_snippet
FROM v_monitor.query_requests
WHERE is_executing = true
ORDER BY memory_acquired_mb DESC LIMIT 5;
输出显示 3 条查询各自消耗 8-12 GB,是同一张 trade_detail 表(30 亿行、380 列)的 GROUP BY 聚合查询。
根因分析:
app_pool配置为MAXMEMORYSIZE = 80%(= 205 GB),PLANNEDCONCURRENCY = 48query_budget = 205 GB / 48 ≈ 4.3 GB- 实际这 3 条大查询各自需要 8-12 GB,远超预算,需要频繁
AcquireAdditional - 当 3 条大查询 + 45 条小查询同时运行时,池内存满 → 新查询被拒绝
修复方案:
-
为复杂分析查询创建独立资源池
analytics_pool,配置更大的query_budget: -
app_pool减小PLANNEDCONCURRENCY让日常查询获得更充足的预算: -
优化
trade_detail查询:添加日期分区过滤条件、收集统计信息。
效果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 每日 RESOURCE_REJECTED | 500-800 次 | < 10 次 |
| 高峰期平均响应时间 | 45 秒 | 8 秒 |
| app_pool 内存使用率 | 97% | 65% |
5.2 并发拒绝案例¶
📝 虚构案例
场景描述:某运营商经分系统 8 节点集群,ETL 任务调度平台在凌晨 2:00-5:00 启动大量并发数据加载任务,etl_pool 频繁出现并发拒绝,每天 200+ 次。任务调度日志显示 Too many concurrent queries。
诊断过程:
第一步,确认并发拒绝:
SELECT resource_type, SUM(rejection_count) AS total
FROM v_monitor.resource_rejections
WHERE pool_name = 'etl_pool'
AND last_rejected_timestamp > sysdate - INTERVAL '1 day'
GROUP BY resource_type;
输出:Concurrency: 234。
第二步,看排队和并发状态:
SELECT pool_name, running_query_count, max_concurrency, planned_concurrency,
memory_inuse_kb / 1024 / 1024 AS mem_gb,
max_memory_size_kb / 1024 / 1024 AS max_gb
FROM v_monitor.resource_pool_status
WHERE pool_name = 'etl_pool';
输出:
pool_name | running | max_concurrency | planned | mem_gb | max_gb
-----------+---------+-----------------+---------+--------+--------
etl_pool | 16 | 16 | 8 | 45 | 160
关键发现:running_query_count = max_concurrency = 16,并发打满。但 mem_gb = 45 GB 远小于 max_gb = 160 GB(28% 使用率)——内存充足,纯粹是并发限制不够。
第三步,分析 ETL 任务特征:
- 共 40 个并发的 COPY 任务
- 每个任务加载约 5 GB 数据
- 之前
MAXCONCURRENCY = 16只能同时运行 16 个 - 剩余 24 个在排队,部分超时被拒绝
根因分析:
MAXCONCURRENCY = 16偏小,内存实际只用 28%,并发却已打满PLANNEDCONCURRENCY = 8也偏小,query_budget 过大(160 GB / 8 = 20 GB/query),但 ETL COPY 实际每条只用 2-3 GB——内存预算严重过剩- 这个资源池配置更像是为分析查询设计的,不适合 COPY 加载
修复方案:
ALTER RESOURCE POOL etl_pool
MAXCONCURRENCY 30
PLANNEDCONCURRENCY 24
MEMORYSIZE '60%';
-- 新 query_budget = (256*60%)/24 ≈ 6.4 GB
调整逻辑:ETL COPY 每条内存需求约 3 GB,PLANNEDCONCURRENCY = 24 时预算 6.4 GB 完全够用,且 MAXCONCURRENCY = 30 覆盖 40 个任务的瞬时并发。
效果对比:
| 指标 | 修复前 | 修复后 |
|---|---|---|
| 每日并发拒绝 | 234 次 | 0 次 |
| ETL 总耗时 | 3.5 小时 | 2.1 小时 |
| 内存使用率 | 28% | 58% |
5.3 query_budget 不足导致拒绝¶
📋 真实案例 · 来源:20210624某运营商 Vertica 数据库性能问题处理报告
背景:某运营商经分系统,138 节点集群,Vertica 10.x 版本。
故障现象:从某天开始,每天上午 9:00-12:00,app_pool 和 hz_pool 出现大量排队和 RESOURCE_REJECTED。23 日 12 点平均响应时间 94 秒,对比 19 日同期的 13 秒,恶化超过 7 倍。
诊断步骤:
- Catalog 大小:均值 15 GB,正常范围。
- 节点文件数:均值约 15 万,正常范围。
-
query_budget 检查(关键发现):
-
app_pool的query_budget = 1 GB,但 40% 的查询实际消耗 > 1 GB 内存 - 这意味着 40% 的查询需要在执行中重新申请内存(AcquireAdditional),在高峰时段因内存资源紧张导致长时间等待或拒绝
- 长时间 SQL 分析:一条 SQL 使用 28 GB 内存,执行 1 小时。执行计划因统计信息过期导致大表被错误选为 Inner 表(驱动表),造成巨大的内存消耗。
修复方案:
-
调整
app_pool的PLANNEDCONCURRENCY将query_budget从 1 GB 提升到 4 GB(覆盖 95% 的查询): -
添加
MAXQUERYMEMORYSIZE = 50G限制单条查询内存上限,超过阈值的查询需要先优化。 - 收集过期统计信息,修正执行计划。
效果:调整后次日峰值平均响应时间从 94 秒降至正常水平。需要 AcquireAdditional 的查询从 40% 降至 < 5%。
5.4 老版本 Bug 导致 OOM¶
📋 真实案例 · 来源:20251009某运营商标签库宕机排查报告
背景:某运营商标签库系统,11 节点集群,Vertica 11.0.2-12 版本。每节点 256 GB 物理内存。
故障现象:2025 年 10 月 9 日,集群连续发生两次宕机。每次宕机都是两个逻辑相邻节点的 Vertica 进程被 Linux OOM Killer 杀掉,触发其余节点 SafetyShutdown。
诊断步骤:
- 检查宕机节点系统日志,确认 oom-killer 杀掉了 Vertica 进程。
- 检查被杀时 Vertica 进程实际内存占用:
anon-rss: 260 GB ≈ 249 GB。 - 检查
general资源池配置:MAXMEMORYSIZE = 80%(= 200 GB)。 - 关键矛盾:实际内存使用 249 GB >> MAXMEMORYSIZE 上限 200 GB。
根因分析:
Vertica 11.0.2-12 版本存在已知 Bug(VER-91820 / VER-91819):执行引擎预取缓冲区内存未在 Resource Manager 登记,完全绕过 MAXMEMORYSIZE 限制。当多个查询同时进行预取时,实际内存占用轻松超过上限,触发系统 OOM Killer。
此外还有 Bug VER-88006(处理大表时索引消耗过多 RAM)和 VER-82729(复杂查询解析阶段内存计算错误)叠加影响。
修复方案:升级数据库版本到 23.3.0-12 或更新版本(这些 Bug 在 12.0.x 和 23.3.x 中已修复)。
教训:
- 老版本(< 12.0.x)的
MAXMEMORYSIZE不一定是有效的硬限制,依赖 Bug 修复版本 - 即使
MAXMEMORYSIZE设置为 80%,也要检查实际anon-rss确认是否真正生效 - RESOURCE_REJECTED 如果没有触发(被绕过),下一步就是 OOM → 集群宕机,后果严重得多
6. 完整诊断流程实战¶
📝 虚构场景 · 完整演练
背景:某电商公司 6 节点集群(384 GB 内存、32 核/节点),MC 报警显示过去 1 小时内 RESOURCE_REJECTED 超过 300 次。你是值班 DBA,需要快速定位和解决问题。
时间线:
10:00 收到 MC 告警:RESOURCE_REJECTED > 300 次/小时
10:02 登录数据库,开始诊断
10:05 定位到问题资源池
10:08 找到根因
10:12 执行临时修复
10:20 观察恢复
10:30 记录后续优化项
10:02 - 步骤 1:量化问题
SELECT resource_type, reason,
SUM(rejection_count) AS total,
MAX(last_rejected_timestamp) AS latest
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 hour'
GROUP BY resource_type, reason
ORDER BY total DESC;
输出:
resource_type | reason | total | latest
---------------+-------------------------------------------+-------+---------------------
Memory | Insufficient resources to allocate memory | 312 | 2026-05-31 10:01:15
Memory | Request exceeds MAXQUERYMEMORYSIZE | 8 | 2026-05-31 09:58:42
判断:内存拒绝占绝对多数(312 次),少量是单查询超限(8 次)。主要问题是内存不足。
10:03 - 步骤 2:定位问题资源池
SELECT pool_name,
SUM(rejection_count) AS total
FROM v_monitor.resource_rejections
WHERE last_rejected_timestamp > sysdate - INTERVAL '1 hour'
GROUP BY pool_name
ORDER BY total DESC;
输出:
判断:azkaban_pool 是问题池。
10:04 - 步骤 3:检查 azkaban_pool 状态
SELECT memory_inuse_kb / 1024 / 1024 AS mem_gb,
max_memory_size_kb / 1024 / 1024 AS max_gb,
query_budget_kb / 1024 AS budget_mb,
running_query_count,
max_concurrency,
planned_concurrency,
ROUND(100.0 * memory_inuse_kb / NULLIF(max_memory_size_kb, 0), 1) AS pct
FROM v_monitor.resource_pool_status
WHERE pool_name = 'azkaban_pool';
输出:
mem_gb | max_gb | budget_mb | running | max_con | planned | pct
--------+--------+-----------+---------+---------+---------+------
275 | 292 | 870 | 22 | 22 | 344 | 94.2
判断:内存使用率 94.2%,并发打满(22/22),query_budget = 870 MB 偏小。注意 planned_concurrency = 344——这是一个极端配置,意味着该池当初按 344 条并发规划预算(292 GB / 344 ≈ 870 MB),每条查询只能拿到极小的初始内存。
10:05 - 步骤 4:找出最耗内存的查询
SELECT user_name, memory_acquired_mb,
request_duration_ms / 1000 AS sec,
LEFT(request, 120) AS sql_snippet
FROM v_monitor.query_requests
WHERE is_executing = true
AND request ILIKE '%azkaban%'
ORDER BY memory_acquired_mb DESC LIMIT 5;
输出显示 5 条 SQL 各消耗 8-15 GB,都是 Azkaban 调度的大表 JOIN 查询,且有多条已经运行超过 30 分钟。
10:06 - 步骤 5:检查是否有 AcquireAdditional 风暴
SELECT COUNT(*) AS acquire_count,
SUM(memory_inuse_kb) / 1024 / 1024 AS total_gb,
AVG(duration_ms) AS avg_wait_ms
FROM v_monitor.resource_acquisitions
WHERE pool_name = 'azkaban_pool'
AND request_type = 'AcquireAdditional'
AND is_executing = true;
输出:
判断:48 个额外内存申请,总需求 35 GB,平均等待 8.5 秒。大量查询在排队等内存。
10:08 - 根因判断:
azkaban_pool内存总量 292 GB(384 GB × 76%),高峰期全部打满PLANNEDCONCURRENCY = 344是根因——当初按 344 条并发规划,导致query_budget仅 870 MB,远不足以支撑实际 8-15 GB 的查询- 5 条长查询各占 8-15 GB,加上其他 17 条查询,几近耗尽池内存
MAXCONCURRENCY = 22偏高,在内存已满的情况下允许太多并发,每条查询都在等 AcquireAdditional,反而降低了效率
10:10 - 临时修复:
-- 1. 降低并发,提升每条查询的预算
ALTER RESOURCE POOL azkaban_pool PLANNEDCONCURRENCY 10 MAXCONCURRENCY 15;
-- 2. 限制单查询内存上限,防止个别大查询耗光池内存
ALTER RESOURCE POOL azkaban_pool MAXQUERYMEMORYSIZE '30G';
调整后 query_budget = 292 GB / 10 ≈ 29.2 GB,远高于之前的 870 MB,大幅减少 AcquireAdditional 需求。
10:20 - 效果验证:
SELECT memory_inuse_kb / 1024 / 1024 AS mem_gb,
max_memory_size_kb / 1024 / 1024 AS max_gb,
running_query_count,
ROUND(100.0 * memory_inuse_kb / NULLIF(max_memory_size_kb, 0), 1) AS pct
FROM v_monitor.resource_pool_status
WHERE pool_name = 'azkaban_pool';
输出:
内存使用率从 94% 降到 68%,响应恢复。随后观察 30 分钟,无新增拒绝。
后续优化项(当周执行):
- 为 Azkaban 任务中最耗内存的几张表收集统计信息
- 优化运行 > 10 分钟的 TOP 5 SQL
- 考虑将 ETL 和查询分离到不同资源池
7. 快速诊断 SQL 工具箱¶
| 诊断目标 | SQL | 输出解读 |
|---|---|---|
| 查看拒绝总体情况 | SELECT resource_type, reason, SUM(rejection_count) FROM v_monitor.resource_rejections WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day' GROUP BY resource_type, reason ORDER BY 3 DESC; |
确定主要拒绝类型和数量 |
| 查看单次拒绝详情 | SELECT * FROM v_monitor.resource_rejection_details WHERE rejected_timestamp > sysdate - INTERVAL '1 hour' ORDER BY rejected_timestamp DESC LIMIT 50; |
定位被拒绝的用户和 session |
| 查看资源池实时状态 | SELECT pool_name, memory_inuse_kb/1024/1024 AS mem_gb, max_memory_size_kb/1024/1024 AS max_gb, running_query_count, max_concurrency, ROUND(100.0*memory_inuse_kb/NULLIF(max_memory_size_kb,0),1) AS pct FROM v_monitor.resource_pool_status WHERE max_memory_size_kb > 0 ORDER BY pct DESC; |
找内存/并发使用率最高的池 |
| 查看高内存查询 | SELECT user_name, memory_acquired_mb, request_duration_ms/1000 AS sec, LEFT(request,200) FROM v_monitor.query_requests WHERE is_executing = true ORDER BY memory_acquired_mb DESC LIMIT 20; |
定位内存消耗最大的查询 |
| 查看额外内存申请 | SELECT pool_name, COUNT(*), SUM(memory_inuse_kb)/1024/1024 AS total_gb, AVG(duration_ms) FROM v_monitor.resource_acquisitions WHERE request_type='AcquireAdditional' AND is_executing=true GROUP BY pool_name; |
判断 query_budget 是否偏小 |
| 查看排队情况 | SELECT pool_name, COUNT(*), MAX(position_in_queue) FROM v_monitor.resource_queues GROUP BY pool_name; |
查看排队深度 |
| 查看磁盘拒绝 | SELECT node_name, rejected_reason, rejected_count FROM v_monitor.disk_resource_rejections WHERE last_rejected_timestamp > sysdate - INTERVAL '1 day' ORDER BY rejected_count DESC; |
排查存储空间问题 |
| 查看级联移送失败 | SELECT source_pool_name, target_pool_name, move_cause, result_reason, success FROM v_monitor.resource_pool_move WHERE move_timestamp > sysdate - INTERVAL '1 hour' AND success = false; |
排查资源池级联配置 |
| 查看系统资源历史 | 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; |
判断是突发还是持续性压力 |
| 当前资源池配置 | SELECT name, memorysize, maxmemorysize, plannedconcurrency, maxconcurrency, executionparallelism, maxquerymemorysize, queuetimeout FROM v_catalog.resource_pools WHERE is_internal = false ORDER BY name; |
查看资源池参数设置 |
8. 最佳实践清单¶
| # | 实践 | 为什么 |
|---|---|---|
| 1 | MAXMEMORYSIZE ≤ 总物理内存的 85% | 操作系统需要内存做 page cache、网络缓冲区、进程开销。95% 太激进,实际案例中多次触发 OOM。参考 20251009某运营商标签库宕机排查报告、20251210某金融公司数据库集群宕机问题排查 |
| 2 | 按工作负载类型创建独立资源池 | 混合 ETL、查询、报表在同一个池会导致互相挤占。ETL 通常需要高并发小内存,分析查询需要低并发大内存——应分池管理 |
| 3 | query_budget 应覆盖 80-90% 查询的初始内存需求 | 如果大量查询需要 AcquireAdditional,不仅增加管理开销,还在高峰期因内存紧张被拒绝。通过分析历史 memory_acquired_mb 分布确定合理预算 |
| 4 | 设置 MAXQUERYMEMORYSIZE 防止单条查询耗尽池内存 | 一条 50 GB 的查询可以让 50 条 1 GB 的查询全部被拒绝。参考 20210624某运营商 Vertica 数据库性能问题处理报告 中的 50 GB 限制实践 |
| 5 | 保持统计信息更新(尤其是大表) | 过时统计信息是 RESOURCE_REJECTED 最常见的间接原因——优化器低估内存 → 查询超预算 → 额外申请 → 拒绝。详见 Vertica 统计信息管理与查询性能 |
| 6 | 监控资源池内存使用率,设置 > 85% 告警 | 等 RESOURCE_REJECTED 发生再去处理已经晚了。主动监控内存趋势,在接近上限前扩容或分流。使用 v_monitor.resource_pool_status 的 memory_pct 字段 |
| 7 | 级联池的 RUNTIMECAP 从短到长递增 | 短查询快速执行,超时的逐级级联到更高预算的池。避免短查询被长查询阻塞。参考 Vertica 资源池配置的最佳实践 第 4 节 |
| 8 | 避免在业务高峰期执行统计信息收集和 TM 操作 | ANALYZE_STATISTICS 和 mergeout 都是内存密集型操作,在高峰期执行会加剧资源竞争 |
| 9 | 升级到受支持的 Vertica 版本(≥ 23.3.x) | 老版本(如 11.0.x)存在导致内存绕过 Resource Manager 的已知 Bug。升级是解决「无论如何调整参数都无效」的最终方案 |
| 10 | 建立 RESOURCE_REJECTED 日常监控和趋势分析 | 每天的拒绝次数、类型分布、影响池是容量规划的关键输入。拒绝趋势持续上升 → 需要扩容或架构调整,不应只靠调参数解决 |
扩展阅读¶
- Vertica 错误日志解读与常见错误处理 — RESOURCE_REJECTED 在错误日志中的定位与监控方法
- Vertica 资源池配置的最佳实践 — 资源池参数详解与配置策略
- Vertica 内存压力诊断与调优 — 内存不足时的诊断路径
- Vertica 查询 Spill 到磁盘的原因与优化 — Spill 与资源池内存的关系
- Vertica 统计信息管理与查询性能 — 统计信息是 RESOURCE_REJECTED 最常见的间接根因