View a markdown version of this page

Limiting process resource usage in AL2027 using systemd - Amazon Linux 2027
Services or capabilities described in Amazon Web Services documentation might vary by Region. To see the differences applicable to the China Regions, see Getting Started with Amazon Web Services in China (PDF).

Limiting process resource usage in AL2027 using systemd

AL2027 Preview

AL2027 is currently available for preview. It is intended for evaluation and testing only and is not recommended for production workloads.

On Amazon Linux 2027 (AL2027), we recommend using systemd to control what resources can be used by processes, or groups of processes. Using systemd is a powerful and easy to use replacement for either manipulating cgroups manually, or using utilities such as cpulimit, which was previously only available for Amazon Linux in a third party repository.

For comprehensive information, see the upstream systemd documentation for systemd.resource-control on the freedesktop.org website, or the man page for systemd.resource-control on an AL2027 instance.

The following examples will use the stress-ng CPU stress test (from the stress-ng package) to simulate a CPU heavy application, and memcached to simulate a memory heavy application.

The following examples cover placing a CPU limit on a one-off command and a memory limit on a service. Most resource constraints that systemd offers can be used in any place that systemd will run a process, and multiple can be used at the same time. The following examples are limited to a single constraint for illustrative purposes.

Resource control with systemd-run for running one-off commands

While commonly associated with system services, systemd can also be used by non-root users to run services, schedule timers, or run one-off processes. In the following example, we are going to use stress-ng as our example application. In the first example, we will run it using systemd-run in the ec2-user default account, and in the second example we will place limits on its CPU usage.

Example Use systemd-run on the command line to run a process, not limiting resource usage
  1. Ensure the stress-ng package is installed, as we are going to use it for our example.

    sudo dnf install -y stress-ng
  2. Use systemd-run to execute a 10 second CPU stress test without limiting how much CPU it can use.

    systemd-run --user --tty --wait stress-ng --cpu 1 --timeout 10 Running as unit: run-p66001-i57336.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [66004] setting to a 10 secs run per stressor stress-ng: info: [66004] dispatching hogs: 1 cpu stress-ng: info: [66004] successful run completed in 10.00 secs Finished with result: success Main processes terminated with: code=exited, status=0/SUCCESS Service runtime: 10.012s CPU time consumed: 9.987s Memory peak: 3M (swap: 0B)

    The --user option tells systemd-run to execute the command as the user we are logged in as, the --tty option means a TTY is attached, and --wait means to wait until the service is finished. The --property command line option can be used to pass systemd-run settings that could be configured in a systemd.unit configuration file.

When instructed to place load on the CPU, the stress-ng program will use all available CPU time to perform its test for the duration you ask it to run. For a real-world application, it may be desirable to place a limit on total run-time of a process. In the following example, we will ask stress-ng to run for a longer time than the maximum duration restriction we place on it using systemd-run.

Example Use systemd-run on the command line to run a process, limiting CPU usage to 1 second
  1. Ensure the stress-ng is installed to run this example.

  2. The LimitCPU property is the equivalent of ulimit -t which will limit the maximum amount of time on the CPU this process will be allowed to use. In this case, since we are asking for a 10 second stress run, and we are limiting the CPU usage to 1 second, the command receives a SIGXCPU signal after roughly 1 second of CPU time and stops early.

    systemd-run --user --tty --wait --property=LimitCPU=1 stress-ng --cpu 1 --timeout 10 Running as unit: run-p68466-i67823.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [68469] setting to a 10 secs run per stressor stress-ng: info: [68469] dispatching hogs: 1 cpu stress-ng: warn: [68469] metrics-check: all bogo-op counters are zero, data may be incorrect stress-ng: info: [68469] successful run completed in 1.01 sec Finished with result: success Main processes terminated with: code=exited, status=0/SUCCESS Service runtime: 1.020s CPU time consumed: 1.017s Memory peak: 3.1M (swap: 0B)

    The service runtime and CPU time consumed confirm that the SIGXCPU signal stopped the process at roughly 1 second, instead of allowing the full 10 second run to complete.

More commonly, you may want to restrict the percentage of CPU time that can be consumed by a particular process. In the following example, we will restrict the percentage of CPU time that can be consumed by stress-ng. For a real-world service, it may be desirable to limit the maximum percentage of CPU time a background process can consume in order to leave resources free for the process serving user requests.

Example Use systemd-run to limit a process to 10% of CPU time on one CPU
  1. Ensure the stress-ng is installed to run this example.

  2. We are going to use the CPUQuota property to tell systemd-run to constrain CPU usage for the command we are going to run. We are not limiting the amount of time the process can run for, just how much CPU it can use.

    systemd-run --user --tty --wait --property=CPUQuota=10% stress-ng --cpu 1 --timeout 10 Running as unit: run-p66058-i66947.service Press ^] three times within 1s to disconnect TTY. stress-ng: info: [66059] setting to a 10 secs run per stressor stress-ng: info: [66059] dispatching hogs: 1 cpu stress-ng: info: [66059] successful run completed in 10.03 secs Finished with result: success Main processes terminated with: code=exited, status=0/SUCCESS Service runtime: 10.069s CPU time consumed: 1.019s Memory peak: 3.1M (swap: 0B)

    Note how the CPU accounting tells us that while the service ran for 10 seconds, it only consumed 1 second of actual CPU time.

There are many ways to configure systemd to limit resource usage for CPU, memory, networking, and IO. See the upstream systemd documentation for systemd.resource-control on the freedesktop.org website, or the man page for systemd.resource-control on an AL2027 instance for comprehensive documentation.

Behind the scenes, systemd is using features of the Linux kernel such as cgroups to implement these limits while avoiding the need for you to configure them by hand. AL2027 uses cgroup v2. The Linux Kernel documentation for cgroup-v2 on the kernel.org website contains extensive details about how cgroups work.

Resource control in a systemd service

There are several parameters that can be added to the [Service] section of systemd services to control system resource usage. These include both hard and soft limits. For the exact behavior of each option, refer to the upstream systemd documentation for systemd.resource-control on the freedesktop.org website, or the man page for systemd.resource-control on an AL2027 instance.

Commonly used limits are MemoryHigh to specify a throttling limit on memory usage, MemoryMax to set a hard upper limit (which, once reached, the OOM Killer is invoked), and CPUQuota (as illustrated in the previous section). It is also possible to configure weights and priorities rather than fixed numbers.

Example Using systemd to set memory usage limits on services

In this example we will set a hard memory usage limit for memcached, a simple key-value cache, and show how the OOM Killer is invoked for that service rather than the whole system.

  1. First, we need to install the packages required for this example.

    sudo dnf install -y memcached libmemcached-awesome-tools
  2. Enable the memcached.service and then start the service so that memcached is running.

    sudo systemctl enable memcached.service Created symlink /etc/systemd/system/multi-user.target.wants/memcached.service → /usr/lib/systemd/system/memcached.service. sudo systemctl start memcached.service
  3. Check that memcached.service is running.

    sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf Active: active (running) since Fri 2026-08-28 04:29:12 UTC; 234ms ago Invocation: 14415487cabc478c9262147770c975d6 Main PID: 66798 (memcached) Tasks: 10 (limit: 18904) Memory: 1.9M (peak: 3.4M) CPU: 25ms CGroup: /system.slice/memcached.service └─66798 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1
  4. Now that memcached is installed and running, we can observe that it functions by inserting some random data into the cache.

    In /etc/sysconfig/memcached the CACHESIZE variable is set to 64 by default, meaning 64 megabytes. By inserting more data into the cache than the maximum cache size, we can see that we fill the cache and some items are evicted using memcached-tool, and that the memcached.service is using around 64MB of memory.

    for i in $(seq 1 150); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done memcached-tool localhost display # Item_Size Max_age Pages Count Full? Evicted Evict_Time OOM 2 120B 0s 1 0 no 0 0 0 39 512.0K 1s 63 126 yes 24 1 0 sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf Active: active (running) since Fri 2026-08-28 04:37:08 UTC; 2s ago Invocation: f0a6ec3dbe06466c92f762fa3a1f59d6 Main PID: 68690 (memcached) Tasks: 10 (limit: 18904) Memory: 66.8M (peak: 67.7M) CPU: 133ms CGroup: /system.slice/memcached.service └─68690 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1
  5. Use the MemoryMax property to set a hard limit for the memcached.service where, if hit, the OOM Killer will be invoked. Additional options can be set for the service by adding them to an override file. This can be done either by directly editing the /etc/systemd/system/memcached.service.d/override.conf file, or interactively using the edit command of systemctl.

    sudo systemctl edit memcached.service

    Add the following to the override to set a hard limit of 32MB of memory for the service.

    [Service] MemoryMax=32M
  6. Tell systemd to reload its configuration.

    sudo systemctl daemon-reload
  7. Observe that the memcached.service is now running with a memory limit of 32MB.

    sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf /etc/systemd/system/memcached.service.d └─override.conf Active: active (running) since Fri 2026-08-28 04:37:35 UTC; 271ms ago Invocation: 2980a4cea0af4014b452c5d011c1b33e Main PID: 69347 (memcached) Tasks: 10 (limit: 18904) Memory: 1.9M (max: 32M, available: 30M, peak: 3M) CPU: 21ms CGroup: /system.slice/memcached.service └─69347 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Aug 28 04:37:35 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  8. The service will function normally while using less than 32MB of memory, which we can check by loading less than 32MB of random data into the cache, and then checking the status of the service.

    for i in $(seq 1 30); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done
    sudo systemctl status memcached.service ● memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf /etc/systemd/system/memcached.service.d └─override.conf Active: active (running) since Fri 2026-08-28 04:37:35 UTC; 7s ago Invocation: 2980a4cea0af4014b452c5d011c1b33e Main PID: 69347 (memcached) Tasks: 10 (limit: 18904) Memory: 18.3M (max: 32M, available: 13.6M, peak: 19.3M) CPU: 47ms CGroup: /system.slice/memcached.service └─69347 /usr/bin/memcached -p 11211 -u memcached -m 64 -c 1024 -l 127.0.0.1,::1 Aug 28 04:37:35 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon.
  9. We can now make memcached to use more than 32MB of memory by attempting to use the full 64MB of cache that the default memcached configuration is.

    for i in $(seq 1 150); do dd if=/dev/random of=$i bs=512k count=1; memcp -s localhost $i; done

    You will observe that at some point during the above command there are connection errors to the memcached server. This is because the OOM Killer has killed the process due to the restriction we placed on it. The rest of the system will function as normal, and no other processes will be considered by the OOM Killer, as it is only the memcached.service that we have restricted.

    sudo systemctl status memcached.service × memcached.service - memcached daemon Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset: disabled) Drop-In: /usr/lib/systemd/system/service.d └─10-timeout-abort.conf /etc/systemd/system/memcached.service.d └─override.conf Active: failed (Result: oom-kill) since Fri 2026-08-28 04:37:50 UTC; 976ms ago Duration: 14.899s Invocation: 2980a4cea0af4014b452c5d011c1b33e Process: 69347 ExecStart=/usr/bin/memcached -p ${PORT} -u ${USER} -m ${CACHESIZE} -c ${MAXCONN} $OPTIONS (code=killed, signal=KILL) Main PID: 69347 (code=killed, signal=KILL) Mem peak: 32M CPU: 149ms Aug 28 04:37:35 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: Started memcached.service - memcached daemon. Aug 28 04:37:50 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: memcached.service: The kernel OOM killer killed some processes in this unit. Aug 28 04:37:50 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: memcached.service: Main process exited, code=killed, status=9/KILL Aug 28 04:37:50 ip-172-31-37-206.us-west-2.compute.internal systemd[1]: memcached.service: Failed with result 'oom-kill'.