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
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
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.
## 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
By default, an integer column can hold up to a 4-byte (32-bit) number. That is