Rails Memory Growth and jemalloc
I hit this on a Rails app that looked fine after boot, then slowly grew until Kubernetes killed it.
At first I checked the usual things: query allocations, background jobs, and object retention. Some of that helped, but the memory graph still kept climbing. The fix that moved the number for us was simpler: run Ruby with jemalloc.
What I saw
Ruby usually uses the system allocator. On Linux that often means glibc malloc.
For a long-running Rails process, the issue is not always a leak. Sometimes the app frees memory, but the allocator keeps fragmented pages around instead of returning them to the OS.
The pod still looks big from the outside.
boot: 400 MB
later: 2 GB+
result: OOMKilled What I changed
On Debian or Ubuntu images:
RUN apt-get update
&& apt-get install -y --no-install-recommends libjemalloc2
&& rm -rf /var/lib/apt/lists/*
ENV LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 On Alpine:
RUN apk add --no-cache jemalloc
ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 I also use a small MALLOC_CONF value so unused memory is released sooner:
ENV MALLOC_CONF=dirty_decay_ms:1000,narenas:2 The exact path depends on the base image. Check it before shipping:
find /usr/lib -name '*jemalloc*.so*' Verifying it
After deploy, I check the running process:
puts `ldd /proc/#{Process.pid}/exe | grep jemalloc` If the command prints a jemalloc path, Ruby loaded it.
You can also watch the pod memory after a normal traffic window. One hour is usually not enough. I prefer comparing after a full workday because the allocator problem tends to show up slowly.
What changed for us
This was the rough production shape:
| Metric | Before | After |
|---|---|---|
| Boot memory | ~400 MB | ~350 MB |
| After 24 hours | ~2.1 GB | ~600 MB |
| OOM restarts | Frequent | Gone |
That does not mean jemalloc fixes every Rails memory issue. If you keep objects forever, it will not save you. But if the graph keeps growing even after you remove obvious leaks, it is worth trying.
I stop tuning early
I keep this change small:
- Install the package in the image.
- Set
LD_PRELOAD. - Confirm the loaded library.
- Compare memory after real traffic.
I would not tune too many MALLOC_CONF options up front. Start with the allocator switch, measure it, then adjust.
For my case, this was enough to stop the OOM loop.