Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC 0022] Minimal module list #22

Closed
wants to merge 3 commits into from

Conversation

edolstra
Copy link
Member

@edolstra edolstra commented Jan 12, 2018

A WIP proposal for reducing NixOS evaluation time by getting rid of our giant module-list.nix.

Rendered: https://github.com/edolstra/rfcs/blob/minimal-module-list/rfcs/0022-minimal-module-list.md

@edolstra edolstra changed the title Minimal module list [RFC 0022] Minimal module list Jan 12, 2018
@dtzWill
Copy link
Member

dtzWill commented Jan 12, 2018

This seems like a very bad change in terms of usability, especially for "normal" users, in return for faster evaluations that favor the power users. The 1%! 😋

Being able to just set services.nginx.enable = true; is so magical it's-- it's on of the top things I use to sell people on NixOS.

I'm optimistic that we can accomplish your goals (and wow these changes really do reduce resource usage!) without negatively impacting normal use.

My other concern is that in the current world all modules are evaluated -- which kinda means they are always evaluate-able. Maybe I overestimate the benefit of that, but it seems this change might make it easier for modules to bitrot badly without anyone noticing.

There is the related problem of backwards compatibility as well, I suppose.


One solution to most of these is to make your suggested changes "under the hood" but by default still include "all" modules. Unless user indicates they want to "roll their own"-- which many may elect to do, myself included-- with a flag or something in the configuration?


Finally I would say that I don't see a fundamental reason including all modules should be so bad, since (for an example) an order of magnitude improvement in the evaluator would very likely eliminate this problem almost entirely. There just aren't that many modules and the growth is linearish.

I'm not saying we can achieve that sort of improvement, but I think it's a touch misleading to say evaluating all modules is inevitably a problem-- there's not an unlimited supply of useful modules. But improving the evaluator is really hard.

This looks like good cleanup we should do anyway, and practically speaking does show huge improvements that are certainly worth pursuing. Hopefully we can have our 🎂 and eat it too.

@dtzWill
Copy link
Member

dtzWill commented Jan 12, 2018

Whoops I see

For compatibility, we can provide a all-modules.nix that imports all modules

Which is where I ended up too. Hmm, well I'll re-read and think some more. :)

module defines an option declared in another. For example,
`network-interfaces.nix` sets `virtualisation.vswitch`, requiring
`virtualisation/openvswitch.nix` to be included even when
`networking.vswitches = {}`. It's not clear what the best way is to
Copy link

Choose a reason for hiding this comment

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

How about a hybrid where we keep all potential dependencies in module-list.nix but the rest is included explicitly? E.g. virtualisation.vswitch would be in, but services.uptimed would not). I did a super quick estimation by

  1. finding configurations:
git grep -h "^  cfg = config." | sort | uniq | head
  1. sampling ~20 items at random like below to see whether they are used as a dependency
git grep services.redmine

And my guess is that <10% of modules are dependencies for other modules.

The major downsides IMO would be inconsistency (some modules are special) and discoverability (contributors need to know that they have to elevate their dependencies when they add them).

@Ericson2314
Copy link
Member

I think that unresolved question @teh mentions raises an interesting point: we could do something like this for isolating modules rather than performance, an argument I find more compelling.

The module system currently allowing anything to affect anything else is great for easily adding functionality in new modules, it's not so good for reasoning about the interactions between modules after the fact. Perhaps the tension between flexibility and encapsulation is fundamental, and given the general insanity of the underlying Unix userland itself, the module system errs in the right direction. But I'd want to make sure of that first.

@Nadrieril
Copy link
Member

Could conditional imports be possible instead ? So far there is no mkIf-like thing for imports, but if there was, we could define all enable options together in some file and only import those modules that have enable = true. Thus we would have both discoverability and less files to parse.

@edolstra
Copy link
Member Author

@Ericson2314 Yes, the fundamental flaw of the module system is that any module can change anything (i.e. they violate the principle of least authority). But replacing the module system with a more functional approach is a much more significant undertaking which is out of scope of this RFC.

@dtzWill Is writing imports = [ nginx.nix ]; to enable Nginx less magical than services.nginx.enable = true? In any case, that's what you have to write anyway for out-of-tree modules, and I think we should move to more out-of-tree modules anyway (we can't expect everybody to have commit access to the Nixpkgs repo).

The alternative is to continue with the current approach of including
every module in `module-list.nix`. However, it's clear that this
approach does not scale. Performance improvements to the evaluator can
only delay the module apocalypse.
Copy link
Member

Choose a reason for hiding this comment

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

How much room for optimization is there in the nix evaluator? I remember while profiling ruby that stat() and open() were a big part of the interpreter startup.

| --------------------------- | -------- | --------- |
| 17.09 | 73.7 | 6470 |
| 17.09 minimal | 19.5 | 2577 |
| 17.09 minimal + memoisation | 12.5 | 1532 |
Copy link
Member

Choose a reason for hiding this comment

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

12 seconds is actually quite reasonable compared to building pretty much anything else. Is it not possible to prepare the system outputs through Hydra?

# Drawbacks
[drawbacks]: #drawbacks

...
Copy link
Member

Choose a reason for hiding this comment

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

I can see two major drawbacks:

If nixpkgs is loaded using fetchFromGitHub then the <nixpkgs/...> notation doesn't work. It has to be pkgs.path + "/...". This causes issues for composability of nixpkgs itself.

How will the https://nixos.org/nixos/options.html# list be populated?

@zimbatm
Copy link
Member

zimbatm commented Jan 15, 2018

Would it not be possible to make the module loading lazy?

{
  myModule = mkLazyModule ./my-module.nix {};
}

Such as myModule.enable = true would load the rest of the module?

That would add a bit of evaluation overhead for each module but since most modules are not used it would lower to overall evaluation time. There is only a subset of modules that use the .enable pattern but I think it's a good thing to move towards that approach anyways.

@Profpatsch
Copy link
Member

Profpatsch commented Jan 15, 2018

… and I think we should move to more out-of-tree modules anyway (we can't expect everybody to have commit access to the Nixpkgs repo).

If that leads to a very well maintained list of core modules and a secondary list of community managed modules, that could be a good step.
I’m very vary of the possibility that everything but the core modules is going to become an ever bigger mess than it already is inside the monorepo (where you can’t even build if options are missing or have changed).

Would it not be possible to make the module loading lazy?

That might lead to strange patterns, where enabling some module could lead to a type error in another part of the repo. Since module errors already are very much non-local that could lead to a major decrease in usability.
At the very least the modules would have to be strictly evaluated on hydra (to catch such problems), so nothing gained.

all modules.

* Most `enable` options defaults should be changed to `true`, so that
including a module activates it automatically. (But see below.)
Copy link

Choose a reason for hiding this comment

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

How would that work with all-modules.nix? Having all modules enabled by default wouldn't be very compatible...

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, all-modules.nix would also have to disable those modules by setting their enable option to false.

@zimbatm
Copy link
Member

zimbatm commented Jan 16, 2018

That might lead to strange patterns, where enabling some module could lead to a type error in another part of the repo. Since module errors already are very much non-local that could lead to a major decrease in usability.

All the lazy modules would be enabled during document generation, which would surface that kind of incompatibility. Another thing I would like to see happen is to enforce the namespace extension such as a module can only define new config option below a specific namespace.

@FRidh
Copy link
Member

FRidh commented Feb 11, 2018

I agree we should not import all modules, so I definitely like to see this.

Such as myModule.enable = true would load the rest of the module?

Not sure whether that is possible, but we could move that flag out:

enabledModules = [
  "networking.networkmanager"
  <nixpkgs/nixos/modules/config/pulseaudio.nix>
]

This is basically just imports = [ ... ] but it also allows using strings for modules that are included in Nixpkgs. We just need to maintain a mapping then (replacing modules-list.nix).

Each module can define a requiredModules = [] which accepts the same values as enabledModules.

@7c6f434c
Copy link
Member

By the way, if there were a large list of enable options (only) in a single file, and enabling would include the real implementation and define the rest of options (a move in the direction of Nixpkgs structure, I guess) — would this help?

@LnL7
Copy link
Member

LnL7 commented Nov 17, 2018

Since everything is global it's currently not possible to make imports conditional. The fact that a module is imported could influence the condition so you end up with recursion.

@edolstra
Copy link
Member Author

Also, a giant list of .enable options would not be very modular.

...

# Drawbacks
[drawbacks]: #drawbacks
Copy link
Member

Choose a reason for hiding this comment

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

One drawback is that it will freeze our file hierarchy as we don't have a mechanism in place to notify of file renames. Each rename would have to also include a symlink from the original to new place.

@zimbatm
Copy link
Member

zimbatm commented Nov 19, 2018

A good start would be to map the nixos options back to a file hierarchy. For example: services.xserver.enable = true; should have a file in either:

  • nixos/modules/services/xserver/enable/default.nix
  • nixos/modules/services/xserver/default.nix
  • nixos/modules/services/default.nix

Once we have that in place we could then create a new module system under a new ./nixos-zero prefix for example that resolves things differently.

@edolstra
Copy link
Member Author

This RFC should be postponed until we have the flake mechanism, because that should make it a lot easier to have dependencies on other repos and split up Nixpkgs. Most modules in NixOS could be moved into separate flakes living in their own repositories. So for example, my NixOS configuration would have a dependency on the dwarffs flake (and maybe on an xserver flake, though perhaps that's too central to NixOS to move into a separate flake).

@zimbatm
Copy link
Member

zimbatm commented Nov 19, 2018

Let's close this RFC then as it will take a different form once flake is implemented

@shlevy
Copy link
Member

shlevy commented Nov 19, 2018

👍

@edolstra edolstra closed this Nov 19, 2018
@arianvp
Copy link
Member

arianvp commented Nov 27, 2018

What is the 'flakes' mechanism? Could it be linked here for people getting on this page later?

@ryantm
Copy link
Member

ryantm commented Nov 27, 2018

@asymmetric
Copy link
Contributor

This seems to be where the actual info is.

@nixos-discourse
Copy link

This pull request has been mentioned on Nix community. There might be relevant details there:

https://discourse.nixos.org/t/minimal-set-of-nixos-modules/2328/2

@nixos-discourse
Copy link

This pull request has been mentioned on Nix community. There might be relevant details there:

https://discourse.nixos.org/t/writing-modules-for-nur/2520/6

@andir
Copy link
Member

andir commented Apr 16, 2019

Seems like there is some work in this area over at https://github.com/tweag/nix/blob/flakes/src/libexpr/primops/flake.hh :-)

@dpausp
Copy link

dpausp commented Apr 25, 2019

more information about the ongoing implementation of Nix flakes: https://gist.github.com/edolstra/40da6e3a4d4ee8fd019395365e0772e7

@roberth
Copy link
Member

roberth commented Jan 2, 2022

I've done some work on this in NixOS/nixpkgs#148456, adding the flexibility to use part of NixOS for configuring images and allowing to avoid the messy nixpkgs invocation module for example.

NixOS/nixpkgs#153211 is the first non-PoC PR. It allows experimentation with a minimal module list, refactoring towards the goal of this RFC, without breaking regular NixOS.

@roberth
Copy link
Member

roberth commented Jan 20, 2022

NixOS/nixpkgs#155892 unit-tests etc using a minimal module list.

@nixos-discourse
Copy link

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

https://discourse.nixos.org/t/restructuring-nixos-to-work-without-systemd-e-g-with-sysvinit/21298/42

@nixos-discourse
Copy link

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

https://discourse.nixos.org/t/2023-08-08-nixpkgs-architecture-team-meeting-42/31454/1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.