Help us learn about your current experience with the documentation. Take the survey.

无停机时间重命名表

借助 GitLab 内置的数据库辅助方法,我们可以在不停机的情况下重命名数据库表。

该技术基于数据库视图构建,使用以下步骤:

  1. 重命名数据库表。
  2. 通过指向新表名,使用旧表名创建数据库视图。
  3. 为 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)。否则,回退到旧表名。这对于避免在零停机部署期间出现错误是必要的。

  1. 编辑 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”。

  1. 执行标准迁移(不是 post-migration):

       def up
         rename_table_safely(:issues, :tickets)
       end
    
       def down
         undo_rename_table_safely(:issues, :tickets)
       end
  1. 使用新名称重命名表的 字典文件(在 db/docs 下,例如本例中的 db/docs/tickets.yml)。更新 introduced_by_urlmilestone 属性。
  1. db/docs/deleted_views 中为临时视图(使用旧表名)创建一个条目。这是因为该视图会被同一合并请求的部署后迁移中的 finalize_table_rename 删除。

重要提示

  • 让其他开发人员知道表将被重命名。
    • 在你的合并请求中 ping @gl-database 组。
    • 在 Engineering Week-in-Review 文档中添加注释:table_name 将在 N.M 版本中重命名。在 N.M 和 N.M+1 版本中不允许对此表进行修改。
  • 辅助方法使用 Rails 的标准 rename_table 辅助方法来重命名表。
  • 该辅助方法会重命名序列和索引。有时它在命名索引时会偏离标准的 Rails 约定,因此有可能并非所有索引都被正确重命名。在本地运行迁移后,检查是否有命名不一致的索引(db/structure.sql)。这些可以在单独的迁移中手动重命名,也可以作为发布 M.N+1 的一部分。
  • 外键列可能仍包含旧表名。对于较小的表,请遵循我们的标准列重命名流程
  • 避免重命名使用触发器的数据库表。
  • 在重命名过程中不允许修改表(添加或删除列)。确保在开始重命名迁移之前(或在下一个版本中)完成对表的所有更改。
  • 由于索引名称可能会更改,请验证模型是否没有使用带有 unique_by: index_name 选项的批量插入(例如 insert_allupsert_all)。在使用这些方法时重命名索引可能会破坏功能。
  • 修改模型代码以指向新的数据库表。通过直接重命名模型或设置 self.table_name 变量来实现。

至此,我们的应用程序中不再有查询使用旧的数据库表名。

  1. 通过 post-migration 删除数据库视图:

      def up
        finalize_table_rename(:issues, :tickets)
      end
    
      def down
        undo_finalize_table_rename(:issues, :tickets)
      end
  2. 表名必须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 在获取数据库表信息时使用新的数据库表名。

新版本的应用程序使用新的数据库表。