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 askip_cache:option that matches against the preloadedall_protected_branchesassociation in Ruby instead of the per-ref Redis cache.BranchCheckpassesskip_cache: truebehind theskip_protected_branch_push_cachefeature 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).
Related
- Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/637905
- Cache service origin: #366724 (closed)
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).