contrib | ||
deps | ||
docs | ||
include/deps/cmph | ||
src | ||
.gitignore | ||
.gitmodules | ||
build.zig | ||
LICENSE | ||
README.md |
Turbo NSS
Turbonss is a plugin for GNU Name Service Switch (NSS)
functionality of GNU C Library (glibc). Turbonss implements lookup for user
and passwd
database entries (i.e. system users, groups, and group
memberships). It's main goal is to run id(1)
as fast as possible.
Turbonss is optimized for reading. If the data changes in any way, the whole file will need to be regenerated. Therefore, it was created, and best suited, for environments that have a central user & group database which then needs to be distributed to many servers/services, and the data does not change very often.
This is the fastest known NSS passwd/group implementation for reads. On my
2018-era laptop a corpus with 10k users, 10k groups and 500 average members per
group, id
takes 17 seconds with the glibc default implementation, 10-17
milliseconds with a pre-cached nscd
, ~8 milliseconds with uncached
turbonss
.
Due to the nature of being built with Zig, this will work on glibc versions as old as 2.16 (may work with even older ones, I did not test beyond that).
Project goals
- Make it as fast as possible. Especially optimize for the
id
command. - Small database size (helps making it fast).
- No runtime, no GC, as little as possible overhead.
- Easy to compile for ancient glibc versions (comes out of the box with Zig).
Project status and known deficiencies
Turbonss works, but, to the author's knowledge, was not deployed to production. If you want to use turbonss instead of a battle-tested, albeit slower nscd, keep the following in mind:
- turbonss has not been fuzz-tested, so it will crash a program on invalid
database file. Please compile with
ReleaseSafe
. It is plenty fast with this mode, but an invalid database will lead to defined behavior (i.e. crash with a stack trace) instead of overwriting memory wherever. - if the database file was replaced while the program has been running, turbonss will not re-read the file (it holds to the previous file descriptor).
- requires a nightly version of zig (that will change with 0.11).
The license is permissive, so feel free. I am also available for consulting to fix all or part of those above, if that's your preference.
Dependencies
- zig around
0.11.0-dev.2560+602029bb2
. - cmph: bundled with this repository.
Building
Clone, compile and run tests first:
$ git clone --recursive https://git.jakstys.lt/motiejus/turbonss
$ zig build test
$ zig build -Dtarget=x86_64-linux-gnu.2.16 -Doptimize=ReleaseSafe
One may choose different options, depending on requirements. Here are some hints:
-Dcpu=<...>
for the CPU microarchitecture.-Dstrip=true
to strip debug symbols.
For reference, size of the shared library and helper binaries when compiled
with -Dstrip=true -Drelease-small=true
:
28K zig-out/bin/turbonss-analyze
20K zig-out/bin/turbonss-getent
24K zig-out/bin/turbonss-makecorpus
140K zig-out/bin/turbonss-unix2db
24K zig-out/lib/libnss_turbo.so.2.0.0
Many thanks to Ulrich Drepper for teaching how to link it properly.
Demo
turbonss is best tested, of course, with many users and groups. The guide below will show how to synthesize 10k users, 10k groups with an avereage membership of 1k users per group, and test ubernss with such corpus.
-
Synthesize some users and groups to
passwd
andgroup
in the current directory:$ zig-out/bin/turbonss-makecorpus wrote users=10000 groups=10000 avg-members=1000 to . $ ls -1hs passwd group 48M group 668K passwd
-
Convert the generated
passwd
andgroup
to the turbonss database. Note thedb.turbo
database is more than 4 times smaller than the textual one:$ zig-out/bin/turbonss-unix2db --group group --passwd passwd total 10968064 bytes. groups=10000 users=10000 $ ls -1hs db.turbo 11M db.turbo
-
Optional: inspect the freshly created database:
$ zig-out/bin/turbonss-analyze db.turbo File: db.turbo Size: 10,968,064 bytes Version: 0 Endian: little Pointer size: 8 bytes getgr buffer size: 18000 getpw buffer size: 57 Users: 10000 Groups: 10000 Shells: 4 Most memberships: u_1000000 (501) Sections: Name Begin End Size bytes header 00000000 00000080 128 bdz_gid 00000080 00000e40 3,520 bdz_groupname 00000e40 00001c00 3,520 bdz_uid 00001c00 000029c0 3,520 bdz_username 000029c0 00003780 3,520 idx_gid2group 00003780 0000d3c0 40,000 idx_groupname2group 0000d3c0 00017000 40,000 idx_uid2user 00017000 00020c40 40,000 idx_name2user 00020c40 0002a880 40,000 shell_index 0002a880 0002a8c0 64 shell_blob 0002a8c0 0002a900 64 groups 0002a900 00065280 240,000 users 00065280 000da580 480,000 groupmembers 000da580 005a69c0 5,030,976 additional_gids 005a69c0 00a75c00 5,042,752 $ zig-out/bin/turbonss-getent --db db.turbo passwd u_1000000 u_1000000:x:1000000:1000000:User 1000000:/home/u_1000000:/bin/bash $ zig-out/bin/turbonss-getent --db db.turbo group g_1000003 g_1000003:x:1000003:u_1000002,u_1000003,u_1000004
-
Now since we will be messing with the system, run all following commands in a container:
$ docker run -ti --rm -v `pwd`:/etc/turbonss -w /etc/turbonss debian:bullseye # cp zig-out/lib/libnss_turbo.so.2 /lib/x86_64-linux-gnu/
-
Instruct
nsswitch.conf
to use both turbonss and the standard resolver:# sed -i '/passwd\|group/ s/files/turbo files/' /etc/nsswitch.conf # time id u_1000000 <...> real 0m0.006s user 0m0.000s sys 0m0.008s
The id
call resolved u_1000000
from db.turbo
.
-
Compare the performance to plain
files
(that is, without turbonss):# sed -i '/passwd\|group/ s/turbo files/files/' /etc/nsswitch.conf # cat passwd >> /etc/passwd # cat group >> /etc/group # time id u_1000000 <...> real 0m17.164s user 0m13.288s sys 0m3.876s
Over 2500x difference.
Documentation
- Architecture is detailed in
docs/architecture.md
- Development notes are in
docs/development.md