Mastering HugePages and Transparent HugePages on Linux for PostgreSQL and Redis

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Context & Why

Six months ago, our primary database cluster hit an invisible wall. CPU utilization hovered comfortably at 42%, NVMe disk I/O had massive headroom, yet p99 query latency climbed from 12ms to past 180ms during midday traffic peaks.

Meanwhile, our Redis caching tier regularly threw 200ms latency spikes whenever background snapshots kicked off. We dug through kernel counters and perf profiles. The culprit was clear: memory translation overhead and massive Translation Lookaside Buffer (TLB) thrashing.

By default, Linux slices virtual memory into 4 KB pages. That works well for lightweight utilities, but big databases break this model quickly. When you assign 64 GB of RAM to database shared memory, the kernel must track 16 million distinct page entries. Modern CPUs cannot keep that many translations in fast L1 and L2 TLB caches. Every miss forces an expensive multi-step walk through main memory, dragging query performance down with it.

Linux gives us two ways to handle large memory footprints:

  • Static HugePages: Memory pre-allocated in 2 MB or 1 GB blocks at boot or early runtime. Applications map this memory directly into their virtual address spaces. PostgreSQL runs exceptionally well with static HugePages. Because its shared buffer pool size is static, HugePages shrink page tables from millions of entries down to a few thousand. In our setup, this single change reduced TLB miss rates by 85%.
  • Transparent HugePages (THP): An automated kernel subsystem that attempts to dynamically allocate 2 MB pages behind the scenes. It requires zero application refactoring. However, THP causes havoc with Redis. When Redis forks a background process for RDB dumps or AOF rewrites, Linux relies on Copy-on-Write (CoW). Under standard 4 KB pages, writing a single key duplicates only 4 KB. With THP enabled, touching that same key forces the kernel to copy an entire 2 MB chunk. That stalls the Redis event loop and balloons memory consumption by 30% to 50%.

Virtual memory tuning touches the core of the Linux kernel. A misconfigured parameter can trigger immediate Out-of-Memory (OOM) killer terminations or block system boots. Always benchmark these changes in staging before rolling them out to production instances.

Installation

Mainline distributions like Debian, Ubuntu, Rocky Linux, and AlmaLinux include HugePages support right in the kernel. You do not need third-party kernel modules. You do, however, need userland tools to inspect memory topology, check page status, and monitor NUMA node balance.

Install the standard memory diagnostics packages for your distribution:

# On Debian / Ubuntu
sudo apt-get update
sudo apt-get install -y procps numactl sysfsutils

# On RHEL / AlmaLinux / Rocky Linux
sudo dnf install -y procps-ng numactl sysfsutils

Check your system’s current HugePages allocation and default page size:

grep -i huge /proc/meminfo

On an unconfigured host, you will see output like this:

AnonHugePages:         0 kB
ShmemPagemap:          0 kB
FileHugePages:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB

The kernel reports a default Hugepagesize of 2048 kB (2 MB). Notice HugePages_Total is set to 0. No static pages are reserved yet.

Configuration

PostgreSQL and Redis require opposite strategies. PostgreSQL needs dedicated, static HugePages reserved in advance. Redis needs Transparent HugePages disabled entirely.

1. Configuring Static HugePages for PostgreSQL

Calculate your required page count directly from PostgreSQL’s shared_buffers. Never allocate your entire physical RAM to HugePages. PostgreSQL client backend processes, work memory (work_mem), and the operating system itself still depend on standard 4 KB pages.

Take a dedicated host with 32 GB of RAM and shared_buffers = 16GB. Add roughly 10% headroom to accommodate process tracking and lock tables:

# Target shared memory: 16 GB * 1024 MB = 16,384 MB
# Base page calculation: 16,384 MB / 2 MB per page = 8,192 pages
# Add 10% safety margin: 8,192 * 1.10 = 9,011 pages (~17.6 GB)

Persist the page reservation in your kernel sysctl configuration:

sudo tee /etc/sysctl.d/90-hugepages.conf <<'EOF'
# Allocate 9011 x 2MB pages (~17.6 GB)
vm.nr_hugepages = 9011
EOF

sudo sysctl --system

Next, give the postgres system user permission to lock this shared memory segment into RAM. Without this, the server cannot lock the pool and will fail to boot:

sudo tee /etc/security/limits.d/99-postgres-hugepages.conf <<'EOF'
postgres  soft  memlock  unlimited
postgres  hard  memlock  unlimited
EOF

Configure postgresql.conf to mandate HugePage usage rather than silently falling back to standard 4 KB pages:

# In postgresql.conf
huge_pages = on         # Enforces static HugePages; fails to boot if unavailable
shared_buffers = 16GB

Setting huge_pages = on acts as a fail-safe. If your sysctl parameters fail to apply after a reboot, PostgreSQL refuses to start. That alert tells you immediately that something broke, rather than masking degraded performance under 4 KB pages. Restart the service to apply the change:

sudo systemctl restart postgresql

2. Disabling Transparent HugePages (THP) for Redis

Redis explicitly warns against THP during startup. Under heavy write traffic, dynamic 2 MB page allocations trigger compaction delays and memory amplification during fork operations.

Check the active THP state on your host:

cat /sys/kernel/mm/transparent_hugepage/enabled
cat /sys/kernel/mm/transparent_hugepage/defrag

If brackets surround [always] or [madvise], THP is active. Because standard sysfs writes do not survive reboots, disable THP permanently via an early-boot systemd unit:

sudo tee /etc/systemd/system/disable-thp.service <<'EOF'
[Unit]
Description=Disable Transparent HugePages (THP)
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=redis.service redis-server.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled && echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=basic.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now disable-thp.service

Redis also requires memory overcommit enabled. This prevents fork failures when Redis background saving requires virtual address space equivalent to the parent process:

sudo tee /etc/sysctl.d/90-redis-overcommit.conf <<'EOF'
# Allow memory overcommit for Redis background saves
vm.overcommit_memory = 1
EOF

sudo sysctl --system
sudo systemctl restart redis-server || sudo systemctl restart redis

Verification & Monitoring

Validating memory states post-boot prevents regressions caused by kernel package updates or configuration drifts.

Verifying PostgreSQL HugePages Consumption

Inspect memory allocation while PostgreSQL is serving connections:

grep -i hugepages /proc/meminfo

Expected production output:

HugePages_Total:     9011
HugePages_Free:       782
HugePages_Rsvd:       782
HugePages_Surp:         0
Hugepagesize:        2048 kB

HugePages_Total reflects your sysctl reservation. When HugePages_Rsvd matches HugePages_Free, PostgreSQL has actively claimed its shared memory segment from the reserved pool. The difference represents our safety buffer.

Verify the active setting directly from psql:

sudo -u postgres psql -c "SHOW huge_pages;"
sudo -u postgres psql -c "SELECT name, setting, unit FROM pg_settings WHERE name IN ('huge_pages', 'shared_buffers');"

Verifying Redis THP Status

Confirm the kernel has disabled THP and defragmentation loops:

cat /sys/kernel/mm/transparent_hugepage/enabled
# Expected: always madvise [never]

cat /sys/kernel/mm/transparent_hugepage/defrag
# Expected: always defer defer+madvise madvise [never]

Check the live Redis diagnostics for memory fragmentation and latency anomalies:

redis-cli info memory | grep -E 'used_memory_human|mem_fragmentation_ratio'
redis-cli latency doctor

If THP remains active, Redis warns you in its logs: WARNING you have Transparent Huge Pages (THP) enabled in your kernel. Once disabled, that log line disappears, and latency doctor will confirm normal fork latencies under 5ms.

Production Takeaways

Keep these real-world operational rules in mind:

  • NUMA Fragmentation: On multi-socket systems, allocating HugePages while the machine is under load causes uneven distribution across NUMA nodes. Always reserve static HugePages at boot time via sysctl to ensure contiguous, balanced memory blocks across all CPU sockets.
  • Kernel Package Updates: Upgrading the kernel or running tuning profiles (like tuned-adm) can quietly reset sysfs flags. Keep your systemd units and /etc/sysctl.d/ files under version control using Ansible, Puppet, or Terraform.

Static HugePages cut down translation bottlenecks for predictable PostgreSQL buffer pools. Disabling THP gives Redis the lean Copy-on-Write behavior it needs for sub-millisecond responsiveness.

Share: