MPP 容错与高可用设计 —— 从 Buddy Projection 到分片订阅,分布式数据库如何在不依赖共享存储的前提下保证数据安全¶
作者:JiangChong | 撰写时间:2026年06月
适用场景框:你正在规划 MPP 集群的节点规模和拓扑,或遇到节点宕机需要理解故障影响范围和恢复机制时。
声明:本文以 Vertica 为主要剖析对象,但在所有关键设计决策处对比其他 MPP 系统(Greenplum、ClickHouse、Snowflake、Doris/StarRocks)的不同实现。MPP 共性与 Vertica 专属机制将在文中明确区分。
关联文章:K-Safety 最佳实践(操作手册)、Vertica Epoch 机制详解(事务基础)、Vertica 数据库的启动和关闭
理解全文脉络:第 1-2 节建立「为什么需要容错」的直观感受后展开核心机制;第 3 节是全文重心,分析每个关键设计决策的取舍;第 4 节将设计映射到日常运维影响,适合 DBA 阅读。时间紧迫可直接读第 6 节设计原则和第 5 节案例。
1. 问题背景 — 这个架构要解决什么问题¶
2010 年前后,一个典型的 MPP 数据仓库集群通常是这样的:8-16 台 x86 服务器组网,每台挂着若干块本地 SATA 盘做 RAID,跑着几千行的分析 SQL,每秒钟吞吐几百万行数据。
单机数据库的思路很简单:数据放共享存储(SAN/NAS),计算节点挂了就换一台,数据不会丢。但这种模式下,存储控制器本身就是单点瓶颈——16 台服务器同时从一台 SAN 读数据,吞吐上不去不说,SAN 坏了全集群瘫痪。
Hadoop HDFS 开创了另一种范式:数据块(block)分散存储在各台 DataNode 上,每个块默认 3 副本,NameNode 做元数据管理。节点故障时,副本自动补齐到其他节点。这套机制解决了「不依赖共享存储也能高可用」的问题,但代价是 3 副本意味着 200% 的存储开销——对于 TB/PB 级分析型数据来说,这个成本相当可观。
Vertica 面临的约束更苛刻。它要在 Commodity Hardware 上实现 Shared-Nothing 部署(每节点本地盘,不做共享存储),同时不能像 HDFS 那样消耗 3 倍存储。它还要求:节点恢复不是全量拷贝数据,而是增量恢复——只补故障期间漏掉的数据——否则 TB 级节点的恢复时间从小时延到天级,对 SLA 是灾难。
这就是 Vertica 容错设计需要解决的几个核心问题:
- 数据冗余:如何在每节点本地盘的前提下,保证单节点故障不丢数据?
- 冗余效率:如何让副本的存储开销在「可接受范围内」?
- 故障恢复:如何让节点故障后的恢复是增量的、在线的、对查询影响最小的?
- 脑裂防护:如何防止网络分区导致两半集群各自独立运行?
这些问题在 2010 年代的所有 Shared-Nothing MPP 系统中都不同程度存在——Greenplum 用 mirror segment,Teradata 用 fallback,Redshift 用 EBS 快照。Vertica 的回答是一套以 K-Safety 为核心的完整方案,我们接下来逐层展开。
各 MPP 系统对同一问题的不同策略,在下文中将反复对比。这里先给出一个速览:
| 系统 | 副本机制 | 存储开销(≈) | 恢复方式 | 脑裂防护 |
|---|---|---|---|---|
| Vertica Enterprise | Buddy Projection(偏移哈希环) | 100%(K=1) | 增量恢复(按 epoch) | Quorum(>N/2) |
| Vertica Eon | Shard Subscription(分片多订阅) | 取决于订阅数 | 节点重订阅 / 增量恢复 | Quorum + 分片覆盖 |
| Greenplum | Mirror Segment(物理 standby) | 100% | WAL replay(全量) | Quorum + FTS |
| ClickHouse | ReplicatedMergeTree(ZooKeeper 协调) | 100%(per replica) | 日志追赶(增量) | ZooKeeper quorum |
| Snowflake | 云存储自动持久化(S3) | 存储层处理 | 无需恢复(计算无状态) | 云存储层保证 |
| Doris / StarRocks | Tablet 多副本(BE 分布) | 100%-200%(可配) | 副本补齐(增量) | FE Master 选举 + 多数派 |
每种策略的选择背后都有架构约束——HDFS 三副本要防 DataNode 故障,SAN 双控要防控制器切换失败,而 Vertica 的 buddy projection 要同时兼顾恢复速度(增量而非全量)和存储成本(K=1 而非 K=2)。本文选择 Vertica 做深度剖析,其他系统在关键决策点显式对比。
2. 核心概念与机制¶
MPP 容错设计可以按四个层次理解——这四层是所有 Shared-Nothing MPP 系统(不管叫 Vertica 还是 Greenplum、ClickHouse、Doris)都必须回答的问题,区别在于每层选了什么方案:
| 层次 | 要解决的问题 | Vertica 的方案 | 其他系统的典型方案 |
|---|---|---|---|
| 数据分布 | 数据怎么分布到各节点? | Hash 分段(Segmentation) | Greenplum: DISTRIBUTED BY; ClickHouse: PARTITION BY + ORDER BY; Doris: DISTRIBUTED BY HASH |
| 副本冗余 | 怎么保证单节点故障不丢数据? | Buddy Projection / Shard Subscription | Greenplum: Mirror Segment; ClickHouse: ReplicatedMergeTree; Snowflake: 云存储层冗余 |
| 恢复机制 | 节点恢复怎么做到增量、在线? | Epoch + Append-Only 增量恢复 | Greenplum: WAL replay; ClickHouse: 日志追赶; Snowflake: 无状态计算节点 |
| 集群仲裁 | 怎么防止脑裂? | Quorum(>N/2) + 分片覆盖 | Greenplum: Quorum + FTS; ClickHouse: ZooKeeper quorum; Doris: FE 多数派 |
下面以 Vertica 为例逐层展开,每层末尾与其他系统做显式对比。
2.1 最直观的起点:数据分段(Segmentation)¶
假设有一张 100 亿行的交易表 trades,分布在 4 个节点组成的集群上。怎么把 100 亿行分到 4 个节点?
最简单的做法是 Round-Robin:第 1 行给节点 1,第 2 行给节点 2……第 4 行给节点 4,第 5 行又回到节点 1。这样数据量绝对均匀,但问题也很明显:当你 WHERE symbol = 'HPQ' 时,匹配的行可能散布在所有节点上,每个节点都要参与查询——JOIN 和聚合也无法利用「相同 key 的数据在同一节点」这个事实来做 Local Join。
更好的做法是 Hash 分段。选一个高基数列——比如交易 ID(trade_id)——对每个行的 trade_id 做哈希,哈希值落在某个范围的行分配给某个节点。这样数据既均匀分布,又保证了「hash 相同值到同一节点」的确定性路由。
-- 一个典型的 hash 分段 projection 定义
CREATE PROJECTION trades_super
SEGMENTED BY HASH(trade_id) ALL NODES
AS SELECT * FROM trades;
Projection 是 Vertica 的物理存储单元——类似于 ClickHouse 的 part + sort key(数据按排序键组织成不可变文件)、Greenplum 的 heap table(按分布键分布到 segment),关键差异是 Vertica 允许同一张表有多个不同排序方式的 projection 副本。
这很像把一副扑克牌按花色分给 4 个人——每人分到等量的牌,你要找「所有的红心」时只需问拿红心的人,不用惊动另外三位(C-Store 7 Years §3.6)。
2.2 从「一份数据」到「两份数据」:Buddy Projection¶
Hash 分段解决了分布问题,但没有解决容错问题。如果节点 2 挂了,HASH(trade_id) 落在节点 2 范围内的所有行就再也读不到了。
Buddy Projection 的意思是说:再创建一份数据,通过偏移分段键将数据分配到不同节点——实际做法是对原始分段表达式的哈希范围做偏移(segment 编号 +1 模节点数),确保同一行数据的主副本和 buddy 副本不会落在同一个节点上。这就形成了一个「偏移对」——节点 1 和节点 2 互为 buddy,节点 2 和节点 3 互为 buddy,以此类推(C-Store 7 Years §3.6 描述了一致性哈希环上的节点分配机制,buddy 通过环上偏移实现)。
节点1 --- 节点2 (buddy pair, 共享 segment S1)
节点2 --- 节点3 (buddy pair, 共享 segment S2)
节点3 --- 节点4 (buddy pair, 共享 segment S3)
节点4 --- 节点1 (buddy pair, 共享 segment S4)
在这个配置下,节点 2 宕机了怎么办?segment S2 在节点 3 的 buddy projection 里还有一份拷贝,segment S1 在节点 1 里还有一份拷贝。只要不是相邻两个节点同时宕机,数据就仍然是安全的(C-Store 7 Years §5.2)。
注意这里的「相邻」不是物理机架相邻,而是 依赖环(Dependency Ring)上的相邻——即两个节点持有同一个 segment 的主副本和 buddy 副本。如果节点 2 依赖节点 3 提供某些 segment 的备份,节点 2 又为节点 1 提供某些 segment 的备份,这三个节点就形成了依赖链。当所有节点的依赖关系画成图,就形成了一个环——每个节点依赖前一个、被后一个依赖。
-- 查看节点依赖位图
SELECT GET_NODE_DEPENDENCIES();
-- 逐对查看 buddy pair
SELECT dependency_id, min(node_name) AS node_x, max(node_name) AS node_y
FROM vs_node_dependencies JOIN nodes ON node_oid = node_id
GROUP BY 1 HAVING count(*) = 2 ORDER BY 1;
解读方式(v8.0.1 之前):最右 bit 代表 node_id 最小的节点。如位图 011 表示 node_id 最小和第二小的节点是 buddy——位图中它们的 bit 同时为 1,与最大的节点(bit=0)不同组。全 1 的位图(如 111)代表非分段投影(UNSEGMENTED),在 K=1 时每个节点各存一份拷贝(K-Safety 最佳实践、C-Store 7 Years §5.3)。
2.3 K-Safety:用 K 来量化容错能力¶
K-Safety 是对上述机制的统一命名:K=1 表示每个 segment 有 2 份拷贝(1 主 + 1 buddy),可以容忍 1 个任意节点的故障;K=2 则表示 3 份拷贝,容忍 2 个任意节点故障。
这比 HDFS 的简单三副本更高效:HDFS 默认 3 副本 = 200% 存储开销,Vertica K=1 = 100% 存储开销,K=2 = 200% 存储开销。对于分析型数据库的 TB/PB 级数据,这个差异直接影响硬件成本。
K-Safety 的约束不是「K+1 个节点宕机就一定 shutdown」——而是 只有当数据真的不可访问时才会触发 shutdown。有可能 K+2 个节点宕机了但因为它们不是相邻节点,集群照样跑得好好的(C-Store 7 Years §5.3)。
-- 查询 K-Safety 级别
SELECT GET_DESIGN_KSAFE();
-- 查询系统级 K-Safety 状态(含当前容错余量)
SELECT node_count, node_down_count, current_fault_tolerance
FROM v_monitor.system;
除了数据安全,还有 Quorum 机制:集群必须保持超过半数的节点在线才能继续运行(⌊N/2⌋ + 1)。这是为了防止网络分区导致「两半集群各自独立运行」的脑裂(Split-Brain)问题。如果 6 节点集群中 3 台宕机 → 只剩 3 台 = ⌊6/2⌋ + 1 = 4,不满足 quorum → 安全 SHUTDOWN(C-Store 7 Years §5.3)。
2.4 Epoch + Append-Only:不用 WAL 的恢复魔法¶
传统数据库靠 Write-Ahead Log (WAL) 做崩溃恢复。节点宕机后从头 replay WAL,行存数据库还好,列存 MPP 每个节点可能有数 TB 数据,WAL 会变得巨大无比。
Vertica 的做法源自 C-Store 的关键设计决策:存储是 Append-Only 的,所有数据一写即定,永不原地修改。每次 INSERT/UPDATE/DELETE(其实是 Insert + Delete Marker)都被打上 epoch 时间戳。Epoch 是 Vertica 的全局逻辑时钟——在 Snowflake 中类似的机制是 Time Travel 窗口(按时间偏移查询历史快照),在 Greenplum 中对应事务 ID horizon(控制 dead tuple 可见性),但 epoch 是数据库级而非行级,粒度更粗、管理成本更低。这意味着:
- 数据本身就是日志:不需要单独的 WAL,epoch 号就是逻辑时间轴
- 查询天然是 Snapshot Isolation:指定 epoch 就能读到那个时间点的完整快照
- 恢复是增量的:宕机节点只需从 buddy 拷贝
宕机期间的 epoch 增量数据
恢复的具体流程(来源:C-Store 7 Years §5.2)分为两阶段:
- 历史阶段(Historical Phase):宕机节点从 LGE(Last Good Epoch,该节点宕机前所有 WOS 数据已迁入 ROS 的最后一个 epoch)开始,向 buddy 拷贝所有后续提交的数据。LGE 是 Vertica 独有的恢复锚点概念——其他 MPP 没有完全对应的东西:Greenplum 用 WAL LSN 定位恢复起点,ClickHouse 用 ZooKeeper 记录的 log pointer,Snowflake 的计算节点无状态因此不需要这个锚点。这期间不持有任何锁,查询和加载正常进行。
- 当前阶段(Current Phase):历史拷贝完成后,以 Shared 锁保护,拷贝 LGE 之后到当前 epoch 的剩余增量。完成后节点恢复正常。
如果 buddy projection 用的是相同的排序键——即「replica buddy」——恢复只需整体拷贝压缩好的 ROS 容器(ROS = Read Optimized Store,Vertica 的列存不可变数据文件,类似于 ClickHouse 的 data part 或 Snowflake 的 micro-partition),不做解压-排序-重新压缩。如果 buddy 用了不同的排序键,则需要走 INSERT ... SELECT ... 式的执行计划来重新组织数据(C-Store 7 Years §5.2)。
2.5 Eon 模式的容错:从 Buddy 到分片订阅¶
以上所有机制都建立在「数据在本地磁盘」的 Enterprise 模式假设上。Eon 模式(计算-存储分离)把数据持久化搬到了 S3/HDFS/MinIO 等公共存储上,节点手里只有本地缓存(Depot)。这会从根本上改变副本模型。
Eon 模式下,数据库的数据被哈希切分成多个分片(Shard),每个分片被 至少 2 个节点订阅:
- Primary Subscriber:负责该分片的 Tuple Mover 操作(mergeout 等)
- Secondary Subscriber:持有分片目录(catalog)元数据副本,不维护独立 depot
| 维度 | Enterprise 模式 | Eon 模式 |
|---|---|---|
| 副本机制 | Buddy Projection(物理副本) | Shard Subscription(分片订阅) |
| 容错单元 | Buddy Pair(两个节点互为备份) | Shard Replica(同一分片多节点订阅) |
| 相邻节点同时宕机 | Unsafe SHUTDOWN | 集群 READONLY |
| 恢复方式 | 恢复节点 + AHM 推进 | 节点重订阅分片 / revive_db |
| 弹性伸缩 | 需 rebalance(小时~天) | 无需 rebalance(分钟级) |
Eon 分片订阅机制的系统描述参考了 K-Safety 最佳实践(基于 Vertica 26.2 官方架构文档),Enterprise 恢复机制的两阶段模型参考了 C-Store 7 Years §5.2。
-- 查看每个分片有几个订阅节点,少于 2 则为单点风险
SELECT shard_name, count(*) AS subscriber_count
FROM v_catalog.node_subscriptions
WHERE subscription_state = 'ACTIVE'
GROUP BY 1
HAVING count(*) < 2;
Eon 模式保持运行也需要满足两个条件:维持 Quorum(超过半数 primary 节点 UP)和维持分片覆盖(每个分片至少有一个 primary 订阅者在服务)。二者只要有一个不满足,集群就进入 READONLY。
Elastic K-Safety 是 Eon 模式额外的自动保护层:当分片的 primary 或 secondary 订阅节点宕机时,Vertica 自动让另一个 primary 节点也订阅该分片作为备份。这个自动补充的保护会在宕机节点恢复后被自动撤销。但补充不是无限的——以 10 个 primary 节点的 K=1 数据库为例,当 UP 节点降到 7 个时就停止新增了(公式:N/2 + K + 1 = 10/2 + 1 + 1 = 7),因为再多一个节点宕掉就逼近 quorum 红线了。
3. 设计决策与 Trade-off¶
3.1 决策一:Buddy Projection 排序键——相同还是不同?¶
Vertica 最初宣称 buddy projection 的一大卖点是两副本不必排序相同——你可以让主投影按 (symbol, date) 排序(优化按股票查日期分组),让 buddy 按 (date, price) 排序(优化按日期做价格分析),花一份冗余存储的钱,拿到两份不同的查询优化。
这在纸面上非常吸引人,但 Design Choices 论文坦诚地记录了这条路的教训(来源:Analytic Database Design Choices - Vertica's Experience and Perspectives §3.4):
| 维度 | 相同排序键(Replica Buddy) | 不同排序键(Optimized Buddy) |
|---|---|---|
| 全节点健康时查询性能 | 基准 | 部分查询可提速 ~ 3x |
| 节点宕机时查询性能 | 基本不变 | 可能退化 42x(1 个真实案例) |
| 优化器复杂度 | 标准 | 显著增加(要同时兼顾两套排序的 plan) |
| 节点重建效率 | 直接拷贝压缩文件 | 需解压→排序→重新压缩 |
| 磁盘使用率要求 | 正常 | 需要额外临时空间做外部排序 |
| 当前生产实践 | 所有大客户使用 | 几乎无人使用 |
这个 trade-off 的启示很深刻:在容错系统中,「正常态」和「降级态」的性能不能差距太大。如果节点健康时跑 10 秒的查询在节点宕机后变成 7 分钟,那用户感受到的不是「高可用」,而是「数据库又出问题了」。容错设计的真正目标不是全节点在线时的峰值性能,而是降级运行时的性能下限。
其他 MPP 系统为什么没有这个选择? Greenplum 的 mirror segment 是物理 standby,永远是原表的精确副本——没有「不同排序」的选项,也就不存在这个 trade-off。ClickHouse 的 ReplicatedMergeTree 同样如此:副本之间 data parts 完全一致,ZooKeeper 只协调「谁有哪个 part」。Snowflake 的副本由云存储层(S3)保证耐久性,数据库引擎根本不管副本排序。Doris/StarRocks 的 Tablet 多副本也是字节级一致。换句话说,Vertica 的「不同排序键 buddy」这个选项是一把双刃剑——纸面上能提升查询性能(一份冗余存储提供两份排序优化),实践上却因为降级态性能退化太严重而被几乎所有人放弃。这个教训对任何「为正常态过度优化」的设计都有警示意义。
3.2 决策二:K=1 还是 K=2?¶
这个问题在生产中频繁出现。直觉是「K=2 更安全」,但实际几乎没有人用(来源:K-Safety 最佳实践):
| 维度 | K=1 | K=2 |
|---|---|---|
| 存储开销 | 基准(1 份冗余 = 100% 额外) | +50%(1 份冗余 → 2 份冗余 = 200% 额外) |
| 任意单节点故障 | ✅ 容忍 | ✅ 容忍 |
| 任意双节点同时故障 | ❌ 不保证(但不相邻可能 OK) | ✅ 容忍 |
| buddy 相邻双节点同时故障 | ❌ Unsafe SHUTDOWN | ✅ 容忍 |
| 日常管理复杂度 | 基线 | 复杂(需 K=2 设计、更多投影维护) |
为什么不推荐 K=2? 因为 K=1 下造成 shutdown 的条件是「相邻两节点同时宕机」。这在物理上意味着两台服务器恰好承载了同一个 segment 的主备副本,在没有任何共同原因的情况下同时故障的概率极低——两台硬件独立故障的概率是平方关系的。真正能让两台一起挂的场景(机架断电、交换机故障、虚拟机宿主机宕机),会在 Eon 模式下被分片订阅的物理分布解决,在 Enterprise 模式下被 Fault Group 机制解决。
跨系统对比——其他 MPP 怎么设置副本数?
系统 默认副本数 存储开销 推荐场景 Vertica K=1 2(1 主 + 1 buddy) 100% 绝大多数生产环境 Greenplum 2(primary + mirror) 100% 默认配置,不提供更高选项 ClickHouse 可配(ReplicatedMergeTree) 100% per replica 通常 2 副本,3 副本少见 Doris/StarRocks 3(默认 replication_num=3)200% 默认 3 副本,可降为 2 HDFS 3 200% Hadoop 生态标准 Snowflake N/A(云存储层冗余) 云厂商定价包含 用户不可控
Doris/StarRocks 默认 3 副本的做法值得注意——它的存储开销基线就是 200%,因为目标是通用 OLAP 场景而非纯分析型,对可靠性要求更高。Vertica 用 K=1 作为默认值能成立,前提是表数据主要来自批量加载(可以重跑)且集群规模足够大(单节点故障不影响 quorum)。如果你的 Vertica 集群只有 3 个节点,K=1 下一个节点宕机后剩 2 个——此时刚好满足 quorum(⌊3/2⌋ + 1 = 2,文档确认需要 2 个 primary 节点宕机才触发 quorum 丢失),集群不会 shutdown。但问题在于:第一个故障后 K-Safety 归零——剩余 2 个节点互为 buddy pair,任何一个再宕机就是相邻节点同时宕机 → Unsafe SHUTDOWN。
3.3 决策三:Enterprise 的 Fault Group vs Eon 的分片物理分布¶
Enterprise 模式下,buddy pair 是数据冗余的基本单元。如果你的 8 节点集群部署在两个机架上,理论上可能出现「一个 buddy pair 的两个节点都在机架 1」——机架断电 → 相邻两节点同时 DOWN → Unsafe SHUTDOWN。
Fault Group 就是 Enterprise 模式的「机架感知」机制:手动定义哪些节点属于同一物理故障域,Vertica 会在计算节点依赖时确保 buddy pair 跨越不同 fault group。
Fault Group 是 Enterprise 模式专属功能。配置后需重新计算节点依赖:
SELECT RECOMPUTE_NODE_DEPENDENCIES();
Eon 模式没有 Fault Group 这个概念——因为它的冗余单元不是 buddy pair,而是分片订阅。你的任务是确保同一个分片的两个订阅节点不落在同一个物理故障域上。本质上达到了相同的目的,实现的路径不同。
其他 MPP 的故障域隔离方案:Greenplum 通过
gp_segment_configuration中的hostname自动确保 mirror 不与 primary 同主机,但没有显式的「机架组」概念——需要运维在部署时手动分散。ClickHouse 依靠 ZooKeeper 协调副本分布,但副本放置逻辑由system.clusters宏定义和 ZooKeeper 路径结构驱动,没有内置的故障域抽象层——用户需要在集群配置文件中手动将 replica 分配到不同 host。Doris/StarRocks 的 FE 在分配 Tablet 副本时会尽量分散到不同 BE 和不同主机,这是自动的但细粒度不可控。Vertica 的 Fault Group 是其中最显式的机制——你定义故障域,系统保证容错单元跨越它们。缺点是如果配置错误(两个 buddy 放到同一 fault group),后果是静默的单点风险而非系统主动告警。Snowflake 由于存储由云厂商管理,故障域隔离是云厂商的职责,用户无需(也无法)操心。
3.4 决策四:Shared-Nothing + 软件副本 vs 共享存储¶
为什么 MPP 选择「每节点本地盘 + 软件副本」而不是「共享存储阵列」?
| 维度 | Shared-Nothing + 软件副本 | 共享存储(SAN/NAS) |
|---|---|---|
| IO 带宽 | 横向扩展(每加节点 = 带宽增加) | 受限于存储控制器 |
| 成本 | 本地 SATA 盘 | 企业级 SAN 贵一个数量级 |
| 单点故障 | 无(但依赖软件实现正确) | 存储控制器是单点 |
| 额外开发成本 | 需要自己实现副本同步 + 恢复 | 无需 |
Vertica 选择了前者,代价是实现复杂(Design Choices §2.7 直接承认了这一点)。但考虑到典型 MPP 集群规模在 4-128 节点之间,共享存储的 IO 瓶颈会非常明显。
4. 设计对实际使用的影响¶
4.1 查询¶
自动生效:当一个节点宕机时,优化器会自动将所有访问该节点的查询重定向到 buddy projection,不需要应用层感知。查询会继续执行,只是性能可能下降(如果用不同排序键的 buddy)或基本不变(如果用 replica buddy)。
需要手动干预:如果集群进入 READONLY(Eon 分片丢失)或 SHUTDOWN(Enterprise 相邻节点宕机),查询无法进行,需要管理员的恢复操作。
4.2 数据加载¶
自动生效:加载操作在集群正常时是无感的——COPY 语句自动将数据按分段键路由到对应节点,主投影和 buddy 投影各自接收一份。
需要手动干预:
- 加载性能在节点恢复期间会下降(恢复操作消耗 IO 和网络带宽)
- 新建 projection 需要通过
REFRESH填充数据,其历史阶段无锁、当前阶段持有 Shared 锁(C-Store 7 Years §5.2)
4.3 运维¶
计划内维护:在 Enterprise 模式下,如果计划同时关停多个节点做维护,务必先用 SELECT * FROM critical_nodes; 检查是否会触发 unsafe shutdown。如果关停的节点集包含了相邻 buddy pair,需要分批操作。
在 Eon 模式下处理双主子集群也一样——关闭主子集群前必须先 SELECT DEMOTE_SUBCLUSTER_TO_SECONDARY(),否则集群直接 READONLY(来源:Eon 双主子集群的注意事项)。
常见误解:
- 「projection 越多越好」:每个 projection 都会增加加载时的 I/O(数据要同步写入多个投影)、占用更多存储空间
- 「K-safety 设为 K=2 就更安全」:如 §3.2 分析,K=2 在物理隔离良好的集群中边际收益极低,但存储开销大 50%
- 「Eon 模式天生比 Enterprise 可靠」:Eon 的容错链引入了公共存储这个新依赖(S3/HDFS 的可用性直接影响数据库)。它的优势是弹性伸缩和恢复速度,并非「更可靠」
5. 案例验证¶
📝 虚构案例:Replica Buddy 的降级运行效果¶
场景:某金融公司的 8 节点 Enterprise 集群,运营着一份 200 亿行的交易分析表。DBA 最初被「buddy 可以用不同排序键」吸引,给主投影按 (symbol, date) 排序,给 buddy 按 (trade_id) 排序——以为这样既能快速按股票筛选,又能在需要查单笔交易时受益。
问题出现:周一早上,节点 3 因为硬件故障宕机。运维发现集群虽然没 shutdown,但所有涉及交易表的查询都从平时 2-3 秒慢到了 80-120 秒。原因很简单——buddy projection 的 trade_id 排序对现有的 WHERE symbol = '...' 的查询近乎无优化效果,优化器被迫用 buddy 做全表扫描然后过滤。
事后重做:DBA 将所有 buddy projection 改为与主投影相同排序键的 replica projection。降级态查询性能恢复到 3-6 秒(略有下降是因为少了并行度)。同时通过 Fault Group 确保 buddy pair 不在同一机架。
原理回看:这正是 Design Choices §3.4 中记录的真实教训——「~3x speedup with all nodes up resulted in a 42x slowdown in the case of node-down operation」。容错设计要把降级态性能摆在优先位置。
🌐 跨系统模拟案例:同一故障场景,不同系统的应对路径¶
📝 跨系统模拟——以下数据为基于各系统文档和架构原理的合理估算,非实测值。
场景:一个由 12 个节点组成的 MPP 集群,存储着 500 亿行交易数据(约 15 TB 压缩后)。某天凌晨 2:00,节点 node07 因磁盘故障宕机。集群剩余 11 个节点继续运行,node07 在 2:30 完成磁盘更换后重新加入集群。
| 维度 | Vertica Enterprise(K=1) | Vertica Eon | Greenplum | ClickHouse | Snowflake |
|---|---|---|---|---|---|
| 宕机时查询 | 自动路由到 buddy,查询继续(可能有性能影响) | 分片自动由另一订阅者接管,查询继续 | 自动切换到 mirror segment,查询继续 | 副本自动接管,查询继续 | 无影响(计算节点无状态) |
| 宕机时加载 | 继续 | 继续,分片订阅者自动接管 | 继续,mirror 承载写入 | 继续,剩余副本承载写入 | 继续(写入直通云存储) |
| 恢复数据量 | ~500 GB(2 小时增量) | ~100 GB(depot 缓存重建) | ~15 TB(全量 mirror 重建) | ~500 GB(增量日志追赶) | 0(无需恢复) |
| 恢复耗时(预估) | ~20 分钟 | ~10 分钟 | 数小时(全量重建) | ~30 分钟 | 0 |
| 恢复期间查询影响 | 历史阶段无锁,当前阶段短暂 Shared 锁 | 节点重订阅,查询基本无感知 | gprecoverseg 期间查询正常但 mirror 不可用 | 追赶期间查询正常 | N/A |
| 恢复期间存储 IO | 中等(单节点拷贝增量 ROS) | 低(depot 预热,按需拉取) | 高(全量文件同步到新磁盘) | 中等(增量 part 下载) | 无 |
| 集群是否有单点 | buddy pair 不能相邻 | 同一分片的订阅者不能同主机 | mirror 不能与 primary 同 host | 副本不能同 host | 云厂商保证 |
解读:这张表揭示了「容错设计的代价付在哪里」这个核心差异:
- Greenplum 的代价是恢复时间——mirror 替换需全量重建,15 TB 数据恢复以小时计。这期间如果再挂一个 primary segment,该 segment 就没有冗余了。
- Vertica Enterprise 的代价是恢复期间 IO 压力——增量恢复虽然快,但 buddy 节点在宕机期间要同时服务两份查询(自己主 segment 的 + 宕机节点 segment 的),查询负载翻倍。
- Vertica Eon 的代价是公共存储依赖——恢复快的代价是 S3/HDFS 必须始终可用。如果故障原因是 S3 区域性中断,那所有节点都会受影响。
- ClickHouse 的代价是 ZooKeeper 压力——副本协调和日志追赶都依赖 ZK,大规模集群中 ZK 本身就是瓶颈。
- Snowflake 的代价是用户不可控——你不需要做任何事情,但你也无法优化任何事情。恢复速度完全取决于云厂商的存储层。
启发:选择容错方案不是选「最好」的那个——它们没有绝对优劣。你选的是「你愿意承受哪种代价」:存储成本(Doris 3 副本 200%)、恢复时间(Greenplum 全量重建)、公共存储耦合(Eon 依赖 S3)、运维复杂度(ClickHouse 需要维护 ZK)、还是可控性(Snowflake 全托管=全放弃控制权)。Vertica 的独特定位是用增量恢复和 Append-Only 存储同时压低恢复时间和存储成本,代价是需要理解 K-Safety 和 epoch 这两个专属概念。
📋 真实案例 · 来源:某电信运营商 VE 集群故障报告¶
背景:某电信运营商 Eon 模式集群(原始报告未记录确切节点数),数据库版本 11.1.1-14。
触发:2025-11-11,一条特定 SQL 触发了产品 Bug(该版本已过保),导致 v_node0013 和 v_node0014 两个节点同时 PANIC 宕机。还有一个节点 v_node0011 也同时宕机(同样因该 SQL 触发)。
故障链:
node0013和node0014恰好是两个订阅了同一分片segment0005的节点(分片的 primary + secondary 订阅者)- 两个节点同时宕机 → 分片
segment0005的订阅完全丢失 - Eon 模式检测到分片覆盖不完整 → 集群所有剩余节点进入 READONLY 状态
- 客户端 SQL 全部报错,批处理业务中断
恢复:启动 3 个宕机节点后,READONLY 节点自动重启,状态从 READONLY 转为 READWRITE,集群恢复服务。
根因分析:
- 直接原因:SQL 触发产品 Bug 导致节点 PANIC
- 容错设计层面:分片
segment0005的 primary 和 secondary 订阅节点是 node0013/0014——这两个节点恰好是物理上的相邻编号,如果它们落在同一个物理机/虚拟机宿主上,就构成了单点风险
经验教训:
- Eon 模式下不仅要关注集群 x% 节点在线,更要关注分片订阅的节点分布——两个订阅同一分片的节点必须做物理级隔离
- 节点 PANIC 是容错的「第一道防线失败」,需要排查根因并升级到在保版本(建议升级到
23.3.0-12+) - 处理节点宕机前,先用
SELECT * FROM v_catalog.node_subscriptions分析分片订阅关系,确认哪些节点是关键节点
跨系统启示:这个案例中「两个订阅同一分片的节点同时宕机→集群 READONLY」的故障链,在 Vertica Eon 模式下是一个特有风险——因为分片订阅是逻辑层的冗余关系,两订阅者可能落在同一物理宿主机上(尤其虚拟机/K8s 部署时),单台宿主机故障就能触发。在 Greenplum 中,mirror 与 primary 不会在同一主机(gpinitsystem 会拒绝),这个风险被从部署层消除了。在 ClickHouse 中,ReplicatedMergeTree 需要用户自己保证副本不在同一 host——如果配置错误,后果同样是数据不可用。在 Doris/StarRocks 中,FE 的 Tablet 分配器会自动避免同一 Tablet 的副本落在同一 BE 或同一主机,用户基本不需要操心。Snowflake 完全不暴露这个层面给用户。
换句话说,Vertica Eon 给了你配置分片订阅分布的灵活性,但也把「确保冗余物理隔离」的责任交给了你。如果你在规划 Eon 集群时不检查分片订阅的物理分布,你实际上是在依赖运气保证高可用。这是一个典型的「自动化不足导致的人因风险」——Vertica 需要用户主动用 SQL 检查,而 Doris 把这个检查内置到了 FE 调度器。
6. 设计原则总结¶
- 【通用】冗余是容错的物质基础,但冗余度 = 存储成本。 K=1(1 主+1 备)在大多数场景下是最优平衡点。只有在物理隔离无法保证的时候才考虑 K=2。如果有明确的双节点同时故障的根因(如机架断电),用 Fault Group(Enterprise)或分片订阅物理分布(Eon)来解决,而不是盲目增加 K 值。反例:Doris 默认 3 副本(200% 开销),如果集群只有 3 个 BE 且都在同一机架,机架断电=所有副本丢失=数据不可恢复。副本数不是越多越安全——副本的物理分布比副本的绝对数量更重要。
- 【通用】降级态性能比正常态性能更重要。 容错系统设计的目标不是「所有节点在线时能跑多快」,而是「节点宕机时性能不能低到不可接受」。这就是为什么所有大客户都用 replica buddy 而非不同排序键的 buddy。反例:Greenplum 中如果 mirror 用的是不同性能的磁盘(比如 primary 是 SSD、mirror 是 HDD),节点切换后查询可能慢 10 倍以上——用户在正常态看到的是 SSD 性能,降级态看到的是 HDD 性能,这种体验落差和不一致的 buddy 排序键问题如出一辙。
- 【Vertica】Append-Only + Epoch 是增量恢复的基础。 不需要传统 WAL,数据本身即日志——这是 Vertica 能在 TB 级数据上实现分钟级增量恢复的根本原因(C-Store 7 Years §5.2)。反例:Greenplum 没有这个机制,恢复必须全量 WAL replay——15 TB 的表恢复以小时计。但这个设计的代价是 epoch 本身是全局单点(AHM 推进依赖所有节点),Snowflake 通过计算-存储完全分离绕开了这个问题,代价是用户失去对存储层的一切控制。
- 【通用】Quorum 防止脑裂,分片覆盖防止数据丢失。 Enterprise 模式和 Eon 模式都依赖这两条线:单节点宕机靠冗余副本扛住(buddy/shard subscription),多节点同时宕机靠 quorum(>
N/2)触发安全 shutdown,防止脑裂。反例:3 节点 K=1 下,一个节点宕机后集群虽不 shutdown,但剩余 2 节点互为 buddy,K-Safety 归零——任何一个再宕机就是 Unsafe SHUTDOWN。即便 4 节点,K=1 下同样不能同时宕 2 台(quorum 需要 ≥3)。K=2 才能保证双节点同时故障不丢数据,但代价是 200% 存储。这就是为什么最小生产集群推荐 4 节点——不是 4 能扛两个故障,而是 3 在扛完第一个后几乎没有余量;4 在扛完第一个后还剩 3 个节点,quorum 有余、查询并行度损失更小。 - 【Vertica】Eon 模式下,分片订阅分布 = 你的故障域设计。 没有 Fault Group 帮你自动做物理隔离,需要运维自己确保同一分片的订阅节点不落在同一故障域。反例:某电信运营商案例中两个订阅同一分片的节点同时宕机,直接导致 READONLY。如果在部署时检查了分片订阅的物理分布,这个故障可能只影响性能而非可用性。
- 【通用】恢复期间的数据加载性能会下降。 恢复过程的历史阶段无锁但消耗 IO/网络资源,当前阶段短暂持有 Shared 锁。大规模恢复(如全节点 revive_db)期间建议降低加载并发度。反例:Greenplum 的
gprecoverseg -F(全量恢复)期间虽然查询正常,但 mirror 处于 "not synchronized" 状态的整个期间(数小时),如果再挂一个 primary segment,数据就丢了。所有 MPP 系统的恢复期都是「脆弱窗口」——恢复期间应避免计划外操作。
扩展阅读¶
| 文章 | 说明 | 适用性 |
|---|---|---|
| K-Safety 最佳实践 | 本文的操作手册版本,包含完整的 SQL 速查表和 Enterprise/Eon 对比决策框架 | Vertica 专属 |
| Vertica Epoch 机制详解 | 理解 epoch 是理解恢复机制的前提——LGE/AHM/Current Epoch 三层 epoch 的分工与推进规则 | Vertica 专属 |
| Vertica 数据库的启动和关闭 | 启动过程中的 quorum 建立、恢复触发条件等实操细节 | Vertica 专属 |
| Vertica 弹性伸缩功能介绍与配置 | Eon 模式弹性伸缩与容错机制的联动(REBALANCE_SHARDS 等) | Vertica 专属 |
| Eon 双主子集群的注意事项 | 双主子集群的正确关闭/恢复流程,避免误操作导致 READONLY | Vertica 专属 |
| The Vertica Analytic Database - CStore 7 Years Later | §5.2 Tolerating Failures(恢复两阶段)、§5.3 Cluster Integrity(K-Safety 定义与 quorum 机制) | MPP 通用(列存 Append-Only 原理可用于理解 ClickHouse/Doris) |
| Analytic Database Design Choices - Vertica's Experience and Perspectives | §3.4 Buddy Projection Sort Order(不同排序键的设计反悔实录) | MPP 通用(降级态性能原则适用于任何有副本的系统) |