Move a Process's NUMA Pages with migratepages
A long-running process ends up with memory on the wrong NUMA node, and migratepages moves the physical pages while virtual addresses stay put. The examples use migratepages from numactl 2.0.18-1ubuntu0.24.04.1, installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes for a read-only inspection and a smoke test. A real migration needs a process you own, a clear maintenance window, and enough free memory on the destination nodes. You need a NUMA-aware kernel with page-migration support. Moving pages changes live process memory placement, so don't start with a production service.
1. Confirm the command's contract
migratepages takes exactly three arguments: a process ID, a source node set, and a destination node set. Ask the installed binary for its usage text:
$ command -v migratepages
/usr/bin/migratepages
$ migratepages --help
usage: migratepages pid from-nodes to-nodes
nodes is a comma delimited list of node numbers or A-B ranges or all.
--help isn't actually a documented option: this build prints usage and exits with status 1 because it wants a PID and two node specifications. Use the command shape above when you write a real invocation.
Checkpoint
Confirm the package version before comparing output with another host:
$ dpkg-query -W -f='\${Package} \${Version}\n'
numactl 2.0.18-1ubuntu0.24.04.1
2. Inspect the process before moving anything
Choose a target process and record its current NUMA map. It's exposed through /proc/<pid>/numa_maps, and reading it is ordinary, non-destructive inspection:
$ PID='12345'
$ sed -n '1,12p' "/proc/$PID/numa_maps"
00400000 default file=/usr/bin/example mapped=4 N0=4 kernelpagesize_kB=4
...
Replace 12345 with a real PID. Don't copy the sample map as expected output: file paths, mapping counts and node numbers all depend on the process and host. An entry such as N0=4 reports pages currently on node 0. An anonymous mapping can show a different node count from a file-backed one.
Checkpoint
Verify that the process still exists immediately before any migration:
$ test -r "/proc/$PID/numa_maps" && printf 'NUMA map is readable for PID %s\n' "$PID"
NUMA map is readable for PID 12345
If this fails, the process may have exited, or its proc access may be restricted. Re-select the PID rather than accidentally applying the command to a different process.
3. Understand node specifications
Node sets can name every node with all, a single node by number, several nodes separated by commas, or a range such as 2-5. A ! prefix inverts the selection. Keep the two sets straight in your head: the source set says which pages are eligible to move, the destination set says where the command should try to put them.
# Examples of node-set syntax. These lines are illustrative only.
all # every NUMA node
0 # node 0
0,2,4 # three nodes
2-5 # nodes 2, 3, 4 and 5
!0 # every node except node 0
The command tries to preserve relative placement when both sets contain multiple nodes, pairing a source range and a destination list in order where it can. That pairing only works when the destination has enough memory, so treat the result as an attempt, not a guarantee that every page landed on the node you had in mind.
4. Run a reversible smoke test on a process you own
For a first test, use your own shell and a narrow destination. This changes physical page placement for that shell, but doesn't rewrite its virtual address space or any persistent configuration:
$ migratepages $$ all 0
$ printf 'migratepages status: %s\n' "$?"
migratepages status: 0
On this machine, the successful command is silent and returns status 0. This example only works where node 0 exists; if the host has no node 0, stop and pick a node your own NUMA tools actually show. Don't paste node numbers from another machine.
There's no persistent undo, because the command changes current page placement rather than a policy file. To move the pages again, run another migration with the source and destination sets you want. Pages can be faulted in again later too, so observed placement can keep changing as the process runs.
Warning
Never run a broad migration on a busy service just because the command is short. Moving a large working set can consume memory bandwidth, compete with the workload, and produce a worse placement than you started with. Stop the test process or restore its workload before judging performance.
5. Migrate a selected process after checking permissions
For a real process, replace both placeholders with values from the same host:
$ PID='12345'
$ FROM_NODES='2-5'
$ TO_NODES='7,9,12-13'
$ migratepages "$PID" "$FROM_NODES" "$TO_NODES"
$ printf 'migration status: %s\n' "$?"
migration status: 0
Don't run this unchanged: the node numbers are examples and the PID is a placeholder. Confirm every node actually exists and that the destination has capacity. A zero status only says the request was accepted; inspect /proc/$PID/numa_maps again to see what actually moved.
- Unshared pages. An ordinary user can move pages that aren't shared with other processes, provided that user has the right to modify the target process.
- Shared pages. These need administrative privilege.
- Root, or an equivalent privilege. Can move all pages.
Use elevated privileges only for a process and maintenance window you've explicitly identified:
$ sudo migratepages "$PID" "$FROM_NODES" "$TO_NODES"
$ printf 'migration status: %s\n' "$?"
migration status: 0
Don't use sudo to paper over an incorrect PID, a missing node or an unreadable map: verify the target first. A privileged command can alter shared pages belonging to other processes, and can affect a wider workload than the one you meant to touch.
6. Diagnose a failed or ineffective move
Capture the status immediately and read the diagnostic. A nonexistent PID, for example, fails safely:
$ migratepages 999999 all 0
migrate_pages: No such process
$ printf 'status: %s\n' "$?"
status: 1
A failure can mean the process ended, the source or destination specification is invalid, the kernel lacks page-migration support, the request needs more privilege, or the destination simply can't satisfy the move. Re-read the target's NUMA map and check the host's available nodes before retrying. Don't infer success from silence; check the exit status.
If the command succeeds but the map barely changes, that can be entirely normal. Some pages are shared, pinned, already on a suitable node, or freshly allocated after the command ran. Compare the relevant mappings before and after, then measure the workload under the same conditions. migratepages changes placement; it does not set CPU affinity, change a NUMA policy, or guarantee lower latency.
Done means
- Contract confirmed. You confirmed the installed numactl version and the three-argument syntax.
- Placement checked first. You inspected
/proc/<pid>/numa_mapsand rechecked the PID before acting. - Real nodes only. You used node sets that exist on this host, not numbers copied from a different machine.
- Privilege level understood. You distinguished an ordinary user's limited migration from a privileged migration of shared pages.
- Result verified. You checked the exit status and inspected NUMA placement after the request.