Add background job to bulk enable SPP for all projects in a group
What does this MR do and why?
Adds a new idempotent background job that asynchronously iterates over nested projects in a group and sets its project-level setting to enabled or disable SPP.
This MR is related to Option 2 from #502826 (comment 2217680840)
Related issue:
Add background job to enable SPP for all projec... (#502826 - closed) • Gal Katz • 17.7
Enable SPP for all projects in a group via a ne... (&15840 - closed) • Gal Katz
Added scopes:
Project - without_security_setting
For all projects
SQL
Generated using Project.without_security_setting.to_sql
SELECT
"projects"."id",
"projects"."name",
"projects"."path",
"projects"."description",
"projects"."created_at",
"projects"."updated_at",
"projects"."creator_id",
"projects"."namespace_id",
"projects"."last_activity_at",
"projects"."import_url",
"projects"."visibility_level",
"projects"."archived",
"projects"."avatar",
"projects"."merge_requests_template",
"projects"."star_count",
"projects"."merge_requests_rebase_enabled",
"projects"."import_type",
"projects"."import_source",
"projects"."approvals_before_merge",
"projects"."reset_approvals_on_push",
"projects"."merge_requests_ff_only_enabled",
"projects"."issues_template",
"projects"."mirror",
"projects"."mirror_last_update_at",
"projects"."mirror_last_successful_update_at",
"projects"."mirror_user_id",
"projects"."shared_runners_enabled",
"projects"."runners_token",
"projects"."build_allow_git_fetch",
"projects"."build_timeout",
"projects"."mirror_trigger_builds",
"projects"."pending_delete",
"projects"."public_builds",
"projects"."last_repository_check_failed",
"projects"."last_repository_check_at",
"projects"."only_allow_merge_if_pipeline_succeeds",
"projects"."has_external_issue_tracker",
"projects"."repository_storage",
"projects"."repository_read_only",
"projects"."request_access_enabled",
"projects"."has_external_wiki",
"projects"."ci_config_path",
"projects"."lfs_enabled",
"projects"."description_html",
"projects"."only_allow_merge_if_all_discussions_are_resolved",
"projects"."repository_size_limit",
"projects"."printing_merge_request_link_enabled",
"projects"."auto_cancel_pending_pipelines",
"projects"."service_desk_enabled",
"projects"."cached_markdown_version",
"projects"."delete_error",
"projects"."last_repository_updated_at",
"projects"."disable_overriding_approvers_per_merge_request",
"projects"."storage_version",
"projects"."resolve_outdated_diff_discussions",
"projects"."remote_mirror_available_overridden",
"projects"."only_mirror_protected_branches",
"projects"."pull_mirror_available_overridden",
"projects"."jobs_cache_index",
"projects"."external_authorization_classification_label",
"projects"."mirror_overwrites_diverged_branches",
"projects"."pages_https_only",
"projects"."external_webhook_token",
"projects"."packages_enabled",
"projects"."merge_requests_author_approval",
"projects"."pool_repository_id",
"projects"."runners_token_encrypted",
"projects"."bfg_object_map",
"projects"."detected_repository_languages",
"projects"."merge_requests_disable_committers_approval",
"projects"."require_password_to_approve",
"projects"."max_pages_size",
"projects"."max_artifacts_size",
"projects"."pull_mirror_branch_prefix",
"projects"."remove_source_branch_after_merge",
"projects"."marked_for_deletion_at",
"projects"."marked_for_deletion_by_user_id",
"projects"."autoclose_referenced_issues",
"projects"."suggestion_commit_message",
"projects"."project_namespace_id",
"projects"."hidden",
"projects"."organization_id"
FROM
"projects"
LEFT OUTER JOIN "project_security_settings" ON "project_security_settings"."project_id" = "projects"."id"
WHERE
"project_security_settings"."project_id" IS NULL
Query Plan
Gather (cost=1001.13..5182625.02 rows=1 width=826) (actual time=185506.599..631264.683 rows=1962 loops=1)
Workers Planned: 2
Workers Launched: 2
Buffers: shared hit=9295621 read=33647651 dirtied=29646 written=1044
WAL: records=31247 fpi=29596 bytes=213840661
I/O Timings: read=1369032.853 write=21.661
-> Merge Anti Join (cost=1.13..5181624.92 rows=1 width=826) (actual time=185499.256..506818.520 rows=654 loops=3)
Merge Cond: (projects.id = project_security_settings.project_id)
Buffers: shared hit=9295621 read=33647651 dirtied=29646 written=1044
WAL: records=31247 fpi=29596 bytes=213840661
I/O Timings: read=1369032.853 write=21.661
-> Parallel Index Scan using projects_pkey on public.projects (cost=0.56..4074426.49 rows=16059313 width=826) (actual time=1.230..495785.602 rows=12946105 loops=3)
Buffers: shared hit=5414255 read=33451971 dirtied=17578 written=1028
WAL: records=18234 fpi=17476 bytes=122335393
I/O Timings: read=1362198.714 write=21.304
-> Index Only Scan using project_security_settings_pkey on public.project_security_settings (cost=0.56..809372.04 rows=38833988 width=8) (actual time=0.041..7285.652 rows=27202320 loops=3)
Heap Fetches: 3758123
Buffers: shared hit=3881366 read=195680 dirtied=12068 written=16
WAL: records=13013 fpi=12120 bytes=91505268
I/O Timings: read=6834.139 write=0.357
Settings: seq_page_cost = '4', effective_cache_size = '472585MB', jit = 'off', work_mem = '100MB', random_page_cost = '1.5'
For a specific project
SQL
Generated using Project.without_security_setting.where(id: 13083).to_sql (id from here)
SELECT
"projects"."id",
"projects"."name",
"projects"."path",
"projects"."description",
"projects"."created_at",
"projects"."updated_at",
"projects"."creator_id",
"projects"."namespace_id",
"projects"."last_activity_at",
"projects"."import_url",
"projects"."visibility_level",
"projects"."archived",
"projects"."avatar",
"projects"."merge_requests_template",
"projects"."star_count",
"projects"."merge_requests_rebase_enabled",
"projects"."import_type",
"projects"."import_source",
"projects"."approvals_before_merge",
"projects"."reset_approvals_on_push",
"projects"."merge_requests_ff_only_enabled",
"projects"."issues_template",
"projects"."mirror",
"projects"."mirror_last_update_at",
"projects"."mirror_last_successful_update_at",
"projects"."mirror_user_id",
"projects"."shared_runners_enabled",
"projects"."runners_token",
"projects"."build_allow_git_fetch",
"projects"."build_timeout",
"projects"."mirror_trigger_builds",
"projects"."pending_delete",
"projects"."public_builds",
"projects"."last_repository_check_failed",
"projects"."last_repository_check_at",
"projects"."only_allow_merge_if_pipeline_succeeds",
"projects"."has_external_issue_tracker",
"projects"."repository_storage",
"projects"."repository_read_only",
"projects"."request_access_enabled",
"projects"."has_external_wiki",
"projects"."ci_config_path",
"projects"."lfs_enabled",
"projects"."description_html",
"projects"."only_allow_merge_if_all_discussions_are_resolved",
"projects"."repository_size_limit",
"projects"."printing_merge_request_link_enabled",
"projects"."auto_cancel_pending_pipelines",
"projects"."service_desk_enabled",
"projects"."cached_markdown_version",
"projects"."delete_error",
"projects"."last_repository_updated_at",
"projects"."disable_overriding_approvers_per_merge_request",
"projects"."storage_version",
"projects"."resolve_outdated_diff_discussions",
"projects"."remote_mirror_available_overridden",
"projects"."only_mirror_protected_branches",
"projects"."pull_mirror_available_overridden",
"projects"."jobs_cache_index",
"projects"."external_authorization_classification_label",
"projects"."mirror_overwrites_diverged_branches",
"projects"."pages_https_only",
"projects"."external_webhook_token",
"projects"."packages_enabled",
"projects"."merge_requests_author_approval",
"projects"."pool_repository_id",
"projects"."runners_token_encrypted",
"projects"."bfg_object_map",
"projects"."detected_repository_languages",
"projects"."merge_requests_disable_committers_approval",
"projects"."require_password_to_approve",
"projects"."max_pages_size",
"projects"."max_artifacts_size",
"projects"."pull_mirror_branch_prefix",
"projects"."remove_source_branch_after_merge",
"projects"."marked_for_deletion_at",
"projects"."marked_for_deletion_by_user_id",
"projects"."autoclose_referenced_issues",
"projects"."suggestion_commit_message",
"projects"."project_namespace_id",
"projects"."hidden",
"projects"."organization_id"
FROM
"projects"
LEFT OUTER JOIN "project_security_settings" ON "project_security_settings"."project_id" = "projects"."id"
WHERE
"project_security_settings"."project_id" IS NULL
AND "projects"."id" = 13083
Query Plan
Nested Loop Anti Join (cost=1.13..7.17 rows=1 width=826) (actual time=4.587..4.588 rows=0 loops=1)
Buffers: shared hit=10 read=6 dirtied=1
WAL: records=1 fpi=1 bytes=409
I/O Timings: read=4.468 write=0.000
-> Index Scan using idx_projects_on_repository_storage_last_repository_updated_at on public.projects (cost=0.56..3.58 rows=1 width=826) (actual time=4.523..4.527 rows=1 loops=1)
Index Cond: (projects.id = 13083)
Buffers: shared hit=2 read=6 dirtied=1
WAL: records=1 fpi=1 bytes=409
I/O Timings: read=4.468 write=0.000
-> Index Only Scan using project_security_settings_pkey on public.project_security_settings (cost=0.56..3.58 rows=1 width=8) (actual time=0.057..0.057 rows=1 loops=1)
Index Cond: (project_security_settings.project_id = 13083)
Heap Fetches: 0
Buffers: shared hit=8
I/O Timings: read=0.000 write=0.000
Settings: jit = 'off', work_mem = '100MB', random_page_cost = '1.5', seq_page_cost = '4', effective_cache_size = '472585MB'
ProjectSecuritySetting - for_projects
SQL
Generated using ProjectSecuritySetting.for_projects([13083,278964]).to_sql (ids from here)
SELECT
"project_security_settings".*
FROM
"project_security_settings"
WHERE
"project_security_settings"."project_id" IN (13083, 278964)
Query Plan
Index Scan using project_security_settings_pkey on public.project_security_settings (cost=0.56..6.15 rows=2 width=31) (actual time=1.095..1.977 rows=2 loops=1)
Index Cond: (project_security_settings.project_id = ANY ('{13083,278964}'::bigint[]))
Buffers: shared hit=5 read=8
I/O Timings: read=1.886 write=0.000
Settings: work_mem = '100MB', random_page_cost = '1.5', seq_page_cost = '4', effective_cache_size = '472585MB', jit = 'off'
Edited by Gal Katz