Skip per-ref Redis cache in protected branch push check

What does this MR do and why?

During git push access checks, Gitlab::Checks::BranchCheck calls ProtectedBranch.protected? once per ref, which goes through the per-ref Redis-backed ProtectedBranches::CacheService. On a push that creates or updates many refs, every lookup is a cache miss, costing four Redis round trips per ref (hget/hset/ttl/hlen). This happens even when the project has no custom push rules.

Background: the protected-branch cache

CacheService was introduced in #366724 (closed). It keeps one Redis hash per project (field = SHA256(ref_name), value = encoded boolean), invalidated only when protection actually changes. It is a good cache for the web read pattern: a project's handful of branch names checked repeatedly across many requests.

It is pathological for the opposite pattern, a single request checking many unique new ref names, which is exactly a large push. Every ref misses, and the CACHE_LIMIT = 1000 safety valve (meant to bound the hash size) unlinks the whole key once it exceeds 1000 fields. During a push with thousands of refs that fires repeatedly, so the cache is dropped mid-push and never serves a hit.

A 20,000-ref push measured ~80,000 Redis calls and ~20s in this check alone. Benchmark (RedisCommands::Recorder around ProtectedBranch.protected? for N distinct new ref names):

refs pushed Redis calls per ref
500 2,001 4.0
1,500 6,003 4.0
20,000 80,039 4.0 (+19 cache drops)

ProtectedTag.protected? does not have this problem: it loads the refs once per request and matches in Ruby. This MR makes the branch push-check do the same.

How

  • ProtectedBranch.protected? gains a skip_cache: option that matches against the preloaded all_protected_branches association in Ruby instead of the per-ref Redis cache.
  • BranchCheck passes skip_cache: true behind the skip_protected_branch_push_cache feature flag.

The bypass applies only to the push access-check path. The web read path still calls protected? without skip_cache, so the cache from #366724 (closed) is untouched there. The matching result is identical; only the caching path differs, taking the check from ~4 Redis calls per ref to ~0 (one SQL load reused across all refs).

Feature flag

Behind skip_protected_branch_push_cache (default disabled).

Screenshots or screen recordings

Not applicable.

How to set up and validate locally

Enable the flag and push a branch; confirm protected? no longer issues per-ref Redis calls (see the benchmark approach in the issue).

Edited by Stan Hu

Merge request reports

Loading
Loading