December 10, 2025 4 min read By Farrizal Alchudry Mutaqien

Rails Memory Growth and jemalloc

A short note on using jemalloc when a Rails app keeps growing memory in production.

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:

MetricBeforeAfter
Boot memory~400 MB~350 MB
After 24 hours~2.1 GB~600 MB
OOM restartsFrequentGone

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.

Back to blog