Lets Get Dugg!



Catalyst vs Rails vs Django Cook off

Today I began working on a new project and decided to benchmark Catalyst and Rails for fun. See how my new favorable framework does against Rails. I was a bit shocked at the results though. I guess this is worth mentioning in hope Catalyst can improve in it's Accessor Generation code. So here are the results:

Benchmark System
Celeron 1.8Ghz
1 Gig of Ram
FreeBSD-6

Interpreters:
Ruby - 1.8.5
Perl - 5.8.8

Frameworks:
Catalyst - 5.7003
Rails - 1.1.6

Run as:
Lighttpd: 1.4.13
FCGI: 3 max proc

Benchmarked as:
ab -n 1000 -c 100 http://siteurl.com/

h3. Some background

I specifically turned off sessions and did not use ActiveRecord/DBIC to keep it as fair as possible between the two frameworks. Both frameworks were run under Lighttpd and FCGI. I tried to keep this as apples to apples as possible.

So lets take a look at the results!

Rails:


Server Software:        lighttpd/1.4.13                                    
Server Hostname:        wansanity.com
Server Port:            9090

Document Path:          /main/index
Document Length:        2142 bytes

Concurrency Level:      100
Time taken for tests:   18.261 seconds
Complete requests:      1000
Failed requests:        0
Broken pipe errors:     0
Total transferred:      2296892 bytes
HTML transferred:       2143288 bytes
Requests per second:    54.76 [#/sec] (mean)
Time per request:       1826.10 [ms] (mean)
Time per request:       18.26 [ms] (mean, across all concurrent requests)
Transfer rate:          125.78 [Kbytes/sec] received

Connnection Times (ms)
            min  mean[+/-sd] median   max
Connect:       74   885 1742.9    138 11785
Processing:   172   661 1216.8    173  8195
Waiting:       84   661 1216.8    173  8194
Total:        172  1547 2123.8    330 11893

Percentage of the requests served within a certain time (ms)
50%    330
66%   1354
75%   2786
80%   3106
90%   4297
95%   6279
98%   8216
99%   9285
100%  11893 (last request)

Thats 54 connections / sec which is great. I have seen it peak at 70 connections/sec which is just awesome!

Catalyst:

	
Server Software:        lighttpd/1.4.13                                    
Server Hostname:        wansanity.com
Server Port:            80

Document Path:          /
Document Length:        2232 bytes

Concurrency Level:      100
Time taken for tests:   43.503 seconds
Complete requests:      1000
Failed requests:        0
Broken pipe errors:     0
Total transferred:      2401300 bytes
HTML transferred:       2238490 bytes
Requests per second:    22.99 [#/sec] (mean)
Time per request:       4350.30 [ms] (mean)
Time per request:       43.50 [ms] (mean, across all concurrent requests)
Transfer rate:          55.20 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:       75   322  808.5     93  6028
Processing:   269  3804  851.8   3928  6754
Waiting:      192  3804  851.7   3928  6754
Total:        269  4126 1178.5   4186 10293

Percentage of the requests served within a certain time (ms)
  50%   4186
  66%   4384
  75%   4404
  80%   4424
  90%   5025
  95%   6422
  98%   7194
  99%   7709
 100%  10293 (last request)
	

22 connections / sec not exactly what I expected from a framework built on top of the fast Perl Interpreter.

Being a bit disappointed with the results, I investigated further.

So here are the perl dprof results.

	
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 0.00   0.605  4.128   1512   0.0004 0.0027  NEXT::AUTOLOAD
 0.00   0.373  0.373  25794   0.0000 0.0000  Class::Accessor::Fast::__ANON__
 0.00   0.235  0.235   1177   0.0002 0.0002  NEXT::ELSEWHERE::ancestors
 0.00   0.211  0.225      1   0.2107 0.2253  YAML::Type::code::BEGIN
 0.00   0.184  5.182     86   0.0021 0.0603  Catalyst::Engine::HTTP::_handler
 0.00   0.177  0.205   2583   0.0001 0.0001  File::Spec::Unix::canonpath
 0.00   0.164  0.309   1942   0.0001 0.0002  File::Spec::Unix::catdir
 0.00   0.156  2.408   3201   0.0000 0.0008  Catalyst::Action::__ANON__
 0.00   0.134  0.739     73   0.0018 0.0101  base::import
 0.00   0.129  0.136   5904   0.0000 0.0000  Class::Data::Inheritable::__ANON__
 0.00   0.109  0.814      7   0.0155 0.1163  main::BEGIN
 0.00   0.108  0.108   1323   0.0001 0.0001  HTTP::Headers::_header
 0.00   0.101  0.116     10   0.0101 0.0116  Template::Parser::BEGIN
 0.00   0.101  0.334     11   0.0092 0.0304  Catalyst::Engine::BEGIN
 0.00   0.101  0.295   1264   0.0001 0.0002  Path::Class::Dir::stringify
	

It seems like the main bottleneck in Catalyst 5.7003 is Next. Jrockway was kind enough to post some new code into Catalyst's trunk for me to try; a new replacement for Next - C3.

Here are the results with the C3 Plugin from Trunk

	
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 0.00   0.211  0.233      1   0.2106 0.2330  YAML::Type::code::BEGIN
 0.00   0.135  0.135   8035   0.0000 0.0000  Class::Accessor::Fast::__ANON__
 0.00   0.126  0.721     73   0.0017 0.0099  base::import
 0.00   0.109  0.116     10   0.0109 0.0116  Template::Parser::BEGIN
 0.00   0.108  0.805      7   0.0155 0.1150  main::BEGIN
 0.00   0.093  0.106      7   0.0133 0.0152  Catalyst::Engine::HTTP::Restarter:
                                             :Watcher::BEGIN
 0.00   0.090  0.105   1023   0.0001 0.0001  File::Spec::Unix::canonpath
 0.00   0.085  0.326     11   0.0077 0.0296  Catalyst::Engine::BEGIN
 0.00   0.081  0.905    196   0.0004 0.0046  Catalyst::execute
 0.00   0.069  0.120      8   0.0087 0.0150  Catalyst::Plugin::Server::XMLRPC::
                                             Request::BEGIN
 0.00   0.064  1.639    444   0.0001 0.0037  next::method
 0.00   0.061  0.313     32   0.0019 0.0098  Catalyst::BEGIN
 0.00   0.054  0.216      7   0.0077 0.0309  Template::Config::load
 0.00   0.054  0.189      4   0.0135 0.0473  HTTP::Body::OctetStream::BEGIN
 0.00   0.054  0.388      4   0.0135 0.0970  Gambit::BEGIN	
	

So there you have it, the results with the C3 Plugin. It only made a slight difference by pushing the Catalyst benchmark score to 25 connections / sec.

I hope this benchmark can get some changes put into place for Catalyst's next release.

Conclusion

It seems like Rails is roughly 62% faster than Catalyst at this time. Keep in mind this benchmark does not take into account the ORM performance. This benchmark tests how quick the frameworks themselves dispatch methods and render views.

Also take into consideration when choosing a framework you need to look at the problem at hand. Catalyst can feed off Perl's vast CPAN resource library. Catalyst has features that Rails does not have. Catalyst's DBIC ORM supports multi-column primary keys and can do relationship mapping just by reading the schema! You don't even have to bother writing any has_many belongs_to definitions!

I am going to have to take a look into Django see how well it fairs in this benchmark. Perhaps an update on this?

Update Django Results


Server Software:        lighttpd/1.4.13                                    
Server Hostname:        fab40
Server Port:            9090

Document Path:          /
Document Length:        2235 bytes

Concurrency Level:      100
Time taken for tests:   13.643 seconds
Complete requests:      1000
Failed requests:        0
Broken pipe errors:     0
Total transferred:      2409769 bytes
HTML transferred:       2253459 bytes
Requests per second:    73.30 [#/sec] (mean)
Time per request:       1364.30 [ms] (mean)
Time per request:       13.64 [ms] (mean, across all concurrent requests)
Transfer rate:          176.63 [Kbytes/sec] received

Connnection Times (ms)
              min  mean[+/-sd] median   max
Connect:       76   483 1068.1    101  8666
Processing:   190   744  726.3    571  6088
Waiting:       93   744  726.4    572  6088
Total:        190  1227 1414.2    692  9606

Percentage of the requests served within a certain time (ms)
  50%    692
  66%    972
  75%   1209
  80%   1445
  90%   3282
  95%   4020
  98%   6414
  99%   8113
 100%   9606 (last request)

72 connections / sec! Amazing and the winner!

And anyone that disagrees with this can go ahead and look at the code for all three projects.

I have the least experience with django for your information

mst Please don't kill me'

Many thanks go out to jrockway to helping me point out the root cause of the bottleneck in Catalyst.


Rails is the latest and greatest web development platform in most people's eyes at this time. It has really changed how people perceive web development. Rails has brought an easy to use ORM to the masses. Its two main mottos are DRY and convention over configuration. This is exactly what makes Rails so simple. However, when want utter flexibility, speed, and vast number of extensions you should look else where; enter Catalyst.

Catalyst is a Perl based web framework. It features everything you would expect from a modern web MVC framework; template engine, an ORM, and RESTful URLs. The thing that sets Catalyst apart from Rails is choice, your free to use various components. You are not limited to a single component. For example, with Rails there is only ActiveRecord and that is all you get. However, with Catalyst your free to choose from DBIx::Class,Rose::DB::Object or the older Class::DBI. The same principle is applied to the template engine where you have access to five various template engines. The down side to all this is the initial take off time. Basically the learning curve is longer than with Rails.

Let's get started on working with Catalyst. First off, you need to understand how to use CPAN. CPAN is the equivalent of rubygems. I will list some comparisons of cpan and rubygems.



Catalyst / Perl Rails / Ruby
Searching the module networks
bash$ cpan
cpan4> i /catalyst/
bash$ gem search -r rails
Installing Modules
bash$ cpan
cpan5> install Task::Catalyst
bash$ gem install -r rails
Upgrading Modules
bash$ cpan
cpan5> r
bash$ gem update
View Installed Modules
bash$ perldoc perllocal
bash$ gem list
Uninstall Modules
bash$ cpanp # Using CPANPLUS
CPAN Terminal> u Task::Catalyst
bash$ gem uninstall rails

CPAN and RubyGems are very alike. RubyGems has the better upgrade functionality and uninstall while CPAN does not even have an uninstall command! When it comes down to volume of software/modules in the repositories CPAN wins hands down. CPAN contains over 10,000 Perl modules while RubyGems only has 1,718! CPAN has been around a lot longer and there are more Perl than Ruby programmers out there.

If you want to search for perl documentation or CPAN modules check out search.cpan.org


Not everyone chooses to go the FCGI way of deploying their web applications. Some people, like I, prefer deploying applications under Catalyst's httpd or Rails' Mongrel. Unfortunately, Lighttpd at this time (version 1.4.13) has a inept mod_proxy module. It does not load balance correctly and nor does it recover from a downed proxy node, requiring a full restart. Obviously this is unacceptable when it comes to a production system.

deployment

Pound comes in and saves the day. It is a fast load balancing proxy that claims it can handle 600 requests/sec. The deployment of choice here is Lighttpd => Pound => web application. However, there is a small snag, Pound appends X-Forwarded-for headers without an option to disable it. So every request that comes in from Pound to your web application comes with "X-Forwarded-For: 127.0.0.1." This means you can't tell from where the client came from. Here is the solution to remedy this issue, but it requires some hacking on the Pound source.

Open http.c and comment out line 902 and 903
====

You basically want to comment out the top two lines. With this out of the way Pound does not append the extra X-Forwarded-for headers. You should now be able to receive the originating IP address of the client connected to your web application.

Now to finish up. Configuring Lighttpd to pass along to Pound and then to your web application.

Sample Lighttpd configuration

$HTTP["host"] =~ "^letsgetdugg.com$|^www.letsgetdugg.com$" {
    server.document-root        ="/home/victori/servers/letsgetdugg/root"
    dir-listing.activate        = "disable"
    accesslog.filename          = "/var/www/lighttpd/log/letsgetdugg.access.log"
    server.errorlog             = "/var/www/lighttpd/log/letsgetdugg.error.log"
    $HTTP["url"] !~ "static/" {
        proxy.server = ( "" => ( "Letsgetdugg" => ( "host" => "127.0.0.1" , "port" => 7999, "check-local" => "disable" )))	 
    }
}

We make sure that anything in /static does not get sent to your web application but gets processed by Lighttpd.

Sample Pound configuration

ListenHTTP 
  Address 127.0.0.1 
  Port    7999 
  Service
    HeadRequire "Host: .*letsgetdugg.com.*"
    BackEnd
      Address 127.0.0.1
      Port    9010
    End
    BackEnd
      Address 127.0.0.1
      Port    9011
    End
    BackEnd
      Address 127.0.0.1
      Port    9012
    End
  End

Thats all! This deployment should suffice till Lighttpd 1.5 goes stable.


How to deploy a self contained Rails application on Tomcat, painlessly!

Here at work we have a Postfix email server which handles all our email for multiple domains. The authentication and users are handled via the PostgreSQL database backend. To make life easy, I used Rails to scaffold a few tables in that Postfix PostgreSQL database. End result, is an easy to use interface to manage emails and domains. However, deployment isn't as fun as it could be, even with mongrel. I still have to have Rails/Ruby installed AND manage startup scripts to start the mongrel instance for my Rails MailManager application. Well JRuby to the rescue!

Here is how I created a self-contained Rails Application that can be reused in any Tomcat deployment.

I used the pure Ruby PostgreSQL implementation instead of the Java-centric ActiveRecord-JDBC solution, so I can develop using native Ruby while deploying via WAR using JRuby without any database.yml changes!

First off we want to instal GoldSpike which adds rake tasks to create WAR files.

script/plugin install svn://rubyforge.org/var/svn/jruby-extras/trunk/rails-integration/plugins/goldspike

Now I needed to implement a self-contained postgres-pr module. I implemented it as a Rails Plugin!

Create the postgres plugin

./script/generate plugin postgres

Copy the postgres pure implementation gem files to your Rails plugin

cp -rf /opt/local/lib/ruby/gems/1.8/gems/postgres-pr-0.4.0/lib/* ./vender/plugins/postgres/lib/

Setup database.yml
I setup host as pdatabase, so on any server you deploy the WAR just add an alias to /etc/hosts ; 127.0.0.1 pdatabase

production:
  adapter: postgresql 
  database: postfix 
  username: victori  
  password: .......
  host: pdatabase

Run the rake task to build your reusable self-contained web application

rake war:standalone:create

Pretty darn easy eh?