From ecc5b7abfd71962f8e6d2ee4ea04d6714dda4f48 Mon Sep 17 00:00:00 2001
From: Shubham Kumar <shukumar@gitlab.com>
Date: Mon, 3 Mar 2025 16:18:53 +0000
Subject: [PATCH] Add and backfill project_id for scan_result_policies

## What does this MR do and why?

Add and backfill project_id for scan_result_policies.

This table has a
[desired sharding key](https://docs.gitlab.com/ee/development/database/multiple_databases.html#define-a-desired_sharding_key-to-automatically-backfill-a-sharding_key)
configured ([view configuration](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/docs/scan_result_policies.yml)).

This merge request is the first step towards transforming the desired sharding key into a
[sharding key](https://docs.gitlab.com/ee/development/database/multiple_databases.html#defining-a-sharding-key-for-all-cell-local-tables).

This involves three changes:

- Adding a new column that will serve as the sharding key (along with the relevant index and foreign key).
- Populating the sharding key when new records are created by adding a database function and trigger.
- Scheduling a [batched background migration](https://docs.gitlab.com/ee/development/database/batched_background_migrations.html)
  to set the sharding key for existing records.

Once the background migration has completed, a second merge request will be created to finalize the background
migration and validate the not null constraint.

## How to verify

We have assigned a random backend engineer from ~"group::security policies" to review these changes. Please review this merge
request from a ~backend perspective. The main thing we are looking to verify is that the added column and association
match the values specified by the [desired sharding key](https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/docs/scan_result_policies.yml)
configuration and that backfilling the column from this other table makes sense in the context of this feature.

When you are finished, please:

1. Trigger the [database testing pipeline](https://docs.gitlab.com/ee/development/database/database_migration_pipeline.html)
   as instructed by Danger.
1. Request a review from the ~backend maintainer and ~database reviewer suggested by Danger.

If you have any questions or concerns, reach out to `@tigerwnz` or @shubhamkrai.

This merge request was generated by a once off keep implemented in
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/143774

This change was generated by
[gitlab-housekeeper](https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-housekeeper)
using the Keeps::BackfillDesiredShardingKeySmallTable keep.

To provide feedback on your experience with `gitlab-housekeeper` please create an issue with the
label ~"GitLab Housekeeper" and consider pinging the author of this keep.

Changelog: other
---
 ...fill_scan_result_policies_namespace_id.yml |  8 ++++
 ...ckfill_scan_result_policies_project_id.yml |  8 ++++
 db/docs/scan_result_policies.yml              |  3 ++
 ...dd_namespace_id_to_scan_result_policies.rb |  9 ++++
 ...scan_result_policies_project_id_trigger.rb | 25 +++++++++++
 ...ackfill_scan_result_policies_project_id.rb | 40 ++++++++++++++++++
 ...ex_scan_result_policies_on_namespace_id.rb | 16 +++++++
 ...dd_scan_result_policies_namespace_id_fk.rb | 16 +++++++
 ...an_result_policies_namespace_id_trigger.rb | 25 +++++++++++
 ...kfill_scan_result_policies_namespace_id.rb | 40 ++++++++++++++++++
 db/schema_migrations/20250301125533           |  1 +
 db/schema_migrations/20250301125534           |  1 +
 db/schema_migrations/20250301125854           |  1 +
 db/schema_migrations/20250301125855           |  1 +
 db/schema_migrations/20250301125856           |  1 +
 db/schema_migrations/20250301125857           |  1 +
 db/schema_migrations/20250301125858           |  1 +
 db/structure.sql                              | 42 +++++++++++++++++++
 ...kfill_scan_result_policies_namespace_id.rb | 10 +++++
 ...ackfill_scan_result_policies_project_id.rb | 10 +++++
 ..._scan_result_policies_namespace_id_spec.rb | 15 +++++++
 ...ll_scan_result_policies_project_id_spec.rb | 15 +++++++
 ...ll_scan_result_policies_project_id_spec.rb | 33 +++++++++++++++
 ..._scan_result_policies_namespace_id_spec.rb | 33 +++++++++++++++
 24 files changed, 355 insertions(+)
 create mode 100644 db/docs/batched_background_migrations/backfill_scan_result_policies_namespace_id.yml
 create mode 100644 db/docs/batched_background_migrations/backfill_scan_result_policies_project_id.yml
 create mode 100644 db/migrate/20250301125854_add_namespace_id_to_scan_result_policies.rb
 create mode 100644 db/post_migrate/20250301125533_add_scan_result_policies_project_id_trigger.rb
 create mode 100644 db/post_migrate/20250301125534_queue_backfill_scan_result_policies_project_id.rb
 create mode 100644 db/post_migrate/20250301125855_index_scan_result_policies_on_namespace_id.rb
 create mode 100644 db/post_migrate/20250301125856_add_scan_result_policies_namespace_id_fk.rb
 create mode 100644 db/post_migrate/20250301125857_add_scan_result_policies_namespace_id_trigger.rb
 create mode 100644 db/post_migrate/20250301125858_queue_backfill_scan_result_policies_namespace_id.rb
 create mode 100644 db/schema_migrations/20250301125533
 create mode 100644 db/schema_migrations/20250301125534
 create mode 100644 db/schema_migrations/20250301125854
 create mode 100644 db/schema_migrations/20250301125855
 create mode 100644 db/schema_migrations/20250301125856
 create mode 100644 db/schema_migrations/20250301125857
 create mode 100644 db/schema_migrations/20250301125858
 create mode 100644 lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id.rb
 create mode 100644 lib/gitlab/background_migration/backfill_scan_result_policies_project_id.rb
 create mode 100644 spec/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id_spec.rb
 create mode 100644 spec/lib/gitlab/background_migration/backfill_scan_result_policies_project_id_spec.rb
 create mode 100644 spec/migrations/20250301125534_queue_backfill_scan_result_policies_project_id_spec.rb
 create mode 100644 spec/migrations/20250301125858_queue_backfill_scan_result_policies_namespace_id_spec.rb

diff --git a/db/docs/batched_background_migrations/backfill_scan_result_policies_namespace_id.yml b/db/docs/batched_background_migrations/backfill_scan_result_policies_namespace_id.yml
new file mode 100644
index 000000000000..c86ac244ae97
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_scan_result_policies_namespace_id.yml
@@ -0,0 +1,8 @@
+---
+migration_job_name: BackfillScanResultPoliciesNamespaceId
+description: Backfills sharding key `scan_result_policies.namespace_id` from `security_orchestration_policy_configurations`.
+feature_category: security_policy_management
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183125
+milestone: '17.10'
+queued_migration_version: 20250301125858
+finalized_by: # version of the migration that finalized this BBM
diff --git a/db/docs/batched_background_migrations/backfill_scan_result_policies_project_id.yml b/db/docs/batched_background_migrations/backfill_scan_result_policies_project_id.yml
new file mode 100644
index 000000000000..0c2b136fc503
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_scan_result_policies_project_id.yml
@@ -0,0 +1,8 @@
+---
+migration_job_name: BackfillScanResultPoliciesProjectId
+description: Backfills sharding key `scan_result_policies.project_id` from `security_orchestration_policy_configurations`.
+feature_category: security_policy_management
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183125
+milestone: '17.10'
+queued_migration_version: 20250301125534
+finalized_by: # version of the migration that finalized this BBM
diff --git a/db/docs/scan_result_policies.yml b/db/docs/scan_result_policies.yml
index a5f19ac96a37..73c1a43b9072 100644
--- a/db/docs/scan_result_policies.yml
+++ b/db/docs/scan_result_policies.yml
@@ -26,3 +26,6 @@ desired_sharding_key:
         sharding_key: namespace_id
         belongs_to: security_orchestration_policy_configuration
 table_size: small
+desired_sharding_key_migration_job_name:
+  - BackfillScanResultPoliciesProjectId
+  - BackfillScanResultPoliciesNamespaceId
diff --git a/db/migrate/20250301125854_add_namespace_id_to_scan_result_policies.rb b/db/migrate/20250301125854_add_namespace_id_to_scan_result_policies.rb
new file mode 100644
index 000000000000..1dd284b373de
--- /dev/null
+++ b/db/migrate/20250301125854_add_namespace_id_to_scan_result_policies.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddNamespaceIdToScanResultPolicies < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+
+  def change
+    add_column :scan_result_policies, :namespace_id, :bigint
+  end
+end
diff --git a/db/post_migrate/20250301125533_add_scan_result_policies_project_id_trigger.rb b/db/post_migrate/20250301125533_add_scan_result_policies_project_id_trigger.rb
new file mode 100644
index 000000000000..454a154d6cb6
--- /dev/null
+++ b/db/post_migrate/20250301125533_add_scan_result_policies_project_id_trigger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddScanResultPoliciesProjectIdTrigger < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+
+  def up
+    install_sharding_key_assignment_trigger(
+      table: :scan_result_policies,
+      sharding_key: :project_id,
+      parent_table: :security_orchestration_policy_configurations,
+      parent_sharding_key: :project_id,
+      foreign_key: :security_orchestration_policy_configuration_id
+    )
+  end
+
+  def down
+    remove_sharding_key_assignment_trigger(
+      table: :scan_result_policies,
+      sharding_key: :project_id,
+      parent_table: :security_orchestration_policy_configurations,
+      parent_sharding_key: :project_id,
+      foreign_key: :security_orchestration_policy_configuration_id
+    )
+  end
+end
diff --git a/db/post_migrate/20250301125534_queue_backfill_scan_result_policies_project_id.rb b/db/post_migrate/20250301125534_queue_backfill_scan_result_policies_project_id.rb
new file mode 100644
index 000000000000..60f5ee2a455c
--- /dev/null
+++ b/db/post_migrate/20250301125534_queue_backfill_scan_result_policies_project_id.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class QueueBackfillScanResultPoliciesProjectId < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+  restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+  MIGRATION = "BackfillScanResultPoliciesProjectId"
+  DELAY_INTERVAL = 2.minutes
+  BATCH_SIZE = 1000
+  SUB_BATCH_SIZE = 100
+
+  def up
+    queue_batched_background_migration(
+      MIGRATION,
+      :scan_result_policies,
+      :id,
+      :project_id,
+      :security_orchestration_policy_configurations,
+      :project_id,
+      :security_orchestration_policy_configuration_id,
+      job_interval: DELAY_INTERVAL,
+      batch_size: BATCH_SIZE,
+      sub_batch_size: SUB_BATCH_SIZE
+    )
+  end
+
+  def down
+    delete_batched_background_migration(
+      MIGRATION,
+      :scan_result_policies,
+      :id,
+      [
+        :project_id,
+        :security_orchestration_policy_configurations,
+        :project_id,
+        :security_orchestration_policy_configuration_id
+      ]
+    )
+  end
+end
diff --git a/db/post_migrate/20250301125855_index_scan_result_policies_on_namespace_id.rb b/db/post_migrate/20250301125855_index_scan_result_policies_on_namespace_id.rb
new file mode 100644
index 000000000000..716189084441
--- /dev/null
+++ b/db/post_migrate/20250301125855_index_scan_result_policies_on_namespace_id.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class IndexScanResultPoliciesOnNamespaceId < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+  disable_ddl_transaction!
+
+  INDEX_NAME = 'index_scan_result_policies_on_namespace_id'
+
+  def up
+    add_concurrent_index :scan_result_policies, :namespace_id, name: INDEX_NAME
+  end
+
+  def down
+    remove_concurrent_index_by_name :scan_result_policies, INDEX_NAME
+  end
+end
diff --git a/db/post_migrate/20250301125856_add_scan_result_policies_namespace_id_fk.rb b/db/post_migrate/20250301125856_add_scan_result_policies_namespace_id_fk.rb
new file mode 100644
index 000000000000..b984c25e9cda
--- /dev/null
+++ b/db/post_migrate/20250301125856_add_scan_result_policies_namespace_id_fk.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddScanResultPoliciesNamespaceIdFk < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+  disable_ddl_transaction!
+
+  def up
+    add_concurrent_foreign_key :scan_result_policies, :namespaces, column: :namespace_id, on_delete: :cascade
+  end
+
+  def down
+    with_lock_retries do
+      remove_foreign_key :scan_result_policies, column: :namespace_id
+    end
+  end
+end
diff --git a/db/post_migrate/20250301125857_add_scan_result_policies_namespace_id_trigger.rb b/db/post_migrate/20250301125857_add_scan_result_policies_namespace_id_trigger.rb
new file mode 100644
index 000000000000..b20ef8e0a027
--- /dev/null
+++ b/db/post_migrate/20250301125857_add_scan_result_policies_namespace_id_trigger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddScanResultPoliciesNamespaceIdTrigger < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+
+  def up
+    install_sharding_key_assignment_trigger(
+      table: :scan_result_policies,
+      sharding_key: :namespace_id,
+      parent_table: :security_orchestration_policy_configurations,
+      parent_sharding_key: :namespace_id,
+      foreign_key: :security_orchestration_policy_configuration_id
+    )
+  end
+
+  def down
+    remove_sharding_key_assignment_trigger(
+      table: :scan_result_policies,
+      sharding_key: :namespace_id,
+      parent_table: :security_orchestration_policy_configurations,
+      parent_sharding_key: :namespace_id,
+      foreign_key: :security_orchestration_policy_configuration_id
+    )
+  end
+end
diff --git a/db/post_migrate/20250301125858_queue_backfill_scan_result_policies_namespace_id.rb b/db/post_migrate/20250301125858_queue_backfill_scan_result_policies_namespace_id.rb
new file mode 100644
index 000000000000..c051d3156c4c
--- /dev/null
+++ b/db/post_migrate/20250301125858_queue_backfill_scan_result_policies_namespace_id.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class QueueBackfillScanResultPoliciesNamespaceId < Gitlab::Database::Migration[2.2]
+  milestone '17.10'
+  restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+  MIGRATION = "BackfillScanResultPoliciesNamespaceId"
+  DELAY_INTERVAL = 2.minutes
+  BATCH_SIZE = 1000
+  SUB_BATCH_SIZE = 100
+
+  def up
+    queue_batched_background_migration(
+      MIGRATION,
+      :scan_result_policies,
+      :id,
+      :namespace_id,
+      :security_orchestration_policy_configurations,
+      :namespace_id,
+      :security_orchestration_policy_configuration_id,
+      job_interval: DELAY_INTERVAL,
+      batch_size: BATCH_SIZE,
+      sub_batch_size: SUB_BATCH_SIZE
+    )
+  end
+
+  def down
+    delete_batched_background_migration(
+      MIGRATION,
+      :scan_result_policies,
+      :id,
+      [
+        :namespace_id,
+        :security_orchestration_policy_configurations,
+        :namespace_id,
+        :security_orchestration_policy_configuration_id
+      ]
+    )
+  end
+end
diff --git a/db/schema_migrations/20250301125533 b/db/schema_migrations/20250301125533
new file mode 100644
index 000000000000..0823b59cc71c
--- /dev/null
+++ b/db/schema_migrations/20250301125533
@@ -0,0 +1 @@
+56f1db3bdeea1aeef9df49e501eb7e1dac093f18f05a4e07172cb4bbd55ceecd
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125534 b/db/schema_migrations/20250301125534
new file mode 100644
index 000000000000..e06e91a80a14
--- /dev/null
+++ b/db/schema_migrations/20250301125534
@@ -0,0 +1 @@
+3226c5fca1dd5555283876ef828ba17e805a246ee2e441d4d05498edfc735e2e
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125854 b/db/schema_migrations/20250301125854
new file mode 100644
index 000000000000..104c90457277
--- /dev/null
+++ b/db/schema_migrations/20250301125854
@@ -0,0 +1 @@
+3a0f04fc4a380d6617e65f9d39dfab44de21cc5584b54c5857ea46e15250561e
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125855 b/db/schema_migrations/20250301125855
new file mode 100644
index 000000000000..25af4c3943ed
--- /dev/null
+++ b/db/schema_migrations/20250301125855
@@ -0,0 +1 @@
+35b534dc4a99b72d70f522fba4cbf4b624cd2ec01439ea13656e72f9204a48f9
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125856 b/db/schema_migrations/20250301125856
new file mode 100644
index 000000000000..1a7407e90889
--- /dev/null
+++ b/db/schema_migrations/20250301125856
@@ -0,0 +1 @@
+8352e0e9e20603bdb7e4fccac26214043c30d8e34dbe8b8b8a998a1716865adb
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125857 b/db/schema_migrations/20250301125857
new file mode 100644
index 000000000000..d2d3b5e7376f
--- /dev/null
+++ b/db/schema_migrations/20250301125857
@@ -0,0 +1 @@
+f152eb71fe1e65c715bdd5341625103e799e6dbf76f13a0c344179770157cdc9
\ No newline at end of file
diff --git a/db/schema_migrations/20250301125858 b/db/schema_migrations/20250301125858
new file mode 100644
index 000000000000..590eb1aa3234
--- /dev/null
+++ b/db/schema_migrations/20250301125858
@@ -0,0 +1 @@
+1f93c1cf8cc282f62a840707a186c9b78eea50ab89511a5c838b5c4226b86a00
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 1d9cfad8d85e..c4846cd79c10 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1990,6 +1990,22 @@ RETURN NEW;
 END
 $$;
 
+CREATE FUNCTION trigger_4c320a13bc8d() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+IF NEW."project_id" IS NULL THEN
+  SELECT "project_id"
+  INTO NEW."project_id"
+  FROM "security_orchestration_policy_configurations"
+  WHERE "security_orchestration_policy_configurations"."id" = NEW."security_orchestration_policy_configuration_id";
+END IF;
+
+RETURN NEW;
+
+END
+$$;
+
 CREATE FUNCTION trigger_4cc5c3ac4d7f() RETURNS trigger
     LANGUAGE plpgsql
     AS $$
@@ -3171,6 +3187,22 @@ RETURN NEW;
 END
 $$;
 
+CREATE FUNCTION trigger_b83b7e51e2f5() RETURNS trigger
+    LANGUAGE plpgsql
+    AS $$
+BEGIN
+IF NEW."namespace_id" IS NULL THEN
+  SELECT "namespace_id"
+  INTO NEW."namespace_id"
+  FROM "security_orchestration_policy_configurations"
+  WHERE "security_orchestration_policy_configurations"."id" = NEW."security_orchestration_policy_configuration_id";
+END IF;
+
+RETURN NEW;
+
+END
+$$;
+
 CREATE FUNCTION trigger_b8eecea7f351() RETURNS trigger
     LANGUAGE plpgsql
     AS $$
@@ -21177,6 +21209,7 @@ CREATE TABLE scan_result_policies (
     action_idx smallint DEFAULT 0 NOT NULL,
     custom_roles bigint[] DEFAULT '{}'::bigint[] NOT NULL,
     licenses jsonb DEFAULT '{}'::jsonb NOT NULL,
+    namespace_id bigint,
     CONSTRAINT age_value_null_or_positive CHECK (((age_value IS NULL) OR (age_value >= 0))),
     CONSTRAINT check_scan_result_policies_rule_idx_positive CHECK (((rule_idx IS NULL) OR (rule_idx >= 0))),
     CONSTRAINT custom_roles_array_check CHECK ((array_position(custom_roles, NULL::bigint) IS NULL))
@@ -35045,6 +35078,8 @@ CREATE UNIQUE INDEX index_scan_execution_policy_rules_on_unique_policy_rule_inde
 
 CREATE UNIQUE INDEX index_scan_result_policies_on_configuration_action_and_rule_idx ON scan_result_policies USING btree (security_orchestration_policy_configuration_id, project_id, orchestration_policy_idx, rule_idx, action_idx);
 
+CREATE INDEX index_scan_result_policies_on_namespace_id ON scan_result_policies USING btree (namespace_id);
+
 CREATE INDEX index_scan_result_policies_on_project_id ON scan_result_policies USING btree (project_id);
 
 CREATE INDEX index_scan_result_policy_violations_on_approval_policy_rule_id ON scan_result_policy_violations USING btree (approval_policy_rule_id);
@@ -38485,6 +38520,8 @@ CREATE TRIGGER trigger_4ad9a52a6614 BEFORE INSERT OR UPDATE ON sbom_occurrences_
 
 CREATE TRIGGER trigger_4b43790d717f BEFORE INSERT OR UPDATE ON protected_environment_approval_rules FOR EACH ROW EXECUTE FUNCTION trigger_4b43790d717f();
 
+CREATE TRIGGER trigger_4c320a13bc8d BEFORE INSERT OR UPDATE ON scan_result_policies FOR EACH ROW EXECUTE FUNCTION trigger_4c320a13bc8d();
+
 CREATE TRIGGER trigger_4cc5c3ac4d7f BEFORE INSERT OR UPDATE ON bulk_import_export_uploads FOR EACH ROW EXECUTE FUNCTION trigger_4cc5c3ac4d7f();
 
 CREATE TRIGGER trigger_4dc8ec48e038 BEFORE INSERT OR UPDATE ON requirements_management_test_reports FOR EACH ROW EXECUTE FUNCTION trigger_4dc8ec48e038();
@@ -38637,6 +38674,8 @@ CREATE TRIGGER trigger_b75e5731e305 BEFORE INSERT OR UPDATE ON dast_profiles_pip
 
 CREATE TRIGGER trigger_b7abb8fc4cf0 BEFORE INSERT OR UPDATE ON work_item_progresses FOR EACH ROW EXECUTE FUNCTION trigger_b7abb8fc4cf0();
 
+CREATE TRIGGER trigger_b83b7e51e2f5 BEFORE INSERT OR UPDATE ON scan_result_policies FOR EACH ROW EXECUTE FUNCTION trigger_b83b7e51e2f5();
+
 CREATE TRIGGER trigger_b8eecea7f351 BEFORE INSERT OR UPDATE ON dependency_proxy_manifest_states FOR EACH ROW EXECUTE FUNCTION trigger_b8eecea7f351();
 
 CREATE TRIGGER trigger_b9839c6d713f BEFORE INSERT ON application_settings FOR EACH ROW EXECUTE FUNCTION function_for_trigger_b9839c6d713f();
@@ -39810,6 +39849,9 @@ ALTER TABLE ONLY push_rules
 ALTER TABLE ONLY merge_request_diffs
     ADD CONSTRAINT fk_8483f3258f FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
 
+ALTER TABLE ONLY scan_result_policies
+    ADD CONSTRAINT fk_84d4bc9abe FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+
 ALTER TABLE ONLY requirements
     ADD CONSTRAINT fk_85044baef0 FOREIGN KEY (issue_id) REFERENCES issues(id) ON DELETE CASCADE;
 
diff --git a/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id.rb b/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id.rb
new file mode 100644
index 000000000000..112749351499
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module BackgroundMigration
+    class BackfillScanResultPoliciesNamespaceId < BackfillDesiredShardingKeyJob
+      operation_name :backfill_scan_result_policies_namespace_id
+      feature_category :security_policy_management
+    end
+  end
+end
diff --git a/lib/gitlab/background_migration/backfill_scan_result_policies_project_id.rb b/lib/gitlab/background_migration/backfill_scan_result_policies_project_id.rb
new file mode 100644
index 000000000000..ead2aed98c12
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_scan_result_policies_project_id.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module BackgroundMigration
+    class BackfillScanResultPoliciesProjectId < BackfillDesiredShardingKeyJob
+      operation_name :backfill_scan_result_policies_project_id
+      feature_category :security_policy_management
+    end
+  end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id_spec.rb
new file mode 100644
index 000000000000..ee463b3067fe
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_scan_result_policies_namespace_id_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillScanResultPoliciesNamespaceId,
+  feature_category: :security_policy_management,
+  schema: 20250301125854 do
+  include_examples 'desired sharding key backfill job' do
+    let(:batch_table) { :scan_result_policies }
+    let(:backfill_column) { :namespace_id }
+    let(:backfill_via_table) { :security_orchestration_policy_configurations }
+    let(:backfill_via_column) { :namespace_id }
+    let(:backfill_via_foreign_key) { :security_orchestration_policy_configuration_id }
+  end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_scan_result_policies_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_scan_result_policies_project_id_spec.rb
new file mode 100644
index 000000000000..237f076c9d5c
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_scan_result_policies_project_id_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillScanResultPoliciesProjectId,
+  feature_category: :security_policy_management,
+  schema: 20250301125533 do
+  include_examples 'desired sharding key backfill job' do
+    let(:batch_table) { :scan_result_policies }
+    let(:backfill_column) { :project_id }
+    let(:backfill_via_table) { :security_orchestration_policy_configurations }
+    let(:backfill_via_column) { :project_id }
+    let(:backfill_via_foreign_key) { :security_orchestration_policy_configuration_id }
+  end
+end
diff --git a/spec/migrations/20250301125534_queue_backfill_scan_result_policies_project_id_spec.rb b/spec/migrations/20250301125534_queue_backfill_scan_result_policies_project_id_spec.rb
new file mode 100644
index 000000000000..2527e424cbe6
--- /dev/null
+++ b/spec/migrations/20250301125534_queue_backfill_scan_result_policies_project_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillScanResultPoliciesProjectId, feature_category: :security_policy_management do
+  let!(:batched_migration) { described_class::MIGRATION }
+
+  it 'schedules a new batched migration' do
+    reversible_migration do |migration|
+      migration.before -> {
+        expect(batched_migration).not_to have_scheduled_batched_migration
+      }
+
+      migration.after -> {
+        expect(batched_migration).to have_scheduled_batched_migration(
+          table_name: :scan_result_policies,
+          column_name: :id,
+          interval: described_class::DELAY_INTERVAL,
+          batch_size: described_class::BATCH_SIZE,
+          sub_batch_size: described_class::SUB_BATCH_SIZE,
+          gitlab_schema: :gitlab_main_cell,
+          job_arguments: [
+            :project_id,
+            :security_orchestration_policy_configurations,
+            :project_id,
+            :security_orchestration_policy_configuration_id
+          ]
+        )
+      }
+    end
+  end
+end
diff --git a/spec/migrations/20250301125858_queue_backfill_scan_result_policies_namespace_id_spec.rb b/spec/migrations/20250301125858_queue_backfill_scan_result_policies_namespace_id_spec.rb
new file mode 100644
index 000000000000..5fac524b8293
--- /dev/null
+++ b/spec/migrations/20250301125858_queue_backfill_scan_result_policies_namespace_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillScanResultPoliciesNamespaceId, feature_category: :security_policy_management do
+  let!(:batched_migration) { described_class::MIGRATION }
+
+  it 'schedules a new batched migration' do
+    reversible_migration do |migration|
+      migration.before -> {
+        expect(batched_migration).not_to have_scheduled_batched_migration
+      }
+
+      migration.after -> {
+        expect(batched_migration).to have_scheduled_batched_migration(
+          table_name: :scan_result_policies,
+          column_name: :id,
+          interval: described_class::DELAY_INTERVAL,
+          batch_size: described_class::BATCH_SIZE,
+          sub_batch_size: described_class::SUB_BATCH_SIZE,
+          gitlab_schema: :gitlab_main_cell,
+          job_arguments: [
+            :namespace_id,
+            :security_orchestration_policy_configurations,
+            :namespace_id,
+            :security_orchestration_policy_configuration_id
+          ]
+        )
+      }
+    end
+  end
+end
-- 
GitLab