Add jemalloc to your Ruby Docker images, now!
By Martijn Storck
The topic of this post is neither new nor original, so I’m going to keep things short. People have been using jemalloc with MRI Ruby for years and praising it’s benefits all the while. The TL;DR here is: for Rails apps, using jemalloc will reduce memory consumption and might increase performance a little.
Why?
To illustrate my own experience, here’s a Grafana graph showing the effect of switching to jemalloc on a production server running a few instances of a Rails application:
There have been no nasty side effects, nor are they to be expected but keep in mind this is only sample size n=1 so be sure to test and measure before you make the switch yourself.
Ok, how?
By default, ruby uses the malloc provided by the operating system’s libc. So on Linux this will either be glibc malloc or musl-libc malloc, depending on your Linux distribution. Some applications that are heavily dependent on memory usage (such as Redis) decided to link against jemalloc on Linux, but for MRI this is not the case. This means there are two options:
-
Recompile Ruby yourself with jemalloc or use a package where someone did that for you, or
-
Inject jemalloc by setting the LD_PRELOAD environment variable to transparently replace the system malloc with jemalloc without ruby being aware of this.
Using LD_PRELOAD might seem like a hack, but I was unable to find any tangible benefits of linking Ruby against jemalloc during the build phase so both are equally acceptable in my opinion.
Building on the official Ruby docker images
Personally, I’ve switched deployments to Docker exclusively. On docker hub there are tons of images by independent developers that do either one of the above. The official community Ruby images however, do not. Fortunately the LD_PRELOAD method allows us to easily install and inject jemalloc on top of the base Ruby image. Here is the Dockerfile to accomplish that:
FROM ruby:2.7-slim
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
The RUN line installs jemalloc from the Debian repositories and cleans up afterward. The ENV line sets the LD_PRELOAD environment variable so any invocation inside your container (be it the entrypoint or a command) will be enhanced by jemalloc.
Building on top of the official Ruby image like this means your not dependent on Docker images maintained by third parties (but to be audited by you!) and instead get the stable, up-to-date, secure image that is maintained by a large community but with the improved memory utilization offered by jemalloc.