Skip to content

Instantly share code, notes, and snippets.

@jjb
Last active July 17, 2025 15:15
Show Gist options
  • Save jjb/9ff0d3f622c8bbe904fe7a82e35152fc to your computer and use it in GitHub Desktop.
Save jjb/9ff0d3f622c8bbe904fe7a82e35152fc to your computer and use it in GitHub Desktop.
Using Jemalloc 5 with Ruby.md

For years, people have been using jemalloc with ruby. There were various benchmarks and discussions. Legend had it that Jemalloc 5 didn't work as well as Jemalloc 3.

Then, one day, hope appeared on the horizon. @wjordan offered a config for Jemalloc 5.

Ubuntu/Debian

FROM ruby:3.1.2-bullseye
RUN apt-get update ; \
    apt-get install -y --no-install-recommends libjemalloc2 ;
ENV LD_PRELOAD=libjemalloc.so.2
ENV MALLOC_CONF='dirty_decay_ms:1000,narenas:2,background_thread:true'

Alpine

FROM ruby:3.2.3-alpine3.19
RUN apk add --update jemalloc
ENV LD_PRELOAD=libjemalloc.so.2
ENV MALLOC_CONF='dirty_decay_ms:1000,narenas:2,background_thread:true'

Heroku

  1. add the apt buildpack (maintained by Heroku staff and owned by the Heroku namespace, but still considered "third-party"/"unofficial")
    heroku buildpacks:add --index 1 heroku-community/apt
  2. in the same branch, create and deploy these two files:
    • Aptfile
      libjemalloc2
      
    • .profile
      export LD_PRELOAD=libjemalloc.so.2
      export MALLOC_CONF=dirty_decay_ms:1000,narenas:2,background_thread:true
  • ENV vars are set in .profile instead of config:set so that jemalloc is only used at runtime and not compile time, and to avoid (harmless) noise when first starting a shell. see discussion here
  • There is also the third-party heroku-buildpack-jemalloc, which will download, build, and install an arbitrary jemalloc version, which is useful if you need a version not distributed by Ubuntu for some reason, such as before the discovery of the improved MALLOC_CONF, necessitating the installation of version 3.6

a note on LD_PRELOAD

On some systems (maybe older?) it might be necessary to provide the full path to the file, such as:

LD_PRELOAD=/usr/lib/libjemalloc.so.2
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2

MALLOC_CONF options

  • dirty_decay_ms:1000,narenas:2,background_thread:true comes from dockerfile-rails. code discussion
  • This is the original "tradeoff between memory and performance". it is likely identical to the above config on recent Jemalloc and Ruby versions, because the extra values are the defaults dirty_decay_ms:1000,muzzy_decay_ms:0,narenas:2,background_thread:true,thp:never
  • This is "more heavily memory-optimized, like jemalloc 3.6" (the version used by Fullstaq Ruby) dirty_decay_ms:0,muzzy_decay_ms:0,narenas:2,background_thread:true,thp:never
  • This is used by gitlab 1 2 narenas:2

Check if jemalloc is working:

MALLOC_CONF="stats_print:true" ruby -e "exit" # will produce no output if jemalloc not being used, a wall of text if it is
MALLOC_CONF="$MALLOC_CONF,stats_print:true" ruby -e "exit" # if a config is set, you can include it like this

Links!

@jjb
Copy link
Author

jjb commented Aug 10, 2023

Info from Shopify employee in a slack group (shared with permission):

We use version 5 without any tuning, mostly because for us latency (generally) has priority over memory usage

@technicalpickles
Copy link

At Gusto, we're using jemalloc 3 via fullstaq ruby

@jjb
Copy link
Author

jjb commented Aug 11, 2023

Info from a GitHub employee in a slack group (shared with permission):

jemalloc3: no MALLOC_CONF afaict

@jjb
Copy link
Author

jjb commented Dec 12, 2023

Apparently the ruby binary can be patched, so that neither compiling ruby with jemalloc nor loading it with LD_PRELOAD is necessary:

apt install patchelf
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby
apt-get purge -y patchelf

taken from here, more info here

@rgaufman
Copy link

rgaufman commented Feb 6, 2024

We've been using jemalloc since 2018 and we literally went from needing to restart our Ruby app every 3-4 days as the memory leaks to memory never leaking, ever and taking a third of without jemalloc. I do not understand why this is not the default for all Ruby builds!

@agrberg
Copy link

agrberg commented Jan 19, 2025

MALLOC_CONF="stats_print:true" ruby -e "exit" is producing a wall of text so I am assuming jemalloc1 is working.

-ljemalloc is not output when running ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS'], RbConfig::CONFIG['MAINLIBS']". I am assuming that both this is fine/jemalloc is still used and this occurs because Ruby wasn't installed --with-jemalloc. Is this correct?

Footnotes

  1. Latest installed Debian version via apt-get install --no-install-recommends -y libjemalloc2

@jjb
Copy link
Author

jjb commented Jan 19, 2025

@agrberg yes, that sounds correct

@bensheldon
Copy link

Noting that Rails' docker entrypoint template now does the lookup dynamically:

LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit)

https://github.com/rails/rails/blob/aa66aece44f191e6330c04ce4942c3edf31d85d7/railties/lib/rails/generators/rails/app/templates/docker-entrypoint.tt#L5C1-L6C1

@agrberg
Copy link

agrberg commented Jun 21, 2025

@bensheldon thanks for the update! I try to keep my Dockerfile and docker-entrypoint generally the same and up-to-date with Rails' 1. Currently I have the following in my Dockerfile in the base step where ENV is being set

# Set production environment
ENV RAILS_ENV="production" \
    …
    LD_PRELOAD="libjemalloc.so.2" \
    MALLOC_CONF="dirty_decay_ms:1000,narenas:2,background_thread:true"

I believe removing these ☝️ in favor of adding the following 👇 to docker-entrypoint would result in the same configuration as both set LD_PRELOAD and the MALLOC_CONFIG options section says these are the current default values.

# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ]; then
    LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit)
    export LD_PRELOAD
fi

Footnotes

  1. One minor addition others may have is the inclusion of .tool-versions when using ruby file: ".tool-versions" in the Gemfile.

@bensheldon
Copy link

@agrberg welp, already changed in Rails with some commentary, though it's now doing a symlink strat: rails/rails#55252

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment