Ruby Enterprise Edition and Passenger
Recently, we evaluated Ruby Enterprise Edition and Apache mod_rails, known as Phusion Passenger, in a Virtual Private Server hosting environment. We compared performance and memory usage against our production instance, which runs this blog and uses an Nginx-powered Mongrel cluster.
[Editor’s note: new results have been published as of June 30, 2008.]
Preparation
First off, doing these kinds of evaluations is super easy with a VPS hosting service like Slicehost. We simply cloned a image of an existing production backup rather than building one from scratch. Then, we shut down Mongrel and Nginx.
Installation
Ruby Enterprise Edition
This was a breeze, following the installation instructions.
# cd /usr/local/src # wget http://rubyforge.org/frs/download.php/38803/ruby-enterprise-1.8.6-20080624.tar.gz # tar xzvf ruby-enterprise-1.8.6-20080624.tar.gz # ./ruby-enterprise-1.8.6-20080624/installer
The installer will check for the GNU C++ compiler, and the Zlib and OpenSSL development headers, which are easy to install if you’re using a package manager like Yum on Fedora Linux.
The only hiccup we ran into was during the installation of the Enterprise Ruby-flavored version of the MySQL gem, getting the infamous “Failed to build gem native extension.” Fortunately, the rest of the installation process went through smoothly. Afterwards, we used this command to install the MySQL gem:
# /opt/ruby-enterprise-1.8.6-20080624/bin/ruby /opt/ruby-enterprise-1.8.6-20080624/bin/gem install mysql -- --with-mysql-include=/usr/include/mysql --with-mysql-lib=/usr/lib64/mysql
Important: The Ruby Enterprise Edition executable is installed in a separate location so that you end up with two versions of Ruby on your machine. This means when installing new gems, you should use Ruby Enterprise Edition version. You can make this the default version via symbolic links:
ln -fs /opt/ruby-enterprise-1.8.6-20080624 /opt/ruby-enterprise ln -fs /opt/ruby-enterprise/bin/gem /usr/bin/gem ln -fs /opt/ruby-enterprise/bin/irb /usr/bin/irb ln -fs /opt/ruby-enterprise/bin/rake /usr/bin/rake ln -fs /opt/ruby-enterprise/bin/rails /usr/bin/rails ln -fs /opt/ruby-enterprise/bin/ruby /usr/bin/ruby
Note that the Ruby Enterprise Edition installer will install the latest stable version of Rails. So if you haven’t frozen your gems in your app and need an earlier version, just install it with
gem install rails -v 2.0.2
Passenger (mod_rails)
Again, the installation process was simple per the docs.
First, the gem:
# gem install passenger
Then, running the following command kick offs the installlation wizard.
# passenger-install-apache2-module
Again, the installer checks for required dependencies and will advise you how to handle missing ones. In our case, we did not have Apache 2 and its development headers installed on our slice (since we were using Nginx).
The installation wizard will also tell you next steps for configuring Apache, which involves copying a few lines of settings. And to make Apache aware of your Rails app, it’s as simple as setting up a Virtual Host.
ServerName www.webficient.com DocumentRoot /u/apps/webficient/current/public
Don’t forget to restart Apache for the changes to take:
# service httpd restart
(You can also use this technique if you don’t want to restart the entire httpd process).
Benchmark Overview
Because our blogging platform caches content, we looked at both dynamic and static content performance to ensure the numbers weren’t misleading. Our dynamic test involved hitting the login page. Our static test requested an existing cached article.
We used Apache Bench to simulate load:
# ab -n 10000 -c 100 http://server/app_path
Both slices had the following aspects in common:
- Linux Fedora 8, 64-bit
- 512MB RAM
- Rails 2.0.2
The differences were type of application server and Ruby VM:
- “Production” slice uses 3 Mongrels (v1.1.5), 3 Nginx (v0.6.31) worker processes (with 1,024 worker connections), and Ruby 1.8.6 patchlevel 114.
- “Test” slice uses Passenger (mod_rails) and Enterprise Ruby 1.8.6 patchlevel 111. Based on the recommendations in the Passenger documentation, we settled with a PassengerMaxPoolSize of 4, given the size of our VPS.
Memory Usage
Since we’re always interested in squeezing out every last meg of RAM in our slice, we were curious to see how things fared under mod_rails and Ruby Enterprise Edition. Before running any benchmarks, it was clear that without the memory hungry Mongrels, our mod_rails test slice was quite a bit leaner.
Idle Comparison
total used free shared buffers cached mongrel: 524460 382736 141724 0 29816 118924 mod_rails: 524460 241684 282776 0 21856 121256
Be aware that Apache allocates very little memory until the first request comes in. You’ll expect to lose about 80 MB in free memory when that happens. So the above numbers will rarely be seen, unless you have a low traffic Web site.
As expected, memory usage surged during the load tests. With a PassengerMaxPoolSize equal to 4, mod_rails consumes more memory than the Mongrel/Nginx configuration. Setting it to 3 produced the opposite result. However, you’ll see this setting also affects requests per second.
Dynamic Content Test
total used free shared buffers cached mongrel: 524460 481800 42660 0 27488 116984 mod_rails: 524460 500948 23512 0 20496 115360
Static Content Test
total used free shared buffers cached mongrel: 524460 439552 84908 0 29564 118908 mod_rails: 524460 257604 266856 0 21544 121192
So under load, mod_rails can take it well. We are also thrilled that the mod_rails version of our blogging app is not leaking memory, unlike our Mongrel-powered production version.
Performance
What about speed? Results were in line with our expectations. Mod_rails does well serving up dynamic content but Nginx continues to dominate when static content is involved.
Dynamic Content – Mongrel/Nginx
Although the numbers shown here represent a PassengerMaxPoolSize set to 4, we also ran the same tests with a value of 3. Number of requests per second decreased while memory usage improved.
Concurrency Level: 100 Time taken for tests: 63.843779 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 30370000 bytes HTML transferred: 25690000 bytes Requests per second: 156.63 [#/sec] (mean) Time per request: 638.438 [ms] (mean) Time per request: 6.384 [ms] (mean, across all concurrent requests) Transfer rate: 464.54 [Kbytes/sec] receive
Dynamic Content – mod_rails/Ruby Enterprise Edition
oncurrency Level: 100 Time taken for tests: 34.190459 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 30900000 bytes HTML transferred: 25690000 bytes Requests per second: 292.48 [#/sec] (mean) Time per request: 341.905 [ms] (mean) Time per request: 3.419 [ms] (mean, across all concurrent requests) Transfer rate: 882.56 [Kbytes/sec] received
Static Content – Mongrel/Nginx
Concurrency Level: 100 Time taken for tests: 1.901919 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 540173910 bytes HTML transferred: 538042632 bytes Requests per second: 5257.85 [#/sec] (mean) Time per request: 19.019 [ms] (mean) Time per request: 0.190 [ms] (mean, across all concurrent requests) Transfer rate: 277358.28 [Kbytes/sec] received
Static Content – mod_rails/Ruby Enterprise Edition
Concurrency Level: 100 Time taken for tests: 2.550226 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 540430000 bytes HTML transferred: 537720000 bytes Requests per second: 3921.22 [#/sec] (mean) Time per request: 25.502 [ms] (mean) Time per request: 0.255 [ms] (mean, across all concurrent requests) Transfer rate: 206947.55 [Kbytes/sec] received
We’re Not Done Yet
Although we like what we see so far, we’re going to run some additional tests and analysis, including:
- Isolating optimal Apache and Passenger settings for handling load while keeping memory usage within limits
- Evaluating a Nginx/Mongrel/Ruby Enterprise Edition stack
- Comparing memory usage and performance against the Thin Web server
About this entry
Posted: Tuesday, June 24th, 2008 at 9:39 pm
- Author:
- Phil Misiowiec
- Category:
- Architecture
- Tags:
- apache, benchmarks, deployment, mod_rails, nginx, performance, ruby, ruby on rails
- License:
- Creative Commons

3 Comments
Jump to comment form | comments rss | trackback uri