Rails on Shared Hosting: Keeping FastCGI Alive

Although we might want to run our rails applications through mongrel, lighttpd or something more exotic some shared hosting environments require that Ruby on Rails run through FastCGI under Apache. It’s sometimes hard to maintain good performance of your application because the FastCGI process is automatically killed after some idle time.

The next access to the website starts a new process; thus, the response is delayed or a 500 error occurs. I’m using a small orange, but the same situation holds with several other companies that offer shared hosting.

The Frao Handler

The frao_handler by Alex Young is a modification to dispatch.fcgi that replaces the exit_now_handler. The replacement, frao_handler, does a restart instead of exiting.

The frao_handler is discussed at:

The risk in that if several processes start the frao handler will keep them alive. Although it seems to work fine for some people I found that under normal use over one day 8 processes were left running. I propose a modification that keeps one or two processes running, ensuring good performance without consuming a lot of shared hosting resources.

Just One Please

My variation keeps the process count down by agreeing to exit if there is another rails process already running. This is a good compromise because new processes can begin (when the load is heavy), the shared server is not bogged down by multiple idle processes, and the response to ordinary queries doesn’t require starting a new process. It can easily be modified to allow two processes for large sites.

I thought that the name frao was a bit harsh for my gentler version, so I’ve called it pfrao (p for please).

The code should be in dispatch.fcgi, between require 'fcgi_handler' and RailsFCGIHandler.process!.


class RailsFCGIHandler
  private
    # improved version of frao_handler
    # by Chris Gaskett chris.gaskett.com
    # tries to keep one fcgi process alive
    # still might need a kill -9 sometimes if processes hangs
    def pfrao_handler(signal)
      dispatcher_log :info, "asked to terminate immediately"
      number_procs = `/bin/ps ux | /bin/grep -c fcgi$`.to_i
      # note: to_i produces 0 if conversion to integer is not successful
      case number_procs
      when 0
        dispatcher_log :info, "pfrao handler error, check number_procs query, paths. Exiting"
        exit
      when 1
        dispatcher_log :info, "pfrao handler restarting instead, only one process left"
        restart_handler(signal)
      else 
        dispatcher_log :info, "pfrao handler agreed to exit, more than one process is running"
        exit
      end
    end
    alias_method :exit_now_handler, :pfrao_handler
end

Test out the ps, grep command by hand first to ensure that the path is correct and it is returning correct results.

Counting processes

Use ps ux and look at log/fastcgi.crash.log to see how the handler is going.

To check performance over the longer term I created a logger, logprocs.sh:


#!/bin/sh
while true; do echo `date; /bin/ps ux | /bin/grep -c dispatch.fcgi$` >> numprocs.dat; sleep 20; done

and ran it in the background (with nohup once I was sure it was working).

Check numprocs.dat occasionally to see how many processes have been running. For example,


grep -c 0$ numprocs.dat
grep -c 1$ numprocs.dat
grep -c 2$ numprocs.dat
grep -c 3$ numprocs.dat

Over a few days I found there were never no processes, 75% of the time 1 process, 25% 2 processes, and never more than two processes.

Risks

Even if a process becomes unresponsive it will still be included in the count. Thus, a new process might exit because the handler has confirmed that another process is running.

I suggest killing all of the processes occasionally to be safe (perhaps once a day by a cron job). You could use pkill -o to just kill the oldest process.

If the process receives an exit ASAP (USR1) rather than a exit immediately (TERM) the new handler function will not be used. The request to exit will not be noticed until that process has handled another web request. In the future I’d like to put a timeout on waiting for the next web request so that local requests to exit etc. will be handled more quickly.

Feedback

I would appreciate it if you could send me any feedback. If this article is at all popular I’ll put a bit more effort in to make it easier to follow and colour the code nicely.

If I’ve helped at all and you are interested in trying a small orange please consider putting gaskett.com in as your referer.

del.icio.us post to del.icio.us

background