Draft: Validate the cascade foreign key on resource_label_events
What it contains
One post-deploy migration, db/post_migrate/20261008115108_validate_fk_resource_label_events_label_id.rb, calling validate_foreign_key(:resource_label_events, :label_id, name: :fk_resource_label_events_label_id). Plus the one-line db/structure.sql change that drops NOT VALID from that constraint.
The migration does not call disable_ddl_transaction!. Postgres wraps a single statement in an implicit transaction anyway, and the Migration/PreventSingleStatementWithDisableDdlTransaction cop flags it if we add one.
Why it exists
MR 4 (!255092) added this foreign key with add_concurrent_foreign_key(..., validate: false), because resource_label_events is on the OverLimitTables list in rubocop/rubocop-migrations.yml and a synchronous validation would scan the whole table while holding a lock. It queued the validation with prepare_async_foreign_key_validation instead.
That queue only runs against the GitLab.com production database. It changes nothing in the repository, so db/structure.sql keeps reporting the key as NOT VALID until a migration in the repository validates it. This is that migration.
On GitLab.com it is a no-op, because the async run already did the work. On self-managed instances, which do not run the async validator by default, this is what validates the key at all. The async queue is not hardcoded to GitLab.com: it sits behind database_async_foreign_key_validation, an ops flag that ships disabled, so an instance that has not turned it on has never validated this key.
Merge precondition
This must not merge until two things are true.
First, MR 4 has to be deployed to GitLab.com. Check with /chatops gitlab run auto_deploy status <merge_sha> and look for db/gprd in the output.
Second, the async validation has to have actually run. That happens over a weekend, and it is confirmed on Database Lab by checking the constraint no longer reports NOT VALID.
If this merges early, VALIDATE CONSTRAINT runs during the post-deploy window and performs the full table scan that MR 4 was written to avoid. The procedure is documented at doc/development/database/foreign_keys.md.
Why the validation is guaranteed to pass
The foreign key this one replaced was ON DELETE SET NULL, so every non-NULL label_id already points at a real label. A NULL value satisfies any foreign key under MATCH SIMPLE. The scan cannot fail. It is bookkeeping, not a data check.
Where it sits
This is not part of the 19.5 merge chain. MR 4b, 5 and 7 are %19.5 and have no migrations. MR 4 (!255092) and MR 6 (!255096) carry migrations and are %19.6, like this one. The batched background migration finalize that MR 6 needs comes in 19.9, so this validation lands first.
It branches off MR 4, so structure.sql already contains the NOT VALID key, and it is marked as dependent on !255092.
Stack for #628135 (INC-12449), merge in this order. The numbers follow the branch names, so there is no MR 2.
- MR 1 (!255082 (merged)): Render group level work item label notes against the group - merged 2026-09-21
- MR 3 (!255091 (merged)): Store the label event reference when the event is written - merged 2026-10-07
- MR 4 (!255092): Delete label events when their label is deleted
- MR 4b (!255238): Record deleted label events in the label audit event
- MR 5 (!255093): Link a project label in a group context to its project
- MR 6 (!255096): Backfill the reference on existing label events
- MR 7 (!255097): Stop writing the label event reference on a read request
How to check by hand
- On the parent branch,
db/structure.sqlcarries the line:ALTER TABLE ONLY resource_label_events ADD CONSTRAINT fk_resource_label_events_label_id FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE NOT VALID; - On this branch the same line has no
NOT VALID. - Against the test database, run:
gdk psql -d gitlabhq_test -c "SELECT conname, convalidated FROM pg_constraint WHERE conname = 'fk_resource_label_events_label_id';"
Observed on this branch after running the migration: conname fk_resource_label_events_label_id, convalidated t.
Verification already done
db/structure.sql was regenerated with TARGET=628135-04-cascade-delete-on-label-delete ./scripts/regenerate-schema, never hand-edited, and the diff is exactly the one line above.
The migration was re-run with bin/rails db:migrate:redo:main VERSION=20261008115108 RAILS_ENV=test. Both the no-op down and the up completed cleanly without disable_ddl_transaction!.
All pre-push hooks pass, rubocop and danger included.
No specs. Validation-only post-deploy migrations in this repository do not carry one, matching db/post_migrate/20260914090001_validate_deployment_merge_requests_bigint_fks.rb.
This needs a database review.