Some of you might know that we are a bootstrapped upstart with a shoestring budget.

This means that we are frugal about how we budget for resources and buy-into new stuff only more carefully. Which is great because then our end users get to enjoy performance of a well tuned app top down.

Through this post we explain how we used jemalloc—a memory allocator—to improve performance of our Rails application. It helped us reduce our RAM usage by 30%.

But first, what is jemalloc?

By default Ruby uses the glibc for memory allocation. Switching over to jemalloc has been discussed often by the experts in the Ruby/Rails community but a decision is yet to come. We are hoping this thought will see the light of the day soon and jemalloc will be considered by the Ruby core group.

It is worthwhile to mention here that several large projects and organizations like Redis, GitHub, GitLab and Discourse are already using jemalloc on production.

Jemalloc

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

While it will take some time for ruby aficionados to arrive on this decision, we decided to take full 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.

An improvement of ~28.62% (>1 Gb RAM) on memory usage was registered on our production server (Linode), but YMMV.

Here are our before and after 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

A single instance of Linode with ~8Gb RAM at our disposal.

How to install:

To install jemalloc on your linux server (Ubuntu 16.04.1 LTS):

ssh into your server or the dev machine.

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

On macOS, you may use homebrew to install jemalloc, like so:

$ brew install jemalloc

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


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

Bubblin is on 2.4.1 as on September, 2018

If ruby is already installed on your machine, as it was in our case, you might need to remove and purge older shims and rehash it again, like so:


$ 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 if your installation of Ruby is now using jemalloc, try following command:

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

Presence of a string like -ljemalloc in the output indicates that the memory allocator library we wanted is correctly loaded before starting ruby. Brilliant! Now let's go ahead and restart our app, and there it is folks: a faster rails app that's also light on your wallet.

Like it? Great! Do you have more tips or tricks to suggest for vertical scaling?

Please share on comments below.


Hi! I'm Marvin Danig CEO and Cofounder of Bubblin Superbooks—the best way to read and share books on web.

Let's follow each other on Twitter?