Draft: Use batched pre-drain in Groups::DestroyService
What this does
Wires Gitlab::Database::BatchedPreDrain into Groups::DestroyService so a group's direct CASCADE foreign-key children are deleted in small, independently committed batches before group.destroy.
Most heavy data lives under projects, which are destroyed first. But direct group children (settings, members, labels, milestones, boards, epics) can still cause a long final transaction for large groups. The pre-drain removes them ahead of time so the final DELETE cascades little or nothing.
This is the group counterpart to the project integration (#619117).
How it works
batched_pre_drain!runs inunsafe_execute, after child projects and subgroups are destroyed and right beforegroup.destroy, matching the existing destroy ordering. It runs outside a transaction so each batch commits on its own.- On
:over_budgetit raisesBatchedPreDrainOverBudget, which is caught before the generic rescue: the group stays marked for deletion andGroupDestroyWorkeris re-enqueued to resume. Gitlab::QueryLimitingis already disabled inGroupDestroyWorker.
Strategy hooks
Group avatars and other group files live in the polymorphic uploads table, which is not a CASCADE FK child of namespaces, so the pre-drain never touches them. The direct CASCADE FK children of a group are plain relational rows that need no external cleanup, so no strategy hooks are registered. (The full side-effect audit for project children is handled separately in #619136.)
Feature flag
Guarded by the default-off batched_pre_drain_on_group_destroy flag (actor: the group).
- Rollout issue: #631109
Stacked MR
Targets 619116-batched-pre-drain-helper (the helper MR !251283 (closed)). Rebase onto master once the helper merges.
Data deletion and recoverability
The pre-drain only deletes rows PostgreSQL would delete anyway when the group DELETE cascades — it moves that deletion earlier and splits it into small committed batches, so the end state is identical to a normal cascading delete. Deleted rows are not independently recoverable; recovery follows the same path as any other row loss (point-in-time restore from database backups). The behaviour is behind a default-off flag.
Testing
New specs in spec/services/groups/destroy_service_spec.rb:
- pre-drain runs before
destroywhen the flag is enabled - worker re-enqueues and the group is left intact on
:over_budget - pre-drain does not run when the flag is disabled
Related #619118