January 7, 2014

A Mojolicious non-blocking web service: why?

Let’s take a trip to the land of Slow APIs. The land of Slow APIs is a horrid land, filled with web services that are almost RESTish, in all ways except those that make development easy. Sometimes those APIs don’t respond, and other times, the APIs respond, but then time out before the information is given.

As I said, it’s a horrible land.

Your job, on this wonderful day, is to write an API that uses a Slow API as a backend data store, and make it capable of supporting thousands of users at a time. The catch? Each Slow API call takes 5 seconds to respond with data.


Ok, you say.

I’ll use Mojolicious, because I like rainbows and unicorns.

Good choice, for reasons we shall soon see.


Let's make a file named coolapi:

use Mojolicious::Lite;
use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

get ‘/‘ => sub {
    my $self = shift;

    $self->render(text => $ua->get(‘http://slowapi.com/')->res->body);
};

app->start;

I’m done, you say!

Nay, you are not.

We have a problem that you have not considered. You need to support thousands of users at once, so you first need to consider how you’ll be serving these requests.

Your first thought is to run the built-in daemon:

 $ perl coolapi daemon

Wow! It’s running. But wait. Make a request. Now make two, within 5 seconds.

Hmm.

The second request cannot begin until the first request has completed. Each request blocks all other requests. Why? Because daemon only runs one thread, and it can only do one thing at a time. Thousands of users at a time?

I think not.

Fine, you say. We’ll run hypnotoad, which has multiple listeners.

 $ hypnotoad coolapi

Hypnotoad runs 5 listeners by default. Now you can serve up to 5 users at a time. Whoa, 5.

Ok, so add some more workers. We’ll add a configuration file called coolapi.conf with the following configuration.

    {hypnotoad => {workers => 2000}}

And we'll tell Mojolicious to use the configuration by specifying the Config plugin:

use Mojolicious::Lite;
use Mojo::UserAgent;

plugin 'Config';

my $ua = Mojo::UserAgent->new;

get '/' => sub {
...

There! You can run up to 2000 listeners at a time! Except, you now have 2000 processes running as well. Waiting. Sometimes, doing nothing. And what if you need 2001? 5000? Are you really prepared to run 10000 processes? I can tell you right now, you’re not. You don’t want to go down that road. It’s dark, much darker than even the citizens in the land of Slow APIs could fathom.

Your shoulders slump, crestfallen. Hope is dimming.

Never fear, my friend. You chose Mojolicious. And Mojolicious has thus saved you with its forethought and shininess.

Behold, we are about to enter the realm of non-blocking web services. It’s a magic realm, filled with mystery and wonder. But there will be no more mystery. Today, as you enter, you will understand, as clear as day, and never write a blocking web service again.

Behold. The strong, colorful, right arm of Mojolicious will save you on this day. And you will fall down in deference in awe of Mojolicious, the non-blocking next generation framework. A double, nay, a triple rainbow will shine grace upon you.

Let’s take a step back, and consider the problem: each process can only do one thing at a time. But it doesn’t have to be this way. We can defer each request, and let Mojolicious handle the processing of it while it remains ready to answer other requests.

Even better, it isn’t difficult. It’s not difficult at all.

Let’s use Mojo::UserAgent in a slightly different way.

$ua->get(‘http://slowapi.com/' => sub {
    my ($ua, $tx) = @_;  
    $self->render(body => $tx->res->body);
});

Passing a callback into the get method will let Mojolicious know that you're intending to make a non-blocking call. When the request returns, whether 5 seconds, or 5 minutes, the render will be called. And because Mojolicious is handling this in the background, the process can still listen for new requests.

Oh snap, you say.

Yes. Oh snap.

How many requests can this support? More. A lot more. As fast as Mojolicious can respond, that’s how many concurrent requests that can be handled.

This is what you want. This is how you want to write your web services. Though the land of Slow API is dark, you can still achieve a long-standing peace.

From now on. No more blocking. Ever.

use Mojolicious::Lite;
use Mojo::UserAgent;

my $ua = Mojo::UserAgent->new;

get ‘/‘ => sub {
    my $self = shift;

    $ua->get(‘http://slowapi.com/' => sub {
        my ($ua, $tx) = @_;  
        $self->render(text => $tx->res->body);
    });
}

app->start;


This post is listed on the Perl Ironman RSS and Atom feeds

Tempire is on Twitter

comments powered by Disqus