Skip to content

Commit 5ce43e1

Browse files
Xaenaltclaudecursoragent
committed
docs(proposals): add unified CacheManager design proposal
Describes the design for a unified cache subsystem with prioritized multi-backend lookup, covering motivation, architecture decisions, backend protocol, CLI surface, and integration points. Co-Authored-By: Claude <claude@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Signed-off-by: Sean Pryor <spryor@redhat.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d249228 commit 5ce43e1

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
Proposal: Unified Cache Manager
2+
================================
3+
4+
Problem
5+
-------
6+
7+
Fromager's caching logic is scattered across multiple modules with no central
8+
coordination. Prebuilt wheels are checked separately from previously built
9+
wheels, remote wheel servers are not consulted during local builds, and there
10+
is no mechanism to share a cache across multiple backends. This leads to:
11+
12+
- Redundant builds when wheels already exist in a remote cache.
13+
- No visibility into cache hit rates, artifact integrity, or staleness.
14+
- No short-circuit path — even a full cache hit still downloads source and
15+
sets up a build environment before discovering the wheel exists.
16+
17+
Proposed Solution
18+
-----------------
19+
20+
Introduce a unified ``CacheManager`` class that centralizes all cache
21+
operations behind a prioritized multi-backend lookup strategy.
22+
23+
Architecture
24+
~~~~~~~~~~~~
25+
26+
.. code-block:: text
27+
28+
CacheManager
29+
├── lookup_backends (searched in order):
30+
│ ├── LocalDirectoryBackend(wheels-repo/build/) [recursive for parallel builds]
31+
│ ├── LocalDirectoryBackend(wheels-repo/downloads/)
32+
│ └── RemotePEP503Backend(https://cache-server/simple/) [optional]
33+
└── store_backend:
34+
└── LocalDirectoryBackend(wheels-repo/downloads/)
35+
36+
Prebuilt wheels stay on the dedicated ``SourceType.PREBUILT`` path and are
37+
not consulted by the general cache short-circuit.
38+
39+
Key components:
40+
41+
- ``WheelCacheKey`` — Content-addresses artifacts by canonicalized package
42+
name, version, and numeric build tag. Wheel compatibility tags
43+
(interpreter, ABI, platform) are not part of the key; instead, backends
44+
filter candidates against the current interpreter's supported tags at
45+
lookup time, keeping the key space small while ensuring only compatible
46+
wheels are returned.
47+
- ``CacheBackend`` protocol — Abstract interface implemented by
48+
``LocalDirectoryBackend`` (filesystem) and ``RemotePEP503Backend``
49+
(PEP 503 simple repository). Thread-safe with internal locking.
50+
- ``CacheManager`` — Orchestrates prioritized lookup across backends with
51+
graceful fallback on fetch failures, and routes all stores to a single
52+
designated local backend.
53+
54+
Lookup and store
55+
~~~~~~~~~~~~~~~~
56+
57+
On lookup, the manager iterates ``lookup_backends`` in order and returns the
58+
first hit. Remote hits are downloaded to the store backend's directory and
59+
registered in the local index for subsequent fast lookups.
60+
61+
On store, newly built wheels are always placed in the single ``store_backend``
62+
(the downloads directory). This keeps the design simple and avoids
63+
collection-routing complexity in core Fromager. Variant-specific routing
64+
can be added as builder-level logic in a follow-up.
65+
66+
Short-circuit optimization
67+
~~~~~~~~~~~~~~~~~~~~~~~~~~
68+
69+
When a cache hit is found during the ``PREPARE_SOURCE`` phase and a
70+
``CacheManager`` is active, ``PrepareSource.run()`` skips build environment
71+
creation and build dependency resolution entirely — proceeding directly to
72+
``ProcessInstallDeps``. Install dependencies are extracted from the cached
73+
wheel's metadata. This eliminates the most expensive steps for packages that
74+
do not need rebuilding.
75+
76+
Remote cache with integrity
77+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
78+
79+
``RemotePEP503Backend`` lazily fetches per-project package indices on first
80+
access and maintains a session-scoped in-memory index. Lookups honor
81+
interpreter wheel tags, skip yanked files (PEP 592), and filter
82+
``data-requires-python`` the same way the resolver does. Downloads are
83+
verified with streaming SHA256 checksums, use atomic temporary files, and
84+
reject plaintext HTTP URLs that lack integrity hashes (unless
85+
``--cache-allow-insecure`` is passed for development workflows). Filenames
86+
are sanitized to prevent path traversal attacks. HTTP 400/404 (and empty
87+
successful pages) are treated as definitive misses for the run; transport
88+
and 5xx failures are retried on later lookups.
89+
90+
Observability
91+
~~~~~~~~~~~~~
92+
93+
A new ``fromager cache`` CLI command group provides:
94+
95+
- ``cache list`` — Show all cached artifacts with versions and build tags.
96+
- ``cache stats`` — Display on-disk inventory counts and sizes per backend.
97+
(Hit/miss rates are process-local during bootstrap and are not persisted.)
98+
- ``cache verify`` — Validate integrity of local cache contents.
99+
- ``cache invalidate`` — Remove specific artifacts by name/version/tag.
100+
- ``cache gc`` — Garbage-collect old build tags, keeping only the N most
101+
recent per package+version.
102+
103+
Scope
104+
-----
105+
106+
The new cache subsystem is opt-in via ``--use-cache-manager`` on the
107+
``bootstrap`` command. When disabled, existing behavior is preserved
108+
unchanged.
109+
110+
Planned implementation touchpoints:
111+
112+
- ``src/fromager/cache.py`` — New module with all cache classes and factory.
113+
- ``src/fromager/commands/cache_cmd.py`` — CLI commands.
114+
- ``src/fromager/commands/bootstrap.py`` — Wiring ``--use-cache-manager`` and
115+
``--cache-allow-insecure`` options.
116+
- ``src/fromager/bootstrapper/_cache.py`` — Short-circuit integration via
117+
``_find_cached_wheel_via_manager``.
118+
- ``src/fromager/context.py`` — ``cache`` property on ``WorkContext``.
119+
- ``tests/test_cache.py`` — Unit tests for the cache subsystem.
120+
121+
Benefits
122+
--------
123+
124+
- Eliminates redundant builds when wheels exist in a remote or local cache.
125+
- Reduces bootstrap time by short-circuiting cached packages (skips source
126+
download, build env setup, and build dep resolution).
127+
- Provides cache observability through dedicated CLI commands.
128+
- Enforces artifact integrity with SHA256 verification and atomic writes.
129+
- Thread-safe design compatible with background I/O pre-fetching.
130+
131+
Security considerations
132+
-----------------------
133+
134+
- Remote downloads are verified against SHA256 hashes declared in PEP 503
135+
index pages. At the backend layer, mismatched files are deleted and a
136+
``ValueError`` is raised — the corrupted artifact is never persisted.
137+
- At the manager layer, any backend fetch failure (including hash mismatches)
138+
is caught and treated as a backend-level miss, falling through to the next
139+
backend or ultimately a fresh build. This two-layer design ensures
140+
integrity enforcement within each backend while providing graceful
141+
degradation across backends.
142+
- Plaintext HTTP URLs without SHA256 hashes are rejected by default.
143+
The ``--cache-allow-insecure`` flag explicitly opts in for internal or
144+
development registries.
145+
- Filenames from remote indices are sanitized to prevent directory traversal.
146+
- Local cache writes use atomic ``tempfile`` + ``rename`` to prevent readers
147+
from observing partial files.
148+
- ``scan()`` skips symlinked wheels to prevent ``invalidate``/``gc`` from
149+
deleting files outside the cache root.
150+
151+
Verification
152+
------------
153+
154+
- All existing unit and e2e tests pass unchanged (legacy path preserved).
155+
- New tests cover cache components, short-circuit logic, concurrency safety,
156+
CLI commands, and error handling.
157+
- Linting (``ruff``), type checking (``mypy``), and formatting all pass.
158+
159+
Future work
160+
-----------
161+
162+
- Integration with ``build_tag_hook`` (issue #1059) for platform-suffixed
163+
cache keys.
164+
- Variant/collection-based store routing as builder-level logic (separate PR).
165+
- Automatic detection of accelerated packages via ELF inspection or wheel
166+
tag analysis.
167+
- Promotion of ``--use-cache-manager`` to default behavior once proven in
168+
production.

0 commit comments

Comments
 (0)