Barefoot Development

JRuby on Rails -- Performance

I have been spending some time evaluating JRuby 1.0 as a possible Ruby on Rails deployment platform. Following RailsConf 2007, I was very excited by the potential of JRuby as a vehicle for deploying Rails apps that would have the advantages of robust J2EE application servers, including multi-threaded load management, database connection pools, and wide-ranging enterprise support.

JRuby Install

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/profile to include the following:
export JRUBY_HOME="/usr/local/jruby-1.0"
export PATH="/usr/local/bin:/usr/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin:$JRUBY_HOME/bin"
export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
export ANT_HOME="/Developer/Java/Ant"

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.xml file:
<Context path="/myapp" reloadable="true" crossContext="true">
<!-- Database Connection Pool -->
<Resource name="jdbc/myapp"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory"
maxWait="1000"
removeAbandoned="true"
maxActive="30"
maxIdle="10"
removeAbandonedTimeout="60"
logAbandoned="true"
username="myuser"
password="mypass"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://127.0.0.1:3306/myapp_development?autoReconnect=true" />
</Context>

This is the corresponding database.yml file that tells Rails where to find the connection pool data source:
development:
adapter: jdbc
jndi: java:comp/env/jdbc/myapp

production:
adapter: jdbc
jndi: java:comp/env/jdbc/myapp

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 /etc/profile:
export JAVA_OPTS="-Xms128m -Xmx512m -Djruby.objectspace.enabled=false -Djruby.jit.enabled=true"

Performance

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
1 554 1070 586 378
2 861 928 519 885
3 719 1450 496 680
4 833 800 484 507
5 863 827 701 414
Average 766 1015 557 572

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

11 comments

  1. Blogger Seth said:  

    JRuby encourages tight coupling between Ruby code and Java libraries. This will hurt when a codebase migrates to the standard Ruby VM. Double-dipping parts of the codebase will have to be rewritten to work with the Ruby peer of the Java framework... assuming a suitable gem can be found.

    Another untested but looming concern is whether JRuby will support Ruby 1.6 and 1.8. That's a lot of work!

    Deploying JRuby to a battle-tested VM on existing servers is neat. Grails can match that. And the VM in Ruby is improving. What are the long-term benefits of JRuby?

  2. Blogger Julian Doherty said:  

    seth - JRuby only has to be as tightly coupled to Java as you want. JRuby's features are a superset of Ruby. If you want pure portable code, just don't include or work with any Java classes directly. The nice thing about Ruby on Rails on J2EE is that you CAN access legacy JMS queues or EJBs if you really need to - once you do there isn't any going back to Ruby VM land though.

    It's really like the arguments you used to hear about keeping J2EE apps pure to the spec, because you might want to migrate app servers - or just using standard SQL in case you want to move databases. It's academic overkill. Real products almost never migrate environments like that, and in practice it just isn't an issue.

    If they do need to change, or run in multiple environments, then it's old fashioned good design and testing that is required.

  3. Blogger cb said:  

    Hey thanks for this.

    My belief is that Rails will likely never perform well on JRuby or anything other than native Ruby on Linux (perhaps YARV eventually).

    However, I think there is room to build a specific rails-like (perhaps far better) framework for JRuby. One that can take full advantage of the JVM as a platform, just as Rails takes full advantage as Linux as a platform (as you found, Rails sucks on pretty much anything other than Linux --windows, osx and jvm included).

    There's room for a new framework that ties the advantages of these two powerful tools together like peanut butter and chocolate.

    JRuby would knock Ruby off the rails with the right framework... ;-)

    Clinton

  4. Blogger Dermot said:  

    I believe that JRuby could really shine as a deployment option once the JRuby team get byte-code compilation of ruby going.

    Who knows when that will be though...

  5. Blogger Seth said:  

    This comment has been removed by the author.

  6. Blogger Seth said:  

    (oops, I accidentally posted instead of previewed)

    julian -- time will tell whether JRuby can keep up with Ruby. Sun is funding it. Charles Nutter is enjoying it -- "Compilers are hard. But not so hard as people would have you believe."

    But as production RoR apps depend on Ruby 1.6, 1.8, 1.9, 2.x it will be increasingly gritty for JRuby to maintain feature & flaw compatibility with each of those versions.

    Groovy naturally works with the dev & runtime environment. Grails has copied a lot from Rails. It's not entangled with interpreting an evolving dynamic language.

    Like Jython, JRuby is an interesting experiment. But it doesn't seem like a long term solution.

  7. Blogger Doug Smith said:  

    Great discussion, all.

    I agree that JRuby does not encourage or require tight coupling with Java libraries. It just provides that opportunity to leverage them in an easy and powerful way. I also don't agree that RoR apps require compatibility with all prior versions of Ruby. Ruby and Rails are so rapidly evolving that new apps will almost always need to start with the latest versions of everything, and upgrades to old apps might need to include Ruby/Rails version upgrades too.

    Regardless, JRuby is exciting because it seeks to leverage Java's 10+ years of experience in rock-solid enterprise deployments. Why re-invent the wheel, when you have a virtual machine that is battle-hardened and proven on so many environments? The respectable performance of JRuby in its 1.0 release, along with the huge energy behind Ruby, Rails, and JRuby, make me think it's unwise to write it off as an experiment this early in the game.

  8. Blogger Todd said:  

    Bruce nailed it. The big advantage for going the JRuby route is that ENTERPRISES have a lot of Java servers an and a lot of tools setup to monitor these servers. Allowing Ruby to run in the JVM makes an easier argument when talking to the manager of the data center in allowing a new application runtime to be installed. I just wish the Ruby/Rails community would start to get on the ball with Oracle/Sql Server support (I know in theory you can hook up, and I've done this, but most Rails apps I've tried to migrate into SQL Server or Oracle, fail). (A lot of enterprises have huge investment in Oracle and SQL, and have an army of DBAs and processes to manage huge data centers, sometimes a new Database just isn't possible...sorry for the tangent.

  9. Blogger Todd said:  

    oh, also, look at what ThoughtWorks.com is doing with releasing commercial, enterprise apps written in Ruby and running on JRuby.

  10. Blogger Cornelius said:  

    I having a problem. I am new to programming in Ruby. I keep getting this error:

    Exception in thread "main" java.lang.NoClassDefFoundError: edu/emory/mathcs/back
    port/java/util/concurrent/locks/ReentrantLock

    Any help that you can give would be great.

    I am running both JAVA1.4.12 and Java1.6

  11. Blogger Doug Smith said:  

    Hi Cornelius, I'll need a lot more info before I can help. Does your app work outside of JRuby? When does this error occur? It looks like you may be missing some important library, so you might re-trace your install steps.

Post a Comment

« Home