Repository navigation
Skip compact-index dependency expansion for excluded versions - #9953
Draft
jeffwidman wants to merge 1 commit into
Draft
jeffwidman wants to merge 1 commit into
jeffwidman wants to merge 1 commit into
Conversation
Pass applicable direct requirements to compact-index fetching and avoid expanding dependencies of versions they exclude. Retain fetched version rows for resolution, checksums, and diagnostics; preserve existing behavior for explicit sources, overrides, and requirements limited to some locked platforms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4 tasks done
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP - please ignore while this is in Draft.
Thanks!
-Jeff
What was the end-user or developer problem that led to this PR?
Bundler's compact-index fetcher recursively expands dependencies from every
version in a fetched gem's info file. That includes versions already excluded
by the Gemfile's direct requirements.
For example, if the Gemfile requires
target < 2.0, dependencies introducedonly by
target 2.0cannot participate in a successful resolution. Fetchingtheir info files, then recursively fetching their dependencies, cannot help
satisfy the request. Long version histories can therefore pull unrelated
dependency branches into metadata preparation.
The important distinction is between fetching a gem's version information and
expanding every version's dependencies: Bundler still needs the former for
resolution, checksums, and available-version diagnostics. This PR skips only
the latter when a mandatory direct requirement rules out the version.
What is your fix for the problem, implemented in this PR?
Pass applicable direct requirements through the default RubyGems source and
fetcher to the compact-index backend. Keep every fetched version row, but
do not enqueue dependency names from a version that fails its direct requirement.
The implementation is 22 added lines and one removed line across five library
files. It is deliberately conservative:
contribute requirements, with one eligible declaration per name.
affinity and ambiguous-source handling.
treated as hard constraints.
root needs it.
This works for exact pins and ranges such as
< 2.0. It does not change versionselection, add a metadata cache, persist a new index, or change fetching
concurrency. Its added costs are a small map referencing existing requirement
objects, per-row requirement checks, and a temporary selection array. Those
costs are why CPU improvement depends on the workload rather than being assumed.
This branch is independent of #9952 and does not include its
exact-pin resolution shortcut.
Benchmarks
Actual isolated patch on current master
A synthetic HTTP registry on loopback serves 20 direct gems, each with versions
0.9 and 1.0 through 8.0. Versions 0.9 and 1.0 depend on one retained child;
each excluded version 2.0 through 8.0 adds a separate two-gem branch. The registry
has 320 gem names and 480 version rows. The input locks 40 names, with the roots
at 0.9; the Gemfile pins all roots to 1.0 and explicitly updates them.
Each sample uses a fresh Ruby process, a fresh application and compact-index
cache, and the same input lockfile. Seven alternating baseline/patched pairs
follow one discarded warmup per library. Timing covers
Definition.build,resolve_remotely!, andto_lock, excluding process startup and fixturegeneration. CPU is the Ruby client process's CPU, not the registry process's CPU.
There is no simulated network delay.
/info/requests per sampleEvery sample produced the same lockfile, and the driver checked the exact
requested paths, not just the counts. All remaining requests returned HTTP 200.
The 280 skipped requests are the two-gem branches introduced only by the seven
excluded versions of each of the 20 roots.
As a no-pruning control, changing all direct requirements to
>= 0.9kept allversions eligible. Seven more alternating pairs made exactly 321 HTTP requests
in both arms, again with identical lockfiles. Median CPU was 0.683 -> 0.694 s
and elapsed time 0.567 -> 0.577 s. That small increase is reported rather than
claiming the additional requirement checks are free; these short shared-host
samples do not establish a stable overhead percentage.
This deliberately branch-heavy fixture demonstrates the avoided work, not an
expected percentage for real repositories. The loopback server and host affect
elapsed time; it is not an Internet-latency benchmark.
Standalone reproduction for both synthetic comparisons
Save the following as
metadata-pruning-benchmark.py. With unmodified andpatched checkouts at the same base revision and the desired Ruby on
PATH:Both commands passed locally with the results above. The driver uses the
Python standard library, serves only synthetic metadata on loopback, and
removes its temporary application/cache directories when finished.
Earlier Dependabot measurements
An earlier runtime prototype was also measured against an internal GitHub
repository with 463 direct dependencies and 663 unique locked gem names.
The same successful three-update sample was compared with and without
metadata pruning. Both arms already included the exact-pin shortcut and the
upstream gzip-verification-removal optimization; only metadata pruning differed.
These are incremental measurements, not this branch's current-master timings.
In the sequential pair:
/info/HTTP GETsThat is 195 fewer HTTP requests: 25.3% fewer RubyGems info requests and 19.3%
fewer requests overall. Other request counts were unchanged. These are counted
requests, not per-helper metadata lookups multiplied by the number of helpers.
CPU results were less consistent. A second pair for that repository showed
128.24 -> 127.16 seconds of native CPU, a 0.85% reduction rather than 5.85%.
A complete root update job for public
dependabot/dependabot-coreshowed220.38 -> 220.78 seconds: effectively unchanged, not a demonstrated CPU win.
The successful proposed dependency records and all proposed text/binary file
contents matched within the comparisons.
Those jobs used Ruby 4.0.5, Bundler 4.0.22, and RubyGems 4.0.14 on a shared
development host with live registry access. They were individual paired jobs,
not controlled statistical wall-time trials. Native-helper CPU is not total
updater/container CPU, and the large repository sample was not a complete
update job. The claim is avoided metadata work and HTTP requests where excluded
versions introduce otherwise-unneeded branches, not a universal speedup.
Validation
Base:
a099e068a86c0a912d558b2c107c6c263cb5b4ff. Local validation uses Linux MRIRuby 4.0.5 and checked-out Bundler/RubyGems 4.1.0.beta2.
shared dependency branches, loose bounds, inactive and additional platforms,
overrides, changed manifests, missing-version diagnostics, explicit sources,
propagation, and preserving fetched rows and checksum metadata.
Passed: eight files inspected, no offenses.
Passed: 4,166 examples, 0 failures, 29 pending.
The network-dependent realworld suite, RubyGems test-unit suite, and the
Windows/JRuby/TruffleRuby matrix have not been run locally.
Make sure the following tasks are checked