# What requires downtime? > 原文:[https://docs.gitlab.com/ee/development/what_requires_downtime.html](https://docs.gitlab.com/ee/development/what_requires_downtime.html) * [Dropping Columns](#dropping-columns) * [Step 1: Ignoring the column (release M)](#step-1-ignoring-the-column-release-m) * [Step 2: Dropping the column (release M+1)](#step-2-dropping-the-column-release-m1) * [Step 3: Removing the ignore rule (release M+2)](#step-3-removing-the-ignore-rule-release-m2) * [Renaming Columns](#renaming-columns) * [Step 1: Add The Regular Migration](#step-1-add-the-regular-migration) * [Step 2: Add A Post-Deployment Migration](#step-2-add-a-post-deployment-migration) * [Changing Column Constraints](#changing-column-constraints) * [Changing Column Types](#changing-column-types) * [Step 1: Create A Regular Migration](#step-1-create-a-regular-migration) * [Step 2: Create A Post Deployment Migration](#step-2-create-a-post-deployment-migration) * [Casting data to a new type](#casting-data-to-a-new-type) * [Changing The Schema For Large Tables](#changing-the-schema-for-large-tables) * [Adding Indexes](#adding-indexes) * [Dropping Indexes](#dropping-indexes) * [Adding Tables](#adding-tables) * [Dropping Tables](#dropping-tables) * [Renaming Tables](#renaming-tables) * [Adding Foreign Keys](#adding-foreign-keys) * [Removing Foreign Keys](#removing-foreign-keys) * [Data Migrations](#data-migrations) # What requires downtime?[](#what-requires-downtime "Permalink") 使用数据库时,可以在不使 GitLab 脱机的情况下执行某些操作,其他操作确实需要停机时间. 本指南介绍了各种操作,其影响以及如何在不停机的情况下执行这些操作. ## Dropping Columns[](#dropping-columns "Permalink") 删除列很棘手,因为正在运行的 GitLab 进程可能仍在使用这些列. 为了安全地解决此问题,您需要在三个版本中执行三个步骤: 1. 忽略列(版本 M) 2. 删除列(版本 M + 1) 3. 删除忽略规则(版本 M + 2) 之所以将其分布在三个发行版中,是因为删除列是一种破坏性操作,不易回滚. 遵循此过程可帮助我们确保没有部署到 GitLab.com 并升级将这些步骤集中在一起的自我管理安装的过程. ### Step 1: Ignoring the column (release M)[](#step-1-ignoring-the-column-release-m "Permalink") 第一步是忽略应用程序代码中的列. 这是必要的,因为 Rails 缓存列并在各个地方重复使用此缓存. 这可以通过定义要忽略的列来完成. 例如,要忽略用户模型中的`updated_at` ,请使用以下命令: ``` class User < ApplicationRecord include IgnorableColumns ignore_column :updated_at, remove_with: '12.7', remove_after: '2019-12-22' end ``` 多列也可以忽略: ``` ignore_columns %i[updated_at created_at], remove_with: '12.7', remove_after: '2019-12-22' ``` 我们要求通过以下方式指示何时可以安全地删除列忽略: * `remove_with` :设置为 GitLab 版本,通常在添加列忽略后两个版本(M + 2). * `remove_after` :设置为一个日期,在该日期之后,我们认为通常可以在 M + 2 版本的开发周期内删除列忽略项. 这些信息使我们能够更好地推理列忽略,并确保对于常规发行版和部署到 GitLab.com 而言,我们都不会过早删除列忽略. 例如,这避免了我们部署大量更改的情况,其中包括同时忽略列的更改和随后删除列忽略的更改(这将导致停机). 在此示例中,忽略列的更改在 12.5 版中进行. ### Step 2: Dropping the column (release M+1)[](#step-2-dropping-the-column-release-m1 "Permalink") 继续我们的示例,删除该列将进入版本 12.6 中*的部署后*迁移: ``` remove_column :user, :updated_at ``` ### Step 3: Removing the ignore rule (release M+2)[](#step-3-removing-the-ignore-rule-release-m2 "Permalink") 在下一个版本中,在此示例 12.7 中,我们设置了另一个合并请求以删除忽略规则. 这将删除`ignore_column`行,并且如果不再需要,还将`IgnoreableColumns` . 只有在`remove_after`日期过去之后,才应将其与`remove_with`指示的发行版合并. ## Renaming Columns[](#renaming-columns "Permalink") 重命名列通常需要停机,因为在数据库迁移期间/之后,应用程序可能会继续使用旧的列名称. 要在不停机的情况下重命名列,我们需要两个迁移:常规迁移和部署后迁移. 这些迁移都可以在同一版本中进行. ### Step 1: Add The Regular Migration[](#step-1-add-the-regular-migration "Permalink") 首先,我们需要创建常规迁移. 此迁移应当前使用`Gitlab::Database::MigrationHelpers#rename_column_concurrently`来执行重命名. 例如 ``` # A regular migration in db/migrate class RenameUsersUpdatedAtToUpdatedAtTimestamp < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! def up rename_column_concurrently :users, :updated_at, :updated_at_timestamp end def down undo_rename_column_concurrently :users, :updated_at, :updated_at_timestamp end end ``` 这将负责重命名列,确保数据保持同步,通过索引和外键进行复制等. **注意:**如果一列包含 1 个或多个不包含原始列名称的索引,则上述过程将失败. 在这种情况下,您首先需要重命名这些索引. ### Step 2: Add A Post-Deployment Migration[](#step-2-add-a-post-deployment-migration "Permalink") 重命名过程需要在部署后迁移中进行一些清理. 我们可以使用`Gitlab::Database::MigrationHelpers#cleanup_concurrent_column_rename`来执行此清理: ``` # A post-deployment migration in db/post_migrate class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! def up cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp end def down undo_cleanup_concurrent_column_rename :users, :updated_at, :updated_at_timestamp end end ``` **注意:**如果要重命名[大表](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3) ,请仔细考虑第一次迁移已运行但第二次清理迁移尚未运行的状态. 使用[Canary](https://about.gitlab.com/handbook/engineering/infrastructure/library/canary/) ,系统可能会在此状态下运行大量时间. ## Changing Column Constraints[](#changing-column-constraints "Permalink") 通常,无需停机即可添加或删除`NOT NULL`子句(或其他约束). 但是,这确实需要*首先*部署所有应用程序更改. 因此,在部署后的迁移中应该发生更改列约束的情况. 避免使用`change_column`因为它会产生无效查询,因为它会重新定义整个列类型. 您可以针对每个特定用例查看以下指南: * [Adding foreign-key constraints](migration_style_guide.html#adding-foreign-key-constraints) * [Adding `NOT NULL` constraints](database/not_null_constraints.html) * [Adding limits to text columns](database/strings_and_the_text_data_type.html) ## Changing Column Types[](#changing-column-types "Permalink") 可以使用`Gitlab::Database::MigrationHelpers#change_column_type_concurrently`来更改列的类型. 此方法的工作方式与`rename_column_concurrently`类似. 例如,假设我们要将`users.username`的类型从`string`更改为`text` . ### Step 1: Create A Regular Migration[](#step-1-create-a-regular-migration "Permalink") 常规迁移用于创建具有临时名称的新列,并设置一些触发器以使数据保持同步. 这样的迁移如下所示: ``` # A regular migration in db/migrate class ChangeUsersUsernameStringToText < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! def up change_column_type_concurrently :users, :username, :text end def down cleanup_concurrent_column_type_change :users, :username end end ``` ### Step 2: Create A Post Deployment Migration[](#step-2-create-a-post-deployment-migration "Permalink") 接下来,我们需要使用部署后迁移来清理更改: ``` # A post-deployment migration in db/post_migrate class ChangeUsersUsernameStringToTextCleanup < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! def up cleanup_concurrent_column_type_change :users, :username end def down change_column_type_concurrently :users, :username, :string end end ``` 就是这样,我们完成了! ### Casting data to a new type[](#casting-data-to-a-new-type "Permalink") 某些类型更改需要将数据转换为新类型. 例如,从`text`更改为`jsonb` . 在这种情况下,请使用`type_cast_function`选项. 确保没有不良数据,并且投射将始终成功. 您还可以提供一个自定义函数来处理转换错误. 迁移示例: ``` def up change_column_type_concurrently :users, :settings, :jsonb, type_cast_function: 'jsonb' end ``` ## Changing The Schema For Large Tables[](#changing-the-schema-for-large-tables "Permalink") 虽然`change_column_type_concurrently`和`rename_column_concurrently`可以用于在`rename_column_concurrently`机的情况下更改表的架构,但对于大型表来说,效果并不理想. 由于所有工作都是按顺序进行的,因此迁移可能需要很长时间才能完成,从而阻止了部署的进行. 由于数据库按顺序快速更新许多行,因此它们也可能给数据库带来很大压力. 为减轻数据库压力,在迁移大表中的列时(例如`issues` ),应改用`change_column_type_using_background_migration`或`rename_column_using_background_migration` . 这些方法的工作方式与并发的类似,但是使用后台迁移将工作/负载分散在更长的时间段内,而不会减慢部署速度. 例如,要使用后台迁移来更改列类型: ``` class ExampleMigration < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers disable_ddl_transaction! class Issue < ActiveRecord::Base self.table_name = 'issues' include EachBatch def self.to_migrate where('closed_at IS NOT NULL') end end def up change_column_type_using_background_migration( Issue.to_migrate, :closed_at, :datetime_with_timezone ) end def down change_column_type_using_background_migration( Issue.to_migrate, :closed_at, :datetime ) end end ``` 这将将`issues.closed_at`的类型更改为`timestamp with time zone` . 请记住,传递给`change_column_type_using_background_migration`的关系*必须*包含`EachBatch` ,否则将引发`TypeError` . 然后,此迁移需要在单独的发行版( *而不是*补丁程序发行版)中进行清除迁移,该清除迁移应从队列中窃取并处理所有剩余的行. 例如: ``` class MigrateRemainingIssuesClosedAt < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false disable_ddl_transaction! class Issue < ActiveRecord::Base self.table_name = 'issues' include EachBatch end def up Gitlab::BackgroundMigration.steal('CopyColumn') Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') migrate_remaining_rows if migrate_column_type? end def down # Previous migrations already revert the changes made here. end def migrate_remaining_rows Issue.where('closed_at_for_type_change IS NULL AND closed_at IS NOT NULL').each_batch do |batch| batch.update_all('closed_at_for_type_change = closed_at') end cleanup_concurrent_column_type_change(:issues, :closed_at) end def migrate_column_type? # Some environments may have already executed the previous version of this # migration, thus we don't need to migrate those environments again. column_for('issues', 'closed_at').type == :datetime # rubocop:disable Migration/Datetime end end ``` 这同样适用于`rename_column_using_background_migration` : 1. 使用帮助程序创建迁移,该迁移将安排后台迁移以将写入分散在更长的时间范围内. 2. 在下一个每月发行版中,创建清理迁移以从 Sidekiq 队列中窃取,迁移所有丢失的行并清理重命名. 如果该列已被重命名,则此迁移应在从 Sidekiq 队列中窃取后跳过步骤. 有关更多信息,请参阅[有关清理后台迁移的文档](background_migrations.html#cleaning-up) . ## Adding Indexes[](#adding-indexes "Permalink") 使用`add_concurrent_index`时,添加索引不需要停机. 另请参阅《 [迁移样式指南》](migration_style_guide.html#adding-indexes) . ## Dropping Indexes[](#dropping-indexes "Permalink") 删除索引不需要停机. ## Adding Tables[](#adding-tables "Permalink") 此操作是安全的,因为还没有使用该表的代码. ## Dropping Tables[](#dropping-tables "Permalink") 使用部署后迁移可以安全地完成删除表的操作,但前提是应用程序不再使用该表. ## Renaming Tables[](#renaming-tables "Permalink") 重命名表需要停机,因为在数据库迁移期间/之后,应用程序可能会继续使用旧表名. ## Adding Foreign Keys[](#adding-foreign-keys "Permalink") 添加外键通常需要 3 个步骤: 1. 开始交易 2. 运行`ALTER TABLE`添加约束 3. 检查所有现有数据 因为`ALTER TABLE`通常会在事务结束之前获取独占锁,所以这意味着该方法将需要停机. GitLab allows you to work around this by using `Gitlab::Database::MigrationHelpers#add_concurrent_foreign_key`. This method ensures that no downtime is needed. ## Removing Foreign Keys[](#removing-foreign-keys "Permalink") 此操作不需要停机. ## Data Migrations[](#data-migrations "Permalink") 数据迁移可能很棘手. 迁移数据的通常方法是采取 3 个步骤: 1. 迁移初始数据 2. 部署应用程序代码 3. 迁移所有剩余数据 通常这有效,但并非总是如此. 例如,如果要将字段的格式从 JSON 更改为其他格式,我们会遇到一些问题. 如果我们在部署应用程序代码之前更改现有数据,则很可能会遇到错误. 另一方面,如果我们在部署应用程序代码后进行迁移,则可能会遇到相同的问题. 如果您只需要更正一些无效数据,则部署后迁移通常就足够了. 如果您需要更改数据格式(例如,从 JSON 更改为其他格式),通常最好为新数据格式添加一个新列,然后让应用程序使用该列. 在这种情况下,程序将是: 1. 以新格式添加新列 2. 将现有数据复制到此新列 3. 部署应用程序代码 4. In a post-deployment migration, copy over any remaining data 通常,没有一个万能的解决方案,因此最好在合并请求中讨论此类迁移,以确保以最佳方式实现它们.