Nix eats more than 1.5 GiB RAM during updates

I just noticed, that the Nix process eats 10.1% of my 16GiB RAM system, while it runs a system upgrade. While this is not a big issue on my personal rig, on 4GiB machines plus DE and modern browser it would start to swap.

Does this mean, I should not use NixOS on a desktop with less than 6GiB of RAM? I am asking for the machine of my mother.

You might be building software, or evaluating multiple nixpkgses. Evaluating nixpkgs is memory intensive, but we’re talking hundreds of megabytes; you’ve probably got something configured incorrectly. Whatever it is probably also adds a hefty overhead for rebuild duration.

Well, arguably, before the powerusers eat me because I advise against what they do, there can be reasons to do multi-eval stuff, and it’s generally true that you can find reasons to do many things with nix - but in the hands of a newcomer a lot of those things are probably inadvertent mistakes born out of ignorance.

Share your config and we can look at what you could optimize, or what’s causing the software builds.

My full config can be found on Github GitHub - Borgvall/nixos: Personal NixOS Configuration · GitHub. I am using a flake, but it has only one nixpkgs input. The measurement was done with top and only the nix process itself took 10.1% of my 16GiB machine and was very consistent during the upgrade started as nixos-upgrade service (see upgradeandgc.nix), even when it did only downloads from the Hydra cache (checked via journalctl). AFAIK if nix would start gcc or something, top would show their RAM usage in distinct lines and does not add it to it’s parent process.

I have done some research and it seems `nix-build` is not freeing eval memory during long builds · Issue #5200 · NixOS/nix · GitHub describes my issue.

A NixOS config takes quite a bit more memory than something like pkgs.hello, so 1.5GiB is pretty unsurprising to me. This config alone maxes out at 835MiB:

{
  boot.loader.grub.enable = false; boot.initrd.enable = false;
  services.desktopManager.gnome.enable = true;
  services.displayManager.gdm.enable = true;
}
$ command time nix eval -f "<nixpkgs/nixos>" --arg configuration ./config.nix config.system.build.toplevel
«derivation /nix/store/qd9snl69bdv7d99pfl9sfqcnhh99505p-nixos-system-nixos-26.11pre-git.drv»
3.42user 0.55system 0:04.62elapsed 85%CPU (0avgtext+0avgdata 835192maxresident)k
14639inputs+0outputs (0major+214457minor)pagefaults 0swaps

(emphasis: 835192maxresident)k)

Even with a modest config, it’s pretty easy to end up using 1.5GiB.

6 Likes

Hm, I’m surprised, I thought it was quite a bit less.

@Borgvall to give more productive advice then, you could build (and evaluate) the configuration on a host with more memory, and push the result with --target-host.

1 Like

I have a weird idea: run the upgrade during shutdown. My mother uses shutdown out of habit. In that way the browser/DE won’t compete for RAM. In Fedora the update during shutdown is opt in (a checkbox is displayed in GNOME’s shutdown dialog), so she knows the basic idea and leaves the PC alone after initiating shutdown. I am not sure, if I really follow this route.

Edit: Maybe the most part of Nix’ memory is cold anyway and gets swapped out easily, but even then the upgrade would interfere with daily usage.

1 Like

For my 1GB machines in the cloud, I do remote builds with

nixos-rebuild --target-host root@cloudmachine.com --flake .#cloudmachine switch

on my desktop. With less than 4G you’re probably going to have to do something similar.

I think 6GiB should be fine for a casual NixOS desktop. In my experience disk io caused by nixis more impacting than memory pressure (although memory pressure adds to disk io).

I prepend all my nixos-rebuild switch, nix store gcand nix store optimisewith chrt -i :

NAME
chrt - manipulate the real-time attributes of a process

SYNOPSIS
chrt [options] [priority] command [argument…]

DESCRIPTION
chrt sets or retrieves the real-time scheduling attributes of an existing PID, or runs command with the given attributes.

POLICY OPTIONS
[..]
 -i, --idle
     Set scheduling policy to SCHED_IDLE (scheduling very low priority jobs)
1 Like

Using scheduling priority to reduce the impact of background tasks on the interactive tasks is a nice bonus. Afaik it doesn’t help much, when one runs out of RAM. In that case the context switch triggers a lot of swap activity, which is often the bottleneck.

To add a data point, I’m using NixOS on a headless machine with 2G of memory and no swap and have out-of-memory issues during updates roughly once a year. The solution is usually: kill all running process, add swap. I think I’ve also resorted to commenting out a dependency to my config temporarily so I spread the load over two rebuild (not completely sure that’s helpful).
If I manage with 2G, I would hope that 6G would be enough, but the answer to that is probably always going to be: it depends.

1 Like

Yes, I think 6GiB should be enough.

The laptop of my mother has 4GiB. The iGPU takes 256MiB. The typical use is Gnome with Firefox/Thunderbird and the standard background services, this needs 2.8-3GiB in my measurements. My mother is used to Gnome, for comparison I also tried Sway anyway and it saves 200-300MiB. Measurable but not significant enough to warrant a switch. When the autoupgrade kicks in, it would eat significantly more memory than available, so the system would become very slow. That is my concern.

Some have suggested to evaluate the system on a machine with more RAM. This works in principle and adds more burden on me. My personal, though unorthodox, idea is to run the system upgrade during shutdown, so it would not compete with the DE and browser.