Writing Non-Blocking Applications with Mojolicious: Part 1
One question I often hear is “Why should I chose Mojolicious versus one of the other major Perl web frameworks?” While I have many answers to that question, I personally believe the most important difference is that Mojolicious is designed to be non-blocking. Many of you will have heard of Node.js. The reason that Node is popular is that it is designed to be non-blocking. By writing your webapp in a non-blocking style using a non-blocking framework, you can often build a faster and smarter application, requiring fewer servers to handle the same amount of traffic. Although Perl has several web frameworks, only one was written with non-blocking design in mind: Mojolicious.
To demonstrate a non-blocking application, I am going to write a simple pastebin using Mojolcious and Mango, a non-blocking MongoDB library (from the same developers as Mojo).
The Templates
Before I dive into the server-side code, let’s look at the templates which will form the view of the application. Mojolicious has its own templating engine which is a thin layer over normal Perl syntax.
@@ layouts/basic.html.ep
<!DOCTYPE html>
<html>
<head>
<title><%= title =%></title>
%= stylesheet '//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css'
</head>
<body>
<div class="container">
<%= content =%>
</div>
</body>
</html>
@@ show.html.ep
% title $doc->{title};
% layout 'basic';
%= stylesheet begin
pre.prettyprint {
background-color:inherit;
border:none;
}
% end
%= tag h1 => $doc->{title}
%= tag div => class => 'well' => begin
%= tag pre => class => 'prettyprint' => begin
<%= $doc->{content} =%>
% end
% end
%= javascript 'https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js'
@@ submit.html.ep
% title 'Paste your content';
% layout 'basic';
%= form_for '/' => role => form => method => POST => begin
%= tag div => class => 'form-group' => begin
%= tag label => for => 'title' => 'Title'
%= text_field 'title', class => 'form-control'
% end
%= tag div => class => 'form-group' => begin
%= tag label => for => 'content' => 'Paste Content'
%= text_area 'content', class => 'form-control'
% end
%= submit_button 'Paste' => class => 'btn btn-primary'
% end
The first template defines a basic html layout which will be applied to any other template that requests it.
The contents of the requesting template will be inserted at the content
directive.
The show
and submit
templates (both of which request the basic layout) are the views that will be seen when a user wants to display a paste or create new one.
Notice that in the show
template, a variable $doc
is seen.
This (template lexical) variable will be created by the controller using the stash
command later.
Any functions you see are called helpers
in Mojolicious parlance and are more accurately curried controller methods.
Some style is applied by using Twitter’s Bootstrap and Google’s Prettify from CDN sites.
These templates will be used in both versions of my following code; they may be inserted as-is in the __DATA__
section of the following files or else placed separately in a directory named templates
.
The Blocking Form
Since most people are comfortable with writing web applications using a blocking style, I will first present my paste application in this way.
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mango;
use Mango::BSON 'bson_oid';
helper mango => sub { state $mango = Mango->new($ENV{PASTEDB}) };
helper pastes => sub { shift->mango->db->collection('pastes') };
get '/' => 'submit';
post '/' => sub {
my $self = shift;
my $title = $self->param('title') || 'Untitled';
my $content = $self->param('content')
or return $self->redirect_to('/');
my $doc = {
title => $title,
content => $content,
};
my $oid = $self->pastes->save($doc);
$self->redirect_to( show => id => "$oid" );
};
get '/:id' => sub {
my $self = shift;
my $id = bson_oid $self->stash('id');
my $doc = $self->pastes->find_one({ _id => $id })
or return $self->redirect_to('/');
$self->stash( doc => $doc );
} => 'show';
app->start;
After importing the necessary libraries (importing Mojo turns on strict
, warnings
and utf8
and all v5.10 features), I build some helpers
of my own.
The mango
helper will connect to a Mongo instance whose uri I will specify in an environment variable (yes I could put it in a configuration file, but I had to draw the line on this example somewhere :-) ).
I have a helper which will return an instance of the collection (read: table) which will store the paste information.
By default Mango will generate a unique document ID, which for our purposes, we will use as our page identifier.
Once the helpers have been created, three routes are defined and connected to controller callbacks (the lite version of controller methods), whose purposes should be rather self explanatory.
One quick thing I note is that since I don’t explicitly called render
, the controller will automatically render the template with the same name, where the name is defined in lite syntax by a string after the controller callback.
I could have as used the render
method, but this is more concise.
Running the application (seen in its completeness here, don’t forget the database env var!) will start the server. This application may be run as-is under CGI, any PSGI server, or using Mojolicious’ built-in servers. The application should run as you expect, however it has a major drawback! Any time any client causes the app to make a request to the database, all clients must wait for it to respond before the server may serve the next client. Meanwhile the server is sitting idle waiting for a response. Seems inefficient doesn’t it?!
The Non-Blocking Form
I can now present a very similar application, but which has a couple tweaks to prevent database calls from blocking the application.
#!/usr/bin/env perl
use Mojolicious::Lite;
use Mango;
use Mango::BSON 'bson_oid';
helper mango => sub { state $mango = Mango->new($ENV{PASTEDB}) };
helper pastes => sub { shift->mango->db->collection('pastes') };
get '/' => 'submit';
post '/' => sub {
my $self = shift;
my $title = $self->param('title') || 'Untitled';
my $content = $self->param('content')
or return $self->redirect_to('/');
my $doc = {
title => $title,
content => $content,
};
$self->render_later;
$self->pastes->save($doc, sub {
my ($coll, $err, $oid) = @_;
$self->redirect_to( show => id => "$oid" );
});
};
get '/:id' => sub {
my $self = shift;
my $id = bson_oid $self->stash('id');
$self->render_later;
$self->pastes->find_one({ _id => $id }, sub {
my ($coll, $err, $doc) = @_;
return $self->redirect_to('/') if ( $err or not $doc );
$self->render( show => doc => $doc );
});
} => 'show';
app->start;
This new code only differs by a few lines, but they are important ones!
First, before I make a non-blocking call, I need to call render_later
to prevent the automatic rendering I mentioned above; the server cannot render when it reaches the end of the action method because it doesn’t have the data yet!
title => $title,
content => $content,
};
- my $oid = $self->pastes->save($doc);
- $self->redirect_to( show => id => "$oid" );
+ $self->render_later;
+ $self->pastes->save($doc, sub {
+ my ($coll, $err, $oid) = @_;
+ $self->redirect_to( show => id => "$oid" );
+ });
};
In the POST handler, you can see that I call the same save
method on the collection, however, I don’t just pass the document, I also pass a subroutine reference (a callback) which will get called when the database finishes inserting the data.
When a client posts the new paste to the server, it will wait until the database finishes storing the document before being redirected to view the document.
Unlike the preview version of the code however, while waiting to serve this client, the server is able to move on to serve other clients which are waiting for other data!
get '/:id' => sub {
my $self = shift;
my $id = bson_oid $self->stash('id');
- my $doc = $self->pastes->find_one({ _id => $id })
- or return $self->redirect_to('/');
- $self->stash( doc => $doc );
+ $self->render_later;
+ $self->pastes->find_one({ _id => $id }, sub {
+ my ($coll, $err, $doc) = @_;
+ return $self->redirect_to('/') if ( $err or not $doc );
+ $self->render( show => doc => $doc );
+ });
} => 'show';
Similar changes are made to the show
controller.
Once again, I call render_later
and then move my post-database call logic into a callback.
This time, the server will make a request of the database to get the relevant document, but then proceeds to serve other clients until the database responds.
When the response comes back, the server invokes the callback and the client sees the requested page.
One small drawback is that now you must deploy using Mojolicious’ built-in servers. No need to fear however, they are very capable.
Ok this is cool but …
Yes it seem like a little more code, but the payoff comes next. If you are running your database on the same computer as the server, you will see little or no difference in performance. However, perhaps you want to use an off-site database host like MongoHQ. Using the tool wrk you can clearly see the benefit of rewriting your blocking application:
$ PASTEDB=mongodb://demo:pass@linus.mongohq.com:10025/MangoTest hypnotoad blocking_paste.pl
$ wrk -t 10 -c 10 -d 1m http://localhost:8080/0
Running 1m test @ http://localhost:8080/0
10 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 286.87ms 331.49ms 1.45s 90.25%
Req/Sec 5.91 4.32 22.00 81.12%
3745 requests in 1.00m, 2.83MB read
Requests/sec: 62.41
Transfer/sec: 48.32KB
$ PASTEDB=mongodb://demo:pass@linus.mongohq.com:10025/MangoTest hypnotoad -s blocking_paste.pl
… in a non-blocking style:
$ PASTEDB=mongodb://demo:pass@linus.mongohq.com:10025/MangoTest hypnotoad nonblocking_paste.pl
$ wrk -t 10 -c 10 -d 1m http://localhost:8080/0
Running 1m test @ http://localhost:8080/0
10 threads and 10 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 59.41ms 18.69ms 365.66ms 97.45%
Req/Sec 16.73 2.19 21.00 75.56%
10290 requests in 1.00m, 7.78MB read
Requests/sec: 171.49
Transfer/sec: 132.77KB
$ PASTEDB=mongodb://demo:pass@linus.mongohq.com:10025/MangoTest hypnotoad -s nonblocking_paste.pl
Please keep in mind, this is only a toy case on a free database and an older computer. That said, it is clear that there is a marked improvement in transfer and requests handled. Try it yourself, the code for both scripts is available here.
Conclusion
Mojolicious is a very powerful web framework which makes hard things easy. This is especially true for non-blocking code. I hope this post is the first in a series on writing non-blocking webapps (and perhaps even more general apps), using Mojolicious. Happy Perling!
P.S. see Mojo::IOLoop::Delay for even more ways that the Mojolicious tool suite (as I like to call it) makes writing non-blocking code easier.
This post is the first in an on-going series, continued in part 2
Also, don’t be scared of my use of the tag helper in the template. As an old LaTeXer it feels really natural to do begin/end rather than
<tag></tag>
. This level of commitment to the templating engine is possibly not to be encouraged :-POn a modern host, you could easily have a couple hundred instances of your blocking script available to serve user requests, without any impact, thus negating the benefit of non blocking io. It’s when you want to get to serving 10’s of thousands of simultaneous requests from the same box that you have to worry about flipping to non blocking style.
Joel,thanks for posting, it was especially cool to see the comparison between the blocking and non-blocking code examples.
Joel it was a good read for those of us new to the Mojolicious framework…keep sharing the knowledge!
Joel - do you know if the built in Mojo servers - or at least Morbo - will support this non blocking behaviour in Windows environments?
@all, thanks for the kind words
@Ross, I’m pretty certain that the daemon and morbo both work perfectly under windows (remember that morbo is just daemon + reload on file change). I don’t have a windows box I can test on, but this is my recollection.
Hypnotoad (the preforking server, also built on daemon) will not work on windows unfortunately. Then again, I’m not sure if there are any working preforking windows servers for any framework.
Good. I want to see the way to stream file by Mojolicious and Mango.
Interesting read, thanks very much! I’m looking forward to the next part. I’d also like to see how non-blocking stuff would look in Catalyst. Any chance?
To me, Mojolicious and Dancer look very similar on the surface. The introduction tutorials for both have almost identical syntax. Is non-blocking one advantage that Mojolicious has over Dancer?
Yes most of the frameworks have a similar syntax, even in other language to some extent. Non-blocking is a feature that Mojolicious provides and (to my knowledge) no other framework does (at least not built-in and well integrated).
Mojolicious also has lots of additional built-in functionality that others pull from external modules. While some have called this anti-cpan (which I think is silly, Mojolicious is on cpan), this allows for these functionalities to be well integrated in Mojolicious. See my Mojolicious Introduction talk for more examples.
Would be of tremendous appreciation if you would consider a rewrite of this Mojo series for current Mojolicious.
I’m struggling to get a hang of it all, and the example code here dont work on current Mojo version.
Really great reading tho.
Sorry for speaking to soon. Fresh coffe did it for me, I missed some typos from my part.