Letsgetdugg

Random tech jargon

Browsing the tag perl

*Update* Patches got accepted into MogileFS Trunk ;-)

Just go check out trunk, it has all my patches already included.

http://code.sixapart.com/svn/mogilefs/trunk/

The only thing you need is my mogstored disk patch which is still pending. All the issues revolving around postgresql and solaris have been already included in trunk.


I fixed a few issues with MogileFS and Solaris. MogileFS should run wonderfully on Solaris with my patches applied.

Directory for all my patches: http://victori.uploadbooth.com/patches

http://victori.uploadbooth.com/patches/solaris-disk-du.patch

This patch fixes mogstored to work with solaris’s df utility.

http://victori.uploadbooth.com/patches/store-max-requests.patch

This patch adds a new feature to the MogileFS Tracker – max_requests.

The default is 0, but it is suggested you set it to 1000 max_requests, to avoid memory leaks.

The tracker will give out the database handle up to the max_requests limit before expiring the connection for a new one. This avoids memory leaks with long running persistent connections. PostgreSQL has issues with long persistent connections, it accumulates a lot of ram and does not let go until the process/connection is killed off. This patch makes sure that the connection is expired after so many dbh handle requests.

http://victori.uploadbooth.com/patches/mogilefs-sunos-pg.patch

This patch applies the InactiveDestroy argument to avoid the MogileFS Tracker locking up with the PostgreSQL store on Solaris.

http://victori.uploadbooth.com/patches/solaris-mogilefs-full.patch

This is the full patch for all my fixes.

I am slowly migrating our fab40 static asset data to MogileFS. I have imported >300,000 images, no issues with my patches so far.

/ PLUG go make an account on uploadbooth!

Enjoy ;-)

There seems to be some interest Catalyst vs Rails vs Django benchmark. The older benchmark is quite old, it was done in 2007. A lot has changed since then. I am re-running the numbers once again to see what has changed. This time around the hardware is faster and the benchmark is slightly more simple. I am just stress testing the controller response performance of the two frameworks.

Benchmark System:
Quad Core Xeon x5355 @ 2.66GHz,8 Gigs Ram,OpenSolaris SNV98

Quick Summary:
Catalyst 5.8/Perl 5.10: 611.78req/sec (Single Process,bsdmalloc)
Catalyst 5.8/Perl 5.10: 1485.53req/sec (Multi Process,bsdmalloc)
Rails 2.3.2/MRI Ruby 1.8.7: 259.93req/sec (Single Process,bsdmalloc)
Rails 2.3.2/JRuby 1.3-dev: 311.71req/sec (Single-Threaded,bsdmalloc)
Rails 2.3.2/JRuby 1.3-dev: 992.32req/sec (Multi-Threaded,libumem)
Rails 2.3.2/MRI Ruby 1.9.1: 603.92req/sec (Single Process,bsdmalloc)

Jump to conclusion….

Catalyst 5.8 / Perl 5.10
Compiled: SUNCC -xO5 -xipo -fast -xtarget=native

# ab -n1000 -c100 http://somedomain:3000/ This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright 2006 The Apache Software Foundation, http://www.apache.org/ Benchmarking localhost (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Finished 1000 requests Server Software: Server Hostname: somedomain Server Port: 3000 Document Path: / Document Length: 11 bytes Concurrency Level: 100 Time taken for tests: 0.673159 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 159000 bytes HTML transferred: 11000 bytes Requests per second: 1485.53 [#/sec] (mean) Time per request: 67.316 [ms] (mean) Time per request: 0.673 [ms] (mean, across all concurrent requests) Transfer rate: 230.26 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 2.3 0 35 Processing: 15 62 10.8 62 103 Waiting: 15 59 11.6 61 98 Total: 15 62 10.6 63 103 Percentage of the requests served within a certain time (ms) 50% 63 66% 66 75% 69 80% 71 90% 74 95% 76 98% 86 99% 91 100% 103 (longest request)

Rails 2.3.2 / Ruby 1.8.7
Compiled: SUNCC -xO5 -xipo -fast -xtarget=native

# ab -n1000 -c100 http://somedomain:3000/main/index This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking somedomain (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Mongrel Server Hostname: somedomain Server Port: 3000 Document Path: /main/index Document Length: 11 bytes Concurrency Level: 100 Time taken for tests: 3.847 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 290003 bytes HTML transferred: 11000 bytes Requests per second: 259.93 [#/sec] (mean) Time per request: 384.718 [ms] (mean) Time per request: 3.847 [ms] (mean, across all concurrent requests) Transfer rate: 73.61 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 1.1 0 16 Processing: 10 369 65.2 389 428 Waiting: 9 368 65.3 388 427 Total: 10 369 65.2 390 428 Percentage of the requests served within a certain time (ms) 50% 390 66% 396 75% 398 80% 400 90% 404 95% 407 98% 413 99% 417 100% 428 (longest request)

Rails 2.3.2 / JRuby 1.3-dev build 6586 (Multi-Threaded), libumem
Platform: JDK7 B56

# ab -n1000 -c100 http://somedomain.com:3000/main/index
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking somedomain.com (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Completed 1000 requests
Finished 1000 requests


Server Software:        
Server Hostname:        somedomain.com
Server Port:            3000

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

Concurrency Level:      100
Time taken for tests:   1.008 seconds
Complete requests:      1000
Failed requests:        1
   (Connect: 0, Receive: 0, Length: 1, Exceptions: 0)
Write errors:           0
Non-2xx responses:      1
Total transferred:      253875 bytes
HTML transferred:       11936 bytes
Requests per second:    992.32 [#/sec] (mean)
Time per request:       100.773 [ms] (mean)
Time per request:       1.008 [ms] (mean, across all concurrent requests)
Transfer rate:          246.02 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.7      0      13
Processing:    10   79  18.8     80     122
Waiting:        9   79  18.8     79     122
Total:         10   80  18.9     80     122

Percentage of the requests served within a certain time (ms)
  50%     80
  66%     88
  75%     94
  80%     98
  90%    102
  95%    108
  98%    113
  99%    114
 100%    122 (longest request)

Rails 2.3.2 / JRuby 1.3-dev build 6586 (Single-Threaded)
Platform: JDK7 B56

# ab -n1000 -c100 http://somedomain:3000/main/index This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking somedomain (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: somedomain Server Port: 3000 Document Path: /main/index Document Length: 11 bytes Concurrency Level: 100 Time taken for tests: 3.208 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 253000 bytes HTML transferred: 11000 bytes Requests per second: 311.71 [#/sec] (mean) Time per request: 320.810 [ms] (mean) Time per request: 3.208 [ms] (mean, across all concurrent requests) Transfer rate: 77.01 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 2.5 0 31 Processing: 37 304 56.4 318 350 Waiting: 36 304 56.5 318 349 Total: 37 305 56.5 318 352 Percentage of the requests served within a certain time (ms) 50% 318 66% 326 75% 330 80% 332 90% 336 95% 341 98% 345 99% 348 100% 352 (longest request)

Rails 2.3.2 / Ruby 1.9.1
Compiled: GCC -O3 -fomit-frame-pointer (SunCC failed to compile)

# ab -n1000 -c100 http://somedomain:3000/main/index This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking somedomain (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: thin Server Hostname: somedomain Server Port: 3000 Document Path: /main/index Document Length: 11 bytes Concurrency Level: 100 Time taken for tests: 1.656 seconds Complete requests: 1000 Failed requests: 0 Write errors: 0 Total transferred: 267001 bytes HTML transferred: 11000 bytes Requests per second: 603.92 [#/sec] (mean) Time per request: 165.585 [ms] (mean) Time per request: 1.656 [ms] (mean, across all concurrent requests) Transfer rate: 157.47 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.6 0 8 Processing: 28 160 39.0 187 222 Waiting: 13 145 38.5 143 209 Total: 28 160 39.0 187 222 Percentage of the requests served within a certain time (ms) 50% 187 66% 190 75% 191 80% 191 90% 196 95% 201 98% 221 99% 221 100% 222 (longest request)

Conclusion

Seems like Catalyst has the edge in controller performance compared to Rails on MRI Ruby 1.8.7. Catalyst’s controller processing is 135% faster than Rails in single process performance and 471% faster as a forking multi process. It is nice to see that the Catalyst team addressed the controller performance short comings of the earlier versions of Catalyst. Like any benchmark take it with a grain of salt. In a real application your data access layer will most likely be the bottle neck.

Rails 2.3.2 under JRuby with threading enabled ran 283% faster than with MRI Ruby 1.8.7. I am anxiously waiting on JDK7 B57 with invoke dynamic support, this should help push JRuby’s performance even further. I guess I know what deployment option I will choose when deploying Rails.

Pick your poison, both frameworks provide excellent controller response performance. Keep in mind scaling is all about architecture and not how fast your controller’s responses are. That said, having an efficient framework does help ;-)

Tagged with , , , ,

You might find this plugin nifty if you have multiple application servers processing requests. The Perlbal BackendHeaders plugin appends X-Backend headers with which backend served the request.

Update 06/26/09 Now on github perlbal-plugin-backendheaders

syris:~ victori$ curl -I http://fabulously40.com/questions HTTP/1.1 200 OK Server: nginx/0.7.52 Content-Type: text/html; charset=utf-8 Expires: Thu, 01 Jan 1970 00:00:00 GMT Content-Language: en X-Backend: 72.11.142.91:8880 X-Dilbert: If you have any trouble sounding condescending, find a Unix user to show you how it's done Content-Length: 48046
package Perlbal::Plugin::BackendHeaders;

use Perlbal;
use strict;
use warnings;

#
# Add $self->{service}->run_hook(‘modify_response_headers’, $self);
# To sub handle_response in BackendHTTP after Content-Length is set.
#
# LOAD BackendHeaders
# SET plugins        = backendheaders

sub load {
    my $class = shift;
    return 1;
}

sub unload {
    my $class = shift;
    return 1;
}

# called when we’re being added to a service
sub register {
    my ( $class, $svc ) = @_;

    my $modify_response_headers_hook = sub {
        my Perlbal::BackendHTTP $be  = shift;
        my Perlbal::HTTPHeaders $hds = $be->{res_headers};
        my Perlbal::Service $svc     = $be->{service};
        return 0 unless defined $hds && defined $svc;

        $hds->header( ‘X-Backend’, $be->{ipport} );

        return 0;
    };

    $svc->register_hook( ‘BackendHeaders’, ‘modify_response_headers’,
        $modify_response_headers_hook );
    return 1;
}

# called when we’re no longer active on a service
sub unregister {
    my ( $class, $svc ) = @_;
    $svc->unregister_hooks(‘BackendHeaders’);
    $svc->unregister_setters(‘BackendHeaders’);
    return 1;
}

1;

Tagged with ,

Well this has been a long time coming but I can declare the Typeface blogging platform to be a dead project. I have migrated this blog to wordpress.

Tagged with ,

Try this fun perl benchmark, to test your dual core, SMP or hyperthreaded system.

Before running, make sure you have perl 5.8 with threading support compiled in.

Perl has native ithreads as of perl 5.8.

#!/usr/bin/perl -w
use threads;
use strict;

my $y1=Bench->new();
print "Bencmarking multi-threadedn";
$y1->benchmark();

print "Benchmarking single-threadedn";
$y1->ncpu(1);
$y1->benchmark();

package Bench;

sub new ()
{
        my $self = {result => 0,ncpu=>0};
       
        my $cpus =`sysctl hw.ncpu`;
        $cpus =~ /: (.*)/g;
        $self->{ncpu}=$1;
       
        my $class = shift;
        bless ($self,$class);
        return $self;
}

sub ncpu {
        my ($self,$num) = @_;
        if(defined $num) { $self->{ncpu}=$num; } else { return $self->{ncpu}; }
}

sub benchmark ()
{
        my ($self)=@_;
        my @thr;
        for(my $i=0;$i < $self->{ncpu};$i++)
        {
                print "Starting thread $in";
                push @thr, threads->create(‘benchmark_thread’);
        }
        my $total=0;
        foreach my $t (@thr)
        {
                $total=$total+$t->join();
        }
        print "Total number of insane floating point divisions in 10 seconds is ". $total . "n";       
}

sub benchmark_thread()
{
        my ($y,$x)=0;
        my $time1 = time();
        my ($self)=@_;
        while(1){
          #$time2 = time();
          if((time()  - $time1)>= 10){last;}
          else {
            $x=1.00/24000000000.001;
            $y++;
          }
        }      
        return $y;
}

Tagged with , ,

Dynamic type languages such as Perl, Ruby, PHP, and Python free you as the developer from managing memory in your application. However, it isn’t a fool proof solution that you won’t have memory leaks in your application. You as the developer should be aware of how the underlying garbage collector of your preferred language works to accommodate for the inadequacies of its garbage collection algorithm.

Currently there are two ways of doing garbage collection; mark and sweep and reference counting. The Perl interpreter uses the latter. Reference counting is a fairly simple garbage collection technique. Each time you declare an instance, the reference count increments by one. When your program reaches the end of scope, objects with a reference of one get collected. However, if your object has a reference count of two it is kept. The one main draw back of reference counting is the fact it can’t deal with circular references. This is when both objects point to each other and they never get garbage collected.

On the other hand, Ruby and Java use the mark and sweep garbage collector. I personally have mixed feelings about it, since I don’t know exactly when my objects will be collected. The way mark and sweep garbage collection works, is it does not collect anything for a period of time. At intervals when the heap gets full, it runs its garbage collection. The downside to this is you don’t know exactly know when this happens and if there are lots of objects to be collected this leads to “stutters” and unresponsiveness of the application. If you have ever used a Java swing application you might have noticed these stutters, this is when garbage collection is taking place. However, it’s not as gloomy as I set the pretense to be with the mark and sweep garbage collection. Mark and sweep garbage collection can handle cyclic references unlike with reference counting, which is a huge boon to its usefulness. There has been much work done on mark and sweep garbage collection, specifically with generational mark and sweep collectors that try to fix the unresponsiveness issue. Java currently uses a generation GC, and Ruby hopes to obtain a generational GC for the Ruby 2.0 interpreter. Ideally a generational garbage collector would be the preferred GC for a long-standing process.

With that little garbage collection background out of the way, lets look at the life cycle of a instance in reference counting garbage collector.

Here is an example of how reference counting works ideally:

foreach (1..5) {
  my $i = 5;
  $i + 5;
  print $i . &lsquo;n‘;
} # $i should be garbage collected when it goes out of scope.

Unlike mark and sweep garbage collection with reference counting, you know exactly when your instance gets collected.

Here is a very simple problematic case for reference counting:

foreach (1..5) {
        my $a;
        my $b;
        $a->{b} = $b;
        $b->{a} = $a;
} # since both are pointing to each other they will never get collected.

This is a fairly simple case of where reference counting falls right on its face. Usually this isn’t a problem since most Perl scripting revolves around short-lived scripts. However, with frameworks such as Catalyst that are long running perl scripts this becomes an issue quickly. Thankfully, with Perl it is extremely easily to nail memory leaks, more so than with Ruby or Java. Enter Devel::Cycle and Devel::Peek, both of these modules can be installed from cpan. Both Devel::Cycle and Devel::Peek can assist you in tracking down the memory leak in a relatively short time.

use Devel::Cycle;
use Devel::Peek;

foreach (1) {
        my $parent = {name => ‘victor’ };
        my $child = {name => ‘victor jr’ };
       
        $parent->{child} = $child;
        $child->{parent} = $parent;

     find_cycle($parent);    
        # find_cycle belongs to Devel::Cycle
        # which prints out the
        # circular reference to STDOUT
        Dump($parent);          
        # Dump belongs to Devel::Peek , its extra verbose
        # which prints out the reference count to STDOUT
}

# Sample output # ibook:~/Desktop victori$ perl blah.pl # Cycle (1): <-- find_cycle tells you literly where the cyclic reference leak is at. # $A->{'child'} => %B # $B->{'parent'} => %A # # SV = RV(0x1817898) at 0x1800ec8 # REFCNT = 1 # FLAGS = (PADBUSY,PADMY,ROK) # RV = 0x18006dc # SV = PVHV(0x1830980) at 0x18006dc # REFCNT = 2 <-- Notice the reference count of 2 , we know we have a leak # FLAGS = (SHAREKEYS) # IV = 2 # NV = 0 # ARRAY = 0x404e60 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "name" HASH = 0xe6e17f14 # SV = PV(0x1801460) at 0x1800ea4 # REFCNT = 1 # FLAGS = (POK,pPOK) # PV = 0x401730 "victor" # CUR = 6 # LEN = 8 # Elt "child" HASH = 0x33ec6b5 # SV = RV(0x1817870) at 0x1832ca4 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x1800484 # SV = PVHV(0x18309b0) at 0x1800484 # REFCNT = 2 # FLAGS = (SHAREKEYS) # IV = 2 # NV = 0 # ARRAY = 0x404db0 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "parent" HASH = 0xa99c4651 # SV = RV(0x18178a0) at 0x1832c44 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x18006dc

So how do we fix this? Quite simple, all we do is weaken the reference count using weaken(). Here is a proper way of patching up the memory leak we introduced in our program.

use Devel::Cycle;
use Devel::Peek;
use Scalar::Util qw/weaken/;

foreach (1) {
        my $parent = {name => ‘victor’ };
        my $child = {name => ‘victor jr’ };
       
        weaken($parent->{child} = $child);  
        # we weaken the reference at the parent and all is well.

        $child->{parent} = $parent;  

     find_cycle($parent);    
        # find_cycle belongs to Devel::Cycle which prints out the
        # circular reference to STDOUT
        Dump($parent);          
        # Dump belongs to Devel::Peek , its extra verbose
        # which prints out the reference count to STDOUT
}

We weaken the reference at the parent level to set it back to a reference count of 1, so when it reaches the end of scope it will be collected and the memory leak will be no more.

Hopefully this is a good primer for other Perl coders out there who are facing memory leaks in their running long running perl scripts.

Tagged with ,

I *really* needed session affinity for our wicket application. HAproxy does session affinity but can’t be reconfigured at runtime without a restart. Perlbal is much more configurable, it lets you add and remove nodes in a pool at runtime. This makes deploying a new version of our web application a lot easier. I have the ability to test a new version of our application before putting it back into the pool of active nodes.

This is my first attempt at writing a sticky sessions plugin for Perlbal.

Update 06/26/09 Now on github perlbal-plugin-stickysessions

Update 04/30/09 Added Perlbal::XS::HTTPHeaders Support. Faster header parsing performance.

Update Fixed the Set-Cookies merge bug with the way Perlbal handles headers.

package Perlbal::Plugin::StickySessions;

use Perlbal;
use strict;
use warnings;
use Data::Dumper;
use HTTP::Date;
use CGI qw/:standard/;
use CGI::Cookie;
use Scalar::Util qw(blessed reftype);

# LOAD StickySessions
# SET plugins        = stickysessions
#
# Add $self->{service}->run_hook(‘modify_response_headers’, $self);
# To sub handle_response in BackendHTTP after Content-Length is set.
#

sub load {
    my $class = shift;
    return 1;
}

sub unload {
    my $class = shift;
    return 1;
}

sub get_backend_id {
    my $be = shift;

    for ( my $i = 0 ; $i <= $#{ $be->{ service }->{ pool }->{ nodes } } ; $i++ )
    {
        my ( $nip, $nport ) = @{ $be->{ service }->{ pool }->{ nodes }[$i] };
        my $nipport = $nip . ‘:’ . $nport;
        if ( $nipport eq $be->{ ipport } ) {
            return $i + 1;
        }
    }

    # default to the first backend in the node list.
    return 1;
}

sub decode_server_id {
    my $id = shift;
    return ( $id - 1 );
}

sub get_ipport {
    my ( $svc, $req ) = @_;
    my $cookie  = $req->header(‘Cookie’);
    my %cookies = ();
    my $ipport  = undef;

    %cookies = parse CGI::Cookie($cookie) if defined $cookie;
    if ( defined $cookie && defined $cookies{ ‘X-SERVERID’ } ) {
        my $val =
          $svc->{ pool }
          ->{ nodes }[ decode_server_id( $cookies{ ‘X-SERVERID’ }->value ) ];
        my ( $ip, $port ) = @{ $val } if defined $val;
        $ipport = $ip . ‘:’ . $port;
    }
    return $ipport;
}

sub find_or_get_new_backend {
    my ( $svc, $req, $client ) = @_;

    my Perlbal::BackendHTTP $be;
    my $ipport = get_ipport( $svc, $req );

    my $now = time;
    while ( $be = shift @{ $svc->{ bored_backends } } ) {
        next if $be->{ closed };

        # now make sure that it’s still in our pool, and if not, close it
        next unless $svc->verify_generation($be);

        # don’t use connect-ahead connections when we haven’t
        # verified we have their attention
        if ( !$be->{ has_attention } && $be->{ create_time } < $now - 5 ) {
            $be->close("too_old_bored");
            next;
        }

        # don’t use keep-alive connections if we know the server’s
        # just about to kill the connection for being idle
        if ( $be->{ disconnect_at } && $now + 2 > $be->{ disconnect_at } ) {
            $be->close("too_close_disconnect");
            next;
        }

        # give the backend this client
        if ( defined $ipport ) {
            if ( $be->{ ipport } eq $ipport ) {
                if ( $be->assign_client($client) ) {
                    $svc->spawn_backends;
                    return 1;
                }
            }
        } else {
            if ( $be->assign_client($client) ) {
                $svc->spawn_backends;
                return 1;
            }
        }

        # assign client can end up closing the connection, so check for that
        return 1 if $client->{ closed };
    }

    return 0;
}

# called when we’re being added to a service
sub register {
    my ( $class, $gsvc ) = @_;

    my $check_cookie_hook = sub {
        my Perlbal::ClientProxy $client = shift;
        my Perlbal::HTTPHeaders $req    = $client->{ req_headers };
        return 0 unless defined $req;

        my $svc = $client->{ service };

        # we define were to send the client request
        $client->{ backend_requested } = 1;

        $client->state(‘wait_backend’);

        return unless $client && !$client->{ closed };

        if ( find_or_get_new_backend( $svc, $req, $client ) != 1 ) {
            push @{ $svc->{ waiting_clients } }, $client;

            $svc->{ waiting_client_count }++;
            $svc->{ waiting_client_map }{ $client->{ fd } } = 1;

            my $ipport = get_ipport( $svc, $req );
            if ( defined($ipport) ) {
                my ( $ip, $port ) = split( /\:/, $ipport );
                $svc->{ spawn_lock } = 1;
                my $be =
                  Perlbal::BackendHTTP->new( $svc, $ip, $port,
                    { pool => $svc->{ pool } } );
                $svc->{ spawn_lock } = 0;
            } else {
                $svc->spawn_backends;
            }
            $client->tcp_cork(1);
        }

        return 0;
    };

    my $set_cookie_hook = sub {
        my Perlbal::BackendHTTP $be  = shift;
        my Perlbal::HTTPHeaders $hds = $be->{ res_headers };
        my Perlbal::HTTPHeaders $req = $be->{ req_headers };
        return 0 unless defined $be && defined $hds;

        my $svc = $be->{ service };

        my $cookie  = $req->header(‘Cookie’);
        my %cookies = ();
        %cookies = parse CGI::Cookie($cookie) if defined $cookie;

        my $backend_id = get_backend_id($be);

        if ( !defined( $cookies{ ‘X-SERVERID’ } )
            || $cookies{ ‘X-SERVERID’ }->value != $backend_id )
        {
            my $backend_cookie =
              new CGI::Cookie( -name => ‘X-SERVERID’, -value => $backend_id );
            if ( defined $hds->header(‘set-cookie’) ) {
                my $val = $hds->header(‘set-cookie’);
                $hds->header( ‘Set-Cookie’,
                    $val .= "\r\nSet-Cookie: " . $backend_cookie->as_string );
            } else {
                $hds->header( ‘Set-Cookie’, $backend_cookie );
            }
        }

        return 0;
    };

    $gsvc->register_hook( ‘StickySessions’, ‘start_proxy_request’,
        $check_cookie_hook );
    $gsvc->register_hook( ‘StickySessions’, ‘modify_response_headers’,
        $set_cookie_hook );
    return 1;
}

# called when we’re no longer active on a service
sub unregister {
    my ( $class, $svc ) = @_;
    $svc->unregister_hooks(‘StickySessions’);
    $svc->unregister_setters(‘StickySessions’);
    return 1;
}

1;

Tagged with , ,

Update: See my newer post that re-evaluates the frameworks. Things have changed since then.

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/

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.