跳转至

Vertica 服务器进程数监控与管理

作者:JiangChong | 发布时间:2026-05-13

适用场景:当你怀疑 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 采用线程池 + 资源池模型,与两者都不同——连接数不等于线程数,线程由全局线程池复用:

传统数据库:
  连接1 → 进程1
  连接2 → 进程2
  连接N → 进程N

Vertica:
  连接1 ─┐
  连接2 ─┤
  连接N ─┘ → 资源池队列 → 线程池执行

但 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 耗尽风险直接相关:

# 查看所有节点的总线程数(每个线程一个 TID)
ps -eLf | wc -l

如何解读

  • 200 ~ 1500:正常范围(取决于集群规模和并发负载)
  • 1500 ~ 2000:偏高,建议检查线程来源
  • > 2000:告警,需立即排查
  • > 10000:危险,可能导致系统无法创建新线程,数据库面临夯死风险

按用户统计线程数

# 统计各用户的线程数(-L 显示线程)
ps -eLo user | sort | uniq -c | sort -rn

如何解读:关注 dbadmin 用户的线程数是否异常偏高。正常情况下 dbadmin 占大头(vertica 主进程的所有线程),但每个节点通常 ≤ 1500。

Vertica 进程的线程数

# 统计 vertica 进程及其所有线程
ps -eLf | grep -c vertica

如何解读ps -eLf-L 显示线程)列出所有 task,grep vertica 过滤出数据库相关线程。如果超过 1500,需结合下文 SQL 分析来源。

进程数(TGID)检查——辅助指标,偏高通常意味着进程泄漏或外部进程异常:

# 查看进程数(不含线程,ps -ef 仅按 TGID 统计)
ps -ef | wc -l

如何解读:正常 Vertica 节点进程数通常在 100 ~ 300。如果 > 500,应排查是否有外部进程泄漏或 fork 风暴。

僵尸进程检查

# 查找僵尸进程
ps aux | awk '$8 ~ /Z|defunct/ {print}'

如何解读:僵尸进程(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 = falsemax_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:用户级最大进程/线程数。推荐 ≥ 65536
  • pids.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;

为什么选这些值?

MAXCONCURRENCYEXECUTIONPARALLELISM 必须配对设置——单独看任何一个都没有意义。两者的乘积决定了该资源池的最大并行线程数(不含算子放大)。

参照 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,496DefaultTasksMax = infinity,经主机管理员协商后调整为 UserTasksMax = 65,535DefaultTasksMax = 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. 最佳实践清单

  1. 设置 MAXCONCURRENCY 硬限制:每个业务资源池必须设置合理的 MAXCONCURRENCY(而非仅 PLANNEDCONCURRENCY)。MAXCONCURRENCY 才是并发硬限制,PLANNEDCONCURRENCY 仅用于 query_budget 预算计算,不限制实际并发
  2. 合理设置 EXECUTIONPARALLELISM:不建议使用默认 AUTO(等于全部 CPU 核数)。对于并发查询场景,建议 4-16,按查询复杂度分层配置——因为并发下每条查询独占全部核数会导致线程爆炸
  3. 定期检查 idle session:建立 idle session 监控,超时自动清理。每个 idle session 虽不执行查询但仍占用少量线程、文件句柄和内存
  4. 设置数据库级 idle session 超时DEFAULTIDLESESSIONTIMEOUT 是防止会话泄漏的最后一道防线。即使应用侧连接池泄漏,数据库也能自动回收超时 session
  5. 配置 ulimit -u ≥ 65536:dbadmin 用户的最大进程/线程数必须足够大。默认值通常很低(如 1024、4096),在复杂查询场景下瞬间就会耗尽
  6. 检查 cgroup pids.max:SUSE 或使用 cgroup v2 的 Linux 发行版务必确认 pids.max = max。cgroup 的 pids controller 会在 ulimit 之上再加一层限制
  7. 复杂 SQL 上线前评估线程消耗:可以先用 EXPLAIN 查看计划中的操作数,结合 EXECUTIONPARALLELISM 估算线程数。操作数 × EXECUTIONPARALLELISM > 1500 时需优化或设严格并发限制
  8. 节点间负载均衡检查:session 和查询应在所有节点均匀分布。连接倾斜会导致个别节点线程数远高于其他节点
  9. 配置僵尸进程告警:cron 任务中的 sendmail/postdrop 僵尸进程是常见来源。定期检查 ps aux | grep defunct,或在 crontab 中添加 MAILTO=root
  10. 建立线程数趋势监控:单次阈值告警不如趋势重要。如果线程数从 800 缓慢增长到 1800 用了 3 个月,即使未超阈也需排查是否泄漏
  11. 突发事件先止血再排查:数据库夯死时,CLOSE_ALL_SESSIONS() 或设置 maxclientsessions=0 可以快速止血,阻止新连接涌入,为排查争取时间

扩展阅读