Rails deployment: wouldn't it be great if it worked like this?
Unless you’ve been living under a rock (or if you never read Ruby on Rails blogs), you should know that there has been a lot of complaints about Rails deployment in the past few weeks. Heck, it’s not just the past few weeks, this issue has existed for far longer. Rails deployment is - so people say - too hard.
The first question that appeared in my head when I read all that, is: “what do they mean by ‘hard’?” I read their posts more carefully. I read the comments. I also asked around on my blog and the Ruby on Rails mailing list. As with most questions in life, there is not a single answer, but multiple.
How is Rails deployment typically done?
Before we dive into the answers, one should understand how Rails deployment currently works.
With PHP, it is straightforward:
- Upload the source files to a directory accessible by the web server.
- There is no step 2.
(I’ve conveniently left out the steps in which the user must edit config files and setup the database; this is application-dependent and is not a core part of PHP.)
PHP web servers typically run Apache, which has mod_php. The number of concurrent requests that a PHP application can handle, is equal to the number of worker threads/processes that Apache is running (see the Apache MPM documentation for more information).
There are several ways to deploy Rails. The most common one at the moment is as follows:
- Upload the Rails app to the web server.
- Configure the Mongrel application server on the web server (or configure a cluster of Mongrel, i.e. multiple Mongrel servers) and start it.
- Configure the web server (which is usually Lighttpd, Apache or Nginx) to proxy HTTP requests to Mongrel (or to one of the Mongrel instances in the cluster).
Since each Mongrel instance can only handle 1 HTTP request at the same time, the number of concurrent requests that your Rails application can handle, is equal to the number of Mongrel servers that you have running. This number is independent of the number of worker threads/processes that Apache is running.
This is not the only way though. It is possible bypass step 2 and 3, by running the Rails application as a CGI process. Of course, nobody does this because it’s insanely slow. On Dreamhost, it seems to take several seconds to load the Rails framework itself. If the web server has to start the Rails application from scratch every time there is an HTTP request, the website will grind to a halt. This is the reason why an application server is needed: it keeps the Rails application running.
There is another method: running a web server that supports FastCGI. FastCGI is like CGI, but it removes the startup overhead by keeping processes persistent. Lighttpd has FastCGI support by default. I’m not sure whether Nginx has FastCGI support by default, but it does have a FastCGI module. Apache has a (third party) FastCGI module as well.
Both Apache and Lighttpd support auto-starting FastCGI processes. That is, when an HTTP request is received, the web server will automatically start a FastCGI process if it hasn’t done that already. It can start more processes (up to a specified maximum) if it notices that the current number of processes can’t handle the current number of concurrent HTTP requests. It will automatically shutdown some processes after a certain idle period, to free memory. Shared hosts seem to typically use Apache with the FastCGI module.
However, there are several big catches with using FastCGI method. Apache’s FastCGI module has a very bad reputation. Many people have reported that it’s very unstable. FastCGI itself seems to be unmaintained in the past few years.
What are the complaints about Rails deployment?
These are the most important reasons why Rails deployment is either hard or perceived to be hard:
- Support on shared hosts. Rails apps don’t seem to play well on shared hosts like Dreamhost. Dreamhost said that their Rails app regularly make Apache choke, and uses too much system resources. Many people who uploaded their Rails app to a shared host find out that their app either doesn’t work, or is unstable.
- Deployment involves multiple steps. Many people seem to come from a PHP background, where they are used to the upload-and-go style of deployment. This is not possible with Rails, at least not in the same way as PHP. Setting up Mongrel is an extra step for them - they don’t want to think about application servers or clusters because they don’t have to with PHP.
- The need to do port administration if one runs Mongrel clusters. One has to remember which ports are reserved for which apps, in order to avoid conflicts in the future. As the number of hosted applications grows, this quickly becomes a serious pain in the ***.
- The installation of dependencies (gems). A J2EE developer pointed out that Java deployment is “easy” because it is the norm that all dependencies are bundled with the application itself. If a Ruby on Rails application depends on third party gems, those will have to be installed on the deployment server as well. Even though gems are easy to install (one command), the installation usually requires root privileges, something that is not always available.
- Perceived performance problems. It’s pretty well-known that Rails is not thread-safe and thus can handle only one request at the same time. Some people think that all Rails sites can only handle one request at the same time. This is of course not true: that’s the whole point of running a Mongrel cluster with multiple Mongrel instances: to be able to handle multiple concurrent requests. The number of Mongrel instances to run should be fine-tuned to the web server’s load. Nevertheless, the fact the Mongrel cluster has to be manually configured, gives people the (false) impression that PHP can (architecturally) handle more concurrent requests than Rails, even though the dispatching mechanism used by mod_php is conceptually not much different.
- Perceived “deployment risk” (whatever this is supposed to mean). Someone has pointed out that, because of the need to manually configure Mongrels, the “deployment risk” is higher than PHP.
As you can see, there are legitimate complaints (1-4) as well as superstition (5 and 6).
Point (1) is particularly interesting. Shared hosts are not able to run Mongrel clusters because they have to host so many websites. Suppose a shared hoster’s web server hosts 100 websites, and suppose that the typical Rails application uses 25 MB memory. Then they’ll need 2500 MB of memory if each website is reserved only 1 Mongrel instance. Ouch. So instead, shared hosts typically use Apache with FastCGI. This is the main source of stability problems that people are complaining about.
A common answer to this is that shared hosts should not use Apache, but use Lighttpd instead (Nginx doesn’t support auto-starting FastCGI processes so they can’t use that). Dreamhost’s answer to this is as follows:
Dreamhost has already standardized on Apache. A lot of their infrastructure depends on Apache. Switching to lighttpd is therefore non-trivial. Dreamhost also says that Apache is the defacto standard, and considers anything non-Apache as “bleeding edge technology”. I suspect that a lot of shared hosters are like this as well.
While Dreamhost’s response may come over like whining, Jon Gretar has formulated the point perfectly:
“Although I like using Litespeed [another web server which can run Rails with FastCGI] at home I just don’t have that luxury at work. Because I might have to use the same server to host a subversion repo for example. There is a reason Apache is loved by ISPs. Apache is a one size fits all solution. Apache can do just about everything. Except host Rails application.”
But even if they switch to Lighttpd, they might still run into memory problems. Ruby on Rails applications typically use more memory than PHP applications. Every Rails FastCGI process contains its own copy of the entire Rails framework as well as the application code itself. Rails applications therefore typically use at least 25 MB of memory, per process.
Another common answer is that people should use a VPS (Virtual Private Server) instead of a shared host. These days VPSses are very very cheap. However, many people are unwilling to purchase a VPS and would prefer to use a shared host for their small applications. They have a point - a VPS, while cheap, secure and powerful (you have root access), needs to be maintained. A shared host will allow one to delegate a lot of the maintenance burden to the hosting company.
Proposed solution
While I think most complaints about Rails deployment is exaggerated - it really isn’t as hard as people make it out to be - they do have a point. Why can’t Rails deployment be as easy as PHP, i.e. upload-and-forget?
To solve this problem, I propose a hypothetical Apache module, which I call mod_rails. It should follow the usual Rails conventions: Dont-Repeat-Yourself and as least configuration as possible.
- Configuration
mod_rails will need only one configuration option: RailsBaseURI, which specifies that the given URI is the base URI of a Rails application. For example, the Apache configuration might look like this:<VirtualHost *> ServerName www.foo.com DocumentRoot /home/webapps/foo/public RailsBaseURI / </VirtualHost>And this is all you have to do to make the Rails app, located in /home/webapps/foo, work. mod_rails will automatically manage Rails processes, just like FastCGI is supposed to, but with no additional configuration. The number of Rails processes is managed automatically, just like mod_php. There will be no need to think about the number of application servers that need to be started.
- Startup time
A lot of the Rails application startup time is to blame on the fact that the Rails framework has to be loaded. mod_rails can solve this by preloading the Rails framework. Since different applications may require different Rails versions, mod_rails can create multiple child processes, each which preload its own version of the Rails framework. mod_rails will then delegate the creation of Rails application processes to the preloader process which has the correct Rails version. - Memory efficiency
Preloading will also save a lot of memory. In modern operating systems, child processes shared most of its memory with the parent process. Thus, starting a Rails process - even for the first time - will be very fast. And the Rails application instances will use a lot less memory than with FastCGI. - Easily restart the Rails application
When the application code has changed, one needs to restart the Rails application before the changes have effect (unless of course when running in development mode, but we’re talking about production mode). Restarting the entire web server is out of the question: it would suddenly abort any downloads that the web server is handling at the moment.Ideally, we want mod_rails to restart the application automatically if it detects changes in the source. But checking all of the application’s source files on disk is expensive if we have to do it for every HTTP request, so that is out of the question. While this can be made very inexpensive by using a filesystem monitor daemon such as FAM or Gamin, we cannot rely on it being installed.
So instead, one could upload a file called “restart.txt” to the Rails application’s “tmp” folder. The file could be empty, its content is not important. If mod_rails detects such a file, it would remove the file and automatically restart the corresponding Rails application instances. If the file cannot be removed because of permission problems, then mod_rails would remember the file’s modification time, and only restart the Rails application again when it notices that restart.txt’s modification time has changed. The user only needs an FTP client to be able to restart the Rails application. Shared hosters will not even have to write special Rails restarting support for their control panels.
This is the easiest way I could think of, while still being low-overhead.
- Security
Shared hosts typically run all PHP scripts as the same user as the web server. So theoretically, customer A’s PHP script can peek into customer B’s files and steal his database password or other files. Not good. (Of course, storing sensitive data on a shared host is inherently unsafe, but still, we can do better than this.) This can be solved by running each customer’s PHP scripts as a different user. There are several ways to set this up, one of which is running PHP as FastCGI processes. Each customer will have its own pool of PHP FastCGI processes, serving only HTTP requests for that customer’s website.The alert reader will notice that this setup is similar to Rails in combination with Mongrel clusters. Indeed, they share the same problems. It wastes a lot more memory.
mod_rails can solve this by spawning Rails application instances via a setuid root program. The web server tells the spawner program to spawn a Rails application as a specific user. The spawner program will then check, through a whitelist of usernames, whether it is allowed to spawn a program as that user. It will then spawn the Rails application, and pass the Rails application’s communication channel back to the web server. The web server can then directly forward HTTP requests to the Rails application, which is now running as a different user than the web server. This can be combined with preloading, for maximum memory efficiency.
I’d like to hear what others think about this solution. Would this be easy and good enough? Can it be better?666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666
666666666666666666666666666666666666666666666666
6666666666666666666666666666666666666666
6666666666666666666666666666666666666666666666666666666666

