Rename table without downtime helper
What does this MR do?
This MR adds a few new database migration helper methods to rename a table without downtime, in one release.
Contributes to #121607 (closed).
How does it work?
Renaming a table currently requires downtime, since two versions (M, M-1) of the app will access the same database. If the table is renamed, the old version of the app will raise errors because the old table is no longer there. The solution in this MR leverages database views. In PostgreSQL database views "behave" like a table so they proxy back changes to the underlying relation (INSERT, UPDATE, DELETE works).
Known issues
When using rename_table
, rails will try renaming the indexes where the index name is generated:
- https://github.com/rails/rails/blob/main/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L378
- https://github.com/rails/rails/blob/d049839e1f07b197c0bf62b622c4313bf2325cc4/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb#L1436
This can cause issues in the old version of the app if .insert_all
is used with the old index name.
Workaround: Do not use the built in .rename_table
helper, provide additional helpers for renaming the indexes.
Testing
Rename projects and run the tests
I renamed the projects
table, however I did not change table_name
. The Project
model will use the view.
== 20210217093000 ProjectsTest: migrating =====================================
-- rename_table(:projects, :renamed_projects)
-> 0.0218s
-- execute("CREATE VIEW projects AS SELECT * FROM renamed_projects")
-> 0.0022s
-- execute("INSERT INTO renamed_tables (old_name, new_name) VALUES ('projects', 'renamed_projects')")
-> 0.0004s
== 20210217093000 ProjectsTest: migrated (0.0276s) ============================
Before running the tests, I create the renamed_tables
record manually and clear the column cache.
Result: tests are passing.
I was also able to boot up the application and things worked as expected.
Rename projects on a replica
== 20210217093000 ProjectsTest: migrating =====================================
-- rename_table(:projects, :renamed_projects)
-> 12.5183s
-- execute("CREATE VIEW projects AS SELECT * FROM renamed_projects")
-> 0.1787s
-- execute("INSERT INTO renamed_tables (old_name, new_name) VALUES ('projects', 'renamed_projects')")
-> 0.1651s
== 20210217093000 ProjectsTest: migrated (13.6895s) ===========================
rename_table
runs for quite a while. I suppose altering metadata for constraints and indexes took some time.
Projects in namespace query (recursive), index is picked up nicely: https://explain.depesz.com/s/FM0w
Bulk insert with unique_by
+ index name:
project = Project.find_by_full_path("gitlab-org/gitlab")
Project.insert_all([{name: 'other project name', id: project.id, created_at: Time.now, updated_at: Time.now, namespace_id: 9970, archived: false }], unique_by: :index_projects_on_id_partial_for_visibility)
# if a bogus index name is passed:
Project.insert_all([{name: 'other project name', id: project.id, created_at: Time.now, updated_at: Time.now, namespace_id: 9970, archived: false }], unique_by: :unknown)
ArgumentError: No unique index found for unknown
Interesting find: the primary key index is not discovered by Rails: ActiveRecord::Base.connection.schema_cache.indexes('issues')
Does this MR meet the acceptance criteria?
Conformity
-
Changelog entry -
Documentation (if required) -
Code review guidelines -
Merge request performance guidelines -
Style guides -
Database guides -
Separation of EE specific content
Availability and Testing
-
Review and add/update tests for this feature/bug. Consider all test levels. See the Test Planning Process. -
Tested in all supported browsers -
Informed Infrastructure department of a default or new setting change, if applicable per definition of done
Security
If this MR contains changes to processing or storing of credentials or tokens, authorization and authentication methods and other items described in the security review guidelines:
-
Label as security and @ mention @gitlab-com/gl-security/appsec
-
The MR includes necessary changes to maintain consistency between UI, API, email, or other methods -
Security reports checked/validated by a reviewer from the AppSec team