Implement Async Worker for Security Scan Profile Bulk Operations

What does this MR do and why?

Implements Security::ScanProfiles::AttachWorker to handle asynchronous bulk attachment of security scan profiles to projects when groups are selected in the attachSecurityScanProfile GraphQL mutation.

This worker is part of the Security Scan Profiles feature that allows administrators to attach security scanning configurations to multiple projects at once. When a group is selected for profile attachment, all projects under that group and its subgroups need to have the profile attached asynchronously to avoid blocking the mutation response.

Changelog: added
EE: true

References

Implement Async Worker for Security Scan Profil... (#582925)

How to set up and validate locally

  1. Find a group with projects:

    group = Group.find(<id>)
  2. Create a security scan profile:

    profile = Security::ScanProfile.create!(
      namespace: group,
      scan_type: :secret_detection,
      name: 'Test Profile',
      gitlab_recommended: true
    )
  3. Enqueue the worker:

    Security::ScanProfiles::AttachWorker.perform_async(group.id, profile.id)
  4. Verify attachments were created:

    Security::ScanProfileProject.where(security_scan_profile_id: profile.id).count
    # Should equal the number of projects in the group hierarchy
    `
    

Query plans

Security::ScanProfileProject.insert_all
Query
INSERT INTO "security_scan_profiles_projects" ("project_id",
                                               "security_scan_profile_id",
                                               "created_at",
                                               "updated_at")
VALUES (76863845, 1, '2025-12-04 12:57:10.290389', '2025-12-04 12:57:10.290389'),
       (74581969, 1, '2025-12-04 12:57:10.290389', '2025-12-04 12:57:10.290389') 
ON CONFLICT ("project_id","security_scan_profile_id") DO NOTHING 
RETURNING "id";
Plan
 ModifyTable on public.security_scan_profiles_projects  (cost=0.00..0.03 rows=2 width=40) (actual time=0.211..0.229 rows=1 loops=1)
   Buffers: shared hit=37
   WAL: records=5 fpi=0 bytes=343
   ->  Values Scan on "*VALUES*"  (cost=0.00..0.03 rows=2 width=40) (actual time=0.068..0.071 rows=2 loops=1)
         Buffers: shared hit=18
Trigger RI_ConstraintTrigger_c_4226359321 for constraint fk_rails_36ece30d24: time=0.610 calls=1
Settings: effective_cache_size = '338688MB', random_page_cost = '1.5', work_mem = '100MB', jit = 'off', seq_page_cost = '4'

Link to postgres.ai

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 Nicolae Rotaru

Merge request reports

Loading