Add issues bigint foreign keys batch one

What does this MR do and why?

Recreates foreign keys on the bigint shadow columns for the first batch of the issues integer-to-bigint ID conversion, tracked in #629827 (closed).

The batch one indexes are already in place (prepared asynchronously in !251347 (merged) and created synchronously in !253447 (merged)). The remaining batch one work for this release is the foreign keys: they must exist and be validated on the *_convert_to_bigint columns before the columns are swapped next release.

Foreign keys added

Five of the batch one columns carry a foreign key:

Migration Column Target ON DELETE Temp FK name
20260904090001 closed_by_id_convert_to_bigint users(id) SET NULL fk_c63cbf6c25_tmp
20260904090002 updated_by_id_convert_to_bigint users(id) SET NULL fk_ffed080f01_tmp
20260904090003 promoted_to_epic_id_convert_to_bigint epics(id) SET NULL fk_df75a7c8b8_tmp
20260904090004 duplicated_to_id_convert_to_bigint issues(id) (self-referential) SET NULL fk_9c4516d665_tmp
20260904090005 moved_to_id_convert_to_bigint issues(id) (self-referential) SET NULL fk_a194299be1_tmp

Each FK is created in its own migration (foreign key constraints should not be added more than once per migration file unless source and target are identical), with the _tmp naming convention (tmp_foreign_key_name) so the swap migration can rename them to the canonical names.

Synchronous validation

The FKs are validated synchronously (the add_concurrent_foreign_key default), rather than validate: false + async validation. Validation skips NULL values, so the row set to validate is a subset of the table.

Timings were measured on a production clone and are within the post-deployment concurrent-operation budget (20 minutes), so synchronous validation is kept:

Migration Column Total runtime
20260904090001 closed_by_id_convert_to_bigint 370.2 s
20260904090002 updated_by_id_convert_to_bigint 68.9 s
20260904090003 promoted_to_epic_id_convert_to_bigint 8.2 s
20260904090004 duplicated_to_id_convert_to_bigint 31.5 s
20260904090005 moved_to_id_convert_to_bigint 122.3 s

The closed_by_id FK is the slowest (~6 minutes) because its column is non-partial and has the most non-NULL values to validate; the others are sparser (partial indexes). All five stay comfortably under the concurrent-operation limit.

Why last_edited_by_id is not here

last_edited_by_id is one of the batch one columns and its index was created, but the column has no foreign key, so there is nothing to add for it.

Self-referential foreign keys (duplicated_to_id, moved_to_id)

These two batch one columns have self-referential foreign keys — they point at issues(id), and id itself is not converted until batch two. We create them now, in batch one, pointing the bigint shadow columns at the still-integer id.

A bigint column referencing an integer primary key is not a problem: PostgreSQL accepts the foreign key, and the cross-type equality operator (bigint = integer) belongs to the integer_ops B-tree family, so issues_pkey is still used to enforce the constraint — there is no sequential-scan penalty. The values involved are also well within integer range, so no value can fail to be represented.

The trade-off is that these two FKs will have to be recreated in batch two once id becomes bigint (pointing at the final bigint id). We accept that recreation cost because the priority for the production instance is to keep the foreign keys present throughout the conversion and to minimize the number of duplicate indexes/constraints held at any one time, rather than to avoid rebuilding these two FKs.

This differs from the merge_requests conversion precedent (#507695 (closed)), which deferred FKs targeting the converting id to the batch that swaps id. For issues we prefer to keep them in place now and recreate them in batch two.

Migration output

db/structure.sql is intentionally unchanged: the temporary _tmp foreign keys live on *_convert_to_bigint columns that are not part of the final schema (which already reflects the all-bigint state for new instances). Each migration is guarded to run only where the conversion column exists, so it is a no-op on instances that already have native bigint IDs.

MR acceptance checklist

  • Database review requested — foreign key creation with synchronous validation; timings confirmed on a production clone.
Edited by Mario Celi

Merge request reports

Loading
Loading