无停机时间重命名表
借助 GitLab 内置的数据库辅助方法,我们可以在不停机的情况下重命名数据库表。
该技术基于数据库视图构建,使用以下步骤:
- 重命名数据库表。
- 通过指向新表名,使用旧表名创建数据库视图。
- 为 ActiveRecord 的 schema cache 添加解决方案。
例如,假设我们将 issues 表名重命名为 tickets。执行:
BEGIN;
ALTER TABLE issues RENAME TO tickets;
CREATE VIEW issues AS SELECT * FROM tickets;
COMMIT;由于数据库视图不暴露底层表的架构(默认值、非空约束和索引),我们需要额外的步骤来更新应用程序以使用新表名。ActiveRecord 严重依赖这些数据,例如用于初始化新模型。
为了解决这个限制,我们需要告诉 ActiveRecord 从使用新表名的不同表中获取这些信息。
迁移策略详解
发布 N.M:标记 ActiveRecord 模型的表
将当前版本视为 “发布 N.M”。
在此版本中,注册数据库表,以便指示 ActiveRecord 使用新表名(如果存在)获取数据库表信息(用于 SchemaCache)。否则,回退到旧表名。这对于避免在零停机部署期间出现错误是必要的。
-
编辑
lib/gitlab/database.rb中的TABLES_TO_BE_RENAMED常量TABLES_TO_BE_RENAMED = { 'issues' => 'tickets' }.freeze
注意,在此版本(N.M)中,tickets 数据库表尚不存在。这一步骤是为在版本 N.M+1 中实际重命名表做准备。
发布 N.M+1:重命名数据库表
将下一个版本视为 “发布 N.M+1”。
-
执行标准迁移(不是 post-migration):
def up rename_table_safely(:issues, :tickets) end def down undo_rename_table_safely(:issues, :tickets) end
- 使用新名称重命名表的 字典文件(在
db/docs下,例如本例中的db/docs/tickets.yml)。更新introduced_by_url和milestone属性。
- 在
db/docs/deleted_views中为临时视图(使用旧表名)创建一个条目。这是因为该视图会被同一合并请求的部署后迁移中的finalize_table_rename删除。
重要提示:
- 让其他开发人员知道表将被重命名。
- 在你的合并请求中 ping
@gl-database组。 - 在 Engineering Week-in-Review 文档中添加注释:
table_name将在 N.M 版本中重命名。在 N.M 和 N.M+1 版本中不允许对此表进行修改。
- 在你的合并请求中 ping
- 辅助方法使用 Rails 的标准
rename_table辅助方法来重命名表。 - 该辅助方法会重命名序列和索引。有时它在命名索引时会偏离标准的 Rails 约定,因此有可能并非所有索引都被正确重命名。在本地运行迁移后,检查是否有命名不一致的索引(
db/structure.sql)。这些可以在单独的迁移中手动重命名,也可以作为发布 M.N+1 的一部分。 - 外键列可能仍包含旧表名。对于较小的表,请遵循我们的标准列重命名流程
- 避免重命名使用触发器的数据库表。
- 在重命名过程中不允许修改表(添加或删除列)。确保在开始重命名迁移之前(或在下一个版本中)完成对表的所有更改。
- 由于索引名称可能会更改,请验证模型是否没有使用带有
unique_by: index_name选项的批量插入(例如insert_all和upsert_all)。在使用这些方法时重命名索引可能会破坏功能。 - 修改模型代码以指向新的数据库表。通过直接重命名模型或设置
self.table_name变量来实现。
至此,我们的应用程序中不再有查询使用旧的数据库表名。
-
通过 post-migration 删除数据库视图:
def up finalize_table_rename(:issues, :tickets) end def down undo_finalize_table_rename(:issues, :tickets) end -
表名必须从
TABLES_TO_BE_RENAMED中移除。为此,编辑
lib/gitlab/database.rb中的TABLES_TO_BE_RENAMED常量:从:
TABLES_TO_BE_RENAMED = { 'issues' => 'tickets' }.freeze到:
TABLES_TO_BE_RENAMED = {}.freeze
零停机部署
当应用程序进行无停机升级时,可能会有运行旧代码的应用程序实例。旧代码仍然引用旧的数据库表。查询仍然可以正常工作,因为向后兼容的数据库视图已经就位。
如果旧版本的应用程序需要重新启动或重新连接到数据库,ActiveRecord 会再次获取列信息。此时,我们之前标记的表(TABLES_TO_BE_RENAMED)会指示 ActiveRecord 在获取数据库表信息时使用新的数据库表名。
新版本的应用程序使用新的数据库表。