views:

217

answers:

3

I am working on a very typical web application. The main component of the user experience is a widget that a site owner would install on their front page. Every time their front page loads, the widget talks to our server and displays some of the data that returns.

So there are two components to this web application:

  1. the front end UI that the site owner uses to configure their widget
  2. the back end component that responds to the widget's web api call

Previously we had all of this running in PHP. Now we are experimenting with Rails, which is fantastic for #1 (the front end UI). The question is how to do #2, the back serving of widget information, efficiently. Obviously this is much higher load than the front end, since it is called every time the front page loads on one of our clients' websites.

I can see two obvious approaches:

A. Parallel Stack: Set up a parallel stack that uses something other than rails (e.g. our old PHP-based approach) but accesses the same database as the front end

B. Rails Metal: Use Rails Metal/Rack to bypass the Rails routing mechanism, but keep the api call responder within the Rails app

My main question:

  1. Is Rails/Metal a reasonable approach for something like this?

But also...

  1. Will the overhead of loading the Rails environment still be too heavy?
  2. Is there a way to get even closer to the metal with Rails, bypassing most of the environment?
  3. Will Rails/Metal performance approach the perf of a similar task on straight PHP (just looking for ballpark here)?

And...

  1. Is there a 'C' option that would be much better than both A and B? That is, something before going to the lengths of C code compiled to binary and installed as an nginx or apache module?

Thanks in advance for any insights.

+2  A: 

Not really the most elaborate answer but:

I would not use metal for this, I would use page caching instead. That way, the requests will be served by the webserver and no dynamic languages at all. When you create a resource clear the corresponding index page. A very basic example would be:

class PostsController < ApplicationController
  caches_page :index

  def index
    @posts = Post.all
    respond_to do |format|
      format.html
      format.xml
    end
  end

  def create
    @post = Post.new(params[:post])
    respond_to do |format|
      if @post.save
        expire_page :action => :index
        format.html { redirect_to posts_path }
        format.xml
      else
        format.html { render :action => "new" }
      end
    end
  end
end

For more information read the Caching Guide.

Ryan Bigg
Thanks Ryan. Just to clarify, are you suggesting that I use the caching mechanism to cache results for the web API? If so, I guess the benefits of this would depend on the degree to which those results are changing over time. Or am I missing your point?
Greg
@Greg: Yes, I am suggesting you cache the API results.If the results are changing *per-request* then it's not much use doing caching, but even if they're changing every 3rd request it's going to be 2 requests served that are not hitting the Rails stack, and even that is good enough to be using caching.
Ryan Bigg
+2  A: 

PHP loads the entire environment on each request. In production mode, Rails loads the entire environment once when the server starts up. There is certainly a fair amount of Ruby code being executed during normal controller-action invocations. However, in production mode, none of this code is related to loading the environment. And using Rails Metal instead of the usual Rails controller stack removes a number of those layers, yielding a few additional milliseconds of time saved per request.

Justice
Thanks Justice -- this is very interesting. Just to make sure that I'm drawing the right implication: since Rails loads the environment once and Metal bypasses a lot of the Rails stack, Rails/Metal might even be faster than an approach based on pure PHP?
Greg
That is correct.
Justice
+1  A: 

I would only start pulling functionality down to Rack/Metal if I had determined the exact cause of any performance issue being encountered. Particular in recent versions of Rails (3, in particular) and Ruby, the stack itself is very rarely the bottleneck. Start measuring, get some real metrics and optimise judiciously.

My rule of thumb: if you don't have metrics, you can't reason intelligently about your performance issue and any possible solution.

The issues in my experience are nearly always: the views and the database.

As Ryan suggests, caching can be incredibly effective ... you can even move you architecture to use a reverse proxy in front of your Rails request stack to provide even more capability. A cache like Varnish provides incredibly high-performance. Rails has built-in support for etags and HTTP headers for facilitating a reverse proxy solution.

The other thing to do is look at the db layer itself. The cache can go a long way here, but some optimisation may be useful here too. Making sure you use Active Record's :include sensibly is a great step to avoid N+1 query situations, but there is fantastic support in Rails for dropping memcached into the stack with little or no configuration, which can provide excellent performance gains.

Toby Hede
Toby, thank you very much -- lots of great info here. Full agreement with the measure before optimizing point. With this question I was really trying to figure out whether the full Rails approach was a reasonable one before starting down that path, and the information from you, Justice and Ryan seems to make that a pretty solid yes. I was guilty of some assumptions around Rails performance though -- it has come a long way in the last 18-24 months since I last evaluated in any detail.
Greg