Avoid group N+1 in approval rule contains_hidden_groups?
What does this MR do and why?
When listing eligible approvers for a merge request, GitLab checks whether each approval rule contains hidden groups. This check issued database queries for every rule — even for rules that have no groups at all, which is the case for code-owner rules. On merge requests with many rules this becomes an N+1.
This MR short-circuits the check for rules without groups and computes the project's invited groups once per merge request instead of once per rule, removing the redundant queries.
Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/602716
Benchmark results
Measured on a local GDK against a synthetic project (spike-593394/codeowners-bench)
with 100 / 400 / 800 code-owner rules. For each rule, contains_hidden_groups?
(the call eligibleApprovers triggers) was invoked and the group-related queries
counted, under a request store (mirrors Puma). "Without" is clean master; "With"
is this MR.
Group queries via contains_hidden_groups?
| Scenario | Rules | Without | With |
|---|---|---|---|
| small | 100 | 200 | 0 |
| medium | 400 | 800 | 0 |
| large | 800 | 1600 | 0 |
Without the fix, group queries scale at ~2 per rule (a Group Load +
Group Exists? pair) — ~1600 on the 800-rule MR. With the fix it is a flat 0:
rules with no groups short-circuit, and the project's invited groups are computed
once per merge request.
Security / authorization considerations
This change touches hidden-group visibility. The short-circuit is sound — a
rule with no groups cannot contain hidden groups. The request cache stores only
invited group ids and is keyed by project alone because Project#invited_groups
is not user-scoped. The feature flag and read_project authorization checks stay
outside the cache and are evaluated for the current user before cached ids are
used, so ids cached by an authorized user cannot leak to an unauthorized user.
This is covered by the applies the authorization gate separately for each user
spec. Visibility behavior for rules that do have (hidden) groups is unchanged
and remains covered by the existing finder specs.
How to set up and validate locally
- On a project with
code_owner_approval_required = trueand a large CODEOWNERS file, open a merge request with many code-owner approval rules. - Query
approvalState { rules { eligibleApprovers { id } } }for that MR and observe the group-related SQL query count. - Run the finder spec:
bundle exec rspec ee/spec/finders/approval_rules/group_finder_spec.rb(12 examples, 0 failures — includes guards that the no-group short-circuit issues no group queries and does not scale queries with rule count).
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.