Skip to content

libstore: reuse parsed derivations in registerValidPaths#14828

Merged
Mic92 merged 4 commits intoNixOS:masterfrom
Zaczero:zaczero/libstore-registerValidPaths
Dec 18, 2025
Merged

libstore: reuse parsed derivations in registerValidPaths#14828
Mic92 merged 4 commits intoNixOS:masterfrom
Zaczero:zaczero/libstore-registerValidPaths

Conversation

@Zaczero
Copy link
Member

@Zaczero Zaczero commented Dec 18, 2025

  • LocalStore::registerValidPaths() parsed derivations twice: once in addValidPath() and again when calling checkInvariants(), despite already having loaded the derivation.
  • Plumb the parsed Derivation out of addValidPath() and reuse it for the invariant check pass, falling back to re-parsing only when a derivation wasn’t newly registered in this call.
  • BM_RegisterValidPathsDerivations/200_mean runs 32% faster

Motivation

Context


Add 👍 to pull requests you find important.

The Nix maintainer team uses a GitHub project board to schedule and track reviews.

@github-actions github-actions bot added the store Issues and pull requests concerning the Nix store label Dec 18, 2025
@Ericson2314
Copy link
Member

@Zaczero just so you know, stepping back a bit, a few of us (@Mic92 and I at least) have considered whether we ought to put derivations in SQLite rather than serialize them to drv files. This is a bigger change, but I suspect it would yield bigger and more durrable perf gains than trying to microoptimize the current approach.

If you are interested in doing performance work around derivations, perhaps you would want take that on?

@Zaczero
Copy link
Member Author

Zaczero commented Dec 18, 2025

@Ericson2314 I don't know if I want to make bigger changes to Nix yet. I am getting familiarized with its source, and I wanted to focus on doable existing FIXMEs/TODOs.

Comment on lines 954 to 962
not be valid yet. */
for (auto & [_, i] : infos)
if (i.path.isDerivation()) {
// FIXME: inefficient; we already loaded the derivation in addValidPath().
readInvalidDerivation(i.path).checkInvariants(*this, i.path);
if (auto drv = derivations.find(i.path); drv != derivations.end()) {
drv->second.checkInvariants(*this, i.path);
} else {
readInvalidDerivation(i.path).checkInvariants(*this, i.path);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need this code at all? isn't addValidPath already calling checkInvariants? The code (before you did anything, to be clear) seems redundant to me.

Copy link
Member Author

Choose a reason for hiding this comment

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

It appears to be the other way around. addValidPath is never called with checkOutputs=true so it never checkInvariants.

Copy link
Member

@Mic92 Mic92 Dec 18, 2025

Choose a reason for hiding this comment

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

I think you are right. The comment was out-dated.

Copy link
Member

Choose a reason for hiding this comment

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

I added:

1f73996

Does this makes sense?

Copy link
Member Author

Choose a reason for hiding this comment

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

Only if this comment no longer applies:

/* Check that the derivation outputs are correct. We can't do
this in addValidPath() above, because the references might
not be valid yet. */

Copy link
Member

Choose a reason for hiding this comment

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

@Zaczero check the commit message, for why I think this is not longer the case.

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks good, I didn't notice the big commit message 🤓

- Add a focused nix-store-benchmarks benchmark that registers many derivation paths into a temporary local store root
- LocalStore::registerValidPaths() parsed derivations twice: once in addValidPath() and again when calling checkInvariants(), despite already having loaded the derivation.
- Plumb the parsed Derivation out of addValidPath() and reuse it for the invariant check pass, falling back to re-parsing only when a derivation wasn’t newly registered in this call.
- BM_RegisterValidPathsDerivations/200_mean runs 32% faster
@Zaczero Zaczero force-pushed the zaczero/libstore-registerValidPaths branch from 97c1b49 to cccfa38 Compare December 18, 2025 05:01
@Mic92 Mic92 force-pushed the zaczero/libstore-registerValidPaths branch 2 times, most recently from 731c718 to cccfa38 Compare December 18, 2025 08:07
@Mic92
Copy link
Member

Mic92 commented Dec 18, 2025

Sorry. Didn't meant to force push. Don't know how that happend. I reverted back to your change.

…riants loop

The separate checkInvariants loop after addValidPath was added in 2014
(d210cdc) to work around an assertion failure:

  nix-store: derivations.cc:242: Assertion 'store.isValidPath(i->first)' failed.

At that time, hashDerivationModulo() contained assert(store.isValidPath(...))
which required input derivations to be registered as valid in the database
before computing their hash. The workaround was to:
1. Call addValidPath with checkOutputs=false
2. Add all references to the database
3. Run checkInvariants in a separate loop after paths were valid

In 2020 (bccff82), the isValidPath assertion was removed to fix a
deadlock in IFD through the daemon (issue NixOS#4235). The fix changed
hashDerivationModulo to use readInvalidDerivation, which reads directly
from the filesystem without requiring database validity.

This made the separate checkInvariants loop unnecessary, but nobody
noticed the code could be simplified. The comment "We can't do this in
addValidPath() above, because the references might not be valid yet"
became stale.

Now we simply call addValidPath() with the default checkOutputs=true,
which runs checkInvariants internally using the already-parsed
derivation. This commit eliminates the separate loop over derivations.
Testing with 10 derivations is sufficient to verify performance
characteristics. The larger test cases (50, 200) don't provide
additional insight and slow down the benchmark unnecessarily.
@Mic92 Mic92 added this pull request to the merge queue Dec 18, 2025
Merged via the queue into NixOS:master with commit 9254fab Dec 18, 2025
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

store Issues and pull requests concerning the Nix store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants