.ai/principles/distilled/database-migrations.md
Prerequisite: If you haven't already, also read .ai/principles/distilled/database-fundamentals.md - it contains foundational rules that apply to all database work.
db/migrate) for schema changes critical to application speed or behavior that complete in ≤ 3 minutesdb/post_migrate) for non-critical schema changes (column removals, non-critical indexes) and data migrations completing in ≤ 10 minutescreate_table or add_column operations — these must be regular schema migrationsmilestone on every new migration (required since GitLab 16.6)Gitlab::Database::Migration[<latest_version>] (e.g., [2.1]) — DO NOT include Gitlab::Database::MigrationHelpers directlyGitlab::Database::Migration (look up Gitlab::Database::Migration::MIGRATION_CLASSES)MigrationRecord and set self.table_name explicitlyreset_column_information on any model before using it after a schema change in the same migration rundown methoddown method with a # no-op comment explaining whydisable_ddl_transaction! when using add_concurrent_index, add_concurrent_foreign_key, or any operation that must run outside a single transactionwith_lock_retries for DDL on high-traffic tables to avoid lock contentionwith_lock_retries inside the change method — define explicit up/down methodseach_batch_range or batched background migrationsadd_concurrent_index (with disable_ddl_transaction!) for adding indexes on non-empty tablesremove_concurrent_index (with disable_ddl_transaction!) for removing indexes on non-empty tablesremove_index in a single-transaction migration only for tables with fewer than 1,000 recordsdb:gitlabcom-database-testing reports index creation exceeding 20 minutes, create the index asynchronouslyi_ instead of index_, skip redundant prefixes, or use a purpose-based nameadd_concurrent_foreign_key for adding foreign keys (has lock retries built in)with_lock_retries when removing foreign keys on high-traffic tablesvalidate: false) and validation (prepare_async_foreign_key_validation) across different migrationsignore_column with remove_with and remove_after attributes when ignoring a columnignore_column to the CE modelrename_column_concurrently + cleanup_concurrent_column_rename (across two migrations) for zero-downtime column renameschange_column_type_concurrently + cleanup_concurrent_column_type_change for zero-downtime column type changesNOT NULL constraints in post-deployment migrations (after application code is deployed); remove NOT NULL constraints in regular migrationschange_column to add/remove constraints — it rewrites the entire column definition inefficientlySafelyChangeColumnDefault two-release process when changing a column default that application code may explicitly writedb/docs/deleted_tables per the database dictionary guideTABLES_TO_BE_RENAMED in lib/gitlab/database.rb one release before executing rename_table_safelyrename_table_safely / undo_rename_table_safely in a standard (non-post) migrationfinalize_table_rename in a post-deployment migration of the same release as the renameTABLES_TO_BE_RENAMED in the same release as finalize_table_renamebigint (:integer, limit: 8) for columns that may exceed 2 GB or for IDs on large tablesadd_timestamps_with_timezone, timestamps_with_timezone, or datetime_with_timezone instead of add_timestamps, timestamps, or :datetimeencrypts attributes as :jsonb, not :textJsonSchemaValidator with a size_limit (recommended max 64 KB) for all JSONB columnsadditionalProperties: falsequote_stringupdate_column_in_batches or each_batch_rangeArel.sql to wrap computed SQL values passed to update_column_in_batchesscripts/refresh-migrations-timestamps when rebasing old branchesdb/structure.sql changes generated by bundle exec rails db:migrate — DO NOT edit it manuallydb/structure.sql for existing tablesdb/schema_migrations/<timestamp> checksum file in the MR that adds the migrationdb/docs/deleted_tableswith_lock_retries for any DDL on high-traffic tableswith_lock_retries with idempotent replace: true / if_exists: true guardsdb/schema_migrations/ are auto-generated and do not require a newline at the end -- do not flag missing newlinesWhen creating a db/docs/batched_background_migrations/<name>.yml, the YAML MUST include:
migration_job_name: <BBM class name in CamelCase>description: <one-line description>feature_category: <category symbol>introduced_by_url: <MR URL> (placeholder OK for unreleased)milestone: '<X.Y>'queued_migration_version: <version timestamp>gitlab_schema: <gitlab_main | gitlab_ci | gitlab_main_user | gitlab_main_org> — match the schema of the BBM's primary tablefinalized_by: <version>For the full picture, see: