在生产环境里,最好一开始就把 replicated 路径选对;从
MergeTree再转ReplicatedMergeTree是可行的,但本质上属于迁移工程。
先说结论
如果这是新建的生产 ClickHouse 集群,我的默认建议是:
- 数据库层优先使用
ENGINE = Replicated; - 需要副本容灾的数据表优先使用
ReplicatedMergeTree家族; - 不要把“后面再转”当成默认计划。
这里第 1 点是 Operator 文档的直接建议;第 2 点是结合 Operator 文档与复制文档做出的归纳,因为 Operator 明确说它不负责 table data replication,而表数据复制仍由 ClickHouse 自身机制承担。
为什么要区分数据库引擎和表引擎
这个问题很容易被一句“生产用 replicated”说糊。
- 数据库引擎
Replicated:解决 schema 和数据库定义在副本间的同步; - 表引擎
ReplicatedMergeTree家族:解决表数据副本的一致性与恢复; - Operator:帮你管理声明式集群与 schema 复制,不替代表引擎级的数据复制语义。
这意味着,哪怕你已经在 Kubernetes 里用了 Operator,真正的高可用数据表依然要认真决定是否落在 ReplicatedMergeTree 家族上。
ReplicatedReplacingMergeTree 里有两种去重
oneuptime-replicated-replacingmergetree 和 clickhouse-issue-20867 放在一起,正好把 ReplicatedReplacingMergeTree 的标准用法和误用边界都补齐了。到了这个引擎,我不能只说“Replacing 会去重”,还必须区分几层不同机制。
第一层是 ReplacingMergeTree 自身的 replacement:它按 ORDER BY 定义的排序键识别可折叠的行,如果传了 version 列,就在同一个 key 下选择 version 最大的行。这里的关键是,version 必须真能表达新旧顺序;如果把同一个 timestamp 同时放进 ORDER BY 和 version 参数,又用 toStartOfDay(now()) 这种日粒度值反复插入,ClickHouse 其实没有拿到一个可靠的“哪条更新”的信号。
第二层是 replicated 表的 insert deduplication:它服务的是重复 insert block 的幂等,不是业务行级去重。如果一个 cron 从多个节点反复插入同一批每日账号快照,payload 完全相同,复制层可能先把后来的 block 当成重复提交跳过,后面的 ReplacingMergeTree merge 逻辑根本没有机会参与。
标准建模方式是:用 ORDER BY 表达逻辑身份,用 version 列表达新旧顺序,用 Keeper / ZooKeeper 承担复制协调。FINAL 可以让查询在后台 merge 之前看到去重后的状态,OPTIMIZE ... FINAL 可以对分区做维护式强制合并,但它们都不能替 schema 补出缺失的版本语义。
这对建模的影响很直接:如果业务要存“每天每个账号最后状态”,我会把 date 这类业务粒度放进去重 key,把高精度 created_at、递增版本或源端 insert_id 作为新旧顺序或插入唯一性,而不是让 account_name 这种业务字段的大小关系意外决定结果。
已有 MergeTree 时有哪些迁移路径
1. 小表直接复制
适合数据量不大、可以接受额外写放大和迁移时长的场景:
- 创建新的
ReplicatedMergeTree表; INSERT INTO new_table SELECT * FROM old_table;- 校验后切流或 rename。
优点是简单;缺点是对大表太重。
2. 旁路新建表,再 ATTACH PARTITION
这是我更偏向的大表生产路径:
- 旁路创建 replicated 新表;
- 用
ALTER TABLE new_table ATTACH PARTITION ... FROM old_table挂入原表分区; - 校验计数与关键查询;
RENAME TABLE完成切换;- 暂时保留旧表作为便宜回滚点。
Altinity KB 强调,这种方式大量利用硬链接,因此对大表更现实。
3. DETACH + ATTACH ... AS REPLICATED
这是官方给出的原地转换路径之一:
DETACH TABLE test;ATTACH TABLE test AS REPLICATED;SYSTEM RESTORE REPLICA test;
但有两个关键前提:
ATTACH不会自动修改 Keeper / ZooKeeper 元数据;- 如果你是在给已有 replicated 表补 replica,本地数据会先被 detach。
所以它更像原地切换工具,不是自动迁移器。
4. convert_to_replicated + 重启
如果能接受重启窗口,官方也支持在数据目录下放置 convert_to_replicated 标记文件,让服务重启时按 replicated 方式加载。
这种方式的关键约束是:
- 依赖
default_replica_path与default_replica_name; - 需要准确知道表数据目录;
- 对已有多副本场景,仍要先确认数据一致性。
5. 更手工的 rename / detached / attach 路线
官方复制文档保留了更底层的手工做法:rename 旧表、新建 replicated 表、把数据移到新表目录下的 detached,再 ATTACH PARTITION。这条路麻烦,但在默认路径不适配时提供了更强控制力。
迁移前必须先想清楚的事
- 当前多份数据是否完全一致:如果不一致,先同步,或者只保留一个权威源。
- 你要的是重启窗口,还是在线旁路切换:这决定你更像走原地转换还是旁路建表。
- Keeper / ZooKeeper 路径是否已准备好:尤其是
ATTACH和重启转换都依赖复制元数据语义。 - 回滚策略是什么:rename 保留旧表通常比直接覆盖更稳。
- 是否已经在生产上使用
Replicated数据库引擎:否则 schema 复制和表复制会变成两套不同步的运维问题。 - 是否误把 replicated insert deduplication 当成表内去重:它跳过的是重复 insert block,不会替
ReplacingMergeTree设计业务版本。
一个实用的生产建议
- 新建生产集群:直接上
Replicated数据库引擎 +ReplicatedMergeTree家族。 - 已有小表:优先考虑旁路新表 +
INSERT SELECT。 - 已有大表:优先考虑旁路新表 +
ATTACH PARTITION+ rename。 - 已有单机或明确维护窗口:可评估
ATTACH ... AS REPLICATED或convert_to_replicated。 - 已有复杂多副本且数据可能漂移:先做一致性治理,不要急着套转换命令。
- 需要低频 upsert 式当前状态表:可以评估
ReplicatedReplacingMergeTree,但前提是 identity、version 和FINAL成本都已经被明确设计。
对我的启发
这几篇文档放在一起之后,一个判断变得非常稳定:ReplicatedMergeTree 的最佳使用时机是建表时,而不是出事后。 转换能力很重要,但它更像补救路径、演进路径和迁移路径,而不是默认开发流程的一部分。
来源:clickhouse-operator-introduction · altinity-converting-mergetree-to-replicated · clickhouse-replicated-table-engines · clickhouse-attach-as-replicated · clickhouse-issue-20867 · oneuptime-replicated-replacingmergetree
相关页面:clickhouse-deployment-topologies · clickhouse-common-pitfalls · clickhouse · clickhouse-operator-introduction · altinity-converting-mergetree-to-replicated · clickhouse-replicated-table-engines · clickhouse-attach-as-replicated · clickhouse-issue-20867 · oneuptime-replicated-replacingmergetree