The `with_lock_retries` helper method can be used when you normally use
standard Rails migration helper methods. Calling more than one migration
helper is not a problem if they're executed on the same table.
Using the `with_lock_retries` helper method is advised when a database
migration involves one of the high-traffic tables:
-`users`
-`projects`
-`namespaces`
-`ci_pipelines`
-`ci_builds`
-`notes`
Example changes:
-`add_foreign_key` / `remove_foreign_key`
-`add_column` / `remove_column`
-`change_column_default`
**Note:**`with_lock_retries` method **cannot** be used with `disable_ddl_transaction!`.
### How the helper method works
1. Iterate 50 times.
1. For each iteration, set a pre-configured `lock_timeout`.
1. Try to execute the given block. (`remove_column`).
1. If `LockWaitTimeout` error is raised, sleep for the pre-configured `sleep_time`
and retry the block.
1. If no error is raised, the current iteration has successfully executed the block.
For more information check the [`Gitlab::Database::WithLockRetries`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/with_lock_retries.rb) class. The `with_lock_retries` helper method is implemented in the [`Gitlab::Database::MigrationHelpers`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/migration_helpers.rb) module.
In a worst-case scenario, the method:
- Executes the block for a maximum of 50 times over 40 minutes.
- Most of the time is spent in a pre-configured sleep period after each iteration.
- After the 50th retry, the block will be executed without `lock_timeout`, just
like a standard migration invocation.
- If a lock cannot be acquired, the migration will fail with `statement timeout` error.
The migration might fail if there is a very long running transaction (40+ minutes)
accessing the `users` table.
## Multi-Threading
## Multi-Threading
Sometimes a migration might need to use multiple Ruby threads to speed up a
Sometimes a migration might need to use multiple Ruby threads to speed up a
...
@@ -364,6 +482,86 @@ to run on a large table, as long as it is only updating a small subset of the
...
@@ -364,6 +482,86 @@ to run on a large table, as long as it is only updating a small subset of the
rows in the table, but do not ignore that without validating on the GitLab.com
rows in the table, but do not ignore that without validating on the GitLab.com
staging environment - or asking someone else to do so for you - beforehand.
staging environment - or asking someone else to do so for you - beforehand.
## Dropping a database table
Dropping a database table is uncommon, and the `drop_table` method
provided by Rails is generally considered safe. Before dropping the table,
please consider the following:
If your table has foreign keys on a high-traffic table (like `projects`), then
the `DROP TABLE` statement might fail with **statement timeout** error. Determining
what tables are high traffic can be difficult. Self-managed instances might
use different features of GitLab with different usage patterns, thus making
assumptions based on GitLab.com is not enough.
Table **has no records** (feature was never in use) and **no foreign
keys**:
- Simply use the `drop_table` method in your migration.
```ruby
defchange
drop_table:my_table
end
```
Table **has records** but **no foreign keys**:
- First release: Remove the application code related to the table, such as models,
controllers and services.
- Second release: Use the `drop_table` method in your migration.
```ruby
defup
drop_table:my_table
end
defdown
# create_table ...
end
```
Table **has foreign keys**:
- First release: Remove the application code related to the table, such as models,
controllers, and services.
- Second release: Remove the foreign keys using the `with_lock_retries`
helper method. Use `drop_table` in another migration file.
**Migrations for the second release:**
Removing the foreign key on the `projects` table:
```ruby
# first migration file
defup
with_lock_retriesdo
remove_foreign_key:my_table,:projects
end
end
defdown
with_lock_retriesdo
add_foreign_key:my_table,:projects
end
end
```
Dropping the table:
```ruby
# second migration file
defup
drop_table:my_table
end
defdown
# create_table ...
end
```
## Integer column type
## Integer column type
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
By default, an integer column can hold up to a 4-byte (32-bit) number. That is