Drop FK fk_2902beee34 from packages_helm_metadata_cache_states.project_id

What does this MR do and why?

Drops the hard foreign key fk_2902beee34 (packages_helm_metadata_cache_states.project_id -> projects.id).

packages_helm_metadata_cache_states.project_id is a denormalized sharding key copied from its parent packages_helm_metadata_caches.project_id by the permanent trigger trigger_489fffe04425 (AssignDesiredShardingKey). The parent column has no hard FK to projects and is cleaned up only via a loose foreign key. This means the parent can legitimately hold the project_id of a deleted project during the cleanup backlog window. When the trigger copies that orphaned value into the child table, the hard FK on the child rejects it with PG::ForeignKeyViolation, aborting the write (incident: https://gitlab.com/gitlab-org/gitlab/-/issues/605940).

The fix is to drop the child FK. Rows are still cleaned up transitively: the loose foreign key on the parent sets its status to pending_destruction, Packages::Helm::CleanupStaleMetadataCacheWorker then destroys the parent, and the child row is removed by the ON DELETE CASCADE on fk_rails_281379b415 (packages_helm_metadata_cache_id). This follows the exact precedent established in #606941 (closed) for packages_nuget_symbol_states and packages_package_file_states (landed in commit cb0b9415), and addresses the write-path exposure left after !246021 (merged).

Scope of the remaining exposure

The backfill is already finalized (20260420233038, recorded as finalized_by in the batched background migration YAML), so what remains is the runtime write path only, and specifically on Geo primaries. Packages::Helm::MetadataCache includes ::Geo::VerifiableModel (ee/app/models/ee/packages/helm/metadata_cache.rb), and the states row is written by after_save :save_verification_details, which returns early unless Gitlab::Geo.primary?. The linked incident was an upgrade-time Sev1; that specific urgency no longer applies, so this is a correctness fix rather than an incident mitigation.

The migration stays in db/migrate because the operation is cheap, not for ordering reasons: table_size: small, and the database testing pipeline showed only 0.2-0.3 ms holding ACCESS EXCLUSIVE on projects under with_lock_retries. It cannot be ordered ahead of the 19.0 post-deploy finalize migration in any case, since Gitlab::Database::Migrations::Version#<=> compares milestone before migration type.

References

Screenshots or screen recordings

N/A - migration-only change.

How to set up and validate locally

Run bundle exec rails db:migrate and confirm fk_2902beee34 no longer appears in \d packages_helm_metadata_cache_states in psql.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Chen Zhang

Merge request reports

Loading
Loading