564.md 14.7 KB
Newer Older
Lab机器人's avatar
readme  
Lab机器人 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
# 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

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