跳转至

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 会做以下检查:

  1. 并发检查:当前资源池的 running_query_count 是否已达到 MAXCONCURRENCY
  2. 内存检查:查询申请的 query_budget + 当前池已用内存是否超过 MAXMEMORYSIZE?如果池有 MEMORYSIZE(独占内存),则先检查独占内存是否足够。
  3. 磁盘检查:数据目录(/data)的剩余空间是否足够创建临时文件?
  4. 单查询内存检查:查询所需内存是否超过 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':并发数不够。检查该池的 MAXCONCURRENCYPLANNEDCONCURRENCY
  • 多个节点都出现拒绝:不是单节点问题,是集群级别的资源池配置偏小。
  • 只有单个节点出现拒绝:可能是数据倾斜导致该节点负载更高,或该节点有其他进程消耗内存。

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 内部,而在操作系统层面。以下命令帮助你快速排除系统级问题。

查看系统内存状态

free -h

如何解读:关注 available 列。如果 available < 物理内存的 10%,操作系统层面内存不足,Vertica 的 MAXMEMORYSIZE 即使设置为 90% 也可能不够——操作系统自身需要内存做 page cache、网络缓冲等。

查看 OOM Killer 历史记录

dmesg | grep -i 'killed process'

如何解读:如果有 Vertica 进程被杀掉(verticavertica-udx),说明问题已升级到 OOM 级别——这比 RESOURCE_REJECTED 严重得多。OOM 意味着 Resource Manager 未能阻止内存超额使用。参见 20251009某运营商标签库宕机排查报告 了解 11.0.2 版本中预取缓冲区绕过 maxmemorysize 的真实案例。

查看磁盘使用

df -h /data /catalog

如何解读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=4query_budget 较小,但如果大量 mergeout 同时发生,会消耗系统总内存(虽然 TM 池独立于 general 池,但总物理内存是共享的)。

详见 Tuple Mover 最佳实践完全指南

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 = falseresult_reason 包含 "not enough resources":目标资源池也满了。需要增大目标池的 MAXMEMORYSIZEMAXCONCURRENCY
  • 移送频繁发生:说明 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 排队超时时间 无限制 避免查询永久排队 超时后查询报错

重要计算关系

query_budget = [MAX]MEMORYSIZE / PLANNEDCONCURRENCY

这里的 [MAX] 是选择逻辑,不是固定值:

  • 如果设置了 MEMORYSIZE(非 0 的独占内存):query_budget = MEMORYSIZE / PLANNEDCONCURRENCY
  • 如果 MEMORYSIZE 为 0 或未设置query_budget = MAXMEMORYSIZE / PLANNEDCONCURRENCY

例如:MAXMEMORYSIZE = 200 GBMEMORYSIZE 未设置、PLANNEDCONCURRENCY = 10,则 query_budget = 200 GB / 10 = 20 GB。如果后来设置了 MEMORYSIZE = 60 GBquery_budget 会立刻变成 60 GB / 10 = 6 GB,减少到原来的三分之一——这可能导致原本够用的查询突然频繁 AcquireAdditional

4.2.1 内存拒绝的调整策略

场景 A:query_budget 太小导致频繁 AcquireAdditional

判断标准:v_monitor.resource_acquisitionsAcquireAdditional 请求频繁,且 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 / PLANNEDCONCURRENCYPLANNEDCONCURRENCY 从 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),说明并发不是瓶颈,纯粹是池的内存配额不够支撑当前查询。

调整方向:

-- 增大该资源池的 MAXMEMORYSIZE
ALTER RESOURCE POOL app_pool MAXMEMORYSIZE '70%';

为什么这个值:调整前需要先算清楚内存账。假设节点物理内存 256 GB,之前 app_poolMAXMEMORYSIZE = 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 池的配额。

调整方向:

-- 为该池设置独占内存
ALTER RESOURCE POOL app_pool MEMORYSIZE '20%';

为什么这个值MEMORYSIZE 设置后该池获得独占内存,不再依赖 general 池外借。20% 是一个较保守的起点,后续根据实际用量调整。当然也要评估这个资源池的业务是否值得为其分配独占内存。

4.2.2 并发拒绝的调整策略

场景 D:MAXCONCURRENCY 硬限制过小

判断标准:running_query_count = max_concurrencymemory_pct < 70%(说明并发是瓶颈而非内存)。

调整方向:

ALTER RESOURCE POOL app_pool MAXCONCURRENCY 30;

为什么这个值:之前 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 条查询占用了池大部分内存。

调整方向:

-- 限制单条查询的最大内存
ALTER RESOURCE POOL app_pool MAXQUERYMEMORYSIZE '50G';

为什么这个值: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 = 48
  • query_budget = 205 GB / 48 ≈ 4.3 GB
  • 实际这 3 条大查询各自需要 8-12 GB,远超预算,需要频繁 AcquireAdditional
  • 当 3 条大查询 + 45 条小查询同时运行时,池内存满 → 新查询被拒绝

修复方案

  1. 为复杂分析查询创建独立资源池 analytics_pool,配置更大的 query_budget

    CREATE RESOURCE POOL analytics_pool
        MAXMEMORYSIZE '30%'
        PLANNEDCONCURRENCY 6
        MAXCONCURRENCY 10
        EXECUTIONPARALLELISM 12
        MAXQUERYMEMORYSIZE '40G';
    

  2. app_pool 减小 PLANNEDCONCURRENCY 让日常查询获得更充足的预算:

    ALTER RESOURCE POOL app_pool PLANNEDCONCURRENCY 24;
    -- 新 query_budget = 205 / 24 ≈ 8.5 GB
    

  3. 优化 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_poolhz_pool 出现大量排队和 RESOURCE_REJECTED。23 日 12 点平均响应时间 94 秒,对比 19 日同期的 13 秒,恶化超过 7 倍

诊断步骤

  1. Catalog 大小:均值 15 GB,正常范围。
  2. 节点文件数:均值约 15 万,正常范围。
  3. query_budget 检查(关键发现)

  4. app_poolquery_budget = 1 GB,但 40% 的查询实际消耗 > 1 GB 内存

  5. 这意味着 40% 的查询需要在执行中重新申请内存(AcquireAdditional),在高峰时段因内存资源紧张导致长时间等待或拒绝
  6. 长时间 SQL 分析:一条 SQL 使用 28 GB 内存,执行 1 小时。执行计划因统计信息过期导致大表被错误选为 Inner 表(驱动表),造成巨大的内存消耗。

修复方案

  1. 调整 app_poolPLANNEDCONCURRENCYquery_budget 从 1 GB 提升到 4 GB(覆盖 95% 的查询):

    ALTER RESOURCE POOL app_pool PLANNEDCONCURRENCY 10 MAXCONCURRENCY 25;
    

  2. 添加 MAXQUERYMEMORYSIZE = 50G 限制单条查询内存上限,超过阈值的查询需要先优化。

  3. 收集过期统计信息,修正执行计划。

效果:调整后次日峰值平均响应时间从 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。

诊断步骤

  1. 检查宕机节点系统日志,确认 oom-killer 杀掉了 Vertica 进程。
  2. 检查被杀时 Vertica 进程实际内存占用:anon-rss: 260 GB ≈ 249 GB
  3. 检查 general 资源池配置:MAXMEMORYSIZE = 80%(= 200 GB)。
  4. 关键矛盾:实际内存使用 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;

输出:

 pool_name      | total
----------------+-------
 azkaban_pool   | 305
 general        | 7

判断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;

输出:

 acquire_count | total_gb | avg_wait_ms
---------------+----------+-------------
 48            | 35       | 8500

判断:48 个额外内存申请,总需求 35 GB,平均等待 8.5 秒。大量查询在排队等内存。

10:08 - 根因判断

  1. azkaban_pool 内存总量 292 GB(384 GB × 76%),高峰期全部打满
  2. PLANNEDCONCURRENCY = 344 是根因——当初按 344 条并发规划,导致 query_budget 仅 870 MB,远不足以支撑实际 8-15 GB 的查询
  3. 5 条长查询各占 8-15 GB,加上其他 17 条查询,几近耗尽池内存
  4. 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';

输出:

 mem_gb | max_gb | running | pct
--------+--------+---------+------
 198    | 292    | 12      | 67.8

内存使用率从 94% 降到 68%,响应恢复。随后观察 30 分钟,无新增拒绝。

后续优化项(当周执行):

  1. 为 Azkaban 任务中最耗内存的几张表收集统计信息
  2. 优化运行 > 10 分钟的 TOP 5 SQL
  3. 考虑将 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_statusmemory_pct 字段
7 级联池的 RUNTIMECAP 从短到长递增 短查询快速执行,超时的逐级级联到更高预算的池。避免短查询被长查询阻塞。参考 Vertica 资源池配置的最佳实践 第 4 节
8 避免在业务高峰期执行统计信息收集和 TM 操作 ANALYZE_STATISTICS 和 mergeout 都是内存密集型操作,在高峰期执行会加剧资源竞争
9 升级到受支持的 Vertica 版本(≥ 23.3.x) 老版本(如 11.0.x)存在导致内存绕过 Resource Manager 的已知 Bug。升级是解决「无论如何调整参数都无效」的最终方案
10 建立 RESOURCE_REJECTED 日常监控和趋势分析 每天的拒绝次数、类型分布、影响池是容量规划的关键输入。拒绝趋势持续上升 → 需要扩容或架构调整,不应只靠调参数解决

扩展阅读