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
sysctlto 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.
