a weblog

by jordan hollinger

Deploying with Thin

I’ve written on how to properly install and configure Thin, how to use Thin to run multi-threaded Rails apps, and even how to get Thin and Bundler to play nicely together. But I just realized that I’ve never discussed an actual deployment, complete with hookup to Nginx, how to cleanly restart the app, pros, cons, etc. I do not claim to be an expert; this is merely my experience. If you have suggestions, please let me know in the comments.

Firstly, this post assumes you will at this point go back and read (at least) the first link above. Secondly, this post is about using Thin behind Nginx. That appears to be the most common setup, and I like it.

Is Thin a good choice for production?

Sure. Here’s one biased benchmark by the Thin guy, and here’s another biased benchmark by a Passenger guy. At worst, it’s on par with its competitors. At best, it blows them away. My experience is somewhere between the two. And it seems to use less RAM than Passenger. I use it for Rails, Sinatra, and a few straight-up Rack apps.

The most important piece is, however, what your app is doing during those requests. Only your code can prevent forest fires determine if Thin will get 10s, 100s, or 1000 reqs/sec.

But why should I use it instead of say, Passenger?

I’ll admit that Passenger is a simpler set up. It takes care of setting up a reverse proxy from your Web server, clean app restarts, process monitoring, and starting and stopping new app instances in response to load. Why give all that up? For me, two reasons. 1) Too much RAM and 2) it didn’t perform very well for large apps with critical yet small to medium load. Our primary Rails app was so huge it took 30-40 seconds to boot up. We eventually broke it into modular, engine-powered apps, but some of those still took 20-30 seconds. It may go hours without being used, then see a flury of activity, then a drop off, then another increase. Regardless of config options designed to help, Passenger would kill of all or some workers at low periods. When usage picked up again, for too many users would be stuck for ~30 seconds waiting for workers to spin back up.

Not a deal-breaker, but it became an itch in the middle of our backs that we couldn’t reach. Eventually I began to wonder why Passenger couldn’t handle more than one request at a time per-worker (e.g. multi-threading). It would eliminate this problem, be a much more efficient use of our resources, and I knew that Rails had the ability. But it is foreign to Passenger’s philosophy. Thin on the other hand, has a threaded option, and it works very well. So we switched.

Following is the setup I used. It is more complicated, but I like the fine-grained control that it offers.

Restarting Thins

You’ll want at a minimum two Thin instances running for your app. That way they can be restarted sequentially, meaning at least one is always running. Add the following to your app’s Thin config:

servers: 2
onebyone: true

Now you (or your deployment script) can restart your app with thin restart -C /etc/thin/app.yml and your thins will be restarted one-by-one.

Thin socket config

Thin can communicate with the Web server using either tcp or sockets. Happily, Nginx can also communicate with an upstream server over either tcp or sockets. Since sockets are faster, we’ll use them. Add the following to your Thin config file (remove the port setting if it’s present):

socket: tmp/sockets/thin.sock

Nginx upstream server config

The docs are quite thorough, but I’ll distill them a bit. In short, this tells Nginx to balance between the two Thins. If one Thin goes down (e.g. a one-by-one restart), requests will be routed to the other. This is a pretty barebones config, but it is functional.

upstream my_app {
  server unix:/var/www/my_app/tmp/sockets/thin.0.sock max_fails=1 fail_timeout=15s;
  server unix:/var/www/my_app/tmp/sockets/thin.1.sock max_fails=1 fail_timeout=15s;
}

server {
  listen 80;
  server_name my_app.com www.my_app.com;

  # An alias to your upstream app
  location @my_app {
    http://my_app;
    # Define what a "failure" is, so it can try the next server
    proxy_next_upstream error timeout http_502 http_503;
    # If the upstream server doesn't respond within n seconds, timeout
    proxy_read_timeout 60s;
  }

  # Set your doc root for static files
  root /var/www/my_app/public;

  # See if the request matches a static file. If not, pass it on to your app.
  try_files $uri @my_app;
}

For a an excellent explanation and example of a very robust Nginx setup, read calomel.org/nginx.html.

Things to watch out for

“Leaking” RAM

This is not unique to Thin, and in fact is a problem with your code itself. Poorly-written Ruby code can chew up tremendous amounts of RAM which are used for moments, then rarely released. (This isn’t “leaking” per-say, but the effect is the same.) If unchecked, this can bring your system to its knees. A good rule of thumb in Ruby is, create as few objects as possible at a time. And remember – everything in Ruby is an object! If you’re using Rails and ActiveRecord, look into the find_each method, which loads records in batches, rather than all at once.

Monitoring

Passenger has a master process that monitors, spawns, and kills its worker processes. Thin’s design allows for no such system, so you may want to look into Monit. It can restart processes if they die, monitor their memory usage, and tons more.

Yeah, so like I said it’s more complicated than Passenger. But as a result, I believe I have a faster, leaner and more transparent deployment system. I may add things occasionally as I think of them. Let me know about your experiences and if I can improve my setup in any way.

Getting Bundler and Thin to play nicely

Before I get your hopes up, let me clearly state that there is no hope. Bundler and system-installed Thin will never play well together. The only bright spot here is that, since the universe is ultimately doomed to self-destruction, none of this matters, cosmically speaking.

You’ve probably seen a message like this when starting a Rack/Rails app on Thin

You have already activated rack 1.3.0, but your Gemfile requires rack 1.2.3.
Consider using bundle exec. (Gem::LoadError)

What that means is Thin (which uses Rack) loaded Rack 1.3.0 when it started up. Now Thin is loading your application (which also uses Rack). Your app has bundled Rack 1.2.3, and when Thin tries to load it, Ruby notices the version conflict. Ruby can’t handle it and promptly explodes.

The problem

You probably tried to start your app with /etc/init.d/thin start. This means that Thin and its dependencies (e.g. Rack 1.3.0) are installed someplace like /usr/local/ruby/. And doubtlessly you used bundle install to install your apps dependencies (e.g. Rack 1.2.3) to /path/to/app/vendor/bundle. As you can see, you are using gems from two separate install points, which were installed irrespective of each other’s dependencies. Sometimes they happen to match up, but that’s rare. Yehuda Katz, a respected Rubyist, has this to say on the matter.

The solution’s problem

The article is a good one, and a convincing case for bundling tools like rake and cucumber with your app. Some, though, have used it as an argument for bundling Thin with your app to fix the above problems. They say you should use bundle exec thin start -C /etc/thin/app.yml to start your app.

I’m telling you that’s a terrible idea, both practically and philosophically. In practice, it means you can’t depend on /etc/init.d/thin anymore. That’s something I am not willing to give up. It’s by far the best way to manage a serious Thin deployment. I suppose you could rewrite it to work somehow, but that brings us to the philosophical discussion.

Bundler is a fine piece of software written by talented people. The problem is that it shouldn’t exist. It’s a tragedy, really. If Ruby Gems had been designed better, there would be no need for extra and confusing dependency management tools like Bundler. It’s a necessary evil, nothing more. As such, it should not unduly influence the rest of your application or deployment strategy. In my book, bundling your app server in with your application “just to make Bundler happy” is undue influence. It’s as inappropriate as bundling Nginx or the Linux kernel with your app. People may want to use Passenger, Apache, and OSX instead.

A better solution? Not really, but maybe a less bad one

My favorite solution would make most Bundlerists gag, but I make no apologies. As I said, Bundler is merely a necessary evil, and beyond installing our app’s dependencies, it doesn’t deserve our attention. Usually, the version of Rack specified in Gemfile.lock is a “soft” dependency – your app will work fine with a wide range of versions. With that in mind, I update my system’s Rack to the latest version, then specify that Rack version in all my apps’ Gemfile.lock files (testing them first, of course). Then, your system and bundled Rack’s will match up, and /etc/init.d/thin start will work just fine.

Yes, that goes against everything in Yehuda’s post. But we’re talking about a Web server here, not some little utility like rake. You have to make some assumptions about your environment, and Rack is just one of them.

The only other option, which I have not actually explored, is to bundle Thin with all your apps and rewrite /etc/init.d/thin to visit each app and run bundle exec thin start -C …. In my opinion that’s a very distant second.

If you’ve found a better solution, please let me know in the comments!

Writing an Ajax long polling server in Ruby, Part 2

Hopefully in Part 1 I wrote about a means of writing and deploying a long-polling server in Ruby. Of course that’s useless by itself, so here we’ll hook it up to a browser client via Ajax.

We’ll touch on

CORS (Cross-Origin Resource Sharing)

Unfortunately you can’t just fire an Ajax request at polling.myapp.com and go. Actually it’s not unfortunate, because it’s an important part of browser security known as the Same Origin Policy . For our purposes, it states that our script loaded from myapp.com can’t make an Ajax request to any other domain.

Obviously that’s quite limiting, so the Sort of People Who Think Up These Kinds of Things thought up CORS. Here’s an article about it and here’s another one .

In short, if your client is IE 8+, Firefox 3.5+, Safari 4+, or a recent Chromium derivative, then you can use CORS without any extra work on the client side. There’s a little server side work, but we already covered that. Remember under the ‘My App’ section of Part 1 where I added the Access-Control-Allow-Origin header? That’s it. Just set that header to a ‘myapp.com’ to allow access. To allow access from anywhere, just use *. (Unfortunately *.myapp.com isn’t supported, which irks me.)

Javascript and browser issues

Using jQuery, I’m putting some example Javascript below. Theoretically it’s quite straightforward, but there’s always that one thing that ruins your day. (Yes, it’s what you’re expecting.) While version 8+ of Microsoft’s “browser-like program” claims to support CORS, it does not do so through the XMLHttpRequest object like every other browser on the freaking planet. They had to go and create a whole new object just for use with cross-domain requests. Why? I suspect someone couldn’t find a puppy to kick that morning.

With that unpleasantness in mind, here are some codes:

// Assume we're in a sane, modern browser
$(function() {
  try {
    // Test the waters with a "ping" to the root of the polling app
    $.ajax({
      url: 'polling.myapp.com',
      type: 'GET',
      dataType: 'json',
      success: function(data, textStatus, jXHR) {
        // Ping went through, initiate long polling
        if ( data.ack && data.ack == 'huzzah!' ) long_poll_walls(walls)
        // Ping failed, fall back to simple polling every 20 sec
        else setInterval(function() { simple_poll_walls(walls) }, 20000)
      },
      error: function(jXHR, textStatus, errorThrown) {
        // Ping failed, fall back to simple polling every 20 sec
        setInterval(function() { simple_poll_walls(walls) }, 20000)
      }
    })
  // Guess we aren't
  } catch (e) {
    // It's Microsoft's browser-like program!
    if ( jQuery.browser.msie && window.XDomainRequest ) ie_long_poll_walls(walls)
    // Fall back to simple polling every 20 sec
    else setInterval(function() { simple_poll_walls(walls) }, 20000)
  }
})

// For normal browsers
function long_poll_walls(walls) {
  d = {last_post_id: {}, session_id: 'some token to prove who you are'}
  // Record the last post under each wall.
  $(walls).each(function() {
    var wall_id = $(this).attr('data-id')
    d.last_post_id[wall_id] = $('.post:last', this).attr('data-id') || 0
  })
  $.ajax({
    url: 'polling.myapp.com',
    async: true,
    timeout: 180000, // 3 minutes
    type: 'POST', // Using POST because it makes the polling app a little harder to abuse
    data: d,
    dataType: 'json',
    success: function(data, textStatus, jXHR) {
      var success = wall_polling_callback(walls, data, textStatus, jXHR)
      if ( success ) long_poll_walls(walls)
      else setTimeout(function() { long_poll_walls(walls) }, 5500)
    },
    error: function(jXHR, textStatus, errorThrown) { long_poll_walls(walls) } // Assume it just timed out and continue
  })
}

// For IE
function ie_long_poll_walls(walls) {
  // Build params to send with request
  var params = ''
  $(walls).each(function() {
    var wall_id = $(this).attr('data-id')
    var last_post_id = $('.post:last', this).attr('data-id') || 0
    params += 'last_post_id['+wall_id+']='+last_post_id+'&'
  })
  params += 'session_id=your happy session token'

  // Set up request
  var xdr = new XDomainRequest()
  xdr.open('post', 'polling.myapp.com')
  xdr.onload = function() {
    var success = wall_polling_callback(walls, JSON.parse(this.responseText), 'success', xdr)
    if ( success ) ie_long_poll_walls(walls)
    else setTimeout(function() { ie_long_poll_walls(walls) }, 5500)
  }
  xdr.send(params)
}

Short-polling fallback

Older browsers without CORS support will need a simpler short polling scheme, every n seconds. For simplicity’s sake, I just make this a part of my Rails app.

function simple_poll_walls(walls) {
  // Just a normal Ajax request to your app with callbacks.
  // You can figure it out.
}

Why write your own?

There are several good Ruby long-polling servers out there (e.g. Goliath) so why would you want to write your own? For starters there’s a small sense of pride. But mostly, it’s the best way to understand what’s going on. Sure, Goliath may be superior to my attempt in many respects. But really, isn’t this more fun?

Writing an Ajax long polling server in Ruby, Part 1

I spent weeks researching ways to build an Ajax chat server for a Rails app. The info is out there, but very fragmented. Nowhere did I find a single resource that explained the whole picture, each piece I needed, and why. Hopefully this can be that resource for you. I do not claim to be an expert, but I did build one, and it works very well. I guess all I claim is, “here’s what I did, hope it gives you some ideas.” Please point out errors and make suggestions. YMMV. I assume you have a good grasp of Ruby, Javascript, and Web-related technologies in general.

Because it’s so lengthy, I’ve broken this up into two parts. Part 1 deals with the server side, while Part 2 deals with tying it into the client side.

Up front I’ll tell you that you’ll need a separate app (ideally on a subdomain) to handle the long polling. It probably should not be Rails, and it should not run on Apache. What we’ll end up with is an Async Sinatra app running on Thin, reverse-proxied through Nginx.

We’ll touch on

WebSockets will one day save us all

WebSockets are the future, providing full-duplex asynchronous push communications between Web browser and Web server, as well as food, shelter and love to everyone on Earth. Unfortunately the future isn’t here yet. Ajax long-polling is a cobbled-together hack until WebSockets and flying cars arrive. When they do, I recommend em-websocket .

Short vs. long polling

Regular, or what I’ll call “short” polling is the youngest kid on Christmas morning pestering, “Can I open my presents now? Can I open my presents now?? Can I open my presents now???!!?” The webserver parents get so overwhelmed that they eventually shut down and stop responding. Long polling is the disinterested teenager who, with headphones on, requests “Tell me when we’re going to open presents.” That one question just hangs there until the parents are ready.

Nginx

Apache’s a great Web server. It’s a wunderkind of the Open Source world, probably only rivaled in success by GNU/Linux. But it is not an asynchronous server . It’s a process-based server, and long polling will bring it to its knees as Apache forks after process for the onslaught of long-running requests.

Apache can do almost anything imaginable; Nginx does the six things you need, and 20x faster . Nginx handles reverse-proxies and load-balancing through TCP or Unix sockets , URL rewrites , SSL , gzipping , easy Cache-Control headers – everything most Web apps need. And of course it’s a great general-purpose Webserver. Here’s a good intro to the basics.

But the killer feature here is that it can easily handle thousands of concurrent requests while using only MB’s of memory. Your Nginx virtual host would look something like below. Brilliantly simple, isn’t it?

upstream  polling-app {
  server  unix:/path/to/thin.sock;
  # Sockets are faster than your TCP/IP stack. Use them if you can!
  #server  127.0.0.1:3000;
}

server {
  listen  80; ## listen for ipv4
  server_name  polling.myapp.com;

  access_log  /var/log/nginx/polling.access.log;
  error_log  /var/log/nginx/polling.error.log;

  location / {
    root /path/to/root/not/sure/it/matters/because/no/files/are/served;
    proxy_pass http://polling-app;
  }
}

Personally, I’ve dropped Apache and switched everything over to Nginx. But if you’re not comfortable doing that, I’d recommend running Nginx in front on 80/443. Your polling app would look like the above example. For everything else, you can switch Apache to use port 8080 (or whatever), and have n Nginx virtual hosts reverse-proxying to your n Apache virtual hosts over port 8080.

Thin

Mongrel? Passenger? Unicorn, Rainbows! or Zbatery? Thin is one of those guys . It’s a Rack app server, running your app’s code behind your Webserver. (Heck, it can even act as a full Webserver with SSL support!) Thin handles requests asynchronously with EventMachine. It can handle a lot at once, which is why it and Nginx are a great pair for this. (Rainbows! and Zbatery might work as drop-ins, but I have more experience with Thin.) Thin’s also a Ruby gem, making it wicked-super-easy to install. In fact it’s probably the least complicated piece of this whole thing. Just install it, write a small config file for your polling app, and you’re done.

Here’s a brief tutorial I wrote on configuring your Thin apps and getting them to start automatically when your system boots up.

Thin is a little unique in that it can be bound to a port or a socket. Since Nginx can reverse-proxy to a socket, and sockets are generally faster than climbing through the TCP/IP stack, I’d
recommend using them if possible. You can find details in thin -h .

EventMachine

EventMachine is what makes all of this possible; a working understanding is critical. The main page of their docs is very good, so give it a read .

  EventMachine::run do
    puts "There's a job to do!"

    job = lambda do
      i = 0
      while i < 10000
        i += 1
      end
      i
    end
    callback = lambda do |num|
      puts "Job done; it counted to #{num}!"
    end

    puts "Starting job..."
    EventMachine::defer job, callback
    puts "Job started!"
    puts "Let's do other stuff while that's running!"
    puts "Other stuff..."
  end

That’s a dump example, but it should get the point across. While it’s counting to 10,000, you can do other stuff. Whenever it’s done it will print out the result. Read over the docs for a whole lot more info.

Sinatra and async_sinatra

Sinatra will be the meat (or tofu, if that’s your thing) of our polling server. It’s a micro-framework written in Ruby. Comparing it to Rails you might say it handles routes and controllers, but everything else is up to you or optional Rack Middleware. It has a great intro and docs , so I’ll let you peruse those at your leisure. But because I’m such a good chap, here’s a small example:

  # Defines a GET action at "/hello"
  get '/hello' do
    sleep 10
    'Hello!'
  end

  # Defines a POST action at "/bienvendidos"
  post '/bienvendidos' do
    'Bienvendidos!'
  end

Notice sleep 10 . Pretend that’s instead a very important, intensive operation that takes about 10 seconds. If you GET /hello and then immediately POST to _/bienvendidos, your bienvendidos request will have to wait on /hello to finish. Put a pin in that.

Async Sinatra is a small yet powerful gem allowing Sinatra to dip down into Thin’s eventmachine-driven innards and deliver responses asynchronously. In short, this means we can easily handle a whole bunch of long-running connections at once. Converting the above example, we would have

  # Defines a GET action at "/hello"
  aget '/hello' do
    big_job = lambda { sleep 10 }
    result = lambda { 'Hello!' }
    EM.defer big_job, result
  end

  # Defines a POST action at "/bienvendidos"
  apost '/bienvendidos' do
    'Bienvendidos!'
  end

Pull that pin out. If you try the same test here, /bienvendidos will return right away while /hello works in the background. As you may have guess, EM is just a handy alias to EventMachine .

My App

Now that you have all the pieces, I’ll show you how I put them together. To understand where my code is coming form, and where yours may want to differ, a brief explanation of what I’m polling and how it’s being used is in order. The larger purpose of the Rails app is unimportant, but one requirement was a real-time, persistent group chat/message board/notification area which I called “Walls.” Groups of users have access to certain Walls. Messages posted to these Walls are stored in the database and can be reviewed at any time. But when users are signed in, they should be able to communicate in real time (long-polling).

For efficiency, I have only one job hitting the database every 1 sec. This job stores a hash like {1 => 57, 2 => 67, 3 => 355}, where 1, 2 and 3 are Wall ids and 57, 67 and 355 are the latest message ids from those Walls. For even more efficiency, this job only runs when any clients are connected. We’ll call this The Global Hash.

Each browser connection (polling request) sends a similar hash containing the latest message ids it has. We’ll call this The Local Hash. While the client’s connected, every 0.5 sec, the polling request sees if The Global Hash has any newer message ids than The Local Hash. If so, it grabs those messages from the db and returns them to the browser.

In the code below, you’ll notice the AppPoller class does most of the heavy lifting. That code is very application-specific and probably wouldn’t do you much good. With that in mind, I’m only showing you the Sinatra code, which should be more than enough to give you some ideas.

config.ru

require './app'
run Pollster

app.rb

require 'rubygems'
require 'sinatra/async'
require './config/boot' # Requires files with custom classes like AppPoller, sets up db connection, etc.

# When the reactor starts...
EM.next_tick do
  # Run this every 1 second
  EM.add_periodic_timer(1) do
    # IF anyone's connected, poll the database for new messages.
    # Take the last message id from each wall and store it in a hash like {1 => 56, 2 => 77}
    AppPoller.poll! if AppPoller.has_clients?
  end
end

class Pollster < Sinatra::Base
  register Sinatra::Async

  # Create a new HTTP verb called OPTIONS.
  # Browsers (should) send an OPTIONS request to get Access-Control-Allow-* info.
  def self.http_options(path, opts={}, &block)
    route 'OPTIONS', path, opts, &block
  end

  # Ideally this would be in http_options below. But not all browsers send
  # OPTIONS pre-flight checks correctly, so we'll just send these with every
  # response. I'll discuss what some of them mean in Part 2.
  before do
    response.headers['Access-Control-Allow-Origin'] = 'myapp.com' # If you need multiple domains, just use '*'
    response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
    response.headers['Access-Control-Allow-Headers'] = 'X-CSRF-Token' # This is a Rails header, you may not need it
  end

  # We need something to respond to OPTIONS, even if it doesn't do anything
  http_options '/' do
    halt 200
  end

  # The root path will serve as a kind of "ping" for our clients.
  # We'll respond to everything with JSON.
  aget '/' do
    response.headers['Content-Type'] = 'application/json'
    body '{"ack": "huzzah!"}'
  end

  # Technically we should use GET, but POST makes it less susceptible to abuse
  apost '/' do
    response.headers['Content-Type'] = 'application/json'

    # Find the user. This is left as an exercise for you.
    user = AppPoller.get_user(params[:session_id])
    unless user
      body '{"errors": ["Invalid user!"]}'
      halt 400
    end

    # Find/parse the last post ids.
    # This is a hash like {1 => 56, 2 => 77} where 1 and 2 are Wall id's,
    # and 56 and 77 are the latest message id's this user has for those
    # walls.
    # user.resolve_last_post_ids is for security, stripping out any 
    # walls the user isn't supposed to have access to. Another exercise for you.
    last_post_id = user.resolve_last_post_ids(params[:last_post_id])
    unless last_post_id.any?
      body '{"errors": ["Invalid parameters!"]}'
      halt 400
    end

    # This is the job that will keep checking for new messages for this
    # user's walls
    pollster = proc do
      AppPoller.add_client user
      time, new_posts = 0, false

      # After a minute, most browsers or proxies will have severed the connection,
      # and we don't want this job running forever.
      until time > 60
        # This just compares the user's latest post_id's to the global hash, so it's very cheap.
        new_posts = AppPoller.posts_since?(last_post_id)
        break if new_posts
        sleep 0.5
        time += 0.5
      end

      # If there were new posts, grab them from the database
      new_posts ? AppPoller.posts_since(last_post_id) : []
    end

    # This job takes the new posts (if any), converts them to JSON,
    # and sends the response.
    callback = proc do |new_posts|
      AppPoller.drop_client user
      walls = {:walls => {}}
      new_posts.each do |p|
        walls[:walls][p.wall_id] ||= {:posts => []}
        walls[:walls][p.wall_id][:posts] << p.to_hash
      and
      body walls.to_json
    end

    # Begin asynchronous work
    EM.defer(pollster, callback)
  end
end

Stay tuned for Part 2!

How to deploy a multi-threaded Rails app

Multi-threaded Rails apps have to be the best kept secret in the Rails community. “What do you mean? It’s been discussed at length for years!” Yes, the benefits, drawbacks, and limitations have been. But, JRuby aside, I have never ever seen someone saying “This is how you do it.”

It’s easy to find instructions for turning threaded mode on in Rails. Just comment out config.threadsafe! in config/initializers/production.rb . But that’s only half the story. Normally, Rails has a mutex around request handling, allowing only one at a time. threadsafe! removes that, telling Rails to handle requests concurrently as they come in. But your app server (Passenger, Mongrel, etc.) is what needs to send your app those concurrent requests in threads. Most never will.

Are you sure?

Go ahead, turn threadsafe! on in one of your apps. Then create an action called foo .

  ...
  def foo
    n = params[:n].to_i
    sleep n
    render :text => "I should have taken #{n} seconds!"
  end
  ..

Open a broswer window and pass it ?n=15. Quickly open another window and pass it ?n=2. Window 1 will render in 15 seconds. With config.threadsafe! on, you’d expect Window 2 to finish well before Window 1. But no, Window 2 takes 17 seconds, because it’s waiting on the first request, which is taking 15 seconds. You may have told Rails it’s safe to be threaded, but your app server isn’t threading .

An aside

I will note, as have many others, that Rails thread-safety means little if your gems are not thread-safe or are doing lots of blocking. You’ll have to deal with that on your own. My understanding is that the mysql2 gem used in Rails 3 took care of this for the average app.

Most Rails deployments can’t go multi-threaded, no matter what

Passenger and Unicorn definitely can’t. Most Mongrel deployments can’t or won’t (not sure which). These are all process-based request servers, like Apache, where a process handles only one request at a time. If you want to handle n concurrent requests, you need n copies of your app running. Rainbows! and Zbatery I believe have event-based concurrency, but that is not the same as multi-threading. So what does that leave us?

I got Thin

The only Rack server I’ve found that mentions multi-threading is Thin . It uses EventMachine to handle concurrent requests. Yay! Everything solved, right? Not quite. As is the case with Rainbows! and Zbatery, event-based processing is not the same as multi-threading, and buys config.threadsafed!‘d Rails apps nothing. But Thin does have a threaded mode which can be enabled by passing “—threaded” on the command line or by setting “threaded: true” in your YAML config file. That’s it! Try the above test now, and Window 2 will finish long before Window 1.

Follow this simple bug work-around if you’re using Ruby 1.9.2. Otherwise all your requests could take almost a minute to complete!

You also may want to read my post on Thin config and managment .

Is Thin the only way?

I hope not. If so, that means Rails has an incredibly powerful features that everyone’s excited about, but at the same time, that no one really cares about. Thin is the only multi-threaded Rack server on which I can find any meaningful discussion or documentation (and even that is scant). If you know of another, please let me and everyone else know!

Look into Nginx

To really take advantage of all these multi-threading and asynchronous goings-ons, you should look into dropping Apache and switching to Nginx. In fact if you’re trying to run as efficiently as possible on a VPS, I insist you look into it! There’s plenty out there, but I’d start with a good comparison of the two.

Threaded Thin is really really slow on Ruby 1.9.2

So you’ve been using Thin’s threaded mode, and it’s been rocking the socks off your shiny config.threadsafe!‘d Rails app. Then, like me, you caught wind that The Dark Ages were over, and decided to upgrade from Ruby 1.8.7 to 1.9.2. But oh no! "What’s all this?," you ask. “Why isn’t Thin responding anymore?”

Rest assured, dear reader, that it is. It’s just taking 30+ seconds; Nginix or Apache aren’t that patient. There’s a 2 year old lighthouse ticket on this issue, and even some related patches in EventMachine. But apparently to little avail.

So until this is all sorted out, as the lighthouse conversation suggests, you’ll have to tell Thin to not use epolling. Supposedly this will slow it down, but it should still be faster than it was in Ruby 1.8.7.

From the command line

thin start --threaded --no-epoll ...

In a config file

...
threaded: true
no-epoll: true
...

Now Thin will be restored to its former, threaded glory. Enjoy!

How to use Thin effectivly

Thin is a super-cool Rack app server written in Ruby. Normally you’ll run it behind a Webserver like Apache or Nginx. It’s great and I use it for most of my Rails and Sinatra Web apps. But my goals isn’t to convert you (like I was converted from Passenger). My goals is to tell you how to use it effectively in production.

Thin has plenty of docs with commands like

thin start -p 3001 -e production —threaded

That’s great for testing and development, but not for deployment. Fortunately Thin has some great options, but unfortunately it took hours of searching to find them. So here they are, all in one place. All of the following assumes you’ve already installed Thin with

gem install thin
are generally comfortable with it, and are running as root or sudo-ing.

Start your Thins on system boot

thin install

You’ll see output like the following. This will boot all your configured Thin apps every time your system starts. Or, on Debian/Ubuntu for instance, you could run /etc/init.d/thin stop|start|restart at any time.

To configure thin to start at system boot:
on RedHat like systems:
  sudo /sbin/chkconfig --level 345 thin on
on Debian-like systems (Ubuntu):
  sudo /usr/sbin/update-rc.d -f thin defaults
on Gentoo:
  sudo rc-update add thin default

Pick one of those and run it.

Configure your Thins

Place config files for all the Thin apps you want auto-started into /etc/thin/ . They’ll be YAML files, so their extentions should be .yml. Here’s an example with lots, but not all, available options. I have yet to find a comprehensive list, but they’re mostly the same as the command-line options listed by thin -h .

--- 
user: www-data
group: www-data
pid: tmp/pids/thin.pid
timeout: 30
wait: 30
log: log/thin.log
max_conns: 1024
require: []
environment: production
max_persistent_conns: 512
servers: 1
threaded: true
no-epoll: true
daemonize: true
socket: tmp/sockets/thin.sock
chdir: /path/to/your/apps/root
tag: a-name-to-show-up-in-ps aux

Stop/restart individual apps

Maybe it’s obvious to you, but it wasn’t to me. For a long time, if I wanted to restart one of my apps, all I knew to do was restart them all with /etc/init.d/thin restart . Eventually I figured out how to do it one at a time with

thin restart -C /etc/thin/app.yml

That’s it. Happy Thinning!