Skip to content

Conversation

edolstra
Copy link
Member

Motivation

Adding paths to the store can be slow due to I/O overhead, but especially when going through the daemon because of the round-trip latency of every wopAddToStore call.

So we now do the addToStore() calls asynchronously from a separate thread from the evaluator. This slightly speeds up the local store, and makes going through the daemon almost as fast as a local store.

Timings doing nix eval github:NixOS/hydra/b812bb5017cac055fa56ffeac5440b6365830d67#nixosConfigurations.container.config.system.build.toplevel:

  • Local store, before: 4.53s
  • Local store, after: 4.30s
  • Daemon, before: 6.05s
  • Daemon, after: 4.67s

The async path writer is currently only used by writeDerivation() (i.e. for adding .drv files) but can be extended in the future to handle other source files.

Includes DeterminateSystems#162 and DeterminateSystems#176.

Context


Add 👍 to pull requests you find important.

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

Adding paths to the store can be slow due to I/O overhead, but
especially when going through the daemon because of the round-trip
latency of every wopAddToStore call.

So we now do the addToStore() calls asynchronously from a separate
thread from the evaluator. This slightly speeds up the local store,
and makes going through the daemon almost as fast as a local store.
@github-actions github-actions bot added new-cli Relating to the "nix" command with-tests Issues related to testing. PRs with tests have some priority repl The Read Eval Print Loop, "nix repl" command and debugger c api Nix as a C library with a stable interface labels Aug 20, 2025
@Ericson2314
Copy link
Member

Shouldn't we just send the daemon a file path or file descriptor and then it can add the data itself? I would think serializing local files over NARs to be unpacked by the daemon (on the same system) is bad performance whether synchronous or asynchronous, compared to letting the OS move the files around.

@Ericson2314
Copy link
Member

@edef1c reminds me of https://en.wikipedia.org/wiki/Confused_deputy_problem, so yes it should be a file descriptor, not path, to cleanly avoid all these problems.

(The way this PR does it is still good for the ssh/non-local case, to be clear.)

@Mic92
Copy link
Member

Mic92 commented Aug 20, 2025

Shouldn't we just send the daemon a file path or file descriptor and then it can add the data itself? I would think serializing local files over NARs to be unpacked by the daemon (on the same system) is bad performance whether synchronous or asynchronous, compared to letting the OS move the files around.

Are there not also remote daemons where this doesn't work? There this eval store option.

@Mic92
Copy link
Member

Mic92 commented Aug 20, 2025

cc @NaN-git

Copy link
Contributor

@xokdvium xokdvium left a comment

Choose a reason for hiding this comment

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

At this point we could just look into porting the kj-async based refactoring to the store layer that Lix has done. Coroutines seem very fitting for this sort of thing. It would require more up-front effort, but it seems worthwhile in the long run. Doing ad-hoc async is going to become more painful as we have more of it.

@nixos-discourse
Copy link

This pull request has been mentioned on NixOS Discourse. There might be relevant details there:

https://discourse.nixos.org/t/2025-08-20-nix-team-meeting-minutes-242-241/68245/1

Comment on lines +128 to +142
// FIXME: use addMultipeToStore() once it doesn't require a
// NAR hash from the client for CA objects.

for (auto & item : items) {
StringSource source(item.contents);
auto storePath = store->addToStoreFromDump(
source,
item.storePath.name(),
FileSerialisationMethod::Flat,
ContentAddressMethod::Raw::Text,
HashAlgorithm::SHA256,
item.references,
item.repair);
assert(storePath == item.storePath);
}
Copy link
Member

Choose a reason for hiding this comment

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

As we discussed briefly in the meeting, we could instead implement pipelining, which does not require an update to the worker protocol.

Another benefit of pipelining that was not brought up is that it also allows the first response to come in before any of the other requests, reducing the latency of individual add operations.

@roberth
Copy link
Member

roberth commented Aug 21, 2025

Coroutines seem very fitting for this sort of thing

In principle yes, but converting the whole evaluator to coroutines might not be the right thing to do, or at least not the first thing to do.
So having a thread boundary between the evaluator and the store layer seems fine to me.

I do agree that an async refactor would benefit the code a lot, just by virtue of getting to use better control flow primitives/abstractions than what std can provide.
So in principle I'm ok to go ahead with both this PR and the async / coroutines work.

@Mic92
Copy link
Member

Mic92 commented Aug 21, 2025

At this point we could just look into porting the kj-async based refactoring to the store layer that Lix has done. Coroutines seem very fitting for this sort of thing. It would require more up-front effort, but it seems worthwhile in the long run. Doing ad-hoc async is going to become more painful as we have more of it.

Something we should discuss in a team meeting I think, so that we are all aligned on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c api Nix as a C library with a stable interface new-cli Relating to the "nix" command repl The Read Eval Print Loop, "nix repl" command and debugger with-tests Issues related to testing. PRs with tests have some priority
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants