diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index 5230cfc985ab12d0d5213574881191428ff7ae1b..c8d0fc02debe126c64466d9781fd776b9d07e7ef 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -466,6 +466,10 @@ user_details:
   - table: namespaces
     column: enterprise_group_id
     on_delete: async_nullify
+virtual_registries_packages_maven_cached_responses:
+  - table: virtual_registries_packages_maven_upstreams
+    column: upstream_id
+    on_delete: async_nullify
 vulnerability_export_parts:
   - table: organizations
     column: organization_id
diff --git a/db/post_migrate/20240823110954_track_virtual_registries_packages_maven_upstream_record_changes.rb b/db/post_migrate/20240823110954_track_virtual_registries_packages_maven_upstream_record_changes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ef4045a31e3ad0aa405df1595eb1af7989f50ead
--- /dev/null
+++ b/db/post_migrate/20240823110954_track_virtual_registries_packages_maven_upstream_record_changes.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class TrackVirtualRegistriesPackagesMavenUpstreamRecordChanges < Gitlab::Database::Migration[2.2]
+  include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers
+
+  milestone '17.4'
+
+  def up
+    track_record_deletions(:virtual_registries_packages_maven_upstreams)
+  end
+
+  def down
+    untrack_record_deletions(:virtual_registries_packages_maven_upstreams)
+  end
+end
diff --git a/db/post_migrate/20240823113710_remove_vregs_pkgs_mvn_upstreams_vregs_pkgs_mvn_cached_responses_upstream_id_fk.rb b/db/post_migrate/20240823113710_remove_vregs_pkgs_mvn_upstreams_vregs_pkgs_mvn_cached_responses_upstream_id_fk.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0ffb64f3d4468715213b67a5cf70a8e81cdfeba6
--- /dev/null
+++ b/db/post_migrate/20240823113710_remove_vregs_pkgs_mvn_upstreams_vregs_pkgs_mvn_cached_responses_upstream_id_fk.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class RemoveVregsPkgsMvnUpstreamsVregsPkgsMvnCachedResponsesUpstreamIdFk < Gitlab::Database::Migration[2.2]
+  milestone '17.4'
+  disable_ddl_transaction!
+
+  FOREIGN_KEY_NAME = 'fk_rails_1167f21441'
+
+  def up
+    with_lock_retries do
+      remove_foreign_key_if_exists(
+        :virtual_registries_packages_maven_cached_responses,
+        :virtual_registries_packages_maven_upstreams,
+        name: FOREIGN_KEY_NAME,
+        reverse_lock_order: true
+      )
+    end
+  end
+
+  def down
+    add_concurrent_foreign_key(
+      :virtual_registries_packages_maven_cached_responses,
+      :virtual_registries_packages_maven_upstreams,
+      name: FOREIGN_KEY_NAME,
+      column: :upstream_id,
+      target_column: :id,
+      on_delete: :nullify
+    )
+  end
+end
diff --git a/db/schema_migrations/20240823110954 b/db/schema_migrations/20240823110954
new file mode 100644
index 0000000000000000000000000000000000000000..37a10ff1b100adb0ef2ee13684f7b2acd978e436
--- /dev/null
+++ b/db/schema_migrations/20240823110954
@@ -0,0 +1 @@
+a64beea59b93163168802d44712c9637dbb255792feb1aed84025ea7dc03ccc7
\ No newline at end of file
diff --git a/db/schema_migrations/20240823113710 b/db/schema_migrations/20240823113710
new file mode 100644
index 0000000000000000000000000000000000000000..c5de49c1becf179a3cb7bd5315bde1e9bb9af791
--- /dev/null
+++ b/db/schema_migrations/20240823113710
@@ -0,0 +1 @@
+1254dd3a1d7e28ab9866877ad278d370c93949d7f0dd9973283c745e4fe5cdf8
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 6ca4b98c2d638887c4ee4a710b4d1b548a112cbc..106d4967753c858f5fbe468134e38818909c471b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -32874,6 +32874,8 @@ CREATE TRIGGER trigger_update_vulnerability_reads_on_vulnerability_update AFTER
 
 CREATE TRIGGER users_loose_fk_trigger AFTER DELETE ON users REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
 
+CREATE TRIGGER virtual_registries_packages_maven_upstreams_loose_fk_trigger AFTER DELETE ON virtual_registries_packages_maven_upstreams REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records();
+
 ALTER TABLE ONLY deployments
     ADD CONSTRAINT fk_009fd21147 FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE CASCADE;
 
@@ -34599,9 +34601,6 @@ ALTER TABLE ONLY ci_sources_projects
 ALTER TABLE ONLY ci_sources_projects
     ADD CONSTRAINT fk_rails_10a1eb379a_p_tmp FOREIGN KEY (partition_id, pipeline_id) REFERENCES p_ci_pipelines(partition_id, id) ON UPDATE CASCADE ON DELETE CASCADE NOT VALID;
 
-ALTER TABLE ONLY virtual_registries_packages_maven_cached_responses
-    ADD CONSTRAINT fk_rails_1167f21441 FOREIGN KEY (upstream_id) REFERENCES virtual_registries_packages_maven_upstreams(id) ON DELETE SET NULL;
-
 ALTER TABLE ONLY zoom_meetings
     ADD CONSTRAINT fk_rails_1190f0e0fa FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
 
diff --git a/spec/factories/virtual_registries/packages/maven/cached_responses.rb b/spec/factories/virtual_registries/packages/maven/cached_responses.rb
index bd16e4892e853b72d3480a95e0989038fa0aee14..0a407638a4e02f0982225b83bc6818119f46e340 100644
--- a/spec/factories/virtual_registries/packages/maven/cached_responses.rb
+++ b/spec/factories/virtual_registries/packages/maven/cached_responses.rb
@@ -5,7 +5,7 @@
     class: 'VirtualRegistries::Packages::Maven::CachedResponse' do
     upstream { association :virtual_registries_packages_maven_upstream }
     group { upstream.group }
-    relative_path { '/a/relative/path/test.txt' }
+    relative_path { |n| "/a/relative/path/test-#{n}.txt" }
     size { 1.kilobyte }
     upstream_etag { OpenSSL::Digest.hexdigest('SHA256', 'test') }
     content_type { 'text/plain' }
diff --git a/spec/models/virtual_registries/packages/maven/cached_response_spec.rb b/spec/models/virtual_registries/packages/maven/cached_response_spec.rb
index b4cd3e176e9f470a486bf928363cfc56065d885b..a532771b5521e1b5d6fec1f7a4fb8a10a85f162e 100644
--- a/spec/models/virtual_registries/packages/maven/cached_response_spec.rb
+++ b/spec/models/virtual_registries/packages/maven/cached_response_spec.rb
@@ -114,4 +114,11 @@
       it { is_expected.to contain_exactly(cached_response) }
     end
   end
+
+  context 'with loose foreign key on virtual_registries_packages_maven_cached_responses.upstream_id' do
+    it_behaves_like 'cleanup by a loose foreign key' do
+      let_it_be(:parent) { create(:virtual_registries_packages_maven_upstream) }
+      let_it_be(:model) { create(:virtual_registries_packages_maven_cached_response, upstream: parent) }
+    end
+  end
 end
diff --git a/spec/models/virtual_registries/packages/maven/upstream_spec.rb b/spec/models/virtual_registries/packages/maven/upstream_spec.rb
index f70fa4108c2562ea3062a885092f90f65deb64ad..c19f30ebef8f808ee5c8dfacc07fc03da147a264 100644
--- a/spec/models/virtual_registries/packages/maven/upstream_spec.rb
+++ b/spec/models/virtual_registries/packages/maven/upstream_spec.rb
@@ -7,6 +7,10 @@
 
   subject(:upstream) { build(:virtual_registries_packages_maven_upstream) }
 
+  it_behaves_like 'it has loose foreign keys' do
+    let(:factory_name) { :virtual_registries_packages_maven_upstream }
+  end
+
   describe 'associations' do
     it do
       is_expected.to have_many(:cached_responses)