A friend of mine shared a surprising fact: The Linux kernel has a built-in key-value database!
keyctl add user key value @u
keyctl list @u
keyctl read <ID>
Actually, keyctl
is intended for managing the kernel keyring, not as a general purpose key-value database. That made me wonder: What other key-value databases are hiding right in plain sight? What else could we use as a database? Ideally, it should be something that’s fast, simple-to-use, and preinstalled in your favorite Linux distro.
Oh, I know! PulseAudio!
Yeah, we’re going to turn a sound server into a database now. After all, who’s stopping us?
The idea is simple. Each key-value pair will be stored as its own sink device. The key will be the name of the sink, and the value will be the volume of the sink. Due to the brilliance of the PulseAudio developers, volumes in PulseAudio don’t range between 0 to 100 but rather 0 to 2^31-1 in case you ever want to blast out your ears or your speakers. Seriously, I don’t recommend turning up the volume to 2147483647. It’s not fun. Anyways, our PulseAudioDB will only be able to store integer keys, which honestly is good enough.
The implementation is also super straightforward. Here’s some Python code, but it’s trivial to port this to any language.
class PulseAudioDB:
def __getitem__(self, key):
try:
return int(subprocess.check_output(['pactl', 'get-sink-volume', key]).split()[2])
except:
raise KeyError
def __setitem__(self, key, value):
run = lambda x: subprocess.run(x, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if run(['pactl', 'set-sink-volume', key, str(value)]).returncode:
# Key doesn't exist yet
run(['pactl', 'load-module', 'module-null-sink', 'channels=1', 'sink_name=' + key])
run(['pactl', 'set-sink-volume', key, str(value)])
Now let’s try our amazing database!
padb = PulseAudioDB()
N = 200
for i in range(N):
padb['a' + str(i)] = i
ans = 0
for i in range(N):
ans += padb['a' + str(i)]
print(ans)
This code is blazingly fast 🚀, prints the correct answer of 19900, and only takes 15.14 seconds to run for an amazing 200 reads and 200 writes. Seriously, I can’t imagine myself reading or writing something that fast! To delete all the sinks and reset the database, just run pactl unload-module module-null-sink
. PulseAudioDB is concurrent by design, so any number of processes can use it at the same time. The key-value pairs will also remain in the database if your process ends or crashes (although the database won’t survive reboots). And because PulseAudio has network transparency, PulseAudioDB might even work over a network!
PulseAudio doesn’t seem to support more than 250 sinks, but who even has that much data? 200 key-value pairs should be enough for anyone, just like how 640K of RAM ought to be enough for anyone. If you’re ever in need of a fast, simple database, look no further than PulseAudio!
By the way, if you enjoyed this post, you’ll also like MangoDB.