JRuby on Rails -- Performance
Dr. B. | Monday, July 16, 2007
We typically deploy Rails apps using mongrel clusters behind an Apache 2 webserver. So, I started with a pretty straightforward Rails app I'm building, and deployed it as normal on the mongrels.
(By the way, unless otherwise mentioned, software versions used are native Ruby 1.8.6, Rails 1.2.3, JRuby 1.0, Tomcat 6.0.13, Java 1.5.0_07 on OSX, Java 1.6.0 on RedHat Linux, Apache web server 2.2.3.)
The next step was to get JRuby and setup a .war file to deploy. I found this blog post to be a very helpful start. I already had the latest Java from Apple installed, so I installed JRuby to
/usr/local/jruby-1.0. I edited
/etc/profileto include the following:
I decided to deploy the .war into Tomcat since I'm more familiar with it than the recommended Glassfish and I knew how to connect it well with Apache. So, I created a JDBC connection pool to the MySQL database by adding a META-INF directory to the Rails app, with the following
<Context path="/myapp" reloadable="true" crossContext="true">
<!-- Database Connection Pool -->
This is the corresponding
database.ymlfile that tells Rails where to find the connection pool data source:
With these configuration files, I went through the JRuby process for creating the .war file. I setup Tomcat and deployed the app, which worked after I put the MySQL driver into Tomcat's /lib directory. I connected the Tomcat app to Apache with mod_proxy_ajp. I can post more details about these configs if there is interest.
One important optimization that I added was to include JAVA_OPTS that launched Tomcat with enhanced memory heap limits for the JVM, and with a couple other parameters I found while searching for JRuby optimizations online. So, I added this line to my
export JAVA_OPTS="-Xms128m -Xmx512m -Djruby.objectspace.enabled=false -Djruby.jit.enabled=true"
After configuring and setting everything up on my MacBook Pro (2GHz Intel Core Duo, 1.5GB RAM), I was excited by the performance of JRuby. Using simple comparison of wget spiders between the native Ruby on Rails (using 3 mongrels behind Apache) and JRuby versions, I was seeing over a 2x improvement in JRuby. The spider of around 24 URLs was taking about 10 seconds on native Ruby, while only about 4 seconds on JRuby/Tomcat.
I then moved to deploying on our RedHat Linux-based staging server, where the 24 URL spider was running in about 1.5 seconds with native Ruby (same 3 mongrel cluster behind Apache 2). With repeated tweaking, I couldn't get the JRuby/Tomcat to run faster than 4 seconds, even swapping out JVMs of Java 5 and Java 6.
I then considered that JRuby might have an advantage under load, so I setup Grinder, an open-source Java-based load testing tool, to "grind" both versions. The test simulated 10 simultaneous threads from three users, with each test repeated three times. Here are some comparison numbers of a couple URLs (times in milliseconds):
|Run #||Native A||Java A||Native B||Java B|
On average, JRuby is either slower or nearly equal in performance under load. It's still respectable considering JRuby's 1.0 status. The main downside was the significant disparity in CPU and RAM usage between JRuby and native Ruby. JRuby pegged the dual core CPUs under load, using upwards of 250MB of RAM, while the mongrels maintained under 30% CPU with about 40MB per mongrel.
I remain excited about JRuby, but can't use it for production work quite yet. I'm sure the JRuby team will continue with optimization -- and I'll be keeping a close eye on its progress for sure.
Doug Smith, Senior Developer, Barefoot