On servers with more than one CPU socket (and on some single large CPUs), memory isn’t equally fast for every core. Each CPU has its “own” memory, and reaching memory attached to another CPU takes longer. This design is called NUMA (Non-Uniform Memory Access). It matters for big databases and high-performance workloads on dedicated hardware, and much less on typical VPS plans.
The idea
Node 0 Node 1
┌──────────┐ interconnect ┌──────────┐
│ CPU 0–15 │◀────────────▶│ CPU 16–31 │
│ RAM 64G │ │ RAM 64G │
└──────────┘ └──────────┘
- A core accessing memory on its own node = local access (fast).
- Accessing the other node’s memory = remote access (slower, and uses interconnect bandwidth).
The Linux kernel tries to keep processes and their memory on the same node, but large processes, such as a database with a 100 GB buffer pool, inevitably span nodes.
Check your topology
lscpu | grep -i numa
numactl --hardware # sudo apt install numactl
numastat # per-node allocation statistics
Example:
NUMA node(s): 2
NUMA node0 CPU(s): 0-15,32-47
NUMA node1 CPU(s): 16-31,48-63
A single NUMA node means NUMA doesn’t affect you.
On a VPS
Most VPS plans present one NUMA node to the guest, and the hypervisor handles placement on the host. lscpu shows NUMA node(s): 1, and there’s nothing to tune inside the VM. NUMA tuning becomes relevant on:
- dedicated servers with two sockets, or large multi-chiplet CPUs. See VPS vs dedicated server;
- very large VMs that span host nodes (the hypervisor may expose a virtual NUMA topology).
When NUMA matters
| Workload | Symptom / concern |
|---|---|
| Large databases (MySQL, PostgreSQL, MongoDB) | Uneven memory allocation, one node swapping while the other has free RAM |
| In-memory caches (Redis, Memcached) | Latency from remote memory |
| HPC, scientific computing | Memory bandwidth limits |
| High packet-rate networking | NIC attached to one node; process on the other |
The classic problem: the “swap insanity” pattern, where a big database fills one node’s memory, and the kernel swaps even though the other node has plenty free.
Tools and settings
Interleave memory across nodes (common for big single-process databases):
numactl --interleave=all mysqld …
MongoDB’s documentation, for example, recommends starting mongod with numactl --interleave=all on NUMA hardware. Packaged systemd units sometimes include this, or you add it with an override (systemctl edit). See systemd unit files.
Pin a process to one node (keeps CPU and memory local, for multiple independent instances):
numactl --cpunodebind=0 --membind=0 ./worker
Kernel setting: keep vm.zone_reclaim_mode = 0 (the default on modern kernels) for most server workloads. Enabling it makes the kernel reclaim local memory aggressively, instead of using remote memory, which often hurts databases.
sysctl vm.zone_reclaim_mode
Automatic NUMA balancing (kernel.numa_balancing) moves memory pages closer to the threads using them. It’s enabled by default, and usually helpful. Some latency-sensitive databases recommend disabling it. Follow your database’s documentation.
Monitoring
numastat -m # memory per node
numastat -p mysqld # one process's memory per node
Strongly unbalanced usage, or high numa_miss counts, suggest that pinning or interleaving could help.
Quick reference
| Task | Command |
|---|---|
| How many nodes | lscpu | grep -i numa |
| Topology | numactl --hardware |
| Per-node stats | numastat -m |
| Interleave memory | numactl --interleave=all <cmd> |
| Pin to a node | numactl --cpunodebind=0 --membind=0 <cmd> |
Related: Linux HugePages and vCPU explained.