Lower memory usage of your Rails app with jemalloc.🍭


Some of you may know that we are a bootstrapped startup on a shoestring budget. This means we are frugal and use resources like RAM only more carefully. Which is great because the end users get to enjoy performance of a well tuned app. In this post we explain how we used jemalloc—a memory allocator—to improve performance of our Rails application.

It helped us lower RAM usage by 30%. First, what is jemalloc?

jemalloc is a general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support.

By default Ruby uses the glibc for memory allocation. Using jemalloc instead of glibc was discussed recently by experts in the Ruby community. Hopefully it will see light of the day soon by the ruby-lang core group. It’s notable to mention here that several large projects and organizations like Redis, GitHub, GitLab and Discourse are using jemalloc on production.

While ruby aficionados ruminate on their next move, we decided to take advantage of what is available immediately. Turns out, by compiling Ruby with jemalloc we were able to reduce memory usage of our Rails application quite drastically. We registered an improvement of 28.62% (>1 Gb RAM) on our overall memory usage on Linode, but YMMV, of course.

Here are our actual stats:


# ssh into your server 

# before jemalloc:
$ free m    
              total        used        free      shared  buff/cache   available
Mem:        8162148     4126424     2439396      282816     1596328     3604716
Swap:        524284       90064      434220


# after jemalloc 
$ free m    
             total        used        free      shared  buff/cache   available
Mem:        8162148     2945404     3157684      282840     2059060     4785128
Swap:        524284       85200      439084

FYI, we’re on just one Linode instance with ~8Gb RAM at our disposal.

Setup:

To install jemalloc on Ubuntu 16.04.1 LTS:


# ssh into your server or on your dev machine. 

$ sudo apt-get update
$ sudo apt-get install libjemalloc-dev  	# Install jemalloc

On macOS, use Homebrew to install jemalloc:

$ brew install jemalloc

Once jemalloc is set up use rbenv or RVM to (re)install ruby 2.4.1 or whichever version of Ruby your app needs with a prefix like so:


$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.1 	# Bubblin uses Ruby 2.4.1

If Ruby is already installed on your machine, as it was in our case, you may need to delete the older shims and rehash Ruby again:


$ RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install 2.4.1

➜ 	rbenv: /home/marvin/.rbenv/versions/2.4.1 already exists continue with installation? (y/N) y
	Downloading ruby-2.4.1.tar.bz2...
	-> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.1.tar.bz2
	Installing ruby-2.4.1...
	ruby-build: use readline from homebrew
	Installed ruby-2.4.1 to /home/marvin/.rbenv/versions/2.4.1

	rbenv: cannot rehash: /home/marvin/.rbenv/shims/.rbenv-shim exists

$ rm /home/marvin/.rbenv/shims/.rbenv-shim
$ rbenv rehash

To check that your installation of Ruby is using jemalloc, run the following command:

$ ruby -r rbconfig -e "puts RbConfig::CONFIG['LIBS']"
 
 -lpthread -ljemalloc -lgmp -ldl -lcrypt -lm 	# Output

Presence of -ljemalloc in the output indicates that the memory allocator library we wanted is correctly loaded before starting Ruby. Now go ahead and restart your app, and there it is folks: a faster Rails app that’s light on your wallet.

Like it? Great! Do you have more tips or tricks to suggest for vertical scaling? Please share on comments below!


P.S.: Did you know that we bring books straight on web with Bubblin Superbooks?