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.

Edited by Eugie Limpin

Merge request reports

Loading
Loading