deniz.in

Markets

Weather

Loading weather

· via dev.to (home feed)

Rust service's 358 MB RSS traced to Transparent Huge Pages and jemalloc; fix cuts it to 87 MB

A dev.to post explains how a Rust service with 52 MB of live allocations reached 358 MB RSS, and how switching Transparent Huge Pages to madvise cut it to 87 MB.

Rust service's 358 MB RSS traced to Transparent Huge Pages and jemalloc; fix cuts it to 87 MB

A Rust service holding around 52 MB of live data was consuming 358 MB of resident memory, and a leak was not the explanation. According to a dev.to post published on 26 August 2026, the resident set size (RSS) was inflated by the Linux kernel's Transparent Huge Pages (THP) feature interacting with the jemalloc allocator and the workload's allocation pattern. Switching THP from always to madvise cut RSS from 358 MB to 87 MB without any change to the service's code.

The symptom: seven times more resident memory than live data

The post's author, who expanded the investigation into a longer write-up on the certstream.dev blog, reports that allocator statistics showed roughly 52 MB of live allocations while the process reported 358 MB of RSS — nearly seven times the memory actually in use. The first suspects were the conventional ones for a native-language service: fragmentation inside the allocator, or extents retained after frees rather than returned to the kernel. Neither could account for a gap that wide.

Following the evidence

The investigation ran through jemalloc's own statistics output and the per-process smaps_rollup summary under /proc, which aggregates the kernel's memory-mapping accounting. Together those views pointed away from a fragmented or retained heap and toward anonymous memory backed by huge pages. Checking the THP configuration — exposed via /sys/kernel/mm/transparent_hugepage/enabled — showed the host running in always mode, which completed the diagnosis.

Why huge pages and jemalloc combine badly

For background: Linux normally backs a process's anonymous memory with 4 KB pages. THP lets the kernel use 2 MB huge pages instead, which reduces TLB misses for workloads that touch memory densely. In always mode the kernel promotes eligible regions aggressively, whether or not an application asked for it.

The cost is in how RSS is accounted. Physical memory is measured at page granularity, so once any byte inside a 2 MB huge page is touched, the whole 2 MB becomes resident and counts toward the process footprint. A long-lived service whose live data is scattered in small amounts across many regions — the kind of layout an arena-based, size-class allocator such as jemalloc can produce — hands the kernel many sparsely used regions to promote. Each promotion then pins 2 MB of physical memory for what may be a few kilobytes of live data, and the waste multiplies across the heap. The dev.to post attributes the inflation in this case to exactly that combination.

The fix and its trade-offs

Setting THP to madvise — the middle mode, in which only address ranges that explicitly request huge pages receive them — brought RSS down from 358 MB to 87 MB, per the post. The residual gap above the 52 MB live heap matches the overhead you would expect from code, stacks, allocator metadata and pages awaiting return to the kernel.

Two caveats are worth noting. always mode exists because dense, sequential workloads genuinely benefit from huge pages, and some distributions ship it as the default. Changing the setting system-wide can alter the performance profile of other services on the same host, so the effect should be measured rather than assumed; opting in per process via madvise, where practical, is the more targeted route.

Why it matters

RSS is the figure that container runtimes, orchestrators and monitoring dashboards actually enforce. An inflated footprint can push an otherwise healthy service over a cgroup memory limit and into an OOM kill, or quietly distort capacity planning and host costs. The story also works as a debugging template: allocator statistics alone could not explain the process's size, and the answer appeared only once kernel-level memory accounting and the THP configuration entered the picture. Interference from THP is a documented problem for long-running native services — major database projects have warned about it for years — so it belongs on the checklist whenever a native process's resident memory looks far larger than its live heap.

  • #rust
  • #linux
  • #memory-management
  • #jemalloc
  • #performance

Related posts