您的位置:  首页 > 技术 > 数据库 > 正文

分布式数据库跨版本升级数据迁移方案

2022-07-14 12:00 https://my.oschina.net/u/5148943/blog/5553792 浪潮云溪数据库 次阅读 条评论

动机

向后兼容性对数据库至关重要,但会给代码库带来相当大的复杂性。 在其他条件相同的情况下,最好将向后兼容性逻辑与分布式数据库软件系统所固有的复杂性隔离开来,尤其是诸如元数据等各种系统数据,需要一个整体的升级迁移方案来统一处理。

 

短期设计

1. 因为在一般情况下处理升级迁移是一个非常广泛的问题,包括许多潜在的迁移类型,我们选择首先处理最简单的情况,即向后兼容的迁移,同时为可以支持向后不兼容的更复杂的方案敞开大门。

2. 在短期内,该方案中的迁移钩子将由名称和工作函数组成。在稳定状态下,代码中有一个迁移名称的有序列表,按添加时间排序。

3. 当一个节点启动时,它会检查所有已知的迁移是否已经运行(可以使用下面描述的 /SystemVersion 键来限制涉及的工作量)。如果没有,它会通过迁移协调器运行它们。

4. 当集群需要赶上多个迁移时,迁移的有序列表可用于对迁移进行排序。

5. 迁移协调器首先获取并维护迁移租约,以确保一次只运行一个租约。在不同节点进行迁移时启动并需要迁移的其他节点将不得不阻塞,直到租约被释放(或在节点故障的情况下到期)。然后对于每个丢失的迁移,迁移协调器运行其工作函数并将完成记录写入 kv 条目/SystemVersion。

6. 每个工作函数都必须是幂等的(以防迁移协调器崩溃)并且与之前版本的云溪数据库兼容,这些版本可以在集群中的其他节点上运行。后者的限制将在长期设计中放宽。

 

示例

简单的迁移,如 CREATE TABLE IF NOT EXISTS system.jobs (...),可以通过一个钩子完成并立即使用。要添加这样的迁移,需要创建一个名为“CREATE system.jobs”的新迁移,其中包含一个创建新系统表并将其添加到迁移列表末尾的函数。每当包含它的数据库节点的版本首次加入现有集群时,它就会自动运行。

将 root 用户添加到system.users 的示例也可以通过单个节点挂迁移钩子来完成,这意味着它可以类似地添加到迁移挂钩列表中并在启动时运行,而无需关心分布式数据库集群中其他活跃节点使用的是什么版本。 

 

长期设计

虽然我们最直接的需求实际上并不需要向后不兼容的更改,但我们希望兼容更多的场景。对于具有预迁移版本的此类钩子,我们必须部署额外的工具以确保安全迁移。

选项1:需要管理员干预

支持不向后兼容迁移的第一个选项是引入新的 CLI 命令,使数据库管理员能够控制迁移发生的时间。例如:./znbase cluster-upgrade

• 列出已在集群上运行的所有迁移

• 列出可用的迁移、它们所依赖的迁移,以及运行每个迁移是安全的数据库版本

• 运行管理员指定的单个迁移

• 运行满足迁移依赖关系的所有可用迁移(请注意,如果集群中的所有节点都不满足迁移的最低版本,可能会很危险)

在升级到新版本之前,管理员将在发行说明中查找他们想要运行的版本,并确保他们已经运行了所有必需的迁移。如果没有,则需要在升级之前执行此操作。如果所需版本的所有升级在集群的当前版本中不可用,则可能需要先升级到支持迁移的中间版本,以便可以运行迁移。如果数据库节点启动,并且它加入的集群尚未运行该节点版本所需的所有迁移,则该节点将退出并显示相应的错误消息。这种方法使管理员能够完全控制潜在的破坏性迁移,但代价是增加了额外的手动工作。从好的方面来说,它更好地支持回滚,在使用新版本启动节点时,系统会自动进行不向后兼容的更改。
CLI 工具将是针对生产场景升级数据库版本的推荐方法,主要适用于小型集群和本地开发。如果需要,单节点集群将能够在其正常启动过程中进行迁移。此外,未指定最低版本的迁移仍然可以在启动时运行(如在短期设计中),因为缺少最低版本可以假定为意味着它们是向后兼容的。

选项 2:自动执行所有迁移

手动干预的主要替代方法是尝试自动为他们完成用户操作的工作。这将为不太熟悉工具的用户提供最佳的用户体验,他们更关心自己的时间和精力,而不是完全控制。与向后兼容的情况不同,我们不一定能在新版本的第一个节点启动时运行所有已知的迁移,因为某些迁移可能会以与集群中其他节点不兼容的方式修改集群状态。我们永远无法自动运行迁移,除非我们知道集群中的所有节点都处于足够新的版本。我们至少可以通过两种不同的方式确保这一点:

• 开始跟踪整个集群的两个节点版本信息,并添加一些能够充分理解我们的语义版本控制方案的代码,以确定哪些节点版本支持哪些迁移。

• 开始跟踪每个节点知道的尚未运行的迁移。然后,一旦所有节点都知道迁移,就可以运行迁移。

 

短期设计代码示例

Sqlmigrations包定义了跨版本升级数据兼容的方法,backwardCompatibleMigrations是在启动时运行的迁移列表,在每个节点启动时按顺序运行,完成版本升级。

var backwardCompatibleMigrations = []migrationDescriptor{...        name:                "create system.comment table",        workFn:              createCommentTable,        includedInBootstrap: true,        newDescriptorIDs:    staticIDs(keys.CommentsTableID),    },...}

1. 创建系统表

创建一些版本升级新增的系统表

func createCommentTable(ctx context.Context, r runner) error {    return createSystemTable(ctx, r, sqlbase.CommentsTable)}func createSnapshotTable(ctx context.Context, r runner) error {    return createSystemTable(ctx, r, sqlbase.SnapshotsTable)}

2. 创建系统schema

func createDefaultdbSchema(ctx context.Context, r runner) error {    return createSystemSchema(ctx, r, sqlbase.DefaultdbPublicSchema)}func createPostgresSchema(ctx context.Context, r runner) error {    return createSystemSchema(ctx, r, sqlbase.PostgresPublicSchema)}

3. 在root下运行一组命令(重试有限次)

包括添加角色、用户、集群密钥以及配置集群参数。

func addAdminRole(ctx context.Context, r runner) error {    const upsertAdminStmt = `          UPSERT INTO system.users (username, "hashedPassword", "isRole") VALUES ($1, '', true)          `    return runStmtAsRootWithRetry(ctx, r, "addAdminRole", upsertAdminStmt, sqlbase.AdminRole)}func addRootUser(ctx context.Context, r runner) error {    const upsertRootStmt = `            UPSERT INTO system.users (username, "hashedPassword", "isRole") VALUES ($1, '', false)            `    return runStmtAsRootWithRetry(ctx, r, "addRootUser", upsertRootStmt, security.RootUser)}

 

展开阅读全文                                    
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接