Most of my clients have no idea about any of this. Ruby, by default, compiles with glibc malloc, which has all kinds of issues.
The reason for this is that it is present in literally every system. Sadly, the ruby-core team refuses to become dependent on any dependencies not part of the operating system (understandable), except for the Rust YJIT compiler written in Rust instead of C. This dependency, however, is optional.
Why would you want to consider jemalloc and YJIT? Memory usage and performance. One project I recently worked on saw memory usage decrease by over 60% after I rolled out Jemalloc to their servers.
Why would you consider Jemalloc, even though it isn't shipped as a default? Let's say you work with a lot of collection type of data where you create things on a background worker. If your workers aren't small enough in their unit of work, they risk bleeding memory. You stand to gain the most if you do much of this work.
To start, we want to install jemalloc, and we want to install rust.
Installing Rust
Rust is installed with the following command:
The reason for this is that it is present in literally every system. Sadly, the ruby-core team refuses to become dependent on any dependencies not part of the operating system (understandable), except for the Rust YJIT compiler written in Rust instead of C. This dependency, however, is optional.
Why would you want to consider jemalloc and YJIT? Memory usage and performance. One project I recently worked on saw memory usage decrease by over 60% after I rolled out Jemalloc to their servers.
Why would you consider Jemalloc, even though it isn't shipped as a default? Let's say you work with a lot of collection type of data where you create things on a background worker. If your workers aren't small enough in their unit of work, they risk bleeding memory. You stand to gain the most if you do much of this work.
To start, we want to install jemalloc, and we want to install rust.
Installing Rust
Rust is installed with the following command:
lang-bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Installing libjemalloc-dev
Then we install jemalloc with:
lang-bash apt install libjemalloc-dev
Unfortunately, jemalloc isn't available by default. To make jemalloc available everywhere, the easiest I've found is to set the LD_PRELOAD environment variable. `/etc/environment` is a great spot to put this to ensure it is available to all users. Otherwise, ~/.bashrc is enough for the account running ruby processes.
lang-bash export export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1
This path is the default for Debian based system. The line above is for Jemalloc installed in Ubuntu 16.04. Most likely, your line should be:
lang-bash export export LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2
Suppose you are on Ubuntu 18.04 and newer. Check which version of libjemalloc.so.n is in the directory /usr/lib/x86_64-linux-gnu/.
Installing ruby
Now to install ruby:
lang-bash RUBY_CONFIGURE_OPTS="--with-jemalloc --enable-yjit" rbenv install 3.2.2
Verifying our installation
To test if ruby is indeed installed with Jemalloc:
lang-bash ruby -r rbconfig -e "puts RbConfig::CONFIG['MAINLIBS']" #=> -lz -lrt -lrt -ljemalloc -ldl -lcrypt -lm -lpthread
You want to see: -ljemalloc in the result.
To verify that ruby is indeed installed with YJIT
lang-bash ruby -v --yjit #=> ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [x86_64-linux]
You should see: +YJIT in the result.