clickhouse-operator 集群 Schema 自动迁移机制详解:扩缩容下的建表与删表流程
【免费下载链接】clickhouse-operatorAltinity Kubernetes Operator for ClickHouse creates, configures and manages ClickHouse® clusters running on Kubernetes项目地址: https://gitcode.com/GitHub_Trending/cl/clickhouse-operator
导读
ClickHouse 集群在 Kubernetes 上扩容(新增分片/副本)或缩容(删除分片/副本)时,如何自动完成数据库、本地表、分布式表和复制表的创建与清理,是保证集群可用性与数据一致性的关键。本文以 Altinity clickhouse-operator 官方文档 docs/schema_migration.md 为骨架,结合pkg/model/chi/schemer模块的真实源码,系统讲解 Schema 自动创建、自动删除的完整流程、schemaPolicy配置控制方式以及底层 SQL 生成原理。读完本文,你将能理解 operator 扩缩容时"建什么表、从哪建、怎么建、何时删",并能据此排查或设计自己的 Schema 迁移方案。
一、什么是 Schema 迁移(Schema Migration)
在 clickhouse-operator 的语境中,"Schema 迁移"特指:当 ClickHouseInstallation(CHI)资源的分片(shard)或副本(replica)数量发生变化时,operator 自动在新实例上补齐数据库与表结构,并在实例被移除时清理相关表对象。
这一点在官方文档 docs/schema_migration.md 开篇即被点明:
clickhouse-operatorautomates schema management when cluster is scaled up or down
即:扩缩容时的 Schema 管理完全由 operator 自动化完成,用户不需要手工对每个新 Pod 执行 DDL。从源码结构看,这一能力由独立模块 pkg/model/chi/schemer(schemer包)实现,它通过 HTTP 方式连接 ClickHouse 实例执行 SQL,并负责生成所有建表/删表语句。
二、Schema 自动创建:扩容时建表流程
2.1 官方文档定义的两条路径
原文档给出了两条清晰的创建逻辑:
新增分片(shard added):
- 分析集群中其他分片的分布式表(distributed tables)及对应的本地表(local tables);
- 创建本地表和分布式表所需的数据库;
- 创建本地表;
- 创建分布式表。
新增副本(replica added):
- 分析同一分片内其他副本的复制表(replicated tables);
- 创建复制表所需的数据库;
- 创建复制表;
- 随后执行与新增分片相同的逻辑(即继续补建分布式表与本地表)。
2.2 源码实现:HostCreateTables与createTablesSQLs
在源码层面,扩容建表的入口是 pkg/model/chi/schemer/schemer.go 中的HostCreateTables(ctx, host)方法,其核心逻辑为:
- 调用
createTablesSQLs()分别获取两类 SQL 集合:getReplicatedObjectsSQLs()(见 replicated.go):收集复制对象的建库、建表、建函数 SQL;getDistributedObjectsSQLs()(见 distributed.go):收集分布式对象的建库、建表、建函数 SQL;
- 先在新 host 上执行复制对象 SQL,再执行分布式对象 SQL(对应原文档"副本逻辑先于分片逻辑"的描述);
- 两类 SQL 均以
SetRetry(true).SetLogQueries(true)选项执行,保证 DDL 失败可重试并留下查询日志。
关键实现片段(schemer.go):
// HostCreateTables creates tables on a new host func (s *ClusterSchemer) HostCreateTables(ctx context.Context, host *api.Host) error { ... replicatedObjectNames, replicatedCreateSQLs, distributedObjectNames, distributedCreateSQLs := s.createTablesSQLs(ctx, host) if len(replicatedCreateSQLs) > 0 { err1 = s.ExecHost(ctx, host, replicatedCreateSQLs, clickhouse.NewQueryOptions().SetRetry(true).SetLogQueries(true)) } if len(distributedCreateSQLs) > 0 { err2 = s.ExecHost(ctx, host, distributedCreateSQLs, clickhouse.NewQueryOptions().SetRetry(true).SetLogQueries(true)) } ... }2.3 复制对象的创建逻辑(getReplicatedObjectsSQLs)
getReplicatedObjectsSQLs在真正生成 SQL 之前,会先调用shouldCreateReplicatedObjects(host)做前置判断(replicated.go):
- 若
schemaPolicy.shard = All且集群实例数 ≥ 2,则显式要求每个分片都建复制对象; - 若
schemaPolicy.replica = None,则不创建复制对象; - 若该分片内副本数 ≤ 1(
len(shard) <= 1),没有可参考的复制来源,不创建复制对象。
通过判断后,依次生成三类 SQL:
| SQL 生成函数 | 作用 | 定义位置 |
|---|---|---|
sqlCreateDatabaseReplicated | 从clusterAllReplicas('<cluster>', system.databases)读取全集群数据库,生成CREATE DATABASE IF NOT EXISTS ... | sql.go |
sqlCreateTableReplicated | 从system.tables读取非系统库、引擎属于Ordinary/Atomic/Memory/Lazy的表,用replaceRegexpOne将CREATE改写为CREATE ... IF NOT EXISTS | sql.go |
sqlCreateFunction | 从system.functions收集用户自定义函数,同样改写为CREATE FUNCTION IF NOT EXISTS | sql.go |
值得注意的是sqlCreateDatabaseReplicated会依据 ClickHouse 版本选择不同的 DDL 片段:版本 ≥ 22.12 时使用engine_full,否则使用engine(sql.go),这正是"版本感知"(version.Matches(">= 22.12"))的体现。
2.4 分布式对象的创建逻辑(getDistributedObjectsSQLs)
shouldCreateDistributedObjects(distributed.go)的判断条件为:
schemaPolicy.shard = None时不创建分布式对象;- 集群内 host 数 ≤ 1 时没有分发目标,不创建。
sqlCreateTableDistributed的实现更具技巧性:它通过正则extract(engine_full, 'Distributed\\([^,]+, *\'?([^,\']+)\'?, *[^,]+')从分布式表的engine_full中解析出其引用的远端数据库名,再LEFT JOIN system.tables找到对应的本地表定义,最终生成"分布式表 + 被引用的本地表"的完整创建语句(sql.go)。所有查询都带有SETTINGS skip_unavailable_shards = 1,保证部分分片不可用时查询依然成功。
三、Schema 自动删除:缩容时清理流程
原文档对删除路径的说明只有一句话,但含义关键:
If cluster is scaled down and some shards or replicas are deleted,
clickhouse-operatordrops replicated table to make sure nothing is left in ZooKeeper.
即:缩容时删除复制表,确保 ZooKeeper 中不残留元数据。这是 Replicated 表模型下必须做的事——如果只删 Pod 不删 ZooKeeper 中的 replica 路径,后续同名校验、重挂载都会失败。
3.1HostDropTables:删表 SQL 的生成
HostDropTables(ctx, host)调用sqlDropTable()生成三类 DROP 语句(schemer.go、sql.go):
- 字典:
DROP DICTIONARY IF EXISTS "db"."name"(来源system.dictionaries); - 表/视图:
DROP TABLE IF EXISTS "db"."name" SYNC(来源system.tables,筛选引擎含%MergeTree%或%View%,并排除system、information_schema、INFORMATION_SCHEMA三个系统库); - 复制数据库:
DROP DATABASE IF EXISTS "name" SYNC(来源system.databases,仅当engine = 'Replicated')。
其中SYNC修饰符保证 DROP 等待副本数据同步完成后再返回,避免竞态。注意:视图(View)没有独立的删除查询,按 ClickHouse 约定统一用DROP TABLE删除——源码注释中明确引用了这一点(sql.go)。
3.2HostDropReplica:清理 ZooKeeper 残留
针对复制表副本残留问题,schemer 提供了HostDropReplica方法(schemer.go),其 SQL 由sqlDropReplica生成(sql.go):
func (s *ClusterSchemer) sqlDropReplica(shard int, replica string) []string { return []string{ fmt.Sprintf("SYSTEM DROP REPLICA '%s'", replica), fmt.Sprintf("SYSTEM DROP DATABASE REPLICA '%d|%s'", shard, replica), } }即对每个被删除的 host 执行SYSTEM DROP REPLICA '<replica>'与SYSTEM DROP DATABASE REPLICA '<shard>|<replica>',从 ZooKeeper 中摘除对应副本注册信息,这正是原文档"make sure nothing is left in ZooKeeper"的实现来源。
3.3 删除流程的调用链
在控制器层,删除逻辑集中在 pkg/controller/chi/worker-deleter.go:
- 对将被删除的 host 调用
HostDropTables(第 504 行附近); - 对复制副本调用
HostDropReplica(第 476 行附近); - 删除前还会通过
HostSyncTables执行SYSTEM SYNC REPLICA以同步复制状态(第 255、312 行附近)。
四、schemaPolicy:控制 Schema 迁移行为的配置项
Schema 迁移并非"一刀切",operator 通过 CHI 资源中的schemaPolicy字段精细控制复制/分布式对象的创建范围。合法取值定义在 pkg/model/chi/schemer/const.go:
| 字段 | 取值 | 含义 |
|---|---|---|
replica | None | 不创建复制对象(Replicated 表) |
replica | All(默认) | 创建复制对象 |
shard | None | 不创建分布式对象 |
shard | All(默认) | 创建分布式对象与复制对象 |
shard | DistributedTablesOnly | 只创建分布式表相关对象 |
配置写法参见完整示例 docs/chi-examples/99-clickhouseinstallation-max.yaml:
schemaPolicy: replica: All shard: All在 pkg/model/chi/normalizer/normalizer.go 的normalizeClusterSchemaPolicy中,这些取值会被大小写归一化处理,未知值统一回退到默认值(replica: All、shard: All),保证任何用户输入都不会破坏 operator 的稳定性:
switch strings.ToLower(policy.Replica) { case strings.ToLower(schemer.SchemaPolicyReplicaNone): policy.Replica = schemer.SchemaPolicyReplicaNone case strings.ToLower(schemer.SchemaPolicyReplicaAll): policy.Replica = schemer.SchemaPolicyReplicaAll default: // Unknown value, fallback to default policy.Replica = schemer.SchemaPolicyReplicaAll }shard分支同样如此,未知值回退到SchemaPolicyShardAll。因此可以推断:只要不显式配置schemaPolicy,operator 默认会在扩缩容时全量维护复制表与分布式表。
schemaPolicy与shouldCreateReplicatedObjects/shouldCreateDistributedObjects两个判断函数直接联动(见 2.3、2.4 节),构成完整的决策链路:normalizer 归一化配置 → schemer 判断是否创建 → 生成 SQL → 执行。
五、从源码结构看 Schema 迁移的完整生命周期
综合上文,可以将 operator 的 Schema 迁移生命周期总结为如下闭环:
- 扩容新增副本:新 host 就绪后,控制器调用
HostCreateTables→ 先建复制对象(数据库 → 复制表 → 函数),再建分布式对象(分布式表及其引用的本地表); - 扩容新增分片:同样走
HostCreateTables,但shouldCreateReplicatedObjects会依据分片内副本数判断是否需要复制对象; - 缩容删除副本:控制器调用
HostDropReplica清理 ZooKeeper 副本注册,再调用HostDropTables删除本地表、视图与复制数据库; - 缩容删除分片:对分片内各 host 执行同样的删除逻辑,确保 ZooKeeper 无残留;
- 任何时刻:
HostSyncTables可执行SYSTEM SYNC REPLICA等待复制追上进度(用于删除前的状态校验)。
整个流程都运行在 worker-migrator.go 与 worker-deleter.go 的协调器上下文里,与 CHI 的滚动更新、Pod 调度等机制解耦,属于独立、可重入的 Schema 维护子流程。
六、使用建议与注意事项
基于以上机制,在实际使用中值得注意:
- 扩容后 Schema 自动补齐,无需手工 DDL:只要新 Pod 正常 Ready,operator 会自动完成建库建表,可参考 docs/schema_migration.md 中的预期行为核对;
- 缩容前关注复制表残留:删除分片/副本后,若观察到 ZooKeeper 中存在旧 replica 路径,说明删除流程未完整执行,可检查
worker-deleter.go中HostDropReplica的执行日志(日志关键字Drop replica); - 合理配置
schemaPolicy:单副本测试环境可设replica: None减少 ZooKeeper 依赖;纯查询分发场景可设shard: DistributedTablesOnly; - 版本差异:
sqlCreateDatabaseReplicated针对 ClickHouse ≥ 22.12 使用了engine_full,低版本集群会自动走兼容分支,升级 ClickHouse 大版本时无需改动 operator 配置; - 视图删除:ClickHouse 无独立 DROP VIEW 语句,schemer 统一以
DROP TABLE IF EXISTS ... SYNC删除视图,这符合 ClickHouse 官方语义。
总结
clickhouse-operator将"集群扩缩容时的 Schema 维护"完整自动化:以 docs/schema_migration.md 描述的行为为准绳,以 pkg/model/chi/schemer 模块为实现核心,通过"复制对象优先、分布式对象次之"的建表顺序和"删表 + 删 ZooKeeper 副本"的删表策略,保证集群在任何规模的扩缩容操作下都能维持正确的表结构与 ZooKeeper 状态。理解本文的决策链(schemaPolicy→shouldCreate*→sql*→ExecHost),即可在遇到 Schema 相关异常时快速定位根因。
【免费下载链接】clickhouse-operatorAltinity Kubernetes Operator for ClickHouse creates, configures and manages ClickHouse® clusters running on Kubernetes项目地址: https://gitcode.com/GitHub_Trending/cl/clickhouse-operator
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考