DevOpsil
Linux
88%
Needs Review

RHEL 9 Performance Tuning: tuned, sysctl, and CPU/Memory Optimization

Raafay AsifRaafay Asif6 min read

Performance Tuning Isn't Optional in Production

A default RHEL 9 installation is tuned for broad compatibility, not for your specific workload. That means you're leaving CPU frequency scaling on conservative settings, the VM page cache consuming RAM your database could use, and network buffers sized for 2005 hardware.

This guide covers the RHEL 9 tooling for workload-specific tuning: tuned for profile-based configuration, sysctl for kernel parameters, and targeted adjustments for database, web, and low-latency workloads.


tuned: Profile-Based Tuning

tuned is the RHEL performance tuning daemon. It applies a set of kernel and system settings based on a profile and monitors the system, dynamically adjusting when needed.

# Install and enable tuned
sudo dnf install tuned -y
sudo systemctl enable --now tuned

# Check current profile
tuned-adm active

# List available profiles
tuned-adm list

Built-in Profiles

ProfileUse Case
throughput-performanceHigh-throughput servers (databases, batch)
latency-performanceLow-latency workloads, trading systems
network-throughputHigh-bandwidth network applications
network-latencyLow-latency networking
virtual-guestVMs running inside a hypervisor
virtual-hostHypervisor hosts
balancedDefault — balance of performance and power
powersaveMinimize energy consumption
# Apply a profile
sudo tuned-adm profile throughput-performance

# Recommend a profile based on system detection
tuned-adm recommend

# Verify active tuning
tuned-adm active
tuned-adm verify

Custom tuned Profiles

For workload-specific tuning beyond what built-in profiles provide:

# Create a custom profile
sudo mkdir /etc/tuned/my-db-server
sudo cat > /etc/tuned/my-db-server/tuned.conf << 'EOF'
[main]
summary=Custom profile for PostgreSQL/MySQL database servers
include=throughput-performance

[vm]
transparent_hugepages=never

[sysctl]
vm.swappiness=10
vm.dirty_ratio=15
vm.dirty_background_ratio=5
net.core.somaxconn=65535
net.ipv4.tcp_max_syn_backlog=4096

[disk]
# readahead appropriate for random I/O workloads
readahead=256
EOF

sudo tuned-adm profile my-db-server

CPU Frequency Scaling

Modern CPUs throttle frequency to save power. For latency-sensitive workloads, you want consistent maximum frequency.

# Check available governors
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors

# Check current governor
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

# Set performance governor on all CPUs (via tuned or manually)
for cpu in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do
    echo performance | sudo tee "$cpu"
done

# Or use cpupower (from kernel-tools)
sudo dnf install kernel-tools -y
sudo cpupower frequency-set -g performance

# Verify
cpupower frequency-info | grep "current policy"

The latency-performance and throughput-performance tuned profiles both set the performance governor automatically.

Processor C-States

For very low-latency requirements, disable deeper C-states that add wake-up latency:

# Check C-state info
cpupower idle-info

# Disable C-states deeper than C1 (via kernel parameter at boot)
# Add to /etc/default/grub GRUB_CMDLINE_LINUX:
# intel_idle.max_cstate=1
# (or amd_idle.max_cstate=1 for AMD)

sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Key sysctl Parameters

Edit /etc/sysctl.d/99-performance.conf:

Network Tuning

# Increase socket buffers for high-throughput
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728

# Connection handling
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 4096
net.core.netdev_max_backlog = 5000

# TIME_WAIT tuning
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# Enable BBR congestion control (RHEL 9 supports it)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

Memory / VM

# Prefer RAM over swap
vm.swappiness = 10

# Dirty page write-back (tune for your storage latency)
vm.dirty_ratio = 20
vm.dirty_background_ratio = 5

# Increase virtual memory areas for JVM/Elasticsearch
vm.max_map_count = 262144

# File descriptor limits
fs.file-max = 1000000

Apply immediately:

sudo sysctl --system
sysctl net.ipv4.tcp_congestion_control

Transparent Huge Pages

THP (Transparent Huge Pages) reduces TLB pressure for some workloads but causes latency spikes in databases (MySQL, PostgreSQL, Redis, MongoDB all recommend disabling it).

# Check current state
cat /sys/kernel/mm/transparent_hugepage/enabled

# Disable THP immediately
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

# Persist via tuned.conf (shown above) or via rc.local / systemd unit

For HPC or in-memory workloads (JVM heap), THP may improve performance — test with your specific workload.


NUMA Topology Awareness

On multi-socket servers, NUMA (Non-Uniform Memory Access) topology significantly affects performance. Memory access to a remote NUMA node is 2-3x slower.

# Install NUMA tools
sudo dnf install numactl -y

# Check NUMA topology
numactl --hardware

# Run a process on a specific NUMA node
numactl --cpunodebind=0 --membind=0 -- ./my-app

# Check NUMA stats (memory allocations per node)
numastat
numastat -p <pid>

# For databases: pin to a NUMA node
# PostgreSQL: start with numactl
# Redis: set numa_node in config if supported

NUMA Balancing

RHEL 9 includes automatic NUMA balancing (numad). For latency-critical workloads, pin manually and disable auto-balancing:

# Disable automatic NUMA balancing
echo 0 | sudo tee /proc/sys/kernel/numa_balancing

# Or via sysctl
echo "kernel.numa_balancing = 0" | sudo tee -a /etc/sysctl.d/99-numa.conf

IRQ Affinity

On high-traffic servers, spreading network interrupt handling across CPU cores prevents bottlenecks on a single core.

# Install irqbalance
sudo dnf install irqbalance -y
sudo systemctl enable --now irqbalance

# For manual control (high-performance NICs like Intel X710)
# Check current IRQ affinity
cat /proc/interrupts | grep eth0

# Set IRQ affinity to specific CPU mask
echo "ff" | sudo tee /proc/irq/<irq_number>/smp_affinity

Disk I/O Scheduling

RHEL 9 uses mq-deadline or none (for NVMe) by default. For specific workloads:

# Check current scheduler for a disk
cat /sys/block/sda/queue/scheduler

# For databases on SSDs/NVMe — none (bypass scheduler)
echo none | sudo tee /sys/block/nvme0n1/queue/scheduler

# For spinning disks with mixed I/O — mq-deadline
echo mq-deadline | sudo tee /sys/block/sda/queue/scheduler

# Adjust read-ahead (lower for random, higher for sequential)
blockdev --setra 256 /dev/sda

Profiling and Measurement

Don't tune blind — measure first:

# System-wide CPU/IO/memory stats
vmstat 1 10
iostat -xz 1 5
mpstat -P ALL 1 5

# Network stats
ss -s
netstat -s | grep -i retransmit

# Per-process profiling
sudo dnf install perf -y
sudo perf stat -a sleep 5     # system-wide counters
sudo perf top                  # live perf counter view

# Flame graphs (for CPU profiling)
sudo perf record -ag -- sleep 10
sudo perf report

Quick Reference by Workload

Workloadtuned ProfileKey Settings
PostgreSQL/MySQLthroughput-performanceTHP=never, swappiness=10, dirty_ratio=15
Redis/Memcachedlatency-performanceTHP=never, TCP_NODELAY, cpu governor=performance
Nginx/HAProxynetwork-throughputtcp_rmem/wmem large, somaxconn=65535
Kubernetes nodethroughput-performancemax_map_count=262144, file-max=1000000
Low-latency tradinglatency-performanceC-states=1, NUMA pinned, IRQ affinity set

Tune iteratively: apply one change, measure the impact, then proceed. A profile that helps under load testing may behave differently in production with real access patterns.

Share:

Was this article helpful?

Raafay Asif
Raafay Asif

Linux Systems Engineer

Everything runs on Linux — I make sure it runs well. From kernel tuning to systemd debugging, I live in the terminal. If your server is misbehaving, I've probably seen that exact dmesg output before.

Related Articles

More in Linux

View all →

Discussion