Batch authorized projects refresh in PruneDeletionsWorker
What does this MR do and why?
Current behavior
Members::PruneDeletionsWorker removes a user from a root namespace. Each job handles up to 10 Members::DeletionSchedule rows. For each row it destroys up to 100 of that user's memberships in the namespace hierarchy through Members::DestroyService. It re-enqueues itself while schedules remain, so a large removal can span many jobs.
Every destroyed membership fires the Member model's after_commit refresh callback. A destroyed GroupMember enqueues AuthorizedProjectsWorker, a full recalculation of the user's project_authorizations. This is deduplicated with until_executed and reschedule_once, so consecutive enqueues collapse, but several can still run per batch. A destroyed ProjectMember enqueues AuthorizedProjectUpdate::ProjectRecalculatePerUserWorker for that one project, one job per project with no deduplication, plus a low priority safety-net job, AuthorizedProjectUpdate::UserRefreshFromReplicaWorker, delayed by one hour and deduplicated to one per user.
See https://gitlab.com/gitlab-org/gitlab/-/work_items/627784#note_3802672567 for an example of this in GitLab.com.
Change and why it is safe
Behind the feature flag member_prune_deletion_per_batch_authorized_projects_refresh (actor: root namespace), the worker sets the existing transient attribute member.skip_authorized_projects_refresh = true on each member while destroying a batch, so the per-member refresh callback is skipped. After the batch, the worker calls UserProjectAccessChangedService.new(user.id).execute once, a single high priority full recalculation for that user.
Behind the feature flag member_prune_deletion_per_batch_authorized_projects_refresh (actor: root namespace), the worker passes skip_authorized_projects_refresh: true to Members::DestroyService. The service sets the transient attribute skip_authorized_projects_refresh on the member right before destroying it, so the per-member refresh callback is skipped. After the batch, the worker calls UserProjectAccessChangedService.new(user.id).execute(priority: UserProjectAccessChangedService::MEDIUM_PRIORITY) once, which schedules a single full recalculation through AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker one minute later. This means the user's project_authorizations rows for the destroyed memberships stay in place for roughly one to three minutes after a batch, instead of seconds.
References
https://gitlab.com/gitlab-org/gitlab/-/work_items/627784+
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.