Vertica 服务器进程数监控与管理¶
作者:JiangChong | 发布时间:2026-05-13
适用场景:当你怀疑 Vertica 节点进程/线程数异常偏高,或数据库出现"夯死"(可以连接但无法执行任何查询)时。
关联文章¶
- Vertica CPU 持续高负载诊断与优化 — 高进程数往往伴随 CPU 飙升
- Vertica 资源池最佳实践 — 资源池参数如何控制线程数
- Tuple Mover 最佳实践完全指南 — TM 并发线程的配置和监控
- Vertica 资源拒绝排查与资源池调优 — 资源拒绝往往与并发数过高相关
理解全文脉络¶
本文从 Vertica 进程/线程模型原理讲起(第 1 节),然后介绍系统级监控方法(第 2 节),接着按根因优先级逐步定位(第 3 节),再给出从快速止血到长期治理的解决方案(第 4 节),通过真实案例 + 虚构案例加深理解(第 5-6 节),最后提供快速诊断工具箱(第 7 节)和最佳实践清单(第 8 节)。
1. 原理理解¶
1.1 Vertica 的进程/线程模型¶
在 Linux 1:1 线程模型中,每个线程拥有独立的 TID(通过 gettid() 获取),与进程的 PID(实际是 TGID)共享同一个 PID 命名空间的数字槽位。每个线程都消耗一个 TID 槽位,当 kernel.pid_max 耗尽时,新线程创建会因 fork: Resource temporarily unavailable 失败。
⚠️ 关键区分:日常说的"进程数"实际上涉及两个不同指标,本文后续章节会明确标注使用的是哪一个:
| 命令 | 统计对象 | 典型值(Vertica 节点) |
|---|---|---|
ps -ef \| wc -l |
进程数(TGID) | 100 ~ 300 |
ps -eLf \| wc -l |
线程数(TID) | 200 ~ 2000 |
ps -eLf \| grep -c vertica |
Vertica 进程的线程数 | 100 ~ 1500 |
线程数(TID)才是与
kernel.pid_max耗尽直接相关的指标,因为每个线程消耗一个槽位。进程数(TGID)偏高通常意味着进程泄漏或 fork 风暴等不同类型的故障。
Vertica 是一个多线程架构的数据库。从操作系统视角看,一个 Vertica 节点包含:
- 1 个主进程:
vertica守护进程 - 大量子线程:包括查询执行线程、Tuple Mover 线程、Recovery 线程、网络 I/O 线程、Epoch 管理线程等
比喻:如果把 Vertica 节点比作一个工厂,vertica 进程是工厂建筑本身,而线程是工厂里的工人。每个查询任务需要分配一组工人(线程)去执行。如果同时来了太多任务,工厂里的工人数量就会超标,新任务找不到工人,工厂就"夯死"了。
1.2 为什么 Vertica 会产生大量线程(与传统数据库对比)¶
传统数据库的连接模型有两种主流模式:PostgreSQL 使用进程模型(每个连接 fork 一个独立进程),MySQL 默认使用线程模型(thread_handling=one-thread-per-connection,每个连接一个线程)。而 Vertica 采用线程池 + 资源池模型,与两者都不同——连接数不等于线程数,线程由全局线程池复用:
但 Vertica 的线程数不等于连接数——它受资源池参数 EXECUTIONPARALLELISM、查询计划中各 pipeline 阶段的活跃算子数、以及全局线程池上限共同决定。极端场景下一条复杂 SQL 可能产生数千个线程,但这个数字受 pipeline 调度和线程池复用的约束——并非所有算子同时全量运行。
1.3 进程数超限的触发条件与来源¶
| 来源 | 触发条件 | 线程数估算 | 严重度 |
|---|---|---|---|
| 复杂查询 | EXECUTIONPARALLELISM × 活跃算子数 × 并发数,但受 pipeline 调度和全局线程池上限约束 | 极端场景单查询可达数千 | 🔴 严重 |
| 会话泄漏 | 应用未正确关闭连接,idle session 累积 | 随 session 数线性增长 | 🟡 渐变 |
| 会话/线程挂起 | 查询执行中线程死锁或长时间挂起不释放 | 挂起线程数 = 执行中查询 × 算子线程 | 🟡 渐变 |
| 僵尸进程(UDx/外部) | UDx 外部进程异常退出后父进程未 wait | 每个僵尸占用 1 TID | 🟡 渐变 |
| TM 并发 | Mergeout 线程数 × 活跃 mergeout 数 | 单节点可达 100+ | 🟢 通常正常 |
| Recovery | 节点恢复期间重放操作 | 短期可达数千 | 🟢 临时 |
| UDx 侧进程 | UDF/UDT 外部进程未回收 | 数到数百 | 🟡 不常见 |
关键参数:EXECUTIONPARALLELISM 就像一个查询能同时用多少只手干活。默认 AUTO = 物理 CPU 核数。它控制的是单个算子内部的并行度(如一个扫描算子可以用多少线程去并行扫描),而非每个算子独占一份线程配额。
线程数的理论上界可以这样估算:EXECUTIONPARALLELISM × 查询计划中的总算子数。假设 48 核节点,EXECUTIONPARALLELISM = AUTO(48),一条 SQL 包含 30 个 UNION ALL 分支和 30+ 个 CASE WHEN 列,优化器生成 60+ 个算子,理论上界 ≈ 48 × 60 = 2,880。
但实际峰值远低于此,原因是:
- Pipeline 调度:查询计划是 DAG,只有同一 pipeline 内的算子才可能同时活跃。60 个算子分布在多个串行 pipeline 阶段中,同一时刻活跃的只有一小部分。
- 全局线程池上限:Vertica 有全局线程池,线程被复用而非每次创建。线程池上限由配置参数控制,即使算子请求更多线程也会被截断。
- 资源池
MAXCONCURRENCY:限制了同时运行的查询数,进一步缩减了并发线程。
因此公式给出的是风险方向的理论上界,用于说明"为什么 Vertica 可能消耗大量线程"的潜在风险,而非精确计算式。实际场景中的极端值(如下文案例中的 2,977 线程/查询)印证了这个风险真实存在,但这需要极高并发的 UNION ALL + 算子确实可以同时运行的条件叠加。
2. 系统级监控¶
2.1 Linux 层面:快速检查进程数¶
总线程数(TID)检查——最直接的指标,与 kernel.pid_max 耗尽风险直接相关:
如何解读:
- 200 ~ 1500:正常范围(取决于集群规模和并发负载)
- 1500 ~ 2000:偏高,建议检查线程来源
- > 2000:告警,需立即排查
- > 10000:危险,可能导致系统无法创建新线程,数据库面临夯死风险
按用户统计线程数:
如何解读:关注 dbadmin 用户的线程数是否异常偏高。正常情况下 dbadmin 占大头(vertica 主进程的所有线程),但每个节点通常 ≤ 1500。
Vertica 进程的线程数:
如何解读:ps -eLf(-L 显示线程)列出所有 task,grep vertica 过滤出数据库相关线程。如果超过 1500,需结合下文 SQL 分析来源。
进程数(TGID)检查——辅助指标,偏高通常意味着进程泄漏或外部进程异常:
如何解读:正常 Vertica 节点进程数通常在 100 ~ 300。如果 > 500,应排查是否有外部进程泄漏或 fork 风暴。
僵尸进程检查:
如何解读:僵尸进程(STAT = Z 或 defunct)虽然不消耗 CPU/内存,但占用 PID。如果累积数百个僵尸进程且系统 PID 上限较低,会导致无法创建新进程。常见来源:cron 任务中邮件发送失败产生的 sendmail/postdrop 僵尸。
2.2 Vertica 系统表:查看线程历史趋势¶
Vertica 的 Data Collector 表 dc_process_info_by_hour 记录了每小时的进程资源信息,包含 thread_count_max_value 列。
-- 查看最近 24 小时各节点的最大线程数
SELECT
node_name,
start_time::char(13) AS hour,
thread_count_max_value,
resident_size_max_value / 1024 / 1024 / 1024.0 AS resident_GB
FROM v_internal.dc_process_info_by_hour
WHERE start_time > sysdate() - 1
ORDER BY start_time DESC, thread_count_max_value DESC;
node_name | hour | thread_count_max_value | resident_GB
-------------------+---------------+------------------------+---------------------------------------------
v_vmart3_node0003 | 2026-06-03 14 | 94 | 0.54334640502929687500000000000000000000000
v_vmart3_node0001 | 2026-06-03 14 | 98 | 0.47456359863281250000000000000000000000000
v_vmart3_node0002 | 2026-06-03 14 | 95 | 0.22926330566406250000000000000000000000000
v_vmart3_node0003 | 2026-06-03 13 | 87 | 0.55018234252929687500000000000000000000000
v_vmart3_node0001 | 2026-06-03 13 | 96 | 0.49652481079101562500000000000000000000000
v_vmart3_node0002 | 2026-06-03 13 | 90 | 0.24108505249023437500000000000000000000000
v_vmart3_node0003 | 2026-06-03 12 | 94 | 0.55142593383789062500000000000000000000000
v_vmart3_node0002 | 2026-06-03 12 | 95 | 0.23972702026367187500000000000000000000000
v_vmart3_node0001 | 2026-06-03 12 | 92 | 0.48247528076171875000000000000000000000000
v_vmart3_node0003 | 2026-06-03 11 | 95 | 0.55282211303710937500000000000000000000000
v_vmart3_node0002 | 2026-06-03 11 | 96 | 0.24127578735351562500000000000000000000000
v_vmart3_node0001 | 2026-06-03 11 | 97 | 0.48545074462890625000000000000000000000000
v_vmart3_node0001 | 2026-06-03 10 | 97 | 0.47250747680664062500000000000000000000000
v_vmart3_node0002 | 2026-06-03 10 | 102 | 0.23217010498046875000000000000000000000000
v_vmart3_node0003 | 2026-06-03 10 | 107 | 0.53700637817382812500000000000000000000000
(15 rows)
如何解读:
thread_count_max_value:该小时内该节点 vertica 进程的最大线程数- < 1000:正常
- 1000 ~ 2000:偏高,关注趋势
- > 2000:告警,需要立即排查根因
- > 10,000:危险,历史上有案例在此阈值下数据库夯死
跨节点对比:如果某个节点的 thread_count 明显高于其他节点(如 2 倍以上),表明该节点存在负载不均或连接倾斜。
3. 逐步定位根因¶
3.1 步骤 1:检查当前活跃会话数¶
做什么:会话数是线程消耗的最直接来源。如果活跃会话数远超预期,继续往下排查。
SQL:
-- 查看当前活跃会话总数和分布
SELECT
node_name,
user_name,
client_hostname,
current_statement IS NOT NULL AND transaction_id != -1 AS has_active_txn,
COUNT(*) AS session_count,
MAX(TIMESTAMPDIFF('minute', login_timestamp, sysdate())) AS max_idle_min
FROM sessions
GROUP BY 1, 2, 3, 4
ORDER BY 5 DESC;
node_name | user_name | client_hostname | has_active_txn | session_count | max_idle_min
-------------------+-----------+-----------------+----------------+---------------+--------------
v_vmart3_node0001 | dbadmin | 127.0.0.1:33348 | f | 1 | 4660
v_vmart3_node0001 | dbadmin | 127.0.0.1:55544 | t | 1 | 95
v_vmart3_node0001 | dbadmin | [::1]:44864 | f | 1 | 27
v_vmart3_node0001 | dbadmin | [::1]:38162 | f | 1 | 4695
v_vmart3_node0001 | dbadmin | [::1]:34654 | f | 1 | 4663
(5 rows)
如何解读:
- 如果存在大量
has_active_txn = false且max_idle_min > 60的 session,说明存在会话泄漏——应用未正确关闭连接 - 关注那些
user_name为业务用户但client_hostname显示异常来源的 session - 如果单个节点 session 数远超其他节点,说明连接负载不均
如果不是:session 数在预期范围内,进入步骤 2。
3.2 步骤 2:检查资源池实时线程消耗¶
做什么:resource_acquisitions 系统表记录了每个 query 实际获取的线程数。这是最直接的线程消耗来源。
SQL:
-- 查看当前执行的查询各自使用了多少线程
SELECT
node_name,
pool_name,
transaction_id,
statement_id,
thread_count,
memory_inuse_kb / 1024 AS memory_inuse_mb,
open_file_handle_count,
TIMESTAMPDIFF('second', queue_entry_timestamp, sysdate()) AS elapsed_sec
FROM resource_acquisitions
WHERE thread_count > 0
ORDER BY thread_count DESC
LIMIT 30;
如何解读:
thread_count:该查询在当前资源池中获取的线程数- < 100:正常轻量查询
- 100 ~ 500:中等复杂度查询
- 500 ~ 1500:复杂查询,需关注
- > 1500:极度复杂查询或 EXECUTIONPARALLELISM 配置不当
如果看到多条查询的 thread_count 都在 500+,累加起来很快就超过系统阈值。
跨节点汇总:
-- 按节点汇总当前线程消耗
SELECT
node_name,
SUM(thread_count) AS total_threads,
COUNT(DISTINCT transaction_id) AS active_queries
FROM resource_acquisitions
WHERE thread_count > 0
GROUP BY node_name
ORDER BY total_threads DESC;
node_name | total_threads | active_queries
-------------------+---------------+----------------
v_vmart3_node0003 | 94594 | 3296
v_vmart3_node0002 | 93650 | 3262
v_vmart3_node0001 | 37536 | 1262
(3 rows)
如何解读:对比各节点的 total_threads,倾斜严重说明某些节点过载。
如果不是:活跃查询的线程数在正常范围,进入步骤 3。
3.3 步骤 3:检查高并发场景下的线程爆炸¶
做什么:线程数 = EXECUTIONPARALLELISM × 每个查询的算子数。当资源池并发度过高时,线程数会在短时间内暴涨。
SQL — 检查资源池并发配置:
-- 查看各资源池的并发和线程限制配置
SELECT
pool_name,
memory_size_kb / 1024 / 1024 AS memory_size_GB,
max_memory_size_kb / 1024 / 1024 AS max_memory_GB,
planned_concurrency,
max_concurrency,
execution_parallelism,
query_budget_kb / 1024 AS query_budget_MB
FROM resource_pool_status
WHERE node_name = (SELECT node_name FROM nodes WHERE node_state = 'UP' LIMIT 1)
ORDER BY query_budget_kb DESC;
如何解读:
execution_parallelism = AUTO:意味着值 = CPU 核数。48 核机器上,单个查询可用 48 线程planned_concurrency:资源池预期并发数。这不是硬限制,而是用于计算query_budget的预算参数max_concurrency:资源池硬性的最大并发查询数。如果为 0,则无限制- 线程数峰值估算 =
execution_parallelism×max_concurrency(实际) × 平均每查询算子数
如果发现 max_concurrency 未被设置(NULL 或 0),当业务高峰期大量查询同时涌入同一个资源池时,相当于没有并发上限,线程数可以无限增长。
SQL — 检查最近的并发峰值:
-- 查看最近 2 天各资源池的并发峰值
SELECT
pool_name,
MAX(current_requests) AS peak_concurrency,
MAX(reserved_memory_kb) / 1024 / 1024 AS peak_memory_GB
FROM (
SELECT
node_name,
pool_name,
current_requests,
reserved_memory_kb,
time
FROM dc_resource_pool_status
WHERE time > sysdate() - 2
) s
GROUP BY pool_name
ORDER BY peak_concurrency DESC;
如何解读:对比 peak_concurrency 与业务预期的并发数。如果 10 个节点的集群 peak_concurrency = 120,单节点平均 12 条并发。
如果此时线程数也偏高,可以进一步查看这些并发查询平均有多少算子——算子数越高,线程消耗越大:
-- 查看当前并发查询各自的算子数
SELECT
node_name,
transaction_id,
statement_id,
COUNT(DISTINCT path_id) AS operator_count
FROM execution_engine_profiles
WHERE is_executing = true
GROUP BY 1, 2, 3
ORDER BY operator_count DESC
LIMIT 20;
如何解读:
operator_count:该查询执行计划中的算子数(每个path_id对应一个算子)- < 20:简单查询
- 20 ~ 60:中等复杂度
- > 60:复杂查询(如多 UNION ALL、多 CASE WHEN),线程消耗风险高
结合公式估算单查询线程理论上界:operator_count × EXECUTIONPARALLELISM。例如某查询 65 个算子 × EXECUTIONPARALLELISM = 48(AUTO)= 理论上界 3,120 线程。实际值受 pipeline 阶段调度约束,同一时刻活跃算子远小于总算子数——上一节已说明。
如果不是:并发在预期范围内,进入步骤 4。
3.4 步骤 4:检查 TM(Tuple Mover)线程消耗¶
做什么:Tuple Mover 负责 Mergeout(合并 ROS 文件)、Moveout(旧版)等后台任务,使用独立的 tm 资源池。如果 TM 任务堆积或配置不当,也会产生大量线程。
SQL — 检查当前 TM 资源池线程消耗:
-- 查看 TM 资源池的实时线程消耗
SELECT
node_name,
pool_name,
COUNT(*) AS active_tm_ops,
SUM(thread_count) AS total_tm_threads,
SUM(memory_inuse_kb) / 1024 / 1024 AS total_tm_memory_MB
FROM resource_acquisitions
WHERE pool_name = 'tm'
AND thread_count > 0
GROUP BY node_name, pool_name
ORDER BY total_tm_threads DESC;
如何解读:
- 如果
total_tm_threads > 50,说明有大量 mergeout 任务并行执行 - 这通常不是问题的根源,但叠加在高查询并发之上可能成为最后一根稻草
SQL — 检查 TM 并发配置:
-- 检查 TM 资源池的并发设置
SELECT
name AS pool_name,
plannedconcurrency,
maxconcurrency,
executionparallelism,
CASE WHEN maxmemorysize LIKE '%' THEN maxmemorysize ELSE maxmemorysize END AS maxmemorysize
FROM resource_pools
WHERE name = 'tm'
OR is_internal = true;
如果不是:TM 线程数正常,进入步骤 5。
3.5 步骤 5:检查系统级 PID 上限¶
做什么:确认 Linux 系统的 PID 上限是否足够。如果上限过低,即使线程数没有异常增长,也可能提前触及天花板。
Linux 命令:
# 查看系统全局 PID 上限
cat /proc/sys/kernel/pid_max
# 查看 dbadmin 用户的最大进程/线程限制
ulimit -u
# 查看 cgroup pids.max 限制(SUSE)
cat /sys/fs/cgroup/pids/system.slice/verticad.service/pids.max
cat /sys/fs/cgroup/pids/system.slice/vertica_agent.service/pids.max
cat /sys/fs/cgroup/pids/system.slice/sshd.service/pids.max
如何解读:
pid_max:系统全局 PID 上限,默认 32768 或 4194304(取决于内核版本)。一般足够ulimit -u:用户级最大进程/线程数。推荐 ≥ 65536pids.max:cgroup 级别的限制。如果值为max表示无限制
3.6 步骤 6:检查 SUSE 系统 UserTasksMax 限制¶
仅适用于 SUSE Linux Enterprise Server。
# 检查 systemd 用户任务限制
systemctl show user-$(id -u dbadmin) | grep TasksMax
# 检查全局默认任务限制
grep DefaultTasksMax /etc/systemd/system.conf
grep UserTasksMax /etc/systemd/logind.conf
如何解读:
UserTasksMax:限制用户登录会话中允许的最大任务数DefaultTasksMax:系统默认的任务上限
4. 解决方案¶
4.1 立即措施(当天可执行)¶
4.1.1 关闭泄漏的会话¶
如果确认存在大量 idle session 泄漏:
-- 查看超长 idle 的 session
SELECT
session_id,
user_name,
client_hostname,
login_timestamp,
TIMESTAMPDIFF('minute', login_timestamp, sysdate()) AS idle_min
FROM sessions
WHERE current_statement IS NULL
AND transaction_id = -1
AND TIMESTAMPDIFF('minute', login_timestamp, sysdate()) > 60
ORDER BY login_timestamp;
-- 关闭单个 session
SELECT CLOSE_SESSION('target_session_id');
-- 或批量关闭所有 session(谨慎)
SELECT CLOSE_ALL_SESSIONS();
⚠️
CLOSE_ALL_SESSIONS()会断开所有 session,包括正在执行的查询。仅在生产环境无法接受且确认没有关键业务运行时使用。
4.1.2 限制新连接¶
在排查期间,临时禁止新连接来防止情况恶化:
-- 临时禁止新连接(保持已有连接)
ALTER DATABASE :dbname SET maxclientsessions = 0;
-- 排查结束后恢复
ALTER DATABASE :dbname SET maxclientsessions = 50;
maxclientsessions = 0仅阻断新连接建立,不影响已有 session 和正在执行的查询。
4.1.3 终止高线程消耗查询¶
-- 找到消耗线程最多的查询
SELECT
node_name,
transaction_id,
statement_id,
pool_name,
thread_count,
memory_inuse_kb / 1024 AS memory_MB
FROM resource_acquisitions
WHERE thread_count > 500
ORDER BY thread_count DESC;
-- 中断指定查询
SELECT INTERRUPT_STATEMENT('target_session_id', target_statement_id);
4.2 短期优化(当周执行)¶
4.2.1 调整资源池并发限制¶
为业务资源池设置合理的并发上限。MAXCONCURRENCY 才是硬限制,PLANNEDCONCURRENCY 仅用于预算计算:
-- 为指定资源池设置最大并发数
ALTER RESOURCE POOL your_pool_name MAXCONCURRENCY 10;
-- 降低单查询的线程并行度
ALTER RESOURCE POOL your_pool_name EXECUTIONPARALLELISM 8;
为什么选这些值?
MAXCONCURRENCY 和 EXECUTIONPARALLELISM 必须配对设置——单独看任何一个都没有意义。两者的乘积决定了该资源池的最大并行线程数(不含算子放大)。
参照 Vertica 资源池最佳实践 中 48 核节点的配置案例:
| 资源池类型 | EXECUTIONPARALLELISM | MAXCONCURRENCY | 乘积(最大并行线程) | 适用场景 |
|---|---|---|---|---|
| fast | 6 | 60 | 360 | 短查询(< 5s),低并行度、高并发 |
| medium | 12 | 15 | 180 | 中等查询,中等并行度 |
| slow | 24 | 5 | 120 | 长查询/ETL,高并行度、低并发 |
| bulk(ETL) | 4 | 4 | 16 | 批量加载宽表,低并行度、极低并发 |
核心原则:EXECUTIONPARALLELISM 越低,MAXCONCURRENCY 可以设得越高——因为每条查询消耗的线程少,可以同时跑更多条。反过来,如果 EXECUTIONPARALLELISM = AUTO(全部核数),则 MAXCONCURRENCY 必须严格限制。
4.2.2 优化复杂 SQL 降低线程数¶
如果根因是复杂 SQL 导致算子数过多:
- 拆分 UNION ALL:将大量 UNION ALL 拆为多次独立 INSERT,每次只处理一批分支
- 减少 CASE WHEN 嵌套:将复杂的 CASE WHEN 逻辑移到 ETL 上游处理
- 避免关联子查询:将
WHERE col IN (SELECT ...)改写为 JOIN
详见 Vertica CPU 持续高负载诊断与优化 中的 SQL 改写策略。
4.2.3 调整系统级 PID 上限¶
# 调整 dbadmin 用户的进程/线程上限(写入 /etc/security/limits.conf)
echo "dbadmin hard nproc 65536" >> /etc/security/limits.conf
# 或直接修改 ulimit
ulimit -u 65536
# SUSE: 调整 cgroup pids.max
echo "max" > /sys/fs/cgroup/pids/system.slice/verticad.service/pids.max
# 调整 UserTasksMax(写入 /etc/systemd/logind.conf)
# UserTasksMax=65536
# 然后执行
systemctl daemon-reload
4.3 长期治理¶
4.3.1 建立进程数监控告警¶
- 单节点进程数 > 2000 触发告警
- 建议接入 Prometheus + node_exporter 或自研巡检脚本定期采集
ps -eLf | wc -l
4.3.2 实施资源池分级策略¶
参照 Vertica 资源池最佳实践 中的设计模式,按查询复杂度创建多级资源池:
fast (RUNTIMECAP=5s, EXECUTIONPARALLELISM=6)
→ medium (RUNTIMECAP=1m, EXECUTIONPARALLELISM=12)
→ slow (RUNTIMECAP=5m, EXECUTIONPARALLELISM=24)
每种池设置不同的并发上限和线程并行度,防止慢查询占用过多线程。
4.3.3 设置会话超时¶
-- 设置 idle session 超时(秒)
ALTER DATABASE :dbname SET DEFAULTIDLESESSIONTIMEOUT = 3600;
-- 或对特定用户
ALTER USER app_user IDLESESSIONTIMEOUT 1800;
-- 已验证:Vertica v26.1.0-2
为什么 3600 秒? 1 小时足够覆盖大多数 ETL 作业的空闲等待间隔,同时防止"永恒 idle"的 session 泄漏。
5. 深入案例¶
5.1 真实案例:复杂 SQL 导致节点线程爆炸¶
真实案例
场景:某运营商 20 节点 Vertica 集群,某日在执行一条业务 SQL 后数据库夯死——dbadmin 在 vsql 中任何查询都无法返回结果。
诊断过程:通过 dc_process_info_by_hour 表发现故障时段多个节点 thread_count_max_value 超过 12,000。进一步分析 dc_resource_acquisitions 发现某节点在 1 分钟内执行了 7 个 transaction,每个 transaction 消耗 2,977 个线程。
根因:一条包含 30 个 CASE WHEN 列 + 27 个 UNION ALL 子查询的复杂 INSERT 语句,EXECUTIONPARALLELISM = AUTO(~48 核),优化器生成的算子数约 60 个,单条 SQL 线程 = 48 × 60 ≈ 2,880。
修复:
- 立即:重启数据库恢复
- 永久:原计划调至
UserTasksMax = 1,030,496、DefaultTasksMax = infinity,经主机管理员协商后调整为UserTasksMax = 65,535、DefaultTasksMax = 10,240 - 建议:业务侧优化 SQL,拆分 UNION ALL
效果:调整后未再出现因线程耗尽导致的夯死。
5.2 真实案例:SUSE cgroup pids.max 限制导致进程数上限¶
真实案例
场景:某运营商 SUSE Linux 上的 Vertica 集群,多次出现数据库夯死,vsql 无法执行查询,vertica.log 中无任何报错。
诊断过程:检查发现 /sys/fs/cgroup/pids/system.slice/verticad.service/pids.max 被设置为非 max 的有限值,vertica 进程数达到此上限后无法 fork 新线程。
根因:SUSE 系统 cgroup v2 的 pids controller 限制了 verticad.service 的进程数。当业务高峰或复杂查询时,线程数触及上限。
修复:将 pids.max 改为 max(无限制)。
效果:修复后不再出现因 cgroup 限制导致的夯死。
5.3 虚构案例:高峰期多资源池无并发限制导致线程风暴¶
虚构案例
场景:某电商 12 节点集群,资源池配置只设置了 MEMORYSIZE 和 MAXMEMORYSIZE,未设置 MAXCONCURRENCY 和 EXECUTIONPARALLELISM。大促期间,120 条查询同时涌入 general 池(默认 EXECUTIONPARALLELISM = AUTO = 48 核)。
诊断过程:
-- 检查资源池峰值并发
SELECT pool_name, MAX(current_requests) AS peak_q
FROM dc_resource_pool_status
WHERE time > sysdate() - 1
GROUP BY pool_name;
显示 general 池 peak_q = 120。
线程数理论峰值:120 × 48 = 5,760,远超安全范围。实际观察到节点线程数超过 15,000,部分查询 RESOURCE_REJECTED。
根因:无 MAXCONCURRENCY 限制,无 EXECUTIONPARALLELISM 限制,查询可以无限制地获取线程。
修复:
-- 按查询复杂度拆分资源池并设置上限
ALTER RESOURCE POOL short_pool MAXCONCURRENCY 20 EXECUTIONPARALLELISM 6;
ALTER RESOURCE POOL medium_pool MAXCONCURRENCY 10 EXECUTIONPARALLELISM 12;
ALTER RESOURCE POOL long_pool MAXCONCURRENCY 4 EXECUTIONPARALLELISM 24;
效果:理论最大线程数 = 20×6 + 10×12 + 4×24 = 336,远在安全范围。实际上线后线程数峰值降至 1200。
6. 完整诊断流程实战¶
虚构场景 · 完整演练
背景:某保险公司 6 节点集群(48 核/节点,256GB 内存),运维接到告警:3 个节点线程数超过 3000(正常值 ~1000)。
时间线:
| 时间 | 步骤 | 操作 | 发现 |
|---|---|---|---|
| 14:00 | Step 1 | ps -eLf \| wc -l |
node03: 3500, node05: 3200, node06: 3100 |
| 14:05 | Step 2 | 检查 session 数 | node03 有 85 个 session,其他节点 ~30 |
| 14:10 | Step 3 | 检查 resource_acquisitions |
node03 上有 15 个查询每个 thread_count > 400 |
| 14:15 | Step 4 | 检查资源池配置 | report_pool 未设置 MAXCONCURRENCY |
| 14:20 | Step 5 | 检查 resource_pool_status |
report_pool 有 22 条并发查询在执行 |
| 14:25 | Step 6 | 检查 SQL 复杂度 | 某 ETL 作业当日新增了包含 18 个 UNION ALL 的查询 |
| 14:30 | 修复 | 1. 终止高线程查询 2. 设 MAXCONCURRENCY=8 | |
| 14:45 | 验证 | 线程数降至 1100,恢复正常 |
关键 SQL 汇总(执行顺序):
-- 1. 定位最高线程消耗节点
SELECT node_name, SUM(thread_count) AS total_threads
FROM resource_acquisitions
WHERE thread_count > 0
GROUP BY node_name
ORDER BY total_threads DESC;
-- 2. 定位高线程查询
SELECT node_name, transaction_id, thread_count, pool_name
FROM resource_acquisitions
WHERE thread_count > 300
ORDER BY thread_count DESC;
-- 3. 检查资源池并发配置
SELECT pool_name, max_concurrency, execution_parallelism
FROM resource_pool_status
WHERE node_name = 'v_xxx_node0001'
AND pool_name = 'report_pool';
-- 4. 修复:设置并发上限
ALTER RESOURCE POOL report_pool MAXCONCURRENCY 8 EXECUTIONPARALLELISM 12;
7. 快速诊断 SQL 工具箱¶
| 诊断目标 | SQL | 阈值 |
|---|---|---|
| 查看节点线程历史峰值 | SELECT node_name, start_time::char(13), thread_count_max_value FROM v_internal.dc_process_info_by_hour WHERE start_time > sysdate()-1 ORDER BY thread_count_max_value DESC; |
> 2000 告警 |
| 当前活跃会话分布 | SELECT node_name, user_name, COUNT(*) FROM sessions WHERE current_statement IS NOT NULL AND transaction_id != -1 GROUP BY 1,2 ORDER BY 3 DESC; |
单节点 > 50 关注 |
| 超长 idle session | SELECT session_id, user_name, login_timestamp FROM sessions WHERE current_statement IS NULL AND transaction_id = -1 AND TIMESTAMPDIFF('minute', login_timestamp, sysdate()) > 60 ORDER BY login_timestamp; |
> 60 min 泄漏风险 |
| 当前线程消耗 TOP 查询 | SELECT node_name, pool_name, thread_count, memory_inuse_kb/1024 AS mem_mb FROM resource_acquisitions WHERE thread_count > 0 ORDER BY thread_count DESC LIMIT 20; |
> 500/查询 复杂 |
| 按节点汇总线程消耗 | SELECT node_name, SUM(thread_count) AS total_threads FROM resource_acquisitions WHERE thread_count > 0 GROUP BY node_name ORDER BY total_threads DESC; |
节点间差距 > 2× 倾斜 |
| 资源池并发配置 | SELECT pool_name, max_concurrency, execution_parallelism FROM resource_pool_status WHERE node_name = (SELECT node_name FROM nodes WHERE node_state='UP' LIMIT 1) ORDER BY pool_name; |
max_conc IS NULL 无限制 |
| 资源池并发峰值 | SELECT pool_name, MAX(current_requests) AS peak FROM dc_resource_pool_status WHERE time > sysdate()-2 GROUP BY 1 ORDER BY peak DESC; |
> expected × 2 关注 |
| 查询算子数(线程消耗预估) | SELECT node_name, transaction_id, statement_id, COUNT(DISTINCT path_id) AS operator_count FROM execution_engine_profiles WHERE is_executing = true GROUP BY 1,2,3 ORDER BY operator_count DESC LIMIT 20; |
> 60 复杂查询风险 |
| TM 线程消耗 | SELECT node_name, SUM(thread_count) AS tm_threads FROM resource_acquisitions WHERE pool_name='tm' AND thread_count > 0 GROUP BY node_name ORDER BY tm_threads DESC; |
> 50 关注 |
| 检查系统 PID 上限 | Linux: ulimit -u, cat /proc/sys/kernel/pid_max, cat /sys/fs/cgroup/pids/system.slice/verticad.service/pids.max |
ulimit -u < 65536 偏低 |
| 僵尸进程 | Linux: ps aux \| awk '$8 ~ /Z\|defunct/ {print}' |
> 0 清理 |
8. 最佳实践清单¶
- 设置 MAXCONCURRENCY 硬限制:每个业务资源池必须设置合理的 MAXCONCURRENCY(而非仅 PLANNEDCONCURRENCY)。MAXCONCURRENCY 才是并发硬限制,PLANNEDCONCURRENCY 仅用于 query_budget 预算计算,不限制实际并发
- 合理设置 EXECUTIONPARALLELISM:不建议使用默认 AUTO(等于全部 CPU 核数)。对于并发查询场景,建议 4-16,按查询复杂度分层配置——因为并发下每条查询独占全部核数会导致线程爆炸
- 定期检查 idle session:建立 idle session 监控,超时自动清理。每个 idle session 虽不执行查询但仍占用少量线程、文件句柄和内存
- 设置数据库级 idle session 超时:
DEFAULTIDLESESSIONTIMEOUT是防止会话泄漏的最后一道防线。即使应用侧连接池泄漏,数据库也能自动回收超时 session - 配置 ulimit -u ≥ 65536:dbadmin 用户的最大进程/线程数必须足够大。默认值通常很低(如 1024、4096),在复杂查询场景下瞬间就会耗尽
- 检查 cgroup pids.max:SUSE 或使用 cgroup v2 的 Linux 发行版务必确认
pids.max = max。cgroup 的 pids controller 会在 ulimit 之上再加一层限制 - 复杂 SQL 上线前评估线程消耗:可以先用
EXPLAIN查看计划中的操作数,结合 EXECUTIONPARALLELISM 估算线程数。操作数 × EXECUTIONPARALLELISM > 1500 时需优化或设严格并发限制 - 节点间负载均衡检查:session 和查询应在所有节点均匀分布。连接倾斜会导致个别节点线程数远高于其他节点
- 配置僵尸进程告警:cron 任务中的 sendmail/postdrop 僵尸进程是常见来源。定期检查
ps aux | grep defunct,或在 crontab 中添加MAILTO=root - 建立线程数趋势监控:单次阈值告警不如趋势重要。如果线程数从 800 缓慢增长到 1800 用了 3 个月,即使未超阈也需排查是否泄漏
- 突发事件先止血再排查:数据库夯死时,
CLOSE_ALL_SESSIONS()或设置maxclientsessions=0可以快速止血,阻止新连接涌入,为排查争取时间
扩展阅读¶
- Vertica CPU 持续高负载诊断与优化 — 高进程数往往伴随 CPU 飙升
- Vertica 资源池最佳实践 — 资源池参数如何控制线程数
- Vertica 客户端连接负载均衡配置 — 连接倾斜导致节点间线程分布不均
- Vertica 资源拒绝排查与资源池调优 — 资源拒绝往往与并发数过高相关