# 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

通常，没有一个万能的解决方案，因此最好在合并请求中讨论此类迁移，以确保以最佳方式实现它们.