Skip to content

Evict superseded index generations from the derived caches - #2360

Merged
markusthoemmes merged 1 commit into
chainguard-dev:mainfrom
markusthoemmes:evict-derived-caches
Aug 4, 2026
Merged

Evict superseded index generations from the derived caches#2360
markusthoemmes merged 1 commit into
chainguard-dev:mainfrom
markusthoemmes:evict-derived-caches

Conversation

@markusthoemmes

Copy link
Copy Markdown
Member

The global resolver and disqualify caches key derived data by NamedIndex combinations and never evict, so every etag rotation of an APKINDEX pins the entire previous generation (parsed index, PkgResolver maps, disqualify sets) for the life of the process. Long-running processes that periodically re-resolve against rotating indexes leak tens of MB per rotation and eventually OOM.

Track the latest generation served per index URL, and purge derived entries for a generation when a newer one replaces it. Cached entries for superseded generations are unreachable for new resolutions anyway (a fresh fetch yields fresh index objects and therefore a cache miss), so this frees memory without changing any resolution result.

Resolutions that fetched their indexes just before a rotation still hold the superseded generation and would re-insert it after the purge, with nothing left to evict it again. The derived caches therefore refuse to memoize any combination containing a superseded index, building and returning the result uncached instead; it dies with the resolution holding it. A re-check after fill closes the window where a replacement races the memoization.

The global resolver and disqualify caches key derived data by NamedIndex
combinations and never evict, so every etag rotation of an APKINDEX pins the
entire previous generation (parsed index, PkgResolver maps, disqualify sets)
for the life of the process. Long-running processes that periodically
re-resolve against rotating indexes leak tens of MB per rotation and
eventually OOM.

Track the latest generation served per index URL, and purge derived entries
for a generation when a newer one replaces it. Cached entries for superseded
generations are unreachable for new resolutions anyway (a fresh fetch yields
fresh index objects and therefore a cache miss), so this frees memory without
changing any resolution result.

Resolutions that fetched their indexes just before a rotation still hold the
superseded generation and would re-insert it after the purge, with nothing
left to evict it again. The derived caches therefore refuse to memoize any
combination containing a superseded index, building and returning the result
uncached instead; it dies with the resolution holding it. A re-check after
fill closes the window where a replacement races the memoization.

@codysoyland codysoyland left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! I especially appreciate the regression tests.

@markusthoemmes
markusthoemmes merged commit ea62bef into chainguard-dev:main Aug 4, 2026
22 checks passed
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.

3 participants