从单节点 ClickHouse 迁到多副本多分片集群,真正要解决的不是“怎么把机器变多”,而是“怎么把写路径、复制语义和切换窗口一起收敛成可控迁移”。
先说结论
- 技术上可以在集群里继续使用
MergeTree; - 但如果你的目标是多副本高可用,那就不应该把核心表继续停留在
MergeTree; - 从单节点迁到集群不能理解成无状态无缝切换,更准确地说,是可以做到业务侧接近无感或短暂停写切换,但本质仍然是一轮受控迁移;
- 表引擎切换可以在迁移过程中一起做,但更稳的方式通常不是原地魔改,而是按目标拓扑新建集群和新表,再迁数据、追增量、最后切流。
多副本多分片集群里还能不能用 MergeTree
可以,但这只回答了“语法上允不允许”,没有回答“架构上合不合理”。
- 在 多分片 场景里,本地 shard 表技术上可以是
MergeTree; - 在 多副本 场景里,如果本地表仍是
MergeTree,那这些副本节点上的表并不会自动形成真正的数据副本; - 也就是说,
MergeTree在集群中不是不能用,而是不提供你通常期待的副本容灾语义。
所以,如果你的生产目标是“多副本多分片”,我会把 ReplicatedMergeTree 家族视为默认项,而不是把 MergeTree 延续到新架构里。
为什么不能把这件事理解成“无缝切换”
从单机到集群,变化的不是一层,而是至少三层:
- 拓扑变了:从单节点变成
shard + replica + Keeper; - 写入路径变了:集群通常通过
Distributed表或显式路由写入,而不是继续把业务直接打到某一个本地表; - 复制语义变了:
MergeTree到ReplicatedMergeTree不是简单改个名字,而是把数据、复制元数据和恢复语义一起切换。
因此,“无缝”最多只能理解成:
- 业务读写几乎无感;
- 或只经历一次可控的短暂停写 / 短时切流;
- 而不是数据库内部什么都不变地自动升级成集群。
能否在迁移过程中同时完成表引擎切换
可以,而且我认为这通常就是更合理的时机。
但我更推荐下面这类路径:
- 先搭建目标集群;
- 在目标集群直接创建最终形态的本地表,也就是
ReplicatedMergeTree; - 再创建对外统一入口的
Distributed表; - 把旧单机数据导入新集群;
- 追平增量数据;
- 最后切换业务流量。
相较之下,先在旧单机上把所有表原地改成 ReplicatedMergeTree,再去拼集群,通常并不更简单。官方给出的 ATTACH ... AS REPLICATED、convert_to_replicated 等方法是受支持的,但更像单表受控转换工具,不是“单机无痛升级成多副本多分片”的总方案。
更适合你场景的迁移思路
你当前是:
- 单节点;
- 无副本;
- 无分片。
这意味着你最大的优势是:不存在多副本数据漂移问题。因此迁移复杂度主要落在:
- 新拓扑怎么设计;
- 数据怎么搬;
- 增量怎么追;
- 业务怎么切。
对这种起点,我更倾向的顺序是:
路线 A:先上单分片双副本,再决定是否分片
这是更稳的默认路线。
- 第一步先做
1 shard + 2 replicas; - 先把单点故障问题解决;
- 等确认容量和并发真的需要横向扩展时,再继续拆 shard。
这样做的好处是,不把“副本高可用”和“分片扩容”两件复杂事同时引入。
路线 B:如果单机已经明显到瓶颈,再一次性迁到多分片多副本
这种路线也可行,但你要同时处理:
- 分片键设计;
- 历史数据重分布;
- 分布式写入入口;
- 副本同步;
- 查询路径调整。
工程风险显著更高。
一次本地演练后的较短路径
如果我不是在写迁移方案,而是要先把路径快速跑通,我会先把问题压缩成一个更短的闭环:
- 准备一个单节点源库,数据库先保持
Atomic,业务表先保持MergeTree; - 在目标集群创建
ENGINE=Replicated的目标数据库; - 在目标集群创建最终本地表,直接使用
ReplicatedMergeTree; - 再创建对外入口的
Distributed表,把分片键一次定下来; - 从目标集群反向连接源库,用
INSERT INTO target_dist SELECT ... FROM remote(...)回灌历史数据; - 校验行数、聚合结果、各 shard 分布和
system.replicas状态; - 这条链路跑通后,再把 mock 源替换成真实云端源,把样例 schema 换成真实 schema。
这次我在本地 clickhouse-operator/examples/minimal.yaml 对应的目标集群上,实际验证的就是这条短路径。它的好处不是“更优雅”,而是把迁移里最容易卡住的几个点尽快暴露出来:
- 目标集群的
Replicated数据库和ReplicatedMergeTree本地表能不能顺利创建; Distributed表的分片键是不是已经想清楚;- 目标集群能不能反向访问源库;
- 源库是否具备专用迁移账号和最小必要权限;
- 数据灌入后,副本状态和分片分布是否符合预期。
我觉得这条短路径最大的价值,是把“从单机迁到集群”从大而空的架构讨论,压缩成一个可以很快得到反馈的工程闭环。只要这个闭环跑通,后面真正需要补的其实就只剩三类东西:真实 schema、真实源端连接信息,以及增量追平方案。
这次演练里,我用一个本地 mock 单节点源构造了 legacy_analytics.events 这张 MergeTree 表,再把数据迁入目标集群中的 migration_demo.events_local 与 migration_demo.events。最后验证到:
- 源端与目标端的
count()、sum(amount)、uniqExact(user_id)一致; - 数据成功按
cityHash64(user_id)分散到两个 shard; - 每个 shard 的 3 个 replica 都完成复制,
queue_size=0、absolute_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 PARTITION、ATTACH ... AS REPLICATED或convert_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