从单节点 ClickHouse 迁到多副本多分片集群,真正要解决的不是“怎么把机器变多”,而是“怎么把写路径、复制语义和切换窗口一起收敛成可控迁移”。

先说结论

  • 技术上可以在集群里继续使用 MergeTree
  • 但如果你的目标是多副本高可用,那就不应该把核心表继续停留在 MergeTree
  • 从单节点迁到集群不能理解成无状态无缝切换,更准确地说,是可以做到业务侧接近无感或短暂停写切换,但本质仍然是一轮受控迁移;
  • 表引擎切换可以在迁移过程中一起做,但更稳的方式通常不是原地魔改,而是按目标拓扑新建集群和新表,再迁数据、追增量、最后切流。

多副本多分片集群里还能不能用 MergeTree

可以,但这只回答了“语法上允不允许”,没有回答“架构上合不合理”。

  • 多分片 场景里,本地 shard 表技术上可以是 MergeTree
  • 多副本 场景里,如果本地表仍是 MergeTree,那这些副本节点上的表并不会自动形成真正的数据副本;
  • 也就是说,MergeTree 在集群中不是不能用,而是不提供你通常期待的副本容灾语义

所以,如果你的生产目标是“多副本多分片”,我会把 ReplicatedMergeTree 家族视为默认项,而不是把 MergeTree 延续到新架构里。

为什么不能把这件事理解成“无缝切换”

从单机到集群,变化的不是一层,而是至少三层:

  • 拓扑变了:从单节点变成 shard + replica + Keeper
  • 写入路径变了:集群通常通过 Distributed 表或显式路由写入,而不是继续把业务直接打到某一个本地表;
  • 复制语义变了MergeTreeReplicatedMergeTree 不是简单改个名字,而是把数据、复制元数据和恢复语义一起切换。

因此,“无缝”最多只能理解成:

  • 业务读写几乎无感;
  • 或只经历一次可控的短暂停写 / 短时切流;
  • 而不是数据库内部什么都不变地自动升级成集群。

能否在迁移过程中同时完成表引擎切换

可以,而且我认为这通常就是更合理的时机。

但我更推荐下面这类路径:

  1. 先搭建目标集群;
  2. 在目标集群直接创建最终形态的本地表,也就是 ReplicatedMergeTree
  3. 再创建对外统一入口的 Distributed 表;
  4. 把旧单机数据导入新集群;
  5. 追平增量数据;
  6. 最后切换业务流量。

相较之下,先在旧单机上把所有表原地改成 ReplicatedMergeTree,再去拼集群,通常并不更简单。官方给出的 ATTACH ... AS REPLICATEDconvert_to_replicated 等方法是受支持的,但更像单表受控转换工具,不是“单机无痛升级成多副本多分片”的总方案。

更适合你场景的迁移思路

你当前是:

  • 单节点;
  • 无副本;
  • 无分片。

这意味着你最大的优势是:不存在多副本数据漂移问题。因此迁移复杂度主要落在:

  • 新拓扑怎么设计;
  • 数据怎么搬;
  • 增量怎么追;
  • 业务怎么切。

对这种起点,我更倾向的顺序是:

路线 A:先上单分片双副本,再决定是否分片

这是更稳的默认路线。

  • 第一步先做 1 shard + 2 replicas
  • 先把单点故障问题解决;
  • 等确认容量和并发真的需要横向扩展时,再继续拆 shard。

这样做的好处是,不把“副本高可用”和“分片扩容”两件复杂事同时引入。

路线 B:如果单机已经明显到瓶颈,再一次性迁到多分片多副本

这种路线也可行,但你要同时处理:

  • 分片键设计;
  • 历史数据重分布;
  • 分布式写入入口;
  • 副本同步;
  • 查询路径调整。

工程风险显著更高。

一次本地演练后的较短路径

如果我不是在写迁移方案,而是要先把路径快速跑通,我会先把问题压缩成一个更短的闭环:

  1. 准备一个单节点源库,数据库先保持 Atomic,业务表先保持 MergeTree
  2. 在目标集群创建 ENGINE=Replicated 的目标数据库;
  3. 在目标集群创建最终本地表,直接使用 ReplicatedMergeTree
  4. 再创建对外入口的 Distributed 表,把分片键一次定下来;
  5. 从目标集群反向连接源库,用 INSERT INTO target_dist SELECT ... FROM remote(...) 回灌历史数据;
  6. 校验行数、聚合结果、各 shard 分布和 system.replicas 状态;
  7. 这条链路跑通后,再把 mock 源替换成真实云端源,把样例 schema 换成真实 schema。

这次我在本地 clickhouse-operator/examples/minimal.yaml 对应的目标集群上,实际验证的就是这条短路径。它的好处不是“更优雅”,而是把迁移里最容易卡住的几个点尽快暴露出来

  • 目标集群的 Replicated 数据库和 ReplicatedMergeTree 本地表能不能顺利创建;
  • Distributed 表的分片键是不是已经想清楚;
  • 目标集群能不能反向访问源库;
  • 源库是否具备专用迁移账号和最小必要权限;
  • 数据灌入后,副本状态和分片分布是否符合预期。

我觉得这条短路径最大的价值,是把“从单机迁到集群”从大而空的架构讨论,压缩成一个可以很快得到反馈的工程闭环。只要这个闭环跑通,后面真正需要补的其实就只剩三类东西:真实 schema、真实源端连接信息,以及增量追平方案。

这次演练里,我用一个本地 mock 单节点源构造了 legacy_analytics.events 这张 MergeTree 表,再把数据迁入目标集群中的 migration_demo.events_localmigration_demo.events。最后验证到:

  • 源端与目标端的 count()sum(amount)uniqExact(user_id) 一致;
  • 数据成功按 cityHash64(user_id) 分散到两个 shard;
  • 每个 shard 的 3 个 replica 都完成复制,queue_size=0absolute_delay=0

所以,如果我要把这次经验浓缩成一句更实用的话,那就是:先不要急着讨论“能不能无缝迁”,先把“源库可读、目标表已建、历史数据可回灌、分片和副本状态可验证”这条短路径跑通。

后来我又把这条短路径,进一步套进一个更贴近真实业务的验证里:目标不是示例表,而是一种“单实例起步、三张核心表、目标拓扑直接上 4 × 2、新增写入走 Vector 双写、目标侧禁止自动建表”的组合。那次验证最后同样跑通了,说明这条短路径并不只适合 demo,而是可以自然扩展成更接近生产的迁移骨架。

不过也正是在这一步,我踩到了一个很值得提前记住的环境细节:在 ClickHouse 24.3 配合 operator 的本地组合里,Replicated 数据库引擎需要实验开关,但这个开关不能简单塞进顶层 server config;否则副本会直接因为配置层级不对而起不来。另一个细节是,这个组合下我最终是通过逐节点执行 DDL 稳定完成建库建表,而不是单纯依赖 ON CLUSTER。我不会把这两个细节神化成普适规律,但它们很好地提醒了我,短路径真正的价值,就是在正式迁移前把这种版本与部署方式耦合出来的小坑尽早踩完。

而当我把目标端切到 ClickHouse 26.3 再重跑一次后,情况又出现了一个很有代表性的变化。Replicated 数据库不再需要实验开关,但这并不等于“从此所有 DDL 都直接 ON CLUSTER 就行了”。我实测到的新版边界更细:

  • 创建 Replicated 数据库本身时,ON CLUSTER 仍然是合理路径;
  • 但数据库建好之后,如果继续在这个 Replicated 数据库里对表使用 ON CLUSTER,会直接报错:ON CLUSTER is not allowed for Replicated database
  • 因此更稳的新版短路径是:先 CREATE DATABASE ... ON CLUSTER ... ENGINE = Replicated,再把表 DDL 当成一次普通初始查询,只打到一个目标 ClickHouse 实例上执行,让 Replicated 数据库自己同步元数据。

这件事对我最大的提醒,是“避免逐节点执行 DDL”里的“节点”,其实指的是每一个 ClickHouse server 实例,也就是 shard × replica 展开后的每个 Pod。以 4 shards × 2 replicas 为例,总共有 8 个实例;旧路径里的逐节点 DDL,本质上就是把同一套表结构在这 8 个实例上重复跑一遍。到了 26.3,这条路径终于可以更干净地收束成“建库集群化,建表单点化,元数据复制化”。

迁移过程中通常需要完成哪些工作

1. 明确目标拓扑

  • 是先上 1 shard + 2 replicas,还是直接上 N shards + M replicas
  • Keeper 用 ClickHouse Keeper 还是 ZooKeeper;
  • 是否使用 Replicated 数据库引擎统一 schema 同步。

2. 部署协调层与集群配置

  • 部署 3 节点或 5 节点 Keeper;
  • 配置 cluster、macros、replica path、replica name;
  • 验证 ON CLUSTER 与复制路径配置是否正确。

3. 在目标集群创建最终表结构

  • 本地表使用 ReplicatedMergeTree 家族;
  • 对外创建 Distributed 表作为统一写入和查询入口;
  • 明确分片键、排序键、分区键是否需要重设计。

4. 设计历史数据迁移方式

  • 小表可直接 INSERT SELECT
  • 大表要按数据量、带宽、停机窗口决定是导数、回灌还是分批迁移;
  • 如果是同机原地改造,才更适合考虑 ATTACH PARTITIONATTACH ... AS REPLICATEDconvert_to_replicated

5. 设计增量追平策略

  • 最简单的是迁移窗口内短暂停写,做最后一次增量补齐;
  • 如果停写代价高,就要考虑业务双写或 CDC 风格的补数流程;
  • 这里没有真正“零代价无缝”的默认方案。

6. 做切换前校验

  • 行数、分区数、关键查询结果是否一致;
  • 副本状态是否正常;
  • Distributed 表写入和查询是否符合预期;
  • Keeper / ZooKeeper 元数据是否健康。

7. 做切流与回滚预案

  • 先切读还是先切写;
  • 旧单机保留多久作为回滚点;
  • 切换后观察哪些指标:写入延迟、复制延迟、查询错误率、后台 merge 压力。

一个更实际的判断

如果你问的是“能不能继续用 MergeTree”,答案是

如果你问的是“迁到多副本生产集群时还应不应该继续用 MergeTree”,我的答案是不建议

如果你问的是“能不能无缝切到集群并顺手改表引擎”,答案是:

  • 可以做到业务侧尽量平滑
  • 但不能把它理解成数据库内部自动完成的一次性透明升级
  • 更稳的现实方案是新集群按最终形态建好,再迁、再追、再切

对我的启发

这个问题让我更清楚地看到,ClickHouse 迁移里真正难的往往不是“数据文件怎么转”,而是业务写路径、复制路径和切换窗口如何同时被工程化。也正因为如此,ReplicatedMergeTree 最好在目标架构设计阶段就定下来,而不是等切到集群当天再临时决定。


来源:clickhouse-replicated-engines-and-conversion · clickhouse-deployment-topologies · ddl-vs-dml · clickhouse-replicated-table-engines · altinity-converting-mergetree-to-replicated

相关页面:clickhouse · clickhouse-replicated-engines-and-conversion · clickhouse-deployment-topologies · ddl-vs-dml · clickhouse-replication-and-scaling