Friday, November 17, 2006

Advanced Rails Deployment with JRuby

There's a lot of work going on right now focusing on various mechanisms for deploying JRuby-based apps. This article will summarize some of the work happening and why it's really, really important for the Ruby world.

Ruby-in-a-JAR

First, a little sideline into general Ruby embeddability work.

Over the past few days I've made modifications to enable running a Ruby app completely out of a single JRuby JAR file (Java ARchive). The major changes required were:

  1. Add jarjar to the project to combine all dependency jars into a single archive, including jline (readline support), asm (compiler, other stuff in the future), bsf (scripting API), jvyaml and plaincharset (Ola's JvYAML library and supporting charset lib).
  2. Add an Ant task to build the "complete" jar and include all Ruby standard libraries in the same archive.
  3. Somewhat unrelated, a small patch for irb/init.rb to allow it to fail gracefully if it can't load locale-specific files from the filesystem.
So by running "ant jar-complete" you get out a single JAR file that contains a complete, working Ruby interpreter plus stdlib. For example:
~ $ java -jar jruby-complete.jar -e "puts 'Hello, Ruby-in-a-JAR!'"
Hello, Ruby-in-a-JAR!
~ $ java -jar jruby-complete.jar -rirb -e "IRB.start"
irb(main):001:0>
Of course, you don't have to take my word for it. I've uploaded a copy of this jar for you to try yourself. The full archive is about 2900kb. That's suitable for embedding in just about any application, and in fact I used a stripped-down 1600kb version for the JRuby Applet. Note: this is JRuby trunk code and mostly experimental...but that's what makes it so fun :)

Ruby-in-a-JAR - The ultimate in Ruby interpreter portability

A Better Deployment for Rails

Now the main course: several folks have been working on several exciting deployment scenarios for JRuby on Rails apps.

The current "best option" for deploying Rails apps into production generally involves the HTTP front-end Mongrel. Although Mongrel is largely written in Ruby, it is very fast, largely because of its native C component for HTTP request parsing. It's also considerably more secure than CGI-based options, largely because of creator Zed Shaw's attention to detail. The typical Rails app will be deployed as a "pack of Mongrels", where the number of desired concurrent requests is multiplied by the number of independent Rails apps to determine a total number of processes. These processes must be managed, monitored, and respawned as appropriate, but the result is a fairly stable and scalable deployment model.

However with JRuby, there will soon be a better option. I previously reported about TAKAI Naoto's efforts to deploy Rails behind an AsyncWeb front-end, showing tremendous performance improvements over a WEBrick-based deployment. Naoto-san has now taken things to the next level: Rails deployment under GlassFish.

The potential here should be obvious. GlassFish, like other Java EE application servers, is extremely good at scaling up many concurrent requests across many independent applications; so good that many organizations deploy only a single appserver-per-machine and stuff it full of applications to serve. That means a single server, a single process to manage. GlassFish also supports clustering, which means you'll be able to hit the deploy button once and have your n-server cluster instantly start serving up Rails. But there's one last area that trumps all the rest:

That single app server can handle as many concurrent requests across as many independent Rails apps as you desire, scaling them across all the CPU cores you can throw at it.

That's right...no more N * M process management, no more zombie processes, no more immature tooling to manage all those servers and all those deployments. One tool, one server process, no headaches.

That's an extremely bright future, and we're almost there.

What Next?

Naoto-san is not the only one working on JRuby on Rails deployment options. There are a number of folks in the JRuby community approaching the same goal from different directions, using innovative techniques like servlets implemented in Ruby and Spring-based service wiring. The JRuby community sees the potential here and things are moving very quickly.

My work on Ruby-in-a-JAR will also play directly into this. Currently most deployment scenarios require Rails app files to remain "loose" on the filesystem, as with the current standard deployment model. However it won't be long before you can zip up your Rails app into a WAR file (Web ARchive) and deploy it lock, stock, and barrel to as many servers as you want.

These efforts combined will create, in my opinion, the most manageable, scalable, powerful Rails deployment model yet available...and it's just around the corner.

We've also launched into Rails compatibility work in earnest. I've created a wiki page on JRuby support for Rails that details the results of running Rails' own test cases. Long story short: we're looking pretty damn good.

JRuby on Rails is in the home stretch. And we're covering ground very quickly.

11 comments:

Chris said...

Hi Charles. I looked at your build.xml and it doesn't look like you're using any jarjar "rule" elements. This means that the class files from your dependency jars are just getting stuffed into the jar without any repackaging. If this is what you want then you could just use the normal "jar" task instead of jarjar.

Charles Oliver Nutter said...

We will likely want to use rule elements in the future, so I figured I'd just start using jarjar now.

Kees Kuip said...

Do you have an applet on your blog?

If visit your blog java starts up and starts eating 100%cpu and 100%network. I'm on Linux and use the latest jdk1.6.0.

Maybe you could look at it. I love to read your blog but I hate to remove the java plugin each time.

Kees.

Charles said...

This is great news -- thank you. I've started building Spring MVC applications with JRuby implementations with great success... but setting the load path to find Ruby libraries always felt dirty.

Being able to run an entire JRuby application from jars lets us go to deployment with a proven method that SysAdmins are comfortable with.

venkat said...

Hello Charles:

I Thank you for all the contributions to the JRuby. One question I have on the deployment with GlassFish:

One benefit of the multi process model is that the developer need not worry about multi threading issues. If my memory is correct, Rails itself is not thread safe. How can such environment be deployed in a multi threaded server?

Thanks in advance for your answer,
Venkat.

Charles Oliver Nutter said...

venkat: There's an absurdly simple solution to this...

Use multiple JRuby runtimes.

Because we can share the vast majority of the JVM across JRuby runtime instances, you can emulate the cumbersome and over-heavy multi-process model using JRuby's "multi-VM" support. You need to handle 100 requests at the same time? Easy, 100 JRuby runtimes. All that's duplicated is the top-most layer of the virtual "Ruby machine" under the Rails app, rather than the entire process. Far more scalable and trivial to implement.

venkat said...

Charles: Thank you very much for your reply. Sorry if these a really dumb points. I thank you for your thoughts on these.

1. Rails being not thread safe by default, isn't Multi-VM approach the only way to run Rails in the WAR deployment style?

2. I agree that not having to launch the heavy process for each run time is a good thing. However, isn't the case that if a JRuby runtime dies it still needs to be restarted manually inside the application code someware as opposed to container doing it with threads and the management flexibility comes with it?

3. Isn't it the case that configuring the number of lightweight JRuby VMs needs to be done in a non standard way inside the app as opposed to doing it at the level of container like Tomcat?

Thanks,
Venkat.

nohmad said...

Did you write jline wrapper for jruby as in readline wrapper for ruby? jruby-complete.jar works really nice. However IRB in applet didn't work in my OSX.

$ jar -tvf jruby-complete.jar | grep -i applet
821 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/DOMScriptingApplet$1.class
1903 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/DOMScriptingApplet.class
1174 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet$1.class
2165 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet$2.class
1346 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet$3.class
1260 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet$4.class
723 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet$5.class
3779 Fri Nov 17 11:50:54 KST 2006 org/jruby/demo/IRBApplet.class

Tony Hursh said...

This is great, Charles! Thanks!

I just put together an experimental template for Xcode on the Mac that uses jruby-complete.jar to generate self-contained JRuby applications for OS X. If anyone wants to give it a whirl, you can find it here:

JRuby Xcode template

This is experimental as all get-out, so caveat coder. :-)


P.S. I think Blogger mangled your "Ruby-in-a-Jar" link above.

Charles Oliver Nutter said...

venkat: Re your additional questions...

1. Yes, most likely. However multiple JRuby VMs is a lot more lightweight than multiple Ruby processes.
2. The JRuby runtime is pretty simple to respawn; just instantiate a new one. That said, we're hoping some of the long-term execution issues that plague C Ruby won't plague us since the JVM has a more robust garbage collector and memory model.
3. It doesn't have to be non-standard; it could certainly be handled by the container, on a per-thread, per-app basis. There's no requirement that it be more cumbersome than setting up a pool of threads or services for any typical Java app.

nohmad: Yes, we have a wrapper for jline that works really, really great! It was contributed by Damian Steer. We've also got assurances from the jline author that he'll accept our patches and fixes, so we should have good solid readline support from now on.

tony hursh: oh, now THAT is cool. I'm on OS X and I'm all over using something like this to build apps. This is a perfect example of how a self-contained Ruby intepreter can be so very flexible. I'm blogging this :) I hope you'd be willing to contribute it to the jruby-extras project on RubyForge and maybe join the project to maintain and improve it. Thanks so much!

Anonymous said...

Hi Charles

I am using the jruby-complete.jar which is a cool idea. It works great when I use erb or csv in the rb file called from java.

However, if I do a require 'test/unit' in the rb file and it was called from java, I am trying to call a rb file not at the top level and get an excpetion.

Rich