Configure OOM killer

Maybe I should ask this on a separate thread, do you have any idea how to control which apps the OOM killer kills? While I agree, zram is amazing, and after using a dedicated swap partition for ages, I completely removed it this year, and have had no issues, except for some rare circumstances.

I do some software development. Sometimes when I’m compiling a project that is particularly demanding on memory, e.g. C++ with lots of templates, the OOM killer strikes, and it’s usually my browser. I would rather have the compilation fail early than my browser go down. Lately, the whole browser doesn’t get killed, but specific tabs (it’s probably one of the WebContent worker threads that gets killed).

I web searched for “linux configure oom” and found this https://www.baeldung.com/linux/memory-overcommitment-oom-killer that looks like it gives you the info you need.

I investigated this a few years ago, the bottom line was (also described in the article linked by @barryascott) systemd-oomd uses pressure metrics. But I don’t know how I can translate my user level wish on the desktop to the pressure metrics.

An example

I was working on my high memory requirement project yesterday, and I deliberately ran an unconstrained compilation (cmake --build build-dir, no -jN flag to limit the cores). Unlike last time (from the thread above), my desktop didn’t crash, my browser wasn’t killed either. In fact, my compilation processes were killed. However I think there are a few differences between what I was doing before and yesterday.

  1. I was on XFCE before, yesterday’s test was on KDE
  2. Previously I was compiling bare metal (open a terminal, and compile), whereas yesterday I was compiling inside a Fedora toolbox.

Another observation, the OOM killer killed the compiler process, not the top-level build process (cmake/make/ninja), so my build actually moved on to the next command. I guess it would stop at some point, because at some point build dependencies won’t be satisfied. But this kinda extends the “sluggish” experience for the user a bit longer.

Hypothesis

I think the 2nd point is the important distinction. From the footnote of the old thread:

man oomd.conf says “… in which all tasks in the control group were delayed.

I don’t really understand cgroups, my naive assumption would be a container is a different cgroup. So maybe the learning is if you anticipate memory issues, run the application containerised, e.g. use toolboxes, or run a flatpak variant of a desktop application. That way rest of your desktop isn’t affected.

Other than this, I think I should also play with the pressure metrics to trigger the killer a bit earlier. During my experiment yesterday, my desktop had effectively “frozen” for ~5-7 mins. If this wasn’t a experiment, probably I would have hard rebooted instead of waiting for the OOM killer to kick-in.

PS: I can do a control experiment later on my desktop where I compile on bare metal.

EDIT: When I repeat my experiment on bare metal, my whole desktop goes down in half a minute!

Hi,

it looks like, that you need:

  • more RAM memory - a bit expensive
  • better: swap on raw partition, or really worse way: as file in fs. You could configure /etc/sysctl.conf ( now it is /etc/sysctl.d/99-vmswap.conf ) parameter vm.swappines to lower value (default is 60%) and swap (pagespace) will be use only in emergency situation…
  • oomkiller is necessary part of OS: there is better to automatically kill any aplication, then OS colaps

Hi,

sorry I forget, that it is more parameters, than vm.swappinest is. There is my /etc/sysctl.d/99-vmswap.conf:

vm.swappiness = 0 " it is very agerssive"
vm.vfs_cache_pressure=50

vm.dirty_writeback_centisecs = 1500

vm.dirty_ratio = 20

vm.dirty_background_ratio = 10

This is for smal and old notebook.

Again, for pagespace: I think, that you have more, then one swap (no limit). it means, that the first could be zram and the second could be standard swap - another is only about priority. You could set up in /etc/fstab. This is problem of VMM (Virtual Memory Management), which

really very complicated…

I don’t think swap is a solution. It only delays the problem for a few minutes.

These are my machine specs, laptop where I did the experiment inside a toolbox:

$ inxi
CPU: 12-core (4-mt/8-st) 12th Gen Intel Core i7-1270P (-MST AMCP-)
speed/min/max: 400/400/4800:3500 MHz Kernel: 7.1.10-200.fc44.x86_64 x86_64 Up: 18h 56m
Mem: 12.37/31 GiB (39.9%) Storage: 476.94 GiB (27.0% used) Procs: 485 Shell: Bash inxi: 3.3.41
$ zramctl
NAME       ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle         8G   4K   80B   12K         [SWAP]

Desktop where I compile bare metal:

$ inxi
CPU: 8-core AMD Ryzen 7 5800X (-MT MCP-) speed/min/max: 1754/556/4854 MHz
Kernel: 7.1.10-200.fc44.x86_64 x86_64 Up: 8h 30m Mem: 5.51/31.23 GiB (17.6%)
Storage: 25.51 TiB (16.2% used) Procs: 482 Shell: Bash inxi: 3.3.41
$ zramctl
NAME       ALGORITHM DISKSIZE   DATA COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle         8G 110.7M 39.3M 126.8M         [SWAP]

Right now I only have zram, but previously (when I started the older thread), I was using the same desktop, but I used to have an additional 16GB dedicated swap (8 + 16 = 24GB total)!

Thanks for your suggested parameters, I’ll see if I can adapt them to my hardware. As you can see, your hardware and my hardware are quite different. The way I see it, the main issue is even if I have a large RAM it doesn’t help because my RAM/core is small because of high core counts in modern CPUs.

In the old thread I was having issues with an application that required a lot of memory, that application has improved, and I don’t have that issue any more. But now I face a different issue, but I do have a workaround (running inside containers). I find it very difficult to express these needs by playing with parameter values.

If you added a 64GiB swap file that would allow large jobs to run, but with the paging in and out of the swap file it will be slow. I’d be surprised if you saw the OOM Killer do anything.

I don’t think you have given enough information about your intended use to answer whether swap is the best solution. In many cases it is.

I think trying to get the OOM killer to make a better choice about which process to kill would be a very poor solution, even if you found a way to do that.

I think zram is just a good solution for the majority of users who hardly need any swap space at all (neither traditional nor zram). If you look at the whole memory management issue together (caching and anonymous memory AND non anonymous memory) zram speeds up the paging of anonymous (the only part of memory that uses swap space to page) but overall is more likely to slow things down.

For those who might use a lot of memory, a big traditional swap file is a better safety net for using too much than expecting the OOM killer to make good choices. Yes, your system slows way down when you have used too much memory, That is pretty painful. But I can’t believe having processes killed by the OOM killer would be less painful.

The one big memory load you mentioned was running a build action with too many processes. If it chooses a default that is too many, you should know to override that. The point of those threads is to build faster, not to work all your cores. There are many reasons why using less than all of them might build faster than using all. Even if you had enough ram for all those processes, the CPU also has shared internal cache. In many cases, that cache is the key performance resource, making more total work get done per second when you use fewer cores to get better cache locality. All that is subtle compared to not having enough ram to support that many processes. I just wanted to point out that using all the cores isn’t as great an idea as you might think.

You have not given any reason justifying the statement. My wish is “simple” (I know implementation might be difficult, hence the quotes), I want the application that is causing the memory pressure to be killed, rather than something else which might be significant in memory footprint, but is not actually actively growing.

My previous case (from the old thread) the culprit was a poorly optimised desktop application, that I cannot reveal due to privacy reasons. I would have expected that application to be killed instead of my whole desktop. In the current case, it’s a C++ build job. Here I have much more control, and indeed now after a few crashes I have found the right amount of threads to control it. But it is still unsatisfactory to me that my whole desktop gets killed instead of the build job that is the culprit.

Your argument about not using as many cores as possible for a build job is quite odd. Compilation jobs are inherently parallel and is the classic case of a multi-threaded application. So much so, Chromium code compilation is the most widely used benchmark for multi-threaded systems (see any phoronix CPU review).

In that case why not have that app quit before you do the build seems the easy fix.
Am I missing something?

Sorry, maybe I wasn’t clear. These are two different instances, 4 yrs apart.

  • 2022: I used a desktop application that had memory issues. I had to keep it running even though I was not actively using it. So it would happen that I’m doing something else, but because of that application growing in memory, my whole desktop would go down, or some other application like Firefox would get killed. In this case, I would have preferred killing the application and restarting it. This application has had fixes since then, I still use it without any memory issues.

    This is also why I setup a pretty large swap back then (8 + 16 = 24 GB, for 32 GB RAM). Only recently I removed the swap drive (it was an Intel Optane SSD) because I needed the NVMe slot for an actual SSD, and I noticed in recent months the swap barely being used.

  • now (since 2025): I started working on a project (I don’t have autonomy to propose large changes) that has a really unoptimised build system. So if I let the build run unconstrained, the memory fills up very fast and crashes the system. There are about 900-1200 build steps depending on build flags, so I do need as many processes as I can get. But as mentioned above, through experimentation I have found the number to limit it where nothing crashes. However it is fragile, because the solution changes depending on hardware, and state of the system, e.g. if I have nothing else running, vs freshly starting my browser, vs my browser running for a long time, or running Microsoft Teams, etc. The number of threads I can use varies depending on the conditions, so while the workaround “works”, it is fragile. Also, sometimes I just forget to add the flag, and realise it too late. On my desktop the time to crash is <30 secs before I realise my mistake.

I’ve had the second case happen to me in other situations as well. E.g. I was using an M1 Macbook for a while with Asahi linux, and even compiling a very small project would sometimes lead to full desktop crashes. And it was not very deterministic. In that case sometimes the compilation would actually succeed!

Project builds vary a lot, so I can’t know for sure. But I expect that using the max number of threads (that doesn’t cause OOM failures) is not a good performance choice and you should use even fewer than that for better performance.

Your view that more threads are better just because the actions are independent, is very simplistic and likely to misguide you.

Long ago I was helping customers of a very complicated simulator system improve performance (despite my working for management that had poorly thought out priorities, not including product performance). That product let the customer choose the number of threads (because management wouldn’t let me spend the large amount of time needed to make the product itself choose wisely). It was very hard to convince customers to use fewer threads (typically best performance at a quarter of total hardware threads). That was due to the behavior of the internal cache in the CPU. That factor can also cause a build operation to take less wall time with fewer threads (not likely for a build operation, but possible).

The bigger factor is file system cache:

Slightly oversimplifying, there are three basic memory uses contending in your build operation: “anonymous” memory is what would use zram or traditional swap space (subject to parameters such as swappiness) if there is too much memory pressure. Mapped memory (including, but not limited to the executable image and shared libraries) is paged without using zram or traditional swap space. The third very important type is file cache (most importantly all the include files, especially if precompiled, that are repeatedly reused by your build operation.

Using more threads, even if it doesn’t cause too much swapping of that anonymous memory, still means having more anonymous memory, causing more paging of the other two types and increasing the elapsed (wall) time needed.

Okay, I follow this argument. But then I don’t know how to use this information effectively. This particular project has more than 700 header files (with a build flag to precompile them), over 250 of them has C++ templates. On the other hand it also has about 1k build steps. So in the regime of low thread count I doubt it matters. I suspect it will matter if I go higher than 10. Last year I did a simple comparison, going from 2 to 4 threads, build time approximately halved. My system starts crashing at >6. I think when running headless I can do 8 threads on the desktop. And build time difference between 4 or more than 4 is very clearly visible to me.

Your choices seems obvious to me.

  1. Add more memory
  2. Limit the number of parallel build threads.

Changing OOM is not going to help as you can simply close all apps that before running a build if you want to maximise available RAM.

I have been looking at memory sticks - possibly using CXMT chips - for the last 2 months, still not within a reasonable price. My problem is doubly worse because currently I have 4x8GB sticks, so can’t just add more.

From reading the press reporting I doubt that memory prices will fall for atleast 2-3years.
That’s the time frame for more memory FAB capacity to start going on line.

Based on lots of experience plus the information you have given, I would not expect 6 threads to be faster than 4. But you seem to be saying you’ve tried that and it is. Test are better than theories, so I stand corrected.

Next theory: When having more threads in a build makes more difference than one would ordinarily expect, that typically means the bottleneck is writing the intermediate files, produced and later used by the build (typically .o files and/or libraries of them). Do you have any flexibility in the choice of drive for those files (I understand that adding ram is cost prohibitive, so likely adding another high speed ssd drive is worse, but I’m asking just in case).

On systems where memory pressure is not the dominant factor, I found that reducing the thread count of a build while putting the intermediate files on a ram drive, gave lower elapsed time than having more threads. In your case, I can’t predict the balance in ram saved by less anonymous memory against the cost of a ram drive. Likely lots of effort for no gain, but I thought a possibility worth mentioning.

On my laptop (a ThinkPad) I have a Samsung OEM NVMe SSD, on the desktop I have a Lexar NVMe SSD.

To attempt this, would I have to put my build directory on a RAM disk? I just measured the size of my build directory, ~10 GB. I guess that’d be unviable then.

Hi,

I’m sorry, but if you don’t buy more RAM memory you should have to set up standard swap. No another way…

I didn’t think what you were asking for was either practical or wise. So I gave the usual answer “address the more basic problem some other way”. Usually I hate it when I get those answers (though on occasion they are helpful). Anyway you made a good enough case for what you want.

So I googled how to actually do what you requested. The basic shape of an answer is to increase the “nice” value of the build operation. But there seem to be cgroup complications I don’t really understand, so this is just a direction, not a complete answer (and the sources I read could easily be wrong. I haven’t tested any of this).

I assume you are NOT running some low priority CPU hog background activity that would take CPU time away from the build if the build had a higher nice value. I also assume that you don’t mind any short term GUI CPU hog (your browser etc.) slowing the build even more than it currently does. So a massive change in the nice value of the build probably has little if any negative consequence.

IIUC, the OOM-killer favors killing process with high nice values. If that bias is enough, that would be much simpler than digging deeper into customizing OOM.

I still think your idea about memory growth patterns (rather than memory use) has far more difficulty and pitfalls than you expect. So this is still somewhat of a “do this instead” answer. But maybe closer to what you want.