Repository navigation
Speed up dependency resolution - #1272
Merged
Merged
Conversation
Vendors the unreleased resolution performance changes from hex_solver's speed-up-resolution branch: an index of root dependencies with sub dependencies, versions and candidates cached for the solve, and lazy loading of older versions' dependencies. The solver no longer reads the dependencies of every release, so the comment on Hex.Registry.Server.dependencies/3 now says it reads the dependencies of releases it doesn't select.
Mix doesn't consolidate the protocols of archives, so every call to Hex.Solver.Constraint dispatched through the unconsolidated protocol. On a copy of hexpm with 153 packages, that made resolution about 3 ms slower than with the protocol consolidated. mix archive.build now consolidates the protocols Hex defines into the compiled beams before building the archive, and CI checks that the protocols in the built archive are consolidated.
Every mix deps.get wrote the whole registry cache back to disk, with an MD5 checksum and a sync, even when nothing in it changed, such as an offline run or one where every registry file was unchanged. That took about 94 ms for a 17 MB cache. Track whether the cache changed since it was loaded or last written, and only write it then.
Prefetching looked up the repository config and compared its URL with the cached one for every package, though most prefetches are for packages from one or two repositories. Check each repository once.
The registry parsed the requirement of every dependency it returned, though releases repeat the same requirements: the historical releases of hexpm's 155 locked packages have 13,045 requirements but only 463 distinct ones. Keep the parsed constraints in the calling process. Unsatisfiable requirements aren't kept, so each release that has one still warns.
The registry cache was an ETS file, which :ets.file2tab/2 reads with one term and MD5 update per object, about 34,000 objects for a 5.6 MB cache. Loading it took 38 ms. Store the cache as a header, a CRC32 and all objects as one term instead, which loads in 14 ms and writes in 15 ms instead of 38 ms. The cache is written to a temporary file that then replaces it, so another Mix process reading the cache never sees a partly written file. The new cache is cache.bin. When it doesn't exist, Hex starts from the cache.ets of earlier versions, or reports why it couldn't read it, and leaves that file, so Hex versions that share the cache directory don't delete each other's caches.
add_apps_to_resolved/2 searched every resolved package's dependencies and the project's requests for each resolved package, and compared each application name with every other. For hexpm's 153 resolved packages that was 36,858 and 23,409 comparisons. Index the packages' dependents and the project's application names once, and group the resolved packages by application name. The first declaration still wins and errors list packages in the same order.
Unlocking a dependency for mix deps.update also unlocks everything it depends on in the lock. The traversal didn't track visited dependencies, so a dependency shared by many paths, and everything below it, was visited once per path: 1,412 visits for hexpm's 158 lock entries. Track the visited dependencies in a set and check unlocked entries against it.
group_dependency_changes/2 appended each resolved package to the end of its change group, copying the group every time. Prepend and reverse the groups at the end instead.
After the solver finished, Hex built a full lock entry for every resolved package, reading its dependencies from the registry and its manifest from deps/, and then kept the current lock entry anyway when the release was the same. Keep the current entry when building the lock instead, so only new and updated entries are built. Annotating the dependency changes read each release's advisories a second time. Use the advisories already read.
With a policy active, Hex classified every version of a package by scanning and validating every override in the policy, and read each version's advisories, retirement and publish time even when the policy had no rules for the package's repository. Skip repositories the policy doesn't restrict, and narrow the policy to the package's valid overrides before classifying its versions, which classifies them the same. Record blocked and cooldown filtered versions with one state update per package, and reuse the publish times the cooldown filter already read.
With a policy configured, the converger fetched it and waited for it before prefetching any package, so the policy request and the package requests ran one after the other. Check the policy configuration first as before, queue the policy ahead of the packages after the authentication checks, and wait for it before resolving.
A cache.bin with a matching checksum but data that binary_to_term or :ets.insert rejects raised in Registry.open, so the registry server crashed on every run until the file was removed. It's now reported and replaced like a cache with a checksum mismatch.
ericmj
force-pushed
the
vendor-hex-solver-v0.3.1
branch
from
October 14, 2026 17:22
d54cad5 to
1424040
Compare
This was referenced Oct 14, 2026
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.
Vendors hex_solver 0.3.2, which has the resolution changes from hexpm/hex_solver#61, and speeds up Hex's side of resolution.
Measured on a copy of hexpm's
mix.exsandmix.lock(158 lock entries) on macOS with Elixir 1.20.4 / OTP 29.1.1.mainis d62dbe4, which vendors hex_solver 0.3.0. Times are medians, with the three archives alternating on each iteration and each using its own project copy andHEX_HOME, warmed from the same 5.6 MB registry cache.mainmix deps.get --check-locked(9 runs)mix deps.get --check-locked, no registry changes (7 runs)mix deps.update --all(9 runs)mix deps.getinto an emptydeps/, tarballs cached (7 runs)mix deps.getwith an empty Hex cache and nodeps/(5 runs)mix help deps.getalone takes 300 ms.mix deps.update --allwrites the samemix.lockwith all three.These are the performance changes on
mainsince v2.5.2:http_concurrency, 32 by defaultThis PR adds these:
mainHex.Solver.Constraintdispatchescache.bininstead of an ETS filemix deps.updatedeps/manifest read per resolved packageThe counts in the last column come from profiling offline
mix deps.get --check-lockedon a copy of hexpm with 155 locked packages at eaa8bd6 (hexpm/hex#1267). The cache timings are from loading and writing the 5.6 MB cache above.Some behaviour changes with this. The registry cache is now
cache.bin, and the first run reads thecache.etsof earlier Hex versions and leaves it in place for them. The solver uses one list of versions per package for the whole solve, so a version that passes a policy cooldown during a solve doesn't change it. Hex only warns about unsatisfiable requirements, and only rejects invalid dependency names, in versions the solver looked at. A policy that fails to fetch online can now fail after an authentication prompt for an organization, while an invalid policy setting or a policy missing from the cache offline still fails first.hex_solver 0.3.1 has hexpm/hex_solver#60 and hexpm/hex_solver#59. With #1269, a failed resolution after compiling in the same command, such as
mix do compile + deps.get, prints the explanation instead of raisingProtocol.UndefinedError.