perf(authz): speed up RedactionService batch authorization
What does this MR do and why?
Authz::RedactionService runs one Ability.allowed? per node the Knowledge Graph (Orbit) redaction exchange returns, so a single graph query authorizes hundreds of resources in one batch. This MR makes that batch cheaper without changing any authorization result. Every change is confined to the redaction layer; nothing outside it changes behavior.
Three redaction-scoped optimizations:
-
Run the batch inside a request store.
#executewraps inGitlab::SafeRequestStore.ensure_request_store, so theApplicationSettingread and the policy-preloader caches are memoized across the per-resource loop. It is a no-op on the Grape request path (a store is already active) and only helps callers without one (the gRPC stream redaction callback, Sidekiq, rake, specs), where each resource otherwise re-deserializes the wholeApplicationSettingthrough Marshal. -
Gated memo for the gem's per-call re-merges.
DeclarativePolicy::Baserebuilds four static-per-class maps (conditions, ability_map, global_actions, delegations) viasuperclass.X.merge(own_X)on every lookup. A new initializer memoizes them only while a redaction batch is active, keyed off aSafeRequestStoreflag thatRedactionServicesets. Every other policy caller evaluates exactly as before; the only global footprint is one presence check. -
Batch the root-ancestor SSO preload (EE). EE SSO and IP enforcement resolve
root_ancestor.saml_provideronce per resource.EE::Authz::RedactionServicepreseeds it for the projects' group namespaces before the policy preloaders run, restricted to Group namespaces (personal namespaces have nosaml_provider). This collapses the largest per-resource query family: project redaction drops from 157 to 48 SQL queries, ci_pipeline from 137 to 24.
Performance
Round-trip count per batch (live data, authorization byte-identical). Fix 3 collapses the largest per-resource query family:
| Entity type | SQL queries before | SQL queries after | Reduction |
|---|---|---|---|
| project | 157 | 48 | -69% |
| ci_pipeline | 137 | 24 | -82% |
Warm-path wall-clock per resource (request store active), from fixes 1 and 2:
| Entity type | Speedup | Entity type | Speedup |
|---|---|---|---|
| milestone | ~5.3x | project | ~2.1-2.5x |
| merge_request | ~3.2x | ci_pipeline | ~2.5x |
| ci_runner | ~3.1x | ci_build | ~2.3x |
Other types are already under 1 ms/resource. In the non-request (cold) regime the request-store wrap alone gives 2x or more for every type. issue/work_item/note improve but stay ~1.1-1.5x warm, bounded by deep stacked-policy delegation, which is outside this MR's redaction-only scope.
Production profiling against the gitlab.com main database on postgres.ai Database Lab confirmed every redaction query is an index lookup at production scale: 0 sequential scans on real tables, max plan cost 178.65, median 5.74. So redaction is not plan-bound; the cost is the per-batch round-trip count, which fix 3 reduces. Exact queries and plans are in the validation note below.
How to set up and validate locally
bundle exec rspec spec/services/authz/redaction_service_spec.rb \
spec/initializers/authz/redaction_policy_conditions_cache_spec.rb \
ee/spec/services/ee/authz/redaction_service_spec.rbRedaction output is byte-identical to a direct Ability.allowed? ground truth, verified per entity type. The new specs cover the request-store wrap, the gated condition-map memo (memoization plus pass-through when no batch is active), and the EE preseed (group namespaces preloaded, personal namespaces skipped without raising).
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.