Add migration to re-sync scan result policies for namespaces
What does this MR do and why?
Addresses #404080 (closed)
We found a bug in scan result policies because of cascading delete of ScanResultPolicyRead
when we sync scan result policies for a project. The problem is, we delete all ScanResultPolicyReads associated to a security_orchestration_policy_configuration
which leads to deletion of ApprovalMergeRequestRule
and ApprovalProjectRule
of other projects within the group too.
We create ScanResultPolicyRead
either if scan_result_role_action
feature flag is enabled or the rule is of license_finding
type. After we enabled the scan_result_role_action
feature flag globally (internal link), we started creating ScanResultPolicyRead
for all both type of scan result policies. When Security::ProcessScanResultPolicyWorker
is executed for the project (with group level policy project), it deletes all ScanResultPolicyRead
.
The cascading delete was stopped in Remove cascading delete of scan_result_policy_r... (!116569 - merged) • Sashi Kumar Kumaresan • 15.11
This MR introduces a migration to trigger the sync worker so that old data that are affected would be fixed.
Query Plan
Query 1
SELECT
"security_orchestration_policy_configurations".*
FROM
"security_orchestration_policy_configurations"
WHERE
"security_orchestration_policy_configurations"."namespace_id" IS NOT NULL
ORDER BY
"security_orchestration_policy_configurations"."id" ASC LIMIT 50
Limit (cost=0.28..48.46 rows=50 width=56) (actual time=17.198..24.803 rows=50 loops=1)
Buffers: shared hit=2549 read=119 dirtied=8
I/O Timings: read=22.495 write=0.000
-> Index Scan using security_orchestration_policy_configurations_pkey on public.security_orchestration_policy_configurations (cost=0.28..360.65 rows=374 width=56) (actual time=17.196..24.791 rows=50 loops=1)
Filter: (security_orchestration_policy_configurations.namespace_id IS NOT NULL)
Rows Removed by Filter: 2756
Buffers: shared hit=2549 read=119 dirtied=8
I/O Timings: read=22.495 write=0.000
Time: 25.894 ms
- planning: 1.038 ms
- execution: 24.856 ms
- I/O read: 22.495 ms
- I/O write: 0.000 ms
Shared buffers:
- hits: 2549 (~19.90 MiB) from the buffer pool
- reads: 119 (~952.00 KiB) from the OS file cache, including disk I/O
- dirtied: 8 (~64.00 KiB)
- writes: 0
Query 2
SELECT "projects".* FROM (WITH RECURSIVE "array_cte" AS MATERIALIZED (SELECT namespaces.traversal_ids[array_length(namespaces.traversal_ids, 1)] AS id FROM "namespaces" WHERE "namespaces"."type" = 'Group' AND (traversal_ids @> ('{5669232}'))), "recursive_keyset_cte" AS ((SELECT NULL::integer AS id, array_cte_id_array, projects_id_array, 0::bigint AS count FROM (SELECT ARRAY_AGG("array_cte".id) AS array_cte_id_array, ARRAY_AGG("projects"."id") AS projects_id_array FROM (SELECT "array_cte".id FROM array_cte) array_cte LEFT JOIN LATERAL (SELECT "projects"."id" AS id FROM "projects" WHERE "projects"."namespace_id" = "array_cte".id ORDER BY "projects"."id" ASC LIMIT 1) projects ON TRUE WHERE "projects"."id" IS NOT NULL) array_scope_lateral_query LIMIT 1)
UNION ALL
(SELECT recursive_keyset_cte.projects_id_array[position], array_cte_id_array, recursive_keyset_cte.projects_id_array[:position_query.position-1]||next_cursor_values.id||recursive_keyset_cte.projects_id_array[position_query.position+1:], recursive_keyset_cte.count + 1 FROM recursive_keyset_cte,
LATERAL (SELECT id, position FROM UNNEST(projects_id_array) WITH ORDINALITY AS u(id, position) WHERE id IS NOT NULL ORDER BY 1 ASC LIMIT 1) AS position_query,
LATERAL (SELECT "record"."id" FROM (VALUES (NULL)) AS nulls LEFT JOIN (SELECT "projects"."id" AS id FROM "projects" WHERE "projects"."namespace_id" = recursive_keyset_cte.array_cte_id_array[position] AND ("projects"."id" > recursive_keyset_cte.projects_id_array[position]) ORDER BY "projects"."id" ASC LIMIT 1) record ON TRUE LIMIT 1) AS next_cursor_values
)) SELECT id FROM "recursive_keyset_cte" AS "projects" WHERE (count <> 0)) projects LIMIT 50
Toggle Full Plan
Limit (cost=645.59..647.22 rows=50 width=4) (actual time=161.534..168.547 rows=50 loops=1)
Buffers: shared hit=979 read=619 dirtied=23
I/O Timings: read=148.431 write=0.000
-> CTE Scan on recursive_keyset_cte projects (cost=645.59..647.86 rows=100 width=4) (actual time=161.533..168.536 rows=50 loops=1)
Filter: (projects.count <> 0)
Rows Removed by Filter: 1
Buffers: shared hit=979 read=619 dirtied=23
I/O Timings: read=148.431 write=0.000
CTE array_cte
-> Bitmap Heap Scan on public.namespaces (cost=10.72..382.62 rows=239 width=4) (actual time=3.880..26.815 rows=281 loops=1)
Buffers: shared hit=1 read=271 dirtied=4
I/O Timings: read=22.727 write=0.000
-> Bitmap Index Scan using index_namespaces_on_traversal_ids_for_groups (cost=0.00..10.66 rows=239 width=0) (actual time=0.447..0.448 rows=281 loops=1)
Index Cond: (namespaces.traversal_ids @> '{5669232}'::integer[])
Buffers: shared hit=1 read=4
I/O Timings: read=0.291 write=0.000
CTE recursive_keyset_cte
-> Recursive Union (cost=150.68..262.97 rows=101 width=76) (actual time=161.256..168.323 rows=51 loops=1)
Buffers: shared hit=979 read=619 dirtied=23
I/O Timings: read=148.431 write=0.000
-> Limit (cost=150.68..150.70 rows=1 width=76) (actual time=161.240..161.244 rows=1 loops=1)
Buffers: shared hit=785 read=609 dirtied=21
I/O Timings: read=147.558 write=0.000
-> Subquery Scan on array_scope_lateral_query (cost=150.68..150.70 rows=1 width=76) (actual time=161.240..161.243 rows=1 loops=1)
Buffers: shared hit=785 read=609 dirtied=21
I/O Timings: read=147.558 write=0.000
-> Aggregate (cost=150.68..150.69 rows=1 width=64) (actual time=161.238..161.241 rows=1 loops=1)
Buffers: shared hit=785 read=609 dirtied=21
I/O Timings: read=147.558 write=0.000
-> Nested Loop (cost=0.44..149.48 rows=239 width=8) (actual time=9.049..160.955 rows=246 loops=1)
Buffers: shared hit=785 read=609 dirtied=21
I/O Timings: read=147.558 write=0.000
-> CTE Scan on array_cte (cost=0.00..4.78 rows=239 width=4) (actual time=3.888..27.353 rows=281 loops=1)
Buffers: shared hit=1 read=271 dirtied=4
I/O Timings: read=22.727 write=0.000
-> Subquery Scan on projects_1 (cost=0.44..0.60 rows=1 width=4) (actual time=0.474..0.474 rows=1 loops=281)
Filter: (projects_1.id IS NOT NULL)
Rows Removed by Filter: 0
Buffers: shared hit=784 read=338 dirtied=17
I/O Timings: read=124.831 write=0.000
-> Limit (cost=0.44..0.59 rows=1 width=4) (actual time=0.473..0.473 rows=1 loops=281)
Buffers: shared hit=784 read=338 dirtied=17
I/O Timings: read=124.831 write=0.000
-> Index Only Scan using index_projects_on_namespace_id_and_id on public.projects projects_2 (cost=0.44..3.84 rows=23 width=4) (actual time=0.472..0.472 rows=1 loops=281)
Index Cond: (projects_2.namespace_id = array_cte.id)
Heap Fetches: 59
Buffers: shared hit=784 read=338 dirtied=17
I/O Timings: read=124.831 write=0.000
-> Nested Loop (cost=0.59..11.03 rows=10 width=76) (actual time=0.139..0.140 rows=1 loops=50)
Buffers: shared hit=194 read=10 dirtied=2
I/O Timings: read=0.874 write=0.000
-> Nested Loop (cost=0.15..1.95 rows=10 width=80) (actual time=0.101..0.102 rows=1 loops=50)
Buffers: shared hit=3
I/O Timings: read=0.000 write=0.000
-> WorkTable Scan on recursive_keyset_cte (cost=0.00..0.20 rows=10 width=72) (actual time=0.000..0.000 rows=1 loops=50)
I/O Timings: read=0.000 write=0.000
-> Limit (cost=0.15..0.16 rows=1 width=12) (actual time=0.100..0.100 rows=1 loops=50)
Buffers: shared hit=3
I/O Timings: read=0.000 write=0.000
-> Sort (cost=0.15..0.18 rows=10 width=12) (actual time=0.100..0.100 rows=1 loops=50)
Sort Key: u.id
Sort Method: top-N heapsort Memory: 25kB
Buffers: shared hit=3
I/O Timings: read=0.000 write=0.000
-> Function Scan on unnest u (cost=0.00..0.10 rows=10 width=12) (actual time=0.022..0.053 rows=246 loops=50)
Filter: (u.id IS NOT NULL)
Rows Removed by Filter: 0
I/O Timings: read=0.000 write=0.000
-> Limit (cost=0.44..0.87 rows=1 width=4) (actual time=0.026..0.026 rows=1 loops=50)
Buffers: shared hit=191 read=10 dirtied=2
I/O Timings: read=0.874 write=0.000
-> Nested Loop Left Join (cost=0.44..0.87 rows=1 width=4) (actual time=0.025..0.025 rows=1 loops=50)
Buffers: shared hit=191 read=10 dirtied=2
I/O Timings: read=0.874 write=0.000
-> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.000 rows=1 loops=50)
I/O Timings: read=0.000 write=0.000
-> Limit (cost=0.44..0.84 rows=1 width=4) (actual time=0.025..0.025 rows=1 loops=50)
Buffers: shared hit=191 read=10 dirtied=2
I/O Timings: read=0.874 write=0.000
-> Index Only Scan using index_projects_on_namespace_id_and_id on public.projects projects_3 (cost=0.44..3.60 rows=8 width=4) (actual time=0.024..0.024 rows=1 loops=50)
Index Cond: ((projects_3.namespace_id = (recursive_keyset_cte.array_cte_id_array)[u."position"]) AND (projects_3.id > (recursive_keyset_cte.projects_id_array)[u."position"]))
Heap Fetches: 10
Buffers: shared hit=191 read=10 dirtied=2
I/O Timings: read=0.874 write=0.000
Time: 177.008 ms
- planning: 8.097 ms
- execution: 168.911 ms
- I/O read: 148.431 ms
- I/O write: 0.000 ms
Shared buffers:
- hits: 979 (~7.60 MiB) from the buffer pool
- reads: 619 (~4.80 MiB) from the OS file cache, including disk I/O
- dirtied: 23 (~184.00 KiB)
- writes: 0
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.