Archive for January, 2008

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:

  1. Upload the source files to a directory accessible by the web server.
  2. 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:

  1. Upload the Rails app to the web server.
  2. Configure the Mongrel application server on the web server (or configure a cluster of Mongrel, i.e. multiple Mongrel servers) and start it.
  3. 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:

  1. 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.
  2. 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.
  3. 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 ***.
  4. 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.
  5. 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.
  6. 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

Comments (15)

Ruby vs PHP performance

It’s a well-known stereotype that “Ruby is slow”. People say it on Slashdot. People say it on OSNews. People say it on my MSN contact list. A few quotes:

  • “If you care even a little about performance, you don’t use Ruby.”
  • “You thought Python is slow? Well Ruby is even slower!”

Another stereotype we hear is that “Ruby on Rails doesn’t scale”. Most of the people who say that seem to use PHP, so they imply that PHP does scale.

How much of this is true? If there’s anything I’ve learned in the past few years, it’s that I shouldn’t always take public opinion seriously. PHP is not really a great language; its interpreter and perhaps even the language specification (if that even exists) has many quirks. My friend Ninh has worked on PHP in the past. He has exposed many of those strange quirks in his blog (which is, unfortunately, down because of a recent data loss incident).

I can’t imagine that PHP is faster than Ruby (the language; I’m not talking about Rails). If Ruby on Rails is slower than PHP, then how much of that is to blame on the language, and how much on the framework? Let’s put this to the test.

Goal of and rationale behind this test

We must first agree on the following premises:
People claim that Ruby is so slow that it’s impossible to build anything in it that’s sufficiently fast for “production environments”. (1)
Yet at the same time, they accept PHP as being fast enough. (2)

From (1) and (2) follows this question: Is it possible at all to write a web framework in Ruby that’s faster than PHP?

If (1) and (2) are true, i.e. if Ruby web apps are indeed slower than PHP web apps, then the culprit might be:

  • The web framework that the Ruby web app is written in. If this is the case, then it is possible to write a faster web framework that matches PHP’s performance. (3)
  • Ruby itself, i.e. the language. If this is the case, then it does not matter what web framework we use or how we write it, it will always be slower than equivalent frameworks PHP. (4)
  • A combination of both, in which both parts play a significant role. (5)

The goal of this benchmark is to find out whether (4) is true. (4) is what people have been arguing for years. And that’s assuming that people know what they’re talking about; they often don’t, and confuse the Ruby language with Ruby on Rails the framework, or even with web server Ruby integration modules.

Benchmarking PHP and Ruby (and Perl, Python, C++)

I want to benchmark PHP and Ruby’s code execution speed.

  • I specifically do not want to benchmark code parsing speed. Ruby on Rails apps run in application servers, so all code is only parsed once. PHP has stuff like PHP Accelerator/Zend Accelerator (or whatever those products are called) which cache the parse tree.
  • I’m not trying to benchmark HTTP request processing. This is only about code execution speed.

So, I decided to implemented the mergesort algorithm in PHP and Ruby. The scripts will sort an array of 100 numbers (which where randomly generated and hardcoded into the source code), and perform this sorting 3000 times. The execution times, in seconds, are as follows:
benchmark-with-mergesort.png
As you can see, I’ve also written a Perl, Python and C++ version just for comparison fun. :)

The sources code of the test programs can be found on the bottom of this blog post. A few notes:

  • Operating system: Ubuntu Linux 7.10
  • Hardware: Intel Core 2 Duo T5300 1.73 Ghz, 2 GB RAM
  • Versions: PHP 5.2.3-1ubuntu6.2, Ruby 1.8.6 (2007-06-07 patchlevel 36), Perl 5.8.8, Python 2.5.1
  • The test programs not only test raw CPU execution speed, but also memory management speed. As you can see in the merge() functions, it creates a new array, and modify the given arrays. As a result, the mergesort() functions will create a lot of objects that will have to be garbage collected.
  • Ruby uses a conservative mark-and-sweep garbage collector. Perl and Python both use reference counting. The C++ test program uses Boost’s shared_ptr, and thus uses reference counting for memory management. I’m not sure how PHP manages its memory, or whether it does it at all, because all objects are freed after an HTTP request anyway.
  • The C++ test program is compiled with g++ 4.1, with -O2.
  • By using Psyco, a JIT optimizer for Python, the Python test program’s execution time went down to a whopping 0.640 seconds. That’s even faster than my C++ version (!)

But as you can see in the graph, Ruby beats PHP even with a full-blown mark-and-sweep garbage collector. Ruby’s garbage collector was run several dozens times while running the test program, and Ruby still beats PHP in execution time.

I have no choice but to conclude that all the “Ruby is slow” stereotypes are bollocks. PHP the language is slower, yet we do not see anybody complaining about PHP “scalability”. The Rails framework could be faster, yes, but at least now we know the language is not as big as a bottleneck as some people might think.
Wait! Do I see a flame thrower there? Read on before you reply!

“But but but… you didn’t benchmark the web environment!”

Yes. I did not benchmark the HTTP request handling, and this is intentional. People claim that Ruby is so slow, that it is impossible to write anything in it that’s acceptably fast. This benchmark proves that, as a language, Ruby is faster than PHP. Therefore, if people can write frameworks in PHP that’s acceptably fast, then it is also possible to write frameworks in Ruby that’s acceptably fast.

Note that I do not make any claims about Ruby on Rails’s speed compared to typical PHP applications.

“Why don’t you use the language’s builtin sort function? This benchmark is crap!”

Implementing sort in the languge instead of using the builtin sort function is intentional. The goal of the benchmark is to test *code execution speed*, not to find out which language can sort faster. This benchmark is a good test because it tests different language primitives, like function calls, comarison operators, arrays, etc. I might as well have implemented A* graph searching, but mergesort is easier.

Appendix: Source codes

Comments (45)

Making Ruby’s garbage collector copy-on-write friendly, part 7

It turns out that part 6 was not final. I’ve been working on the garbage collector again. These are the changes:

  • The garbage collector is now 20% faster! For every object to be marked or sweeped, the garbage collector has to find the heap that an object belongs to. Empirical evidence shows that the chance is very high that the current object belongs to the same heap as the last object’s heap. So caching the last lookup result improved performance dramatically.
  • It is now possible to enable debugging of the GC at runtime, using environment variables.
    Set RUBY_GC_DEBUG=1 to enable GC debugging. It will print a message to stderr every time the garbage collector is started.
  • If GC debugging is enabled or if the environment variable RUBY_GC_ALLOC_HEAP_WITH_FILE is set, it will use mmap() on /dev/zero to allocate the Ruby heaps. This makes it possible to see exactly which heap has been made dirty, and by how much. I’m going to use this for further investigations.
  • A new method, GC.cow_friendly?, has been added. This allows apps to dynamically determine whether the garbage collector is copy-on-write friendly.

You can download the Ruby 1.8.6 patch here.

In addition, I’ve put my SVN repository online. It’s here:

http://public.railsplugins.net/repos/ruby/

The ‘vendor/’ folder contains the original Ruby source.
The ‘tags/’ folder marks important milestones in my development progress. In chronological order, they are:

  • mark-table-1: the first milestone. See my past blog posts for details.
  • mark-table-2: the second milestone. See my past blog posts for details. If I remember correctly, this is where I started implementing memory pools.
  • mark-table-3: the third milestone. If I remember correctly, this is where I optimized the memory pools and performance.
  • bitfield-1: the first try using bit fields instead of sets.
  • bitfield-2: the second try using bit fields instead of sets. If I remember correctly, this contains more optimizations than the previous milestone.

The ‘trunk/’ folder is the latest version.

Comments (20)

What is so hard about Rails deployment?

Recently there has been a lot of fuss about the ease of Rails deployment. People made various claims, such as “I’ve deployed PHP, Java, (insert something else here) and Rails. Rails is by far the most painful.” and “Java deployment (with Tomcat) is easier.”

I have no experience with Java deployment. But I have a friend who has worked on several high-end software systems written in J2EE. He says that deploying J2EE apps involves creating a .war file and uploading that to the web container (Tomcat). While uploading is easy, creating the .war file is not, and involves editing tons of XML configuration files.

PHP deployment is easy:
1. Upload files to server.
2. There is no step 2. (assuming we don’t have to setup the database; but that’s app-dependent)

People say Rails deployment is painful, but I really don’t understand what’s so painful about it:
1. Upload files to server.
2. Setup Mongrel (or lighttpd) on a port.
3. Setup your web server to proxy a virtual host’s request to Mongrel/lighttpd.
It has 2 more steps than PHP, but neither steps are hard. (2) can be done with 1 command. (3) is just copy-pasting some configuration snippets and changing the port numbers.

Granted, it took me a while to figure this out. Back in 2005, deployment documentation was quite bad. I expected to be able to integrate Rails into Apache, only to find out that there is no mod_rubyonrails, and that FastCGI on Apache is broken (at least, it was on Fedora 4 with Apache 2). After lots of Googling I eventually settled with lighttpd, and the thing Just Worked(tm). Today, we have tons of books about Rails deployment.
And I agree, this setup is not usable for shared hosts, who hosts many many websites on a server. Keeping the Mongrel instances running will require far too much memory. But I still do not understand why people claim that Rails deployment, in general, is hard.

I’d like to hear from you why exactly you think Rails deployment is painful, or (if applicable) why it is by far the most painful compared to anything else you’ve tried.

Comments (12)