Skip to content

cacheserver: bound the number of entries and keys - #215

Merged
linyiru merged 1 commit into
mainfrom
cacheserver-entry-limits
Nov 8, 2026
Merged

linyiru merged 1 commit into
mainfrom
cacheserver-entry-limits

Conversation

@linyiru

@linyiru linyiru commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

The byte caps alone left two costs unbounded:

  • A write-scoped job could finalize any number of empty entries. Each one stays in the index, gets rewritten with it under the lock, and is scanned on every lookup.
  • A lookup could name any number of restore keys, each costing two scans of every entry under the lock.

Changes:

  • Keys: requests keep to @actions/cache's own limits: ten keys in all, each at most 512 characters, counted in characters. A request past them gets 400.
  • Per repository: at most --max-repo-entries entries (default 2000). Past it, a new reservation evicts that repository's own least-recently-used entry, so one repository cannot flush the others.
  • Per host: at most --max-entries entries (default 20000).
  • Per job: at most 64 unfinished uploads, because reservations are never evicted. Past that, or once reservations alone fill a cap, the create gets ok:false. The toolkit turns that into a warning, not a failed job.
  • Eviction: it sorts once, so trimming a large index is not quadratic. It runs after a stale reservation is reclaimed, so a create for an existing key evicts nothing.

Tests are in internal/cacheserver/entrylimit_test.go. scripts/mutate.sh confirmed that each of these is caught by a test: the repository filter, the job reservation cap, the LRU order, the reservations-only refusal, the key count and the character count.

Review: a fresh reviewer agent reviewed the first version. Its main finding was that a host-wide count alone let one job evict every repository's cache and then block all saves. That led to the per-repository and per-job caps here. Its other should-fixes are fixed too: the quadratic eviction, the check running before the reclaim, and keys counted in bytes. Left for later: saving the whole index on every lookup hit.

The byte caps let a write-scoped job finalize any number of empty
entries, each kept in the index, rewritten with it under the lock and
scanned on every lookup, and a lookup could name any number of restore
keys, each costing two scans of every entry under the lock.

Requests now keep to @actions/cache's own limits: ten keys in all, each
at most 512 characters. A repository holds at most --max-repo-entries
(default 2000), and past it a new reservation evicts that repository's
own least-recently-used entry, so one repository cannot flush the
others; the host holds at most --max-entries (default 20000). A job may
hold at most 64 unfinished uploads, since reservations are never
evicted. Eviction sorts once, so trimming a large index is not
quadratic, and runs after a stale reservation is reclaimed, so a create
for an existing key evicts nothing.

Signed-off-by: Yi-Ru Lin <deduce@gmail.com>
@linyiru
linyiru enabled auto-merge November 8, 2026 04:32
@linyiru
linyiru merged commit 5b28066 into main Nov 8, 2026
7 checks passed
@linyiru
linyiru deleted the cacheserver-entry-limits branch November 8, 2026 04:35
linyiru added a commit that referenced this pull request Nov 8, 2026
…y IDs

The cache-protocol test's identity records had no repository_id, so every request was denied once Validate required one, and the entry-cap test from #215 compared entries against the bare name, which no longer is the scope.

Signed-off-by: Yi-Ru Lin <deduce@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant