在线数据库模式升级

https://blueprints.launchpad.net/cinder/+spec/online-schema-upgrades

Mitaka 周期的一个优先事项是使 Cinder 能够执行滚动升级。 这项工作需要大量的纪律来阻止任何可能破坏不同服务版本互操作性的更改。

一些数据库迁移可能会破坏这种互操作性。 本规范旨在提供处理此类更改的指南,以确保 Cinder 能够执行滚动升级。

问题描述

非向后兼容的数据库迁移包括

  • 非累积迁移

    • 列、表删除

    • 列、表重命名

    • 添加约束

  • 长时间运行的数据迁移(例如从一列到另一列)

所有这些都应该在所有服务停止运行时执行。 对于少量数据,这可能不是问题,但当涉及到数千行时,停机时间可能会很长。

用例

部署者希望能够在不停止控制平面服务的情况下执行 Cinder 升级。

用户希望在使用云时,在云升级时不会遇到控制平面停机。

提议的变更

一般来说,我们希望建立在 Nova 的经验之上,并根据我们的需求调整他们的解决方案。 Nova 如何以实时方式执行数据库迁移的详细描述在 Dan Smith 的博客 中介绍。

目前请注意,我们不是基于较旧的 Nova 方法,即 在线模式更改,该方法根据 models.py 中描述的当前和期望的数据库模型自动执行累积和收缩数据库迁移。 这种方法最终会导致开发人员无需手动编写迁移。 我们不会采用此解决方案,因为它被认为是实验性的,已知会破坏数据,在 Liberty 后期无法正常工作,并且 最近从 Nova 中删除

Cinder 和 Nova 之间的主要区别,影响在线数据库模式升级解决方案,是 Cinder 中的所有服务都在查询数据库。 在 Nova 中,只有 nova-conductor 才能访问数据库。 因此,我们需要稍微修改一下方法。 这些更改迫使我们将迁移跨越 3 个版本,而不是像 Nova 那样跨越 2 个版本。

首先,我们应该引入一个单元测试,该测试将阻止收缩迁移。 基本上,当树中存在使用 DROP 或 ALTER 命令的迁移时,该测试将失败。 我们将以类似于 Nova 的方式执行此操作。

在添加这样的测试之后,我们应该只允许扩展或累积迁移,即添加新列或表。 好消息是,自从 Juno 以来,我们只合并了像这样的迁移。 这意味着我们很少需要特殊的方法。

并且(有点复杂)特殊方法如下。 让我们使用一个现实世界的例子来使它更有用。 我们将尝试模拟实时数据库模式更改,该更改将正确建模一致性组和卷类型之间的关系。 这是一种 n-n 关系,当前由 consistencygroups 表中的 volume_type_id 列表示,该列存储与一致性组相关的卷类型的逗号分隔的 ID。 正确的表示方法是使用 ConsistencyGroupVolumeTypeMapping 表,该表维护 CG 和 VT 之间的连接。

我们将通过 3 个版本(加上当前版本)来完成,让我们使用字母来识别它们

  • L - 当前

  • M - 开发中

  • N

  • O

M 版本

在 L 版本中,我们只有这种关系的旧表示形式。 在 M 中,我们应该合并一个更改,该更改添加 ConsistencyGroupVolumeTypeMapping 模型以及 ConsistencyGroupVolumeType 模型中所需的列,这些列将形成与上述表的连接。 这种累积迁移可以在数据库中轻松执行,同时 L 服务正在运行。

在升级时,我们将同时运行 L 和 M 版本,因此我们应该修改 M 的 CG 版本对象以

  • 将数据写入两个地方。

  • 从旧地方读取数据。

这样,我们保持与 M->L 的兼容性(M 写入旧地方)和 L->M 的兼容性(M 从旧地方读取)。

一旦升级完成,管理员应该使用一个工具来完成数据迁移。 该工具将在 cinder-manage 中开发,并对 CG 行的块(以限制性能影响)进行操作。 它将简单地迭代并在新地方设置关系。

最后,一切都在运行 M 服务,所以一切都写入两个地方并从旧地方读取。 此外,我们应该已经迁移了所有数据,因此对于所有 CG 都有新旧方式定义的关联。

N 版本

现在在升级时,我们将拥有 M 和 N 版本。 N 的版本对象

  • 将数据写入两个地方。

  • 从新地方读取数据。

这样,我们保持与 M->N 的兼容性(N 写入的地方 M 读取)和 N->M 的兼容性(M 写入的地方 N 读取)。

由于 N 从新地方读取,在启动任何 N 服务之前,我们需要确保所有 CG 都已迁移。 为了强制执行,作为第一个 N 迁移,我们应该合并一个虚拟迁移,如果存在未迁移的项目,则阻止后续迁移。

请注意,我们仍然在 SQLAlchemy 模型中拥有旧的表示形式,因此即使我们设法将 N 服务切换为仅写入新地方,我们也无法删除该列。

O 版本

这一次,我们将拥有 N 和 O 服务协同工作。

在 O 中,我们最终修改 SQLAlchemy 模型以删除旧的 ConsistencyGroup volume_type_id 字段。 这次我们能够做到,因为保证所有服务都写入和读取新地方。 我们还将其从 O 的版本对象中删除 - 它现在将只读取和写入新地方的数据。

升级后,一切都只从新地方写入和读取。 此外 - O 服务的 SQLAlchemy 模型没有 volume_type_id 列的概念。 因此,我们可以有一个升级后迁移脚本来最终删除该列(或者我们可以将其作为 P 版本中的迁移添加,如果我们想限制升级所需的步骤数)。

备选方案

我们可以简单地阻止收缩和数据迁移。 但是,这将阻止我们修复过去做出的糟糕决定,这可能会损害该项目,因为我认为它还没有成熟。

可以实现 在线模式更改,使数据库迁移分为两个步骤自动进行。 Nova 认为这是一种实验性的方法。 此外,该代码在 Mitaka-1 中被删除。 它也不能解决长时间运行的数据迁移问题,因此不能消除此规范提出的所有复杂性。

我们也可以保持现状,并假设您需要停机时间才能升级。 这可能不是操作员想要的结果,并且我们已经做出的所有努力(版本对象、RPC 兼容性)都将被浪费。

数据模型影响

我们不期望对数据模型本身产生影响,而是对我们进行数据模型迁移的方式产生影响。

对模型的更改需要仔细审查,以确保它们遵循此指南。

REST API 影响

安全影响

通知影响

其他最终用户影响

性能影响

由于我们将排队并保存额外的列而不是立即删除未使用的列,因此我们可能会对数据库调用产生一些性能影响。 但是,这不应该非常明显。

在云运行期间执行小块数据库迁移时,数据库上会有一些开销。 但是,这是一个权衡,因为替代方案是整个云的停机时间。

Nova 以类似的方式执行此操作,并且他们没有报告性能问题。

其他部署者影响

部署者需要了解如何以正确的方式执行数据库升级。 这将被记录下来,但需要传播知识。

开发人员影响

开发人员和审查人员将产生巨大的影响,因为这两个团队都需要了解 Cinder 如何进行数据库迁移,并相应地编写/审查代码,以防止破坏滚动升级功能。

实现

负责人

主要负责人

michal-dulko-f (dulek)

其他贡献者

需要整个核心团队的参与,以确保代码也从实时升级的角度进行审查。

工作项

  • 添加阻止收缩迁移的单元测试(Mitaka 中需要)。

  • 将在线数据库数据迁移位添加到 cinder-manage。

  • 编写关于如何正确编写数据库迁移的开发人员指南。

  • 将数据库迁移位添加到 Cinder 的升级文档。

依赖项

它本身没有。 整个滚动升级故事取决于 RPC 兼容性(API 版本控制和具有兼容性模式的版本对象)。

测试

应添加部分 Grenade 测试,以确保逐个升级服务不会破坏滚动升级。 这也应该涵盖数据库迁移步骤。

文档影响

我们需要编写详细的开发人员指南,介绍如何在新的模型中编写数据库迁移,以确保我们拥有一个明确的资源,我们可以将其指向开发人员。

为了教育管理员和部署者,关于如何执行滚动升级数据库迁移部分,应将其添加到 Cinder 的通用升级说明中。

参考资料