跳转至

Vertica CPU 持续高负载诊断与优化

作者:JiangChong | 发布时间:2026-04-07

适用场景:集群 CPU 持续高负载(idle < 20%),查询性能下降,资源竞争严重。

关联Vertica 资源池最佳实践 | Vertica 监控最佳实践 | Vertica 客户端连接负载均衡配置 | Vertica 性能调优 - 2 使用系统表排除 Vertica 查询性能故障 | Vertica 性能调优 - 1 如何阅读执行计划


理解全文脉络

本文按照「发现问题 → 定位根因 → 解决问题」的路径组织。如果你是第一次遇到 CPU 高负载问题,建议从第 1 节读起逐步推进;如果你已经有明确的怀疑方向(比如知道是某条 SQL 导致的),可以直接跳到第 3 节。


1. 理解 Vertica 的 CPU 消耗模型

1.1 为什么 CPU 对 Vertica 如此重要

Vertica 是一个 MPP(大规模并行处理)列存分析型数据库。与传统行存数据库(如 MySQL、PostgreSQL)不同,Vertica 的设计哲学是:

  • 列存压缩:数据按列存储在磁盘上,读取时需要在内存中解压缩。压缩算法(LZO、Delta Val、RLE 等)虽然节省了磁盘空间,但解压缩本身需要消耗 CPU。
  • 无索引设计:Vertica 不使用 B-Tree 索引,而是依赖投影(Projection)的排序键和分区裁剪来加速查询。这意味着每次查询都是对原始数据的扫描和过滤,CPU 参与度远高于传统数据库。
  • 大规模并行:一个查询会被拆分成数十甚至数百个并行算子,分布在所有节点上同时执行。每个算子都是独立的线程,都需要占用 CPU 时间片。
  • 内存换时间:Vertica 倾向于将数据尽量保留在内存中处理(如 Hash Join、Hash GroupBy),这能避免磁盘 I/O,但代价是大量 CPU 用于哈希计算。

理解这四点之后,就容易明白:CPU 高负载在 Vertica 中不一定是坏事——它说明集群正在全力工作。但持续超过 80% 的 CPU 利用率则说明计算资源已经饱和,新的查询将被迫排队等待。

1.2 CPU 消耗的四大来源

当我们在操作系统层面看到 CPU 负载很高时,这些 CPU 周期究竟花在了哪里?在 Vertica 内部,主要可以归为四类:

来源 说明 典型表现
查询执行(ExecutePlan) SQL 查询的算子执行,如 Scan、Join、GroupBy、Sort。这是最核心的 CPU 消耗来源,通常占总 CPU 的 70%-95%。 大量查询同时运行或单条查询并行度过高
Tuple Mover(TM) 后台数据整理任务,包括 mergeout(将多个小 ROS 文件合并为一个大文件)和 moveout(将 WOS 内存数据刷写到磁盘 ROS)。 ROS container 数量过大(>500),或加载频率极高
Recovery(节点恢复) 当某个节点宕机后重新加入集群时,需要从 buddy 节点同步缺失的数据。这个过程涉及大量数据校验和网络传输,CPU 开销很大。 节点频繁 UP/DOWN,或集群刚重启
其他系统开销 编码解码、网络数据收发、全局 catalog 锁争抢、JVM GC 等。虽然单项占比不大,但叠加后也会推高 CPU。 编码类型复杂、跨节点数据传输量大

1.3 诊断路由图

在实际排查中,建议按以下优先级依次排查,每排除一项再进入下一项:

第 1 步:系统级 CPU 使用率 → 确认是否真的高、哪些节点高
  ↳ 若各节点 CPU 差异 > 3 倍 → 优先排查连接倾斜(详见第 2.1 节)
第 2 步:排除 Recovery → 是否有节点在恢复中
第 3 步:排除 Tuple Mover → 是否有大量 mergeout/moveout
第 4 步:定位高消耗查询 → 找出具体的 SQL 语句
第 5 步:算子级分析 → 找到 SQL 内部的 CPU 热点操作符
第 6 步:针对性优化 → 调整资源池、改写 SQL、优化投影

下面我们将按照这个路线逐一展开。


2. 系统级 CPU 监控:先看清全局

2.1 使用 system_resource_usage 查看历史 CPU

Vertica 内置的 v_monitor.system_resource_usage 表会以固定间隔(默认每 10 秒)采集每个节点的 CPU、内存、网络、磁盘 I/O 指标。这是排查问题时首先要看的数据。

SELECT node_name,
       end_time,
       average_cpu_usage_percent,
       average_memory_usage_percent,
       net_rx_kbytes_per_second,
       net_tx_kbytes_per_second,
       io_read_kbytes_per_second,
       io_written_kbytes_per_second
FROM v_monitor.system_resource_usage
WHERE end_time > sysdate - INTERVAL '1 hour'
ORDER BY end_time DESC;

如何解读结果:

  • average_cpu_usage_percent > 80% 且持续超过 5 分钟:说明 CPU 确实成为瓶颈。此时操作系统调度器会频繁切换线程上下文,反而降低整体吞吐量。
  • 各节点 CPU 使用率差异很大:比如 node1 和 node2 跑满 95%,而 node3 只有 30%——这说明可能存在数据倾斜(某些节点承担了更多数据),或者连接倾斜(所有客户端连接集中在少数节点,这些节点的 initiator 承担了全部 SQL 的编译和计划生成工作)。此时先用以下 SQL 快速确认是否为连接倾斜:
-- 检查过去 1 天各节点发起的查询数,差异大则说明连接未均衡
SELECT node_name, COUNT(*) AS query_count
FROM v_internal.dc_requests_issued
WHERE "time" > sysdate() - 1
GROUP BY 1 ORDER BY 1;

如果 80% 以上的查询集中在 30% 以下的节点上,则是连接倾斜问题。先修连接均衡再继续排查——连接倾斜修复后 CPU 通常会自动恢复,无需继续后面的步骤。详见 Vertica 客户端连接负载均衡配置。 - CPU 高但内存、网络、磁盘 I/O 都正常:这是典型的纯计算密集型负载,优化重点放在 SQL 和并行度上。 - CPU、网络收发、磁盘 I/O 同时高:可能是大表全表扫描 + 跨节点重分段(RESEGMENT)。查询不仅要读大量数据,还要通过网络传输到其他节点重新分布。

2.2 使用 Linux 命令辅助定位

有时问题不一定出在 Vertica 内部,也可能是操作系统层面的资源争抢。常用的 Linux 排查命令:

# 1. 查看 Vertica 进程的总体 CPU 占用
top -b -n1 -p $(pgrep -d',' vertica)

# 2. 按线程级别查看——Vertica 是多线程执行,单个线程可能跑满一个核心
top -H -b -n1 -p $(pgrep -d',' vertica) | head -20

# 3. 查看系统整体负载(1 分钟、5 分钟、15 分钟均值)
uptime

如何解读:

  • load average 远超 CPU 核心数:比如 48 核机器,load average 持续在 60+,说明有大量进程/线程在排队等待 CPU,系统严重过载。
  • top 中 %us(user space)高:说明 CPU 主要花在 Vertica 的用户态计算上,属于正常消耗。
  • top 中 %sy(system/kernel)高:说明 CPU 大量花在内核态,可能是系统调用过多、上下文切换过频、或网络/磁盘中断密集。需要检查操作系统层面配置(如 IRQ affinity、NUMA 绑定)。

3. 定位 CPU 消耗来源:从宏观到微观

3.1 第一步:确认是否有 Recovery 在进行

Recovery 是 CPU 消耗大户,而且它是「被动」的——不是由你的查询触发,而是节点状态变化触发。所以要首先排除。

SELECT node_name,
       recover_epoch,
       recovery_phase,
       current_completed,
       current_total,
       is_running
FROM v_monitor.recovery_status
WHERE is_running = 't'
ORDER BY node_name;

如果 is_running = 't'

  • recovery_phase 告诉你当前处于恢复的哪个阶段(拷贝数据、重放 WOS、校验一致性等)
  • current_completed / current_total 显示恢复进度
  • 此时 CPU 高是正常的,不建议做其他优化——等 Recovery 完成后再观察
  • 如果 Recovery 频繁发生,重点排查该节点的硬件健康度(内存、磁盘)和网络稳定性

如果 Recovery 早就结束了但 CPU 还是高:继续下一步排查。

3.2 第二步:确认是否有 Tuple Mover 大量工作

Tuple Mover(TM)是 Vertica 的后台数据整理组件。它有两个核心操作:

  • Moveout:将 WOS(Write Optimized Store,写入优化存储)中的数据批量转移到 ROS(Read Optimized Store,读取优化存储)。WOS 是内存结构,ROS 是磁盘文件。
  • Mergeout:将多个小 ROS 文件合并为较大的 ROS 文件。ROS 文件数量过多会影响查询性能(因为每次查询需要打开大量文件句柄),所以 TM 会定期将小文件合并。

这两个操作在处理大量数据时 CPU 消耗很高——因为涉及排序、压缩、重新编码等计算密集型任务。

SELECT node_name,
       operation_name,
       table_schema,
       table_name,
       is_executing,
       operation_start_timestamp
FROM v_monitor.tuple_mover_operations
WHERE is_executing = 't'
ORDER BY operation_start_timestamp;

TM 高 CPU 的常见诱因及解决思路:

诱因 现象 解决方向
大量小批次加载(每次几百行) ROS container 数量快速增长到 500+,mergeout 频繁触发 合并加载批次,每次至少加载几十万行
分区键设计不当,每次加载跨多个分区 单次加载产生 N 个分区 × K 个节点个 ROS 文件 重新评估分区策略:分区键是否粒度太细?
MoveOutInterval 设置过短 WOS 还没满就被频繁刷写到磁盘 适当增大 MoveOutInterval,让 WOS 积累更多数据再批量写入
表没有分区,数据无限增长 单个 ROS 文件无限增大,mergeout 耗时指数增长 为大表添加分区,利用 DROP_PARTITION 定期清理旧数据

3.3 第三步:通过 query_requests 找到高消耗的 SQL

排除 Recovery 和 TM 之后,剩下最大的 CPU 消耗者就是用户查询。此时我们需要从 Vertica 系统表中找出「罪魁祸首」。

v_monitor.query_requests 是 Vertica 最重要的查询监控表之一。它记录了所有已完成和正在执行的 SQL 请求的详细信息——包括谁提交的、什么时候开始、执行了多久、用了多少内存。

-- 先看当前正在运行的查询,按执行时间从长到短排列
SELECT node_name,
       user_name,
       session_id,
       transaction_id,
       statement_id,
       request_duration_ms,
       memory_acquired_mb,
       LEFT(request, 200) AS query_text
FROM v_monitor.query_requests
WHERE is_executing = 't'
ORDER BY request_duration_ms DESC
LIMIT 20;
-- 如果当前没有异常,再看最近 1 小时的历史慢查询
SELECT user_name,
       start_timestamp,
       request_duration_ms,
       memory_acquired_mb,
       request
FROM v_monitor.query_requests
WHERE start_timestamp > sysdate - INTERVAL '1 hour'
  AND request_type = 'QUERY'
  AND request_duration_ms > 30000  -- 只关注运行超过 30 秒的查询
ORDER BY request_duration_ms DESC
LIMIT 20;

如何解读:

  • request_duration_ms 很大(几十万毫秒甚至更多):该查询运行了很长时间。如果它还在运行中(is_executing = 't'),它很可能就是当前 CPU 高负载的元凶。
  • 多条查询同时具有很大的 request_duration_ms:说明不是某一条 SQL 的问题,而是并发度过高,大量查询同时争抢 CPU。这时需要从资源池层面做并发控制,而不是优化单条 SQL。
  • memory_acquired_mb 异常大(几十 GB):该查询申请了极多内存。大内存通常伴随大 CPU 消耗——因为大量数据在内存中参与哈希计算和排序。

确定要分析的查询后,将 transaction_idstatement_id 记下并赋给 vsql 变量,后续所有深入分析的 SQL 都会复用它:

\set t_id 45035996274879950
\set s_id 1

3.4 第四步:用 PROFILE 分析查询的执行引擎细节

这是 CPU 诊断最关键的一步。PROFILE 关键字会让 Vertica 在执行查询的同时,将每个算子(operator)的详细性能指标写入 execution_engine_profiles 系统表。

操作流程:

-- 1. 打开计时器,关闭结果输出(因为我们要的是性能数据,不是查询结果本身)
\timing
\o /dev/null

-- 2. 用 PROFILE 关键字重新执行你要分析的查询
PROFILE SELECT ...;

-- 3. 恢复输出
\o

PROFILE 之后 Vertica 会给出几个关键提示,理解这些提示是诊断的起点:

NOTICE 4788: Statement is being profiled
HINT: Select * from v_monitor.execution_engine_profiles where transaction_id=... and statement_id=...;
NOTICE 3557: Initiator memory for query: [on pool general: 1548488 KB, minimum: 1209481 KB]
NOTICE 5077: Total memory required by query: [1548488 KB]
Time: First fetch (1000 rows): 718.507 ms. All rows formatted: 832.033 ms
  • Initiator memory for query:查询在发起节点申请的内存总量。如果这个值接近或超过资源池的 query_budget,说明内存预算刚好够用甚至偏紧。
  • Total memory required by query:查询在所有节点上总共需要的内存。如果这个值远大于 query_budget,说明查询需要多次向 Resource Manager 申请额外内存(AcquireAdditional 请求),每次申请都有开销。
  • Time: First fetch:返回第一批结果的时间。对于大查询,如果这个值远大于总执行时间的一半,说明瓶颈在查询的中间步骤(如 JOIN 或 GROUP BY),而不是最后的排序输出。

3.5 第五步:在 execution_engine_profiles 中定位 CPU 热点算子

execution_engine_profiles 表可能包含数千行数据——每一个算子的每一个计数器都是一行。我们需要聚合分析,按算子类型和路径汇总 CPU 时间。

-- 找出最耗 CPU 的算子和执行路径
SELECT operator_name,
       path_id,
       SUM(counter_value) AS total_time_us
FROM execution_engine_profiles
WHERE transaction_id = :t_id
  AND statement_id = :s_id
  AND counter_name ILIKE 'execution%'
GROUP BY operator_name, path_id
ORDER BY 3 DESC
LIMIT 20;

先理解两个关键概念:

  • path_id:执行路径的编号。一个查询的执行计划由多条路径组成,每条路径代表一个数据流分支。路径之间有的是串行依赖关系(路径 A 的结果必须传给路径 B),有的是并行独立关系(路径 A 和路径 B 可以同时执行)。
  • operator_name:算子类型。Vertica 的执行引擎将 SQL 拆解成一系列算子,数据从底部的 Scan 算子流入,经过 Join、Filter、GroupBy 等算子逐层处理,最终从 Root 算子输出结果。

常见高 CPU 算子的深入解读:

算子 它做什么 为什么会 CPU 高 优化方向
GroupByHash 在内存中构建哈希表来聚合数据。所有 GROUP BY 列的值被哈希后放入哈希桶中。 聚合的数据量太大,哈希表装不下内存,数据被迫溢出到磁盘(Spill)。溢出的数据需要排序后再聚合,排序是 CPU 密集操作。 ① 增大资源池的 query_budget,让哈希表有更多内存;② 创建按 GROUP BY 列排序的投影,让优化器用 GroupByPipe(流式聚合,不需要哈希表)替代 GroupByHash;③ 减少 GROUP BY 的列数。
JoinHash 将 JOIN 的内表(较小的那张表)完全加载到内存中构建哈希表,然后扫描外表进行匹配。 内表太大导致哈希表构建时间长;或者哈希表太大导致内存频繁换页。 ① 确保统计信息准确,这样优化器能正确选择内外表顺序;② 对齐两张表的 SEGMENTED BY 键,使 JOIN 在同一节点内完成,避免跨节点数据传输;③ 如果内表确实太大,可考虑预关联投影(pre-join projection)。
Scan 从磁盘读取列数据并解压缩。每一列的解压都需要 CPU 参与运算。 扫描的数据量太大(全表扫描);列编码类型复杂导致解压 CPU 高;没有分区裁剪导致读取了不需要的 ROS 文件。 ① 添加 WHERE 谓词匹配分区键,启用分区裁剪;② CPU 瓶颈时考虑将编码改为 NONERLE(牺牲压缩率换 CPU);③ 确保查询只 SELECT 需要的列,不用的列不要写 SELECT *
NetworkSend / NetworkRecv 将数据通过网络发送到其他节点(send),或接收其他节点发来的数据(recv)。 表的 SEGMENTED BY 键与 JOIN 键不一致,导致数据需要跨节点重新分布(RESEGMENT)。每一条数据都要经过序列化、网络传输、反序列化三个 CPU 密集步骤。 ① 对齐 JOIN 两端表的 SEGMENT BY 键;② 如果 RESEGMENT 不可避免,确保网络带宽充足(万兆网卡、UDP 缓冲区足够)。
Sort 对数据流进行排序。如果内存足够,用快排;不够则用外部归并排序。 排序的数据量超过内存,退化为外部排序,涉及大量磁盘 I/O 和多次归并比较。 ① 确保投影的 ORDER BY 列与查询的 ORDER BY 一致,这样数据本身已经排好序,Vertica 可以跳过 Sort 算子;② 增大 query_budget 提供更多排序内存。
ExprEval 计算表达式,如 C1 + C2UPPER(name)CASE WHEN 等。 表达式复杂(嵌套多层函数),或者表达式应用于数亿行数据。 ① 简化表达式,将复杂计算在 ETL 阶段预先算好存入表;② 避免在 WHERE 子句中对列使用函数(如 WHERE UPPER(name) = 'ABC'),这会阻止分区裁剪和索引优化。

3.6 第六步:按节点拆分,识别数据倾斜

同样的查询,在不同节点上的 CPU 消耗应该大致均衡。如果某个节点的 CPU 时间明显比其他节点高,说明数据分布不均匀。

SELECT node_name,
       operator_name,
       path_id,
       SUM(counter_value) AS sum_time,
       COUNT(DISTINCT operator_id) AS num_operators
FROM dc_execution_engine_profiles
WHERE transaction_id = :t_id
  AND statement_id = :s_id
  AND counter_name ILIKE 'execution%'
GROUP BY node_name, operator_name, path_id
ORDER BY 4 DESC
LIMIT 20;

如何解读:

  • 某节点的 sum_time 是其他节点的 2 倍以上:该节点存储的数据量远超其他节点(数据倾斜)。需要检查投影的分段键(SEGMENTED BY)是否选择了基数不均匀的列。比如按 gender 分段(只有男/女两个值),数据只会分布到两个节点上。
  • num_operators 远小于 EXECUTIONPARALLELISM:该查询的并行度没有达到资源池配置的期望值。可能的原因包括:ROS container 数量太少不足以拆分为更多并行算子;或者 EXECUTIONPARALLELISM 设置过高但实际数据量无法支撑那么多并行线程。

如何验证数据倾斜:

SELECT node_name,
       projection_schema,
       projection_name,
       SUM(row_count) AS row_count
FROM v_monitor.projection_storage
WHERE projection_name = 'your_projection_name'
GROUP BY 1, 2, 3
ORDER BY 4 DESC;

如果某节点的 row_count 占总量 50% 以上(在 3 节点集群中),说明数据严重倾斜。根本原因几乎都是分段键选择不当。

3.7 第七步:区分 CPU 时间与墙上时钟时间

这是容易被忽略但非常有用的分析维度。execution_engine_profiles 有两个关键计数器:

  • execution time (us):线程实际占用 CPU 的计算时间(CPU time)。如果你有 4 个并行线程各跑了 10 秒,execution time = 40 秒。
  • clock time (us):算子从开始到结束的墙上时钟时间(wall-clock time),包括等待 I/O、等待网络、等待上游算子的时间。同样 4 线程各跑 10 秒,clock time = 10 秒。
SELECT path_id, operator_name,
       SUM(DECODE(counter_name, 'execution time (us)', counter_value, NULL)) AS cpu_time_us,
       SUM(DECODE(counter_name, 'clock time (us)', counter_value, NULL)) AS clock_time_us,
       SUM(DECODE(counter_name, 'execution time (us)', counter_value, NULL)) /
       NULLIF(SUM(DECODE(counter_name, 'clock time (us)', counter_value, NULL)), 0)::FLOAT AS cpu_ratio
FROM execution_engine_profiles
WHERE transaction_id = :t_id
  AND statement_id = :s_id
GROUP BY path_id, operator_name
ORDER BY cpu_time_us DESC;

如何解读 cpu_ratio

  • cpu_ratio ≈ 1.0 或更高(高可能因为多线程并行):纯 CPU 密集型。算子在拼命计算,几乎没有等待。优化方向是提高单线程效率(改进算法、简化表达式)或降低并行度释放 CPU 给其他查询。
  • cpu_ratio < 0.5:算子大部分时间在等待——要么等磁盘 I/O(Scan 算子常见),要么等网络数据(NetworkRecv 算子常见),要么等上游算子产出数据(对应 input queue wait (us) 计数器)。优化方向是减少等待源(优化 I/O、减少数据传输)。

3.8 第八步:检查查询阶段耗时——CPU 花在了哪一步

一个查询从提交到完成,要经历多个阶段。dc_query_executions 表记录了每个阶段在初始化节点和执行节点的耗时。

-- 初始化节点(initiator node)的各阶段耗时
SELECT node_name, execution_step,
       (completion_time - "time") AS duration
FROM v_internal.dc_query_executions
WHERE transaction_id = :t_id
  AND statement_id = :s_id
  AND node_name = (SELECT local_node_name())
ORDER BY "time";

各阶段的含义和异常判断:

阶段 做什么 异常信号 可能原因
Plan 优化器分析 SQL、评估统计信息、生成最优执行计划 耗时 > 1 秒 涉及的投影太多(>1000 个),或统计信息缺失导致优化器评估不准,或需排队等 catalog 锁
SerializePlan 将执行计划序列化并通过 spread 发送给所有执行节点 耗时 > 500ms UDP 网络问题(丢包、超时),集群 spread 通信异常
CompilePlan:ReserveResources 向 Resource Manager 申请内存和线程 耗时异常长 资源池资源紧张,需排队等待其他查询释放资源
CompilePlan:EEpreexecute 初始化算子:分配内存、启动线程、打开网络连接 耗时异常长 需要打开的文件句柄太多(ROS container 过多),或线程启动开销大
ExecutePlan 实际执行查询,数据在各算子间流动处理 占总时间 90%+ 是正常的 如果占比太低(比如只占 50%),说明时间花在了规划和资源申请上,问题不在查询本身
AbandonPlan 清理资源、释放锁、关闭连接 耗时 > 1 秒 也是 UDP 通信问题(发送 AbandonPlan 消息到所有节点)

4. 使用资源池控制 CPU 消耗

前面的步骤定位了 CPU 消耗来源。接下来我们讨论如何控制和限制 CPU 使用——这是治标(防止单条查询或某类工作负载独占 CPU)和治本(合理分配资源)结合的手段。

4.1 理解资源池与 CPU 的关系

Vertica 的资源池(Resource Pool)本质上是一个资源限额系统。它定义了:

  • 内存:每个池最多能用多少内存(MAXMEMORYSIZE
  • 并发:每个池最多同时运行多少条查询(MAXCONCURRENCY
  • 并行度:每条查询最多能使用多少个 CPU 线程(EXECUTIONPARALLELISM
  • 查询预算:每条查询的初始内存配额(query_budget

这些参数共同决定了 CPU 的分配方式。核心逻辑是:

EXECUTIONPARALLELISM 决定了单条查询能吃多少 CPU → MAXCONCURRENCY 决定了同时有多少条查询在吃 CPU → 两者相乘大致决定了该资源池的总 CPU 占用。

默认资源池 general 的行为:

  • EXECUTIONPARALLELISM = AUTO(等于 CPU 核心数,如 48 核则 48 线程)
  • MAXCONCURRENCY 无硬限制
  • PLANNEDCONCURRENCY = AUTO(也等于 CPU 核心数)

这意味着:默认配置下,每一条查询都可能尝试占用所有 CPU 核心。如果同时有 5 条查询在运行,每条申请 48 个线程,就会有 240 个线程争抢 48 个核心,上下文切换开销急剧上升,整体吞吐量反而下降。

4.2 核心参数详解

参数 作用 对 CPU 的直接影响 经验建议
EXECUTIONPARALLELISM 单条查询最多使用的线程数 最关键参数。值越大,单查询能并行处理的算子越多,CPU 占用越高。设置为 AUTO 时等于节点 CPU 核心数。 高并发 OLTP 类短查询:4-8;中等分析查询:16-24;大型 ETL/报表:32-48。总原则是 核心数 × 资源池数 × 40% 左右。
MAXCONCURRENCY 资源池最多同时运行的查询数 超出后新查询进入队列等待,不会消耗 CPU。这是防止 CPU 过载的「硬刹车」。 设置为 PLANNEDCONCURRENCY 的 50%-80%。过大会导致 CPU 过载,过小会导致查询排队时间过长。
PLANNEDCONCURRENCY 优化器用于计算 query_budget 的预期并发数 不直接控制 CPU,但影响内存预算,间接影响查询是否会 spill 到磁盘(spill = 额外 CPU 消耗)。 设置为高峰期预期的并发查询数的 1.2-1.5 倍,留一些余量。
RUNTIMECAP 单条查询的最长运行时间 超时后查询被自动终止,释放 CPU。防止「失控查询」长期占用资源。 短查询池:1-5 分钟;中等查询:15-30 分钟;长报表/ETL:设置一个合理上限(如 2 小时)。
QUEUETIMEOUT 查询在队列中最长等待时间 超时后查询被取消。防止查询永久排队。 通常设置为 300-600 秒。过短会导致高并发时大量查询被取消。

4.3 策略一:降低 EXECUTIONPARALLELISM —— 牺牲单查询速度换吞吐

这是解决 CPU 过载最直接有效的方法。原理很简单:不让任何单条查询独占所有 CPU 核心。

-- 创建一个适合高并发场景的资源池
CREATE RESOURCE POOL high_concurrency_pool
    MEMORYSIZE '0%'              -- 不预留独占内存
    MAXMEMORYSIZE '60%'          -- 池中所有查询最多用 60% 节点内存
    PLANNEDCONCURRENCY 60        -- 预期 60 条查询同时运行
    MAXCONCURRENCY 30            -- 硬限制最多 30 条
    EXECUTIONPARALLELISM 4       -- 每条查询最多 4 个线程
    PRIORITY 0;

不同工作负载的推荐值:

工作负载类型 EXECUTIONPARALLELISM 原因
ETL / COPY 数据加载 4-8 数据加载主要是 I/O 密集型,过多的 CPU 线程反而增加锁竞争
短 ad-hoc 查询(< 5 秒) 8-16 短查询需要快速响应,适度并行能显著降低延迟
BI 报表查询(10-120 秒) 16-32 需要较高并行度扫描大量数据,但也不能无限放大
大型批处理 / 数据导出 AUTO / 2 保留一半核心给其他工作负载

将用户分配到自定义资源池:

-- 将用户分配到指定资源池
ALTER USER analyst_user RESOURCE POOL high_concurrency_pool;

-- 或者在会话级别临时切换
SET SESSION RESOURCE POOL high_concurrency_pool;

4.4 策略二:级联资源池 —— 长短查询分离

理想情况下,短查询(几秒完成)和长查询(几分钟甚至几小时)应该分开处理。但用户往往不知道自己的查询是长是短。级联资源池(Cascade Resource Pool)的思路是:

  1. 所有查询先进入「快速池」,获得较小的内存预算和适中的并行度
  2. 如果查询在规定时间内(RUNTIMECAP)没完成,自动级联到下一级池
  3. 下一级池可以设置更大的内存预算和更高的并行度,但并发限制更严格
  4. 逐级级联,直到最大超时被终止
-- 第一级:快速池(3 秒内完成)
-- 内存预算小(fast in, fast out),并行度适中
CREATE RESOURCE POOL fast_pool
    MAXMEMORYSIZE '40%'
    PLANNEDCONCURRENCY 30
    EXECUTIONPARALLELISM 8
    RUNTIMECAP '00:00:03'
    CASCADE TO medium_pool;

-- 第二级:中速池(3 秒到 15 秒)
-- 级联到此的查询会重新规划,获得更大的内存预算和更高的并行度
CREATE RESOURCE POOL medium_pool
    MAXMEMORYSIZE '20%'
    PLANNEDCONCURRENCY 15
    EXECUTIONPARALLELISM 16
    RUNTIMECAP '00:00:15'
    CASCADE TO slow_pool;

-- 第三级:慢速池(15 秒到 5 分钟)
-- 最大并行度但最少并发数,只允许少数几条大查询同时跑
CREATE RESOURCE POOL slow_pool
    MAXMEMORYSIZE '10%'
    PLANNEDCONCURRENCY 3
    EXECUTIONPARALLELISM 24
    RUNTIMECAP '00:05:00';

关键配置:启用级联重新规划

默认情况下,级联到新池的查询不会重新规划(replan),只是换个地方继续跑。但配合 CascadeResourcePoolAlwaysReplan = 1 会在级联时重新规划查询——这样可以获得新池的更大 query_budget,对大查询的性能提升非常明显。

ALTER DATABASE DEFAULT SET CascadeResourcePoolAlwaysReplan = 1;

级联池的工作流程图:

所有 SELECT 查询 → fast_pool(3s / 8线程 / 小预算)
                    ├─ 3秒内完成 → 返回结果
                    └─ 超时 → 级联到 medium_pool(15s / 16线程 / 中等预算)
                                ├─ 15秒内完成 → 返回结果
                                └─ 超时 → 级联到 slow_pool(5min / 24线程 / 大预算)
                                            ├─ 5分钟内完成 → 返回结果
                                            └─ 超时 → 查询被终止

4.5 策略三:监控资源池健康度

创建资源池后,需要持续监控它是否按预期运行:

-- 查看各资源池的当前并发和内存状态
SELECT pool_name,
       running_query_count,
       planned_concurrency,
       max_concurrency,
       query_budget_kb,
       memory_inuse_kb,
       general_memory_borrowed_kb
FROM resource_pool_status
WHERE pool_name NOT IN ('sysquery', 'sysdata')
ORDER BY running_query_count DESC;

需要关注的信号:

  • running_query_count 持续等于 max_concurrency:并发已打满,新查询在排队。考虑增大 MAXCONCURRENCY 或优化慢查询。
  • general_memory_borrowed_kb > 0:该池从 general 池借了内存,说明 MAXMEMORYSIZE 不够用。
  • query_budget_kb 只有几百 MB:内存预算太小,几乎所有查询都会频繁 spill 到磁盘,导致 CPU 消耗翻倍。

5. 调整查询计划来降低 CPU 消耗

资源池控制的是「量」(多少查询同时跑、每条能用多少资源),而查询计划优化控制的是「质」(每条查询本身的效率)。两者配合才能真正解决 CPU 高负载问题。

5.1 负面事件检查 —— 快速定位问题方向

Vertica 在执行每条查询时,会记录「查询事件」(Query Events)。事件分为正面(优化做得好)和负面(有问题需要关注)。排查时应首先关注负面事件。

SELECT event_type,
       event_category,
       operator_name,
       path_id,
       event_description,
       suggested_action
FROM query_events
WHERE transaction_id = :t_id
  AND statement_id = :s_id
  AND event_category = 'EXECUTION'
ORDER BY operator_name;

最常见的几个负面事件及其解决方向:

事件类型 含义 为什么会增加 CPU 解决方向
GROUP_BY_SPILLED GROUP BY 的哈希表装不进内存,数据溢出到磁盘 溢出到磁盘的数据需要排序后再聚合——排序是 CPU 密集操作,而且磁盘 I/O 增加了等待时间,线程空转更多 ① 增大 query_budget;② 创建按 GROUP BY 列排序的投影,让优化器用 GroupByPipe(流式聚合)代替 GroupByHash
JOIN_SPILLED JOIN 的内表装不进内存,退化为外部归并连接 外部归并连接需要先排序再归并,CPU 消耗远超哈希连接 ① 增大 query_budget;② 检查统计信息是否准确(优化器可能选错了内外表);③ 考虑为 JOIN 建预连接投影
RESEGMENTED_MANY_ROWS 大量数据行在查询执行期间被跨节点重新分段 每条数据都要序列化 → 网络发送 → 网络接收 → 反序列化,每一步都是 CPU 消耗 ① 对齐 JOIN 两张表的 SEGMENTED BY 键;② 如果无法对齐,检查是否是真正的查询需求——能否改变查询逻辑避免跨节点 JOIN
MEMORY_LIMIT_HIT 优化器在规划查询时用光了分配的内存(默认 100MB) 优化器无法生成最优计划,退而求其次生成次优计划,导致执行效率下降 ① 增加 MaxOptMemMB 配置参数;② 删除不用的旧投影减少优化器评估对象

5.2 虚构案例一:GROUP BY Spill 的深入诊断与修复

📝 虚构案例

场景:某报表查询对 5 亿行数据做 GROUP BY region, product_category, date 聚合,执行时间超过 10 分钟,CPU 使用率持续 95%。

诊断步骤:

-- 1. 确认负面事件
SELECT event_type, suggested_action
FROM query_events
WHERE transaction_id = :t_id AND statement_id = :s_id;
-- 结果: GROUP_BY_SPILLED + "Consider a sorted projection. Increase memory available to the plan."

-- 2. 查看 GroupByHash 算子的内存和溢出情况
SELECT operator_name, counter_name, SUM(counter_value)
FROM execution_engine_profiles
WHERE transaction_id = :t_id AND statement_id = :s_id
  AND operator_name = 'GroupByHash'
GROUP BY operator_name, counter_name
ORDER BY 3 DESC;
-- 关注: memory allocated, size of raw temp data (bytes)
-- 如果 temp data 很大 → 确实大量溢出到磁盘

修复方案(按推荐优先级):

  1. 创建按 GROUP BY 列排序的投影(首选,效果最好):

    CREATE PROJECTION sales_grouped_proj AS
    SELECT * FROM sales_fact
    ORDER BY region, product_category, date
    SEGMENTED BY HASH(region) ALL NODES;
    
    这样数据按 region → product_category → date 顺序物理排序存储在磁盘上。查询时 Vertica 可以直接用 GroupByPipe 流式聚合——每读完一组的最后一行就输出聚合结果,全程不需要哈希表,也不会有 Spill。

  2. 增大内存预算(次选,快速见效):

    -- 为该报表用户创建专用资源池
    -- query_budget = MAXMEMORYSIZE / PLANNEDCONCURRENCY
    -- 节点 256GB × 30% / 9 ≈ 8.5GB 预算
    CREATE RESOURCE POOL report_pool
        MAXMEMORYSIZE '30%'
        PLANNEDCONCURRENCY 9
        EXECUTIONPARALLELISM 32;
    ALTER USER report_user RESOURCE POOL report_pool;
    

  3. 减少 GROUP BY 粒度(如果业务允许): 如果用户不需要按 date 分组,去掉它可以显著减少哈希表大小。

5.3 虚构案例二:Join RESEGMENT 的深入诊断与修复

📝 虚构案例

场景:两个大表 orders(按 order_id 分段)和 customers(按 customer_id 分段)做 JOIN,JOIN 条件是 orders.customer_id = customers.customer_id。执行计划中出现 RESEGMENTED_MANY_ROWS 事件。

为什么会有 RESEGMENT?

因为 orders 的分段键是 order_id,但 JOIN 键是 customer_id。Vertica 无法在同一节点内完成 JOIN(因为 order_id 分段的 orders 数据分布在所有节点,customer_id 分段的 customers 数据也分布在所有节点,但两者的分布规则不同)。数据库必须把其中一张表的数据按 customer_id 重新分布到各节点——这就是 RESEGMENT。

修复方案:

orders 的分段键改为 customer_id,与 JOIN 键对齐:

CREATE PROJECTION orders_segmented_by_cust AS
SELECT * FROM orders
SEGMENTED BY HASH(customer_id) ALL NODES;

这样两张表的数据按相同的键(customer_id)分布在相同的节点上,JOIN 可以在本地完成,不需要任何网络传输。

如何判断是否应该改分段键?

如果这个 JOIN 是系统中最频繁执行的查询之一,那么值得为该查询调整分段设计。如果只是偶尔跑一次,那么 RESEGMENT 的开销是可以接受的。

5.4 虚构案例三:编码与 CPU 的权衡

📝 虚构案例

Vertica 的列编码(Encoding)是个「用 CPU 换磁盘空间」的机制。编码类型越复杂,压缩率越高,但解压缩需要的 CPU 也越多。

常见编码类型的 CPU 消耗排序(从低到高):

NONE(无编码)< RLE(游程编码)< BLOCK_DICT(块字典)< DELTAVAL(增量值)< LZO(通用 LZO 压缩)

AUTO 编码在大多数情况下选择了压缩率较高的类型,对于 CPU 瓶颈的场景可能不够友好。

如何判断编码是否成为 CPU 热点:

-- 查看 Scan 算子的 execution time 和 rows produced
-- 如果 execution time 很大但 rows produced 不是特别多
-- 可能是编码解压消耗了大量 CPU

SELECT operator_name, counter_name, AVG(counter_value)
FROM execution_engine_profiles
WHERE transaction_id = :t_id AND statement_id = :s_id
  AND operator_name = 'Scan'
  AND counter_name IN ('execution time (us)', 'rows produced',
                        'rle rows produced', 'bytes read from disk')
GROUP BY operator_name, counter_name;
  • rle rows produced 接近 rows produced:说明大部分数据以 RLE 格式直接处理,没有物化(解压)——这是理想状态,CPU 消耗低。
  • bytes read from disk 很小但 execution time 很大:数据压缩率很高,但解压很花 CPU——可以尝试降低编码复杂度。

修改编码:

-- 对 CPU 瓶颈场景,改为 NONE 或 RLE
ALTER PROJECTION big_fact_b0 ALTER COLUMN metric_col SET ENCODING NONE;
ALTER PROJECTION big_fact_b0 ALTER COLUMN dim_key_col SET ENCODING RLE;

注意:修改编码需要刷新投影(REFRESH PROJECTION),会触发数据重写。在生产环境操作前先评估影响范围。

5.5 统计信息的重要性

你可能会疑惑:为什么统计信息缺失会导致 CPU 高?因为优化器依赖统计信息来估算每个执行计划步骤的成本。没有统计信息,优化器只能猜测——猜错了就生成次优计划,导致不必要的 CPU 消耗。

例如:优化器需要决定 JOIN 的内外表顺序。如果它不知道小表只有 1000 行而大表有 1 亿行,它可能错误地把大表当作内表加载到内存中构建哈希表——这会导致巨大的内存消耗和后续的 spill。

-- 已验证:Vertica v26.1.0-2
SELECT projection_name,
       projection_column_name,
       statistics_type,
       statistics_updated_timestamp
FROM v_catalog.projection_columns
WHERE table_name IN ('your_key_table1', 'your_key_table2')
  AND sort_position >= 0  -- 只查排序键列的统计信息
ORDER BY projection_name, sort_position;

-- 补充或更新统计信息
SELECT ANALYZE_STATISTICS('schema.table_name');

最佳实践:每次大批量加载数据后,对受影响的大表运行 ANALYZE_STATISTICS。可以设置定时任务自动执行。

5.6 分区裁剪 —— 让 Vertica 少读不必要的数据

分区裁剪(Partition Pruning)是 Vertica 减少 Scan 消耗的核心机制。它的原理很简单:如果表按日期分区,查询中带有 WHERE date = '2026-05-01',Vertica 只会读取 date=2026-05-01 这个分区的 ROS 文件,其他分区的文件直接跳过。

没有分区裁剪 → 全表扫描所有 ROS 文件 → 解压所有数据 → 在过滤器中丢弃不需要的行 → 巨大 CPU 浪费。

-- 检查表是否有分区
SELECT table_schema, table_name, partition_expression
FROM v_catalog.tables
WHERE partition_expression IS NOT NULL;

-- 为没有分区的大表添加分区
ALTER TABLE large_fact_table PARTITION BY date_column;

分区键的选择原则:

  1. 选择查询中最常用的过滤条件列(通常就是日期列)
  2. 分区基数不宜太大也不宜太小——每天一个分区很合理,每分钟一个分区就是灾难
  3. 分区表达式会应用到该表的所有投影,不需要在每个投影上单独定义

5.7 SIP(横向信息传递)的性能评估

SIP(Sideways Information Passing)是 Vertica 的一种优化技术:在 Hash Join 中,优化器会将内表的过滤条件「传递」给外表,在 Scan 阶段就提前过滤掉不匹配的行,减少后续 JOIN 的数据量。

但 SIP 本身也有 CPU 开销(过滤逻辑需要在 Scan 时执行)。如果 SIP 过滤掉的行很少(比如 < 5%),它的开销可能大于收益。

-- 评估 SIP 的过滤效果
SELECT node_name, sip_expr_id,
       rows_processed, rows_pruned,
       ROUND((rows_pruned::FLOAT / NULLIF(rows_processed, 0)) * 100, 2) AS prune_pct
FROM dc_sips_statistics
WHERE transaction_id = :t_id AND statement_id = :s_id
ORDER BY prune_pct DESC;
  • prune_pct > 20%:SIP 效果很好,过滤掉了大量不必要的数据,应保留。
  • prune_pct < 5%:SIP 几乎没过滤掉什么数据,但开销仍存在。可以考虑在该查询中禁用 SIP:
    SELECT /*+add_vertica_options(BASIC, DISABLE_SIPS) */ ...;
    

5.8 真实案例一:CPU 降频导致集群性能持续恶化

📋 真实案例

背景:某运营商经分系统,138 节点 Vertica v11.1.1-20 集群,运行于国产欧拉操作系统(BigCloud Enterprise Linux For Euler 21.10)。硬件为 HP DL380 Gen9 及少量 RH2288HV5 服务器。

现象:业务高峰期(下午至晚间)查询响应时间逐步恶化,长查询从正常 2-3 分钟延长至 15-20 分钟。系统级排查发现 CPU 使用率并不高(约 60%),但 uptime 显示的 load average 异常。

诊断过程

# 第 1 步:确认 CPU 频率状态
cpupower frequency-info
# 输出显示:当前频率仅 1.2 GHz,而硬件基础频率为 2.6 GHz
# 频率降幅超过 50%,说明 CPU 处于深度降频状态

# 第 2 步:查看 CPU 调速器
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
# 输出:powersave ← 这是问题根源
# powersave 调速器在国产欧拉系统上过于激进,CPU 稍有空闲就降频

# 第 3 步:查看当前频率与最大频率的比值
grep -E 'cpu MHz|model name' /proc/cpuinfo | head -20

根因分析:国产欧拉操作系统的默认 CPU 调速策略为 powersave,该策略在 Vertica 这种「突发计算型」负载下表现很差——查询一来需要全力计算,但 CPU 从低频升到高频有延迟(P-State 切换延迟约 100-500μs),导致查询执行时间被人为拉长。此外,国产 CPU 的 P-State 控制机制不如 Intel SpeedStep 成熟。

修复措施

# 1. 将所有 CPU 调速器切换为 performance(立即生效,无需重启)
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
    echo performance > $cpu
done

# 2. 可选:通过 BIOS 彻底固化(需重启,更彻底)
# Power Profile → Maximum Performance
# Processor Monitor/Mwait Support → Disabled

效果:CPU 频率固化在 2.6 GHz 后,同样查询执行时间从 15-20 分钟恢复至 2-3 分钟。load average 从 60+ 降至 15-20(对应正常排队水平)。CPU 使用率从 60% 升至 85%——这看似变差了,实际上是「好事」:CPU 不再偷懒降频,全力工作。

关键教训:CPU 使用率低≠CPU 没有瓶颈。降频状态下 CPU「假装不忙」但实际处理能力大幅下降。排查 CPU 问题时务必先 cpupower frequency-info 确认频率状态。

5.9 真实案例二:国产 CPU 架构导致跨节点网络性能骤降

📋 真实案例

背景:某政务系统,Vertica Eon 模式集群,混合部署了 Intel Xeon Gold 6330N 服务器和国产 Hygon C86 7390 服务器。扩容时新加入了 28 台 Hygon 节点。

现象:扩容后数据库整体性能反而比扩容前更差。业务高峰期查询平均延迟从 800ms 升至 3200ms。初期怀疑是数据分布或资源池问题。

诊断过程

# 第 1 步:vnetperf 测试各节点网络延迟
/opt/vertica/bin/vnetperf
# Intel 节点间 RTT:180-200 μs ← 正常
# Hygon 节点间 RTT:400-600 μs ← 明显偏高
# Intel ↔ Hygon 跨架构 RTT:800-1200 μs ← 严重异常

# 第 2 步:UDP 吞吐测试
iperf3 -u -c <target> -b 10G
# Intel 节点:UDP 约 800 MB/s
# Hygon 节点:UDP 仅约 200 MB/s ← 只有 Intel 的 1/4!

根因分析:三层根因叠加:

  1. 网卡固件兼容性:Hygon 服务器使用 NIC-10GE-2P-530F-B2 网卡,在 Red Hat 7.6 上的固件版本(1.712.30-0)未经官方测试认证
  2. Bonding 方式差异:Hygon 使用同一张网卡的两个口做 Bond4,Intel 使用两张独立网卡各取一个口做 Bond4——Hygon 的单卡方案使中断全部汇聚到一个 PCIe 通道,CPU 中断风暴严重
  3. UDP 缓冲区不足:国产 CPU 处理网络中断的效率低于 Intel,默认 UDP 缓冲区无法吸收突发流量,导致大量 UDP 丢包和重传

修复措施

# 1. 调整网卡 Ring Buffer 到最大(减少丢包)
ethtool -G ens5f0 rx 4096 tx 4096

# 2. 调整网卡队列到最大(利用多核并行处理网络中断)
ethtool -L ens5f0 combined 32

# 3. 增大 UDP 缓冲区(内核参数)
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728

# 4. 将业务流量从 Hygon 集群切回 Intel 集群
# 待 Hygon 网络问题修复后再逐步切回

效果:业务切回 Intel 集群后性能立即恢复。Hygon 集群网络优化后 UDP 吞吐从 200 MB/s 提升至约 600 MB/s,仍低于 Intel,但已可支撑业务。

关键教训:MPP 数据库中 CPU 架构不仅要看计算性能,更要看网络处理能力。Vertica 的 spread 协议依赖低延迟高吞吐的 UDP 通信,国产 CPU + 未认证网卡驱动的组合是高风险配置。


6. 完整诊断流程实战示例

📝 虚构场景 · 完整演练

下面通过一个完整案例,展示从发现到解决的整个流程。

场景

  • 3 节点 Vertica 集群(每节点 48 核 / 256GB 内存)
  • 用户反馈:下午 2 点到 4 点期间,所有查询都非常慢
  • 检查发现:CPU 持续 95%,load average > 60

诊断过程

Step 1:确认系统状态

SELECT node_name, average_cpu_usage_percent
FROM v_monitor.system_resource_usage
WHERE end_time > sysdate - INTERVAL '1 hour'
ORDER BY end_time DESC;

→ 三个节点的 CPU 都在 92%-98% 之间,确认 CPU 是瓶颈。

Step 2:排除 Recovery 和 TM

SELECT * FROM recovery_status WHERE is_running = 't';    -- 0 rows
SELECT * FROM tuple_mover_operations WHERE is_executing = 't'; -- 0 rows

→ 不是 Recovery,也不是 TM。问题在查询层面。

Step 3:找慢查询

SELECT user_name, request_duration_ms, memory_acquired_mb, LEFT(request, 150)
FROM v_monitor.query_requests
WHERE is_executing = 't'
ORDER BY request_duration_ms DESC
LIMIT 10;

→ 发现 analyst_user 有 3 条查询跑了超过 15 分钟,SQL 都是对 10 亿行销售表做多维度聚合。同时有 25 条短查询在排队。

Step 4:PROFILE 最慢的查询

\timing
\o /dev/null
PROFILE SELECT region, category, SUM(amount), COUNT(*)
FROM sales_fact WHERE date BETWEEN '2026-05-01' AND '2026-05-28'
GROUP BY region, category;
\o

→ 执行时间 18 分钟。看到 GROUP_BY_SPILLED 事件和 total temp data = 45GB。

Step 5:分析算子级 CPU

SELECT operator_name, path_id, SUM(counter_value)
FROM execution_engine_profiles
WHERE transaction_id = :t_id AND statement_id = :s_id
  AND counter_name ILIKE 'execution%'
GROUP BY 1, 2 ORDER BY 3 DESC;

GroupByHash: 480 秒(占总时间 44%) → Scan: 320 秒(占总时间 30%) → JoinHash: 180 秒(占总时间 17%)

分析结论

  1. GroupByHash 是最大瓶颈——数据量太大,哈希表装不下,频繁 spill 到磁盘
  2. Scan 时间也很长——sales_fact 表没有分区,全表扫描了所有数据
  3. 资源池是默认的 general,没有并发限制,25 条查询同时争抢资源

修复措施

立即措施(当天执行):

-- 1. 限制 analyst_user 的并发数
CREATE RESOURCE POOL analyst_pool
    MAXMEMORYSIZE '40%'
    PLANNEDCONCURRENCY 8
    MAXCONCURRENCY 4          -- 最多同时跑 4 条分析查询
    EXECUTIONPARALLELISM 16   -- 每条最多 16 个线程
    RUNTIMECAP '00:30:00';
ALTER USER analyst_user RESOURCE POOL analyst_pool;

短期优化(当周执行):

-- 2. 为 sales_fact 表添加日期分区
ALTER TABLE sales_fact PARTITION BY date;

-- 3. 创建按分组列排序的投影
CREATE PROJECTION sales_analyst_proj AS
SELECT * FROM sales_fact
ORDER BY region, category, date
SEGMENTED BY HASH(region) ALL NODES;

-- 4. 更新统计信息
SELECT ANALYZE_STATISTICS('public.sales_fact');

效果验证:

  • 同样的查询从 18 分钟降到 45 秒
  • CPU 使用率从 95% 降到 60%
  • 查询排队现象消失

7. 快速诊断 SQL 工具箱

诊断目标 SQL
系统 CPU 历史 SELECT * FROM system_resource_usage ORDER BY end_time DESC;
连接均衡检查(CPU 不均时优先) SELECT node_name, COUNT(*) FROM dc_requests_issued WHERE "time" > sysdate() - 1 GROUP BY 1 ORDER BY 1;
当前慢查询 SELECT * FROM query_requests WHERE is_executing='t' ORDER BY request_duration_ms DESC;
算子 CPU 热点 SELECT operator_name, path_id, SUM(counter_value) FROM execution_engine_profiles WHERE counter_name ILIKE 'execution%' GROUP BY 1,2 ORDER BY 3 DESC;
负面事件 SELECT event_type, event_category, suggested_action FROM query_events WHERE transaction_id=:t_id AND statement_id=:s_id;
查询阶段耗时 SELECT execution_step, (completion_time-"time") FROM dc_query_executions WHERE transaction_id=:t_id AND statement_id=:s_id;
CPU vs Clock 比值 SELECT path_id, operator_name, SUM(DECODE(counter_name,'execution time (us)',counter_value,NULL))/NULLIF(SUM(DECODE(counter_name,'clock time (us)',counter_value,NULL)),0) FROM execution_engine_profiles WHERE ... GROUP BY 1,2;
节点数据均衡 SELECT node_name, SUM(row_count) FROM projection_storage WHERE projection_name='...' GROUP BY 1 ORDER BY 2 DESC;
资源池并发 SELECT pool_name, running_query_count, max_concurrency, query_budget_kb FROM resource_pool_status;
资源拒绝 SELECT * FROM resource_rejections;
TM 操作 SELECT * FROM tuple_mover_operations WHERE is_executing='t';
Recovery 状态 SELECT * FROM recovery_status WHERE is_running='t';
SIP 过滤效果 SELECT sip_expr_id, rows_processed, rows_pruned FROM dc_sips_statistics WHERE transaction_id=:t_id AND statement_id=:s_id;

8. 最佳实践清单

  1. 为不同工作负载分离资源池:ETL、短查询、长报表各用独立池,互不干扰。这是投入产出比最高的优化。
  2. 启用客户端连接负载均衡:服务端 SET_LOAD_BALANCE_POLICY('ROUNDROBIN') + 客户端 ConnectionLoadBalance=1。避免所有 SQL 的编译和计划生成集中在单一 initiator node,这是节点间 CPU 不均的首要排查项。详见 Vertica 客户端连接负载均衡配置
  3. 设置 EXECUTIONPARALLELISM 小于 CPU 核心数:默认 AUTO 太激进,高并发场景下 4-16 通常更合理。宁可让单条查询稍慢,也要保证整体吞吐。
  4. 设置合理的 MAXCONCURRENCY:资源池的硬并发限制是防止 CPU 过载的最后防线。一般设为 PLANNEDCONCURRENCY 的 50%-80%。
  5. 定期更新统计信息:大表每次加载数据后执行 SELECT ANALYZE_STATISTICS('')。统计信息是优化器做出正确决策的基础。
  6. 对齐 JOIN 表的分段键:这是避免 RESEGMENTED_MANY_ROWS 最有效的手段。如果两张表经常 JOIN,它们的 SEGMENTED BY 键应该一致。
  7. CPU 瓶颈时降低编码复杂度:改为 NONERLE 可以减少解压缩的 CPU 开销,代价是更多磁盘占用。在磁盘充足、CPU 紧张的场景下值得考虑。
  8. 开启 resource_rejections 监控:资源拒绝事件是 CPU/内存不足的前兆。如果拒绝次数快速增长,说明资源池配置需要调整。
  9. 使用 RUNTIMECAP + CASCADE 防止失控查询:级联资源池能自动分离长短查询,避免一条大查询拖垮整个系统。
  10. 为大表添加分区:分区裁剪能减少 90% 以上的无效 SCAN 操作。分区键选择查询中最常用的过滤列(通常就是日期)。
  11. 为高频聚合查询创建排序投影:投影的 ORDER BY 与查询的 GROUP BY/ORDER BY 一致时,Vertica 可以用流式算子(Pipe)代替哈希算子(Hash),既省内存又省 CPU。

扩展阅读