跳转至

Vertica 集群 Rebalance 完全指南

综合翻译整理自 Vertica Knowledge Base 三篇文章:

  • Best Practices for Preparing Your Cluster for Rebalance(Rakesh Bankula & Soniya Shah 合著)

  • Understanding Rebalancing, Part 1: What Happens During Rebalancing

  • Understanding Rebalancing, Part 2: Optimizing for Rebalancing

前言

在 Vertica 集群中增删节点后,必须执行 rebalance 操作来重新分布数据。Rebalance 是一个 CPU、磁盘和网络密集型的复杂过程,涉及大量数据迁移,可能耗时数小时到数十小时。

本文整合了 Vertica 官方三篇关于 rebalance 的核心文档,涵盖:内部原理 → 准备工作 → 执行监控 → 事后验证 → 耗时估算的完整闭环。无论你是第一次操作 rebalance,还是已有经验想做更精细的规划,都可以在本文中找到对应的指导和 SQL 脚本。


第一部分:Rebalance 内部原理

来源:Understanding Rebalancing, Part 1: What Happens During Rebalancing

在开始准备之前,先理解 rebalance 过程中到底发生了什么,这有助于你理解每项准备工作的意义。

1.1 何时需要 Rebalance

场景 说明
数据量增长 磁盘接近写满,需要增加节点扩容
分析负载增加 查询并发量或复杂度上升,现有节点 CPU/内存不足
提高 K-safety 增加节点以提升高可用性(更多 buddy projection 副本)
节点下线 缩容、硬件维护、升级替换(swap 不需要 rebalance)

重要:Vertica 不允许在违反 K-safety 的前提下移除节点。例如 K-safety=1 时,如果移除某节点会导致部分数据只剩 0 个副本,操作将被拒绝。

1.2 数据如何移动

Rebalance 的核心目标:将数据从 N 个节点上各占 1/N 重新分布到 M 个节点上各占 1/M。Vertica 通过以下策略最小化数据移动量:

  • 新节点插入位置经过优化:新节点不是简单追加到集群末尾,而是插入到能最小化数据迁移的位置
  • 分段投影(segmented projection):原有节点上的 ROS container 被拆分(split),然后将相应的数据段转移到目标节点
  • 非分段投影(unsegmented projection):直接从 buddy 节点完整复制到新节点,因为每个节点本身就存有全量副本
  • rebalance 完成后:目标节点上的数据分段由 Tuple Mover 在下次 mergeout 时合并,这一步不属于 rebalance 本身

数据移动量取决于

  • 集群原有节点数和新增/移除节点数
  • 分段投影 vs 非分段投影的数量比例

示例(基于 Part 1 原文的 3→4 节点场景):每个节点从持有 1/3 数据变为持有 1/4 数据。Vertica 将新节点插入到数据迁移最少的位置,仅移动约 4/12(约 1/3)的总数据量——Node1 移出 1/12、Node2 移出 2/12、Node3 移出 1/12。对于 4→5 节点场景原理相同,移动比例类似。

磁盘空间不足时的级联 rebalance:如果现有节点剩余空间很少,Vertica 不得不分多阶段执行 rebalance:

  1. 第一阶段:将数据分布到新节点
  2. 第二阶段:数据从释放了空间的节点继续向其他节点迁移
  3. 后续阶段:重复上述过程直到平衡

每多一个阶段,总耗时大幅增长。这解释了为什么确保 40% 以上空闲空间至关重要。

1.3 REFRESH 资源池

Rebalance 始终使用内置的 REFRESH 资源池(不是 General pool,也不是用户自定义 pool)。

参数 说明
PLANNEDCONCURRENCY 控制同时 rebalance 的 projection buddy group 数量。这是唯一有效的并发控制参数
MAXCONCURRENCY 对 REFRESH 资源池无效(官方文档明确说明)

建议:优先使用默认设置,不要随意调大 PLANNEDCONCURRENCY,过高的并发可能导致 I/O 争抢反而拖慢进度。

1.4 Rebalance 的四个阶段

阶段一:插入新节点

Vertica 将新节点插入到数据迁移最小的位置。对于大规模集群,插入位置对性能影响显著。

阶段二:数据重分段(Resegmenting)

这是 rebalance 最耗时的阶段,最多可占总耗时的 80%

对非分段投影(unsegmented projection)

  • 对每个 projection 获取 X 锁(排他锁)
  • 在新节点上执行 CREATE PROJECTION ... UNSEGMENTED ALL NODES KSAFE
  • 从 buddy projection 刷新数据

对分段投影(segmented projection)

  • 对表获取 S 锁(共享锁),对 projection 获取 X 锁(排他锁)
  • 分离 primary、buddy、live aggregate projection 的数据段
  • 刷新 projection

重分段过程需要临时存储空间作为分段数据的暂存区,因此 Vertica 每次只处理少量表和 projection,以高效利用临时空间。

阶段三:数据传输(Transferring)

Vertica 使用哈希函数决定数据在节点间的分布。

  • 非分段投影传输:源节点读数据,目标节点写数据。多个源节点可以并行向多个目标节点传输,CPU 开销很小
  • 分段投影传输:源节点需要完成读、拆分、写三个步骤,需要时间和临时磁盘空间。这一步最消耗资源

阶段四:数据合并(Merging)

这一步不属于 rebalance 过程本身。Rebalance 完成后,Tuple Mover 在下次 mergeout 时将目标节点上的数据片段合并。如果有 ephemeral 节点(缩容场景),Vertica 会在此阶段删除不再需要的非分段投影。

1.5 影响 Rebalance 耗时的因素

因素 影响
Projection 数量 每个 projection 都要独立处理,数量越多越慢
每张表的分区数 分区越多,ROS container 越多
数据量和行数 直接影响 split 和 transfer 的数据量
目标节点上的 merge 时间 取决于 Tuple Mover 的后续 mergeout
最繁忙节点的读写总量 决定了整体耗时下限
数据倾斜 倾斜越严重,某些节点需要移动的数据越多
网络吞吐 1Gbps vs 10Gbps,传输时间差一个数量级
I/O 瓶颈 vs 网络瓶颈 取决于集群的硬件瓶颈在哪
集群上的其他负载 ETL、用户查询抢占资源

关键认知:分段投影的 resegment 和 ROS container split 可占 总耗时的 80%。这就是为什么准备工作要围绕「减少 ROS 数量」和「加速 split」展开。


第二部分:准备工作

来源:Best Practices for Preparing Your Cluster for Rebalance + Understanding Rebalancing Part 1 & 2

分为「必须执行」和「强烈推荐」两类。

2.1 必须执行的操作

① 清理无用的数据库对象

Rebalance 耗时与数据库中的对象数量强相关。每个 projection、每个 ROS container 都需要参与 rebalance。

删除冗余 schema 和表:已废弃但未删除的表仍占用对象计数。

检查并删除冗余 projection:一张表上可能有多个 projection(不同排序键、分段键用于优化不同查询),但并非越多越好。每个 projection 都需要独立完成 ROS split 和 transfer。

-- 查看每张表的 projection 数量
SELECT projection_schema, anchor_table_name, count(distinct projection_name) proj_count
FROM projections
GROUP BY 1, 2
ORDER BY 3 DESC;

proj_count 特别高的表值得审视——super projection 是基础投影,其他多为 live aggregate projectiontop-K projection。如果某些查询场景已不再使用,对应的 projection 可以删除。

清理历史分区:事实表通常按时间分区,过期分区如果不再需要查询,其中的 ROS container 仍在 storage_containers 中。

-- 查看分区数异常多的表
SELECT table_schema, projection_name, count(distinct partition_key) partition_count
FROM partitions
GROUP BY 1, 2
ORDER BY 3 DESC;

可用 DROP_PARTITIONMOVE_PARTITIONS_TO_TABLE 清理不再需要的分区。

② 推进 AHM 并清理删除记录

Vertica 使用 MVCC(多版本并发控制),DELETE 操作不物理删除数据,仅标记为删除。这些记录直到 AHM(Ancient History Mark)推进后、Tuple Mover mergeout 时才真正清除。

如果 AHM 滞后于 current_epoch 太多,rebalance 中 split ROS container 时将触发 replay delete(重放历史删除操作),对 CPU 和 I/O 造成沉重负担。

-- 第一步:推进 AHM 到最近的 epoch
SELECT make_ahm_now();

-- 第二步:验证 AHM 是否接近 current_epoch(相差 1 以内理想)
SELECT get_ahm_epoch(), get_last_good_epoch(), get_current_epoch();

确认 AHM 已推进后,对事实表(尤其是存在数百万条已删除记录的表)执行 PURGE

SELECT PURGE_TABLE('schema.fact_table_name');

⚠️ PURGE 物理删除已标记删除的记录,一旦执行无法回滚。务必确认备份可用。

③ 关闭 Rebalance 期间的磁盘空间检查

Rebalance 过程中 Vertica 多次查询 storage_containers 来确定 projection 的 rebalance 顺序(避免中间状态写满磁盘)。对于拥有数十万 ROS container 的数据库,单次查询可能耗时数分钟,多次重复累计可超过数小时。

如果你的集群磁盘空间充足(远未达到容量上限),可以关闭此检查:

-- Vertica 7.2.3-2 及以上版本支持
SELECT set_config_parameter('RebalanceQueryStorageContainers', 0);

低于此版本建议先升级再执行 rebalance。

关于磁盘空间的定量建议:Part 1 文档建议至少保留数据库大小的 40% 作为空闲空间。如果低于此阈值,Vertica 会进入多阶段 rebalance,耗时显著增加。可以用以下方式确认:

# Linux 级别检查各节点磁盘
df -h /vertica/data/
-- 检查各节点磁盘使用情况
SELECT host_name, disk_space_used_mb, disk_space_total_mb
FROM host_resources;

-- 更细粒度的存储信息(也可查看 DISK_STORAGE / COLUMN_STORAGE 系统表)
SELECT node_name, projection_name, used_bytes
FROM projection_storage;

④ 调整资源池配置

-- 查看当前 REFRESH 资源池配置
SELECT name, is_internal, plannedconcurrency, maxmemorysize
FROM resource_pools
WHERE name = 'REFRESH';

建议将 PLANNEDCONCURRENCY 设为与单节点 CPU 核数相同,以便 rebalance 的 ROS split 操作能充分利用 CPU 并行度。

如果在维护窗口执行 rebalance,临时释放用户自定义资源池占用的内存:

ALTER RESOURCE POOL <user_defined_pool> MEMORYSIZE '0%';

rebalance 完成后恢复原值。

⑤ 新增节点硬件和系统验证

如果是扩容场景,新节点配置必须不低于现有节点:

检查项 工具 说明
磁盘 I/O vioperf 直接决定 ROS split 和 transfer 的 IO 吞吐
网络吞吐 vnetperf 决定 ROS transfer 阶段的传输速度
CPU vcuperf 决定 ROS split 时的并行计算能力
OS 参数 对比 sysctl、limits.conf 等 内存锁定、文件句柄、网络缓冲区必须一致

新节点性能低于现有节点 → rebalance 以最慢节点为瓶颈 → 日常查询性能也退化。

⑥ 禁用本地分段

在执行 rebalance 之前,必须禁用 local segmentation:

SELECT DISABLE_LOCAL_SEGMENTS();

Local segmentation 默认是禁用的,但建议在操作前确认。

⑦ 验证 Rebalance 成功

SELECT get_node_dependencies();

成功时返回 节点数 + 1 行。例如 10 节点集群返回 11 行,每行包含 10 个 1 或 0。行数异常或数值异常说明 rebalance 未完全成功。

2.2 强烈推荐的操作

① 升级 Vertica 版本

使用 Vertica 8.0.1 以下版本建议先升级。新版本改进:

  • ROS split 并行度提升(多线程处理不同尺寸范围的 ROS)
  • rebalance_table_statusrebalance_projection_status 监控表效率优化
  • Vertica 8.0 新增 rebalance_operations 系统表,可按操作步骤实时追踪进度

② 执行全量备份

增删节点和 rebalance 是对集群的重大变更。尽管 rebalance 自身可失败回滚,但硬件故障、磁盘满、网络中断等极端情况仍可能导致数据丢失。

vbr backup --config-file /path/to/backup_config.ini

③ 在维护窗口中执行

理想情况下应完全停止

  • ETL / 数据加载(COPY 语句创建新 ROS,干扰 split 执行计划)
  • UPDATE / DELETE(产生新 delete vector)
  • 所有用户查询(抢占 CPU、内存、磁盘 I/O)

如果无法独占窗口,至少做到最小化 DML 操作,只保留必要的 SELECT 查询。

④ 重启数据库

长时间运行的数据库 catalog 持续膨胀,拖慢 catalog 更新操作。重启后 catalog 完全加载到内存并重新组织,读写效率显著提升。

admintools -t stop_db -d <dbname>
admintools -t start_db -d <dbname>

第三部分:执行与监控

来源:Understanding Rebalancing, Part 2: Optimizing for Rebalancing

3.1 启动 Rebalance

有三种方式:

方式 说明
admintools UI 图形界面操作
Management Console (MC) Web 管理控制台
SQL 函数 SELECT REBALANCE_CLUSTER();(最灵活)

SQL 方式适合脚本化和自动化场景。注意:全局只能有一个 rebalance 操作在运行

3.2 监控查询大全

以下查询可在 rebalance 执行期间随时运行,帮助判断进度和发现问题。

当前正在 rebalance 的表

SELECT * FROM rebalance_table_status;

如果 DML/DDL 操作干扰了 rebalance,会看到:

ERROR 3007: DDL statement interfered with this statement

当前活跃的 rebalance 操作

SELECT node_name, session_id, session_start_timestamp, description
FROM system_sessions
WHERE session_type = 'REBALANCE_CLUSTER'
  AND is_active;

通过 session_start_timestamp 可以了解 rebalance 已运行了多久。

整体进度概览

SELECT rebalance_method AS Rebalance_method,
       Status,
       COUNT(*) AS Count
FROM (
    SELECT rebalance_method,
           CASE
               WHEN (separated_percent = 100 AND transferred_percent = 100)
                   THEN 'Completed'
               WHEN (separated_percent <> 0 AND separated_percent <> 100)
                 OR (transferred_percent <> 0 AND transferred_percent <> 100)
                   THEN 'In Progress'
               ELSE 'Queued'
           END AS Status
    FROM rebalance_projection_status
    WHERE is_latest
) AS tab
GROUP BY 1, 2
ORDER BY 1, 2;

结果示例:

Rebalance_method Status Count
ELASTIC_CLUSTER Completed 8
ELASTIC_CLUSTER In Progress 2
ELASTIC_CLUSTER Queued 2
REPLICATE Completed 50
  • ELASTIC_CLUSTER = 分段投影的 rebalance
  • REPLICATE = 非分段投影的复制
  • REFRESH = 投影刷新

非分段投影的刷新进度

SELECT session_id, projection_name, refresh_status, refresh_method, refresh_phase
FROM projection_refreshes
WHERE refresh_method = 'rebalance'
  AND is_executing;

refresh_phase 可能值:

  • current:正在刷新当前数据
  • historical:正在刷新历史数据

分段投影的分离和传输进度

SELECT projection_name, rebalance_method, separated_percent, transferred_percent
FROM rebalance_projection_status
WHERE rebalance_method = 'ELASTIC_CLUSTER'
  AND ((separated_percent <> 0 AND separated_percent <> 100)
    OR (transferred_percent <> 0 AND transferred_percent <> 100))
  AND is_latest;

separated_percent:ROS container 已分离的百分比 transferred_percent:已传输到目标节点的百分比

Tuple Mover 正在执行分离操作

SELECT TM.projection_name, TM.node_name, TM.operation_start_timestamp
FROM tuple_mover_operations TM
JOIN system_sessions USING (session_id)
WHERE system_sessions.is_active
  AND session_type = 'REBALANCE_CLUSTER'
  AND operation_status = 'Running';

ROS Container 创建/删除统计

SELECT CASE
           WHEN is_destroyed THEN 'deleted'
           ELSE 'created'
       END AS container,
       projection_name,
       SUM(row_count) AS rows_processed,
       COUNT(*) n_containers
FROM vs_rebalance_separated_storage_containers
GROUP BY 1, 2
ORDER BY 1, 2;

数据传输详情

SELECT projection_name,
       from_node_name,
       to_node_name,
       SUM(row_count) AS rows_transferred,
       SUM(size_in_bytes) AS bytes_transferred
FROM vs_rebalance_transferred_storage_containers
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3;

每个 Projection 的 Rebalance 耗时

SELECT node_name, projection_schema, projection_name,
       start_time,
       time - start_time AS duration
FROM dc_rebalanced_projections
ORDER BY 5 DESC;

可以帮助你识别哪些 projection 是耗时大户。

3.3 锁竞争处理

如果在 ETL 作业运行期间执行 rebalance,可能出现锁竞争,导致 ETL 作业或 rebalance 操作失败。

哪些操作会与 rebalance 竞争锁

  • DELETE
  • UPDATE
  • DROP_PARTITION
  • SWAP_PARTITION_BETWEEN_TABLES
  • MOVE_PARTITION_TO_TABLE

三种应对策略:

策略一:调整 LockTimeout

LockTimeout 参数决定 ETL 作业等待锁释放的超时时间,默认 300 秒(5 分钟)。如果 ETL 作业超时则报错退出。

先查看哪些事务曾经持锁超过 5 分钟:

SELECT DATE_TRUNC('hour', grant_time), node_name,
       COUNT(*) number_of_tx,
       MAX(time - grant_time) max_time_lock_held
FROM dc_lock_releases
WHERE time - grant_time > '5 min'
  AND mode IN ('X', 'S', 'O')
  AND object_name NOT LIKE 'ElasticCluster'
GROUP BY 1, 2
ORDER BY 4 DESC;

精确定位具体锁竞争的事务和 SQL 请求

\x
SELECT t2.time, t2.node_name, t2.grant_time, t2.mode,
       t2.object_name, t2."time" - t2.grant_time AS lockheld,
       (t2."time" - t2.grant_time) * 1000 AS millisecond,
       t3.request
FROM dc_lock_releases t2
JOIN query_requests t3 ON t2.transaction_id = t3.transaction_id
  AND t2.statement_id = t3.statement_id
WHERE t2."time" - t2.grant_time > '5 min'
  AND t2.mode IN ('X', 'S', 'O')
  AND t2.object_name NOT LIKE 'ElasticCluster'
ORDER BY millisecond DESC;
\x

加大 LockTimeout:

-- 查看当前值
SELECT GET_CONFIG_PARAMETER('LockTimeout');
-- 默认 300 秒

-- 调大到 600 秒
SELECT SET_CONFIG_PARAMETER('LockTimeout', 600);

rebalance 完成后记得恢复原值!

策略二:优先保障 Rebalance(设置 DMLCancelTM)

默认情况下,如果 DML 作业试图访问被 rebalance 锁定的表,DML 作业会抢占锁并取消 rebalance,5 分钟后 rebalance 重新尝试。

如果希望 rebalance 不受干扰完成,设置:

-- 禁止 DML 抢占 rebalance 的锁
SELECT SET_CONFIG_PARAMETER('DMLCancelTM', false);

-- 执行 rebalance
SELECT REBALANCE_CLUSTER();

-- 完成后恢复
SELECT SET_CONFIG_PARAMETER('DMLCancelTM', true);

⚠️ 如果 DML 作业是关键业务(如实时数据加载),不要改 DMLCancelTM,而应该把 rebalance 安排在没有 DML 作业的时间窗口。

策略三:手动分批 Rebalance 大表

如果集群表非常多,一个维护窗口无法完成,可以每个窗口手动 rebalance 固定数量的表:

-- 单表 rebalance
SELECT REBALANCE_TABLE('schema.table_name');

-- 查看哪些表已完成/进行中/未开始
SELECT table_name,
       CASE
           WHEN separated_percent + transferred_percent = 200 THEN 'REBALANCED'
           WHEN (separated_percent + transferred_percent) < 200
            AND (separated_percent + transferred_percent) > 0 THEN 'REBALANCING'
           ELSE 'NOT REBALANCED YET'
       END AS status
FROM rebalance_table_status
WHERE is_latest;

手动 rebalance 时确保没有 ETL 作业在运行。

3.4 常见竞争错误

错误 原因 处理
ERROR 3007: DDL statement interfered with this statement 其他作业对 rebalance 锁定的表执行了 DDL 等待 DDL 完成或错开时间
ERROR 5157: Unavailable: lock table for query - Locking failure: Timed out 获取锁超时 增大 LockTimeout 或错开负载
ERROR 7121: Staging table and target table do not match 在两个 rebalance 状态不一致的表之间 SWAP PARTITION 只允许两张都已 rebalance 或两张都未 rebalance 的表之间交换分区

Rebalance 失败后的重启机制

如果 rebalance 因错误失败或被 DML 取消,Vertica 会在 300 秒(5 分钟)后自动尝试重新运行。

重要:一旦故障原因解决,rebalance 将从失败点继续执行,而不是从头开始。这避免了对已完成工作的重复浪费。

SELECT LIST_SERVICES('TM');
-- Service: 'RebalanceCluster' is enabled, interval 300 second(s)

查询失败原因

SELECT time, session_id, error_level, node_name, log_message
FROM dc_errors
WHERE session_id IN (
    SELECT DISTINCT session_id
    FROM dc_session_starts
    WHERE session_type = 'REBALANCE_CLUSTER'
)
ORDER BY time DESC;

第四部分:完成后验证

来源:Understanding Rebalancing, Part 2: Optimizing for Rebalancing

4.1 确认成功

SELECT get_node_dependencies();

返回行数 = 节点数 + 1 即成功。

4.2 检查 K-safety

确认 K-safety 与预期一致(通常为 1 或 2)。

4.3 清理过期的 Projection

如果 rebalance 失败后重试,可能留下过期的 projection,需手动清理:

SELECT projection_name, anchor_table_name, is_prejoin, is_up_to_date
FROM projections
WHERE is_up_to_date = FALSE;

is_up_to_date = FALSE 的 projection 执行 DROP PROJECTION

4.4 建立新基线

Rebalance 完成后,在空闲时段运行 vioperfvnetperf 建立新集群的性能基线,便于日后对比。

⚠️ 这些工具对系统性能影响显著,不要在 rebalance 期间运行


第五部分:Rebalance 耗时估算

来源:Best Practices for Preparing Your Cluster for Rebalance

完整的 rebalance 生命周期包含四个阶段:

总耗时 = ROS Split + ROS Transfer + 系统表查询 + 杂项操作

5.1 ROS Split 耗时估算

Split ROS container 的耗时等于同尺寸 ROS 的 mergeout 耗时(操作逻辑对称)。

公式

ROS Split 耗时 = Σ(各尺寸 ROS 数量 × 该尺寸平均 mergeout 耗时) / 单节点 CPU 核数

计算示例(24 核,105,901 个 <1GB ROS + 94 个 1-2GB ROS):

= (105,901 × 9 + 94 × 18) / 24
= 954,801 / 24
= 39,783 秒 ≈ 11 小时

关键洞察:小 ROS 数量决定了 split 耗时的主体部分。每个 ROS 的 split 操作有固定的启动开销(打开文件、解析元数据、初始化排序上下文),ROS 再多再小也得逐个处理。这也是为什么「清理无用对象」是准备工作中的第一优先级。

5.2 ROS Transfer 耗时估算

取决于网络带宽和每节点数据量。

示例(10Gbps,每节点 3TB,扩容 1 倍):

传输量 ≈ 3TB × 50% = 1.5TB
耗时 ≈ 1.5TB / 900 MB/s ≈ 1,700 秒 ≈ 0.5 小时(单线程理想值)

多线程并行、带宽共享、TCP 拥塞控制等因素使实际耗时落在 1-1.5 小时。如果是 1Gbps 网络,传输时间增长约 10 倍,成为瓶颈。

5.3 系统表查询

对于 50 万 ROS container 的数据库,单次 storage_containers 查询可能耗时 3-5 分钟。rebalance 过程中 10-20 次查询累计可达 1-2 小时。

优化:磁盘使用率低于 50-60% 时,设置 RebalanceQueryStorageContainers = 0 可安全跳过。

5.4 杂项操作

包括 catalog 更新、node dependencies 计算、projection 统计刷新、事务提交等。

根据对 250+ 客户 scrutinize 的分析,每个 projection 平均耗时约 250ms单线程串行处理(catalog 更新必须独立以避免锁冲突)。

示例(30,000 个 projection):

杂项耗时 ≈ 30,000 × 0.25 秒 = 7,500 秒 ≈ 2 小时

这个阶段的耗时主要取决于 projection 总数,与硬件配置关系不大。

5.5 客户案例验证

某客户(v7.1.2-6,HP Gen8 24 核 256GB,10Gbps,每节点 2.1TB,36,000 个 projection)从 16 节点缩容至 11 节点(移除 5 个):

阶段 估算 实际
ROS Split ~8-9 小时
ROS Transfer ~1-1.5 小时
系统表查询 ~1-2 小时
杂项操作 ~2.5 小时
合计 ~12.5-15 小时 13 小时

高度吻合。客户在无 ETL/用户查询的独占维护窗口中执行。


第六部分:附录

A. 各尺寸 ROS 平均 Mergeout / Split 耗时

基于 250+ 客户 scrutinize 统计(未采集本库数据时可作参考):

尺寸范围 代码 平均耗时 (秒)
< 100MB A_Less_than_100MB 0.844
100MB - 200MB B_Between_100MB_to_200MB 2.000
200MB - 400MB C_Between_200MB_to_400MB 6.833
400MB - 600MB D_Between_400MB_to_600MB 11.095
600MB - 800MB E_Between_600MB_to_800MB 13.000
800MB - 1GB F_Between_800MB_to_1GB 23.000
1GB - 2GB G_Between_1GB_to_2GB 35.000
2GB - 4GB H_Between_2GB_to_4GB 68.000
4GB - 8GB I_Between_4GB_to_8GB 137.846
8GB - 16GB J_Between_8GB_to_16GB 320.444
16GB - 32GB K_Between_16GB_to_32GB 617.650
> 32GB L_Greater_than_32GB 2528.500

注意:

ROS 大小与 split 耗时非线性

一个 32GB+ ROS 平均耗时 42 分钟,远超 32 个 1GB ROS 的总耗时(32 × 35 = 1120 秒 ≈ 19 分钟)。

这说明过大的 ROS 对 rebalance 更加不利——Tuple Mover 的后台 mergeout 将小 ROS 合并的原因之一正是在寻找这个平衡点。

B. 精确估算脚本(可选)

如果需要从本库统计实际 mergeout 耗时(比附录 A 经验值更准确)。

B1. 创建 merge_time 表

CREATE TABLE merge_time (
    ros_size varchar(25),
    duration_sec float
);

填充数据(从 dc_tuple_mover_events 提取历史 mergeout 耗时):

INSERT INTO merge_time
SELECT ros_size, max(avg_duration) time_sec
FROM (
    SELECT
        s.node_name,
        CASE
            WHEN s.total_size_in_bytes < 100000000 THEN 'A_Less_than_100MB'
            WHEN (s.total_size_in_bytes > 100000000
              AND s.total_size_in_bytes < 200000000) THEN 'B_Between_100MB_to_200MB'
            WHEN (s.total_size_in_bytes > 200000000
              AND s.total_size_in_bytes < 400000000) THEN 'C_Between_200MB_to_400MB'
            WHEN (s.total_size_in_bytes > 400000000
              AND s.total_size_in_bytes < 600000000) THEN 'D_Between_400MB_to_600MB'
            WHEN (s.total_size_in_bytes > 600000000
              AND s.total_size_in_bytes < 800000000) THEN 'E_Between_600MB_to_800MB'
            WHEN (s.total_size_in_bytes > 800000000
              AND s.total_size_in_bytes < 1000000000) THEN 'F_Between_800MB_to_1GB'
            WHEN (s.total_size_in_bytes > 1000000000
              AND s.total_size_in_bytes < 2000000000) THEN 'G_Between_1GB_to_2GB'
            WHEN (s.total_size_in_bytes > 2000000000
              AND s.total_size_in_bytes < 4000000000) THEN 'H_Between_2GB_to_4GB'
            WHEN (s.total_size_in_bytes > 4000000000
              AND s.total_size_in_bytes < 8000000000) THEN 'I_Between_4GB_to_8GB'
            WHEN (s.total_size_in_bytes > 8000000000
              AND s.total_size_in_bytes < 16000000000) THEN 'J_Between_8GB_to_16GB'
            WHEN (s.total_size_in_bytes > 16000000000
              AND s.total_size_in_bytes < 32000000000) THEN 'K_Between_16GB_to_32GB'
            ELSE 'L_Greater_than_32GB'
        END AS ros_size,
        avg(DATEDIFF(SECOND, s.time, c.time)) as avg_duration
    FROM dc_tuple_mover_events s
    JOIN dc_tuple_mover_events c
        ON  s.node_name = c.node_name
        AND s.projection_oid = c.projection_oid
        AND s.transaction_id = c.transaction_id
        AND s.session_id = c.session_id
    WHERE s.operation = 'Mergeout'
      AND c.operation = 'Mergeout'
      AND s.event = 'Start'
      AND c.event = 'Complete'
      AND s.container_count > 1
      AND c.container_count > 1
      AND s.transaction_id NOT IN (
          SELECT DISTINCT transaction_id
          FROM dc_tuple_mover_events
          WHERE event ILIKE '%replay delete%'
      )
    GROUP BY 1, 2
) f
GROUP BY 1;
COMMIT;

关键过滤条件说明

  • container_count > 1:只统计真正合并至少 2 个 ROS 的 mergeout,排除单 ROS resize/moveout
  • transaction_id NOT IN (... replay delete ...):排除涉及 replay delete 的 mergeout——这些操作的耗时包含了重放删除的开销,会高估纯 split 时间(前提是已按本文要求推进 AHM,split 阶段不会触发 replay delete)。此外该估算也不考虑与用户发起 purge 操作相关的 mergeout 操作
  • 如果 dc_tuple_mover_events 历史数据不足(数据库刚启动),直接用附录 A 的经验值

B2. 创建 ros_count 表

CREATE TABLE public.ros_count (
    ros_size varchar(25),
    ros_count int
);

INSERT INTO ros_count
SELECT ros_size, max(ros_count) max_ros_cnt
FROM (
    SELECT
        node_name,
        CASE
            WHEN used_bytes < 100000000 THEN 'A_Less_than_100MB'
            WHEN (used_bytes > 100000000
              AND used_bytes < 200000000) THEN 'B_Between_100MB_to_200MB'
            WHEN (used_bytes > 200000000
              AND used_bytes < 400000000) THEN 'C_Between_200MB_to_400MB'
            WHEN (used_bytes > 400000000
              AND used_bytes < 600000000) THEN 'D_Between_400MB_to_600MB'
            WHEN (used_bytes > 600000000
              AND used_bytes < 800000000) THEN 'E_Between_600MB_to_800MB'
            WHEN (used_bytes > 800000000
              AND used_bytes < 1000000000) THEN 'F_Between_800MB_to_1GB'
            WHEN (used_bytes > 1000000000
              AND used_bytes < 2000000000) THEN 'G_Between_1GB_to_2GB'
            WHEN (used_bytes > 2000000000
              AND used_bytes < 4000000000) THEN 'H_Between_2GB_to_4GB'
            WHEN (used_bytes > 4000000000
              AND used_bytes < 8000000000) THEN 'I_Between_4GB_to_8GB'
            WHEN (used_bytes > 8000000000
              AND used_bytes < 16000000000) THEN 'J_Between_8GB_to_16GB'
            WHEN (used_bytes > 16000000000
              AND used_bytes < 32000000000) THEN 'K_Between_16GB_to_32GB'
            ELSE 'L_Greater_than_32GB'
        END AS ros_size,
        COUNT(*) AS ros_count
    FROM storage_containers
    GROUP BY 1, 2
    ORDER BY 1, 2
) f
GROUP BY 1;
COMMIT;

使用 max(ros_count) 取各节点中的最大值——基于最坏情况估算,因为 rebalance 期间所有节点同时进行 ROS split,耗时由 ROS 最多的节点决定。

B3. 计算最终预估

SELECT sum(duration_sec * ros_count) AS split_time_seconds
FROM merge_time m
JOIN ros_count r ON r.ros_size = m.ros_size;

结果单位,除以单节点 CPU 核数得到 ROS Split 阶段预估时间。加上 transfer、系统表查询、杂项操作三个阶段的估算即得总耗时。


总结

一个高效可控的 rebalance 操作,核心在于四点:

  1. 准备阶段做减法:清理无用对象、推进 AHM 清理删除记录、关闭不必要的系统表检查——目标是减少 rebalance 需要处理的对象数量
  2. 资源配置对齐:确保 40% 以上空闲磁盘空间(避免多阶段 rebalance)、REFRESH pool 并发数合理、释放用户池抢占的内存
  3. 监控驱动决策:利用本文第三部分的 SQL 查询实时追踪进度,遇到了锁竞争用 LockTimeout / DMLCancelTM / 手动分批三种策略应对
  4. 事后验证闭环get_node_dependencies() 确认成功、清理过期 projection、运行 vioperf/vnetperf 建立新基线

参考

扩展阅读