The Workerless Pattern
One of the most interesting talks I saw at railsconf this year was presented by Ryan Smith about the worker pattern. After seeing the talk, I thought about how the same pattern could be implemented using an evented framework and ended up building a proof-of-concept in node. Before we look at my implementation, let’s briefly review the worker pattern.
The Worker Pattern Overview
In a nutshell, the worker pattern is about breaking pages down into separate fragments that can be requested concurrently by the browser after the page loads. In a language like ruby, there are three main components to the solution: the client side code, the workers and a cache.
In the user’s browser, javascript is used to request the fragments after the page is loaded. The requests are made using a polling technique, continually calling the server until the fragment is ready to be serverd. All fragments are requested concurrently.
On the server, fragment requests are delegated to workers that run outside of the request/response lifecycle. Generating these fragments usually involves external api calls, long database requests or other timely operations. By removing these long operations from the request/response lifecycle we can avoid overloading our server with many slow requests.
Finally, the results of these long-running operations are cached to save time on subsequent requests.
The Workerless Pattern
Using a framework like node, we can implement the worker pattern with an interesting twist: workers outside of the request/response lifecycle are unnecessary. Why? Because node is evented. This means that operations with long running i/o can be executed in a non-blocking fashion. Although a request to an external api may take several seconds to return, this call will not stop our server from continuing to serve other requests.
Without the complication of background workers, our pattern becomes much easier to implement. Let’s look at each of the pieces in detail.
Client Side Code
The client side code is straighforward: make several concurrent requets to the server using javascript and populate the page with the results of these calls. A benifit of using node on the server is that we do not need to implement a polling strategy. Our server will simply hold the requests while building the fragement and return the contents when ready. Again, holding the requests does not block our server from continuing to serve requests.
The html just provides some empty divs into which the fragment content will be loaded. It also loads the clientside coffeescript.
<h1>Node Worker Patter</h1>
<div id="a">
<img src="/images/spinner.gif" />
</div>
<div id="b">
<img src="/images/spinner.gif" />
</div>
<div id="c">
<img src="/images/spinner.gif" />
</div>
<script type="text/coffeescript" src="/javascripts/client.coffee">
</script>
The client coffeescript is below.
makeRequest = (url, callback) ->
$.ajax
url: url
success: (data, status, request) ->
callback(data)
error: (request, status) ->
console.log(status)
$ ->
makeRequest '/fragment/a', (content) ->
$('#a').replaceWith(content)
makeRequest '/fragment/b', (content) ->
$('#b').replaceWith(content)
makeRequest '/fragment/c', (content) ->
$('#c').replaceWith(content)
We make an ajax request to the server for each fragment. After receiving the fragment respones from the server, we replace the divs on our page with the fragment’s contents.
Server Side Code
The server is written in node using the express framework. First, we’ll take a look at setting up the express app.
express = require "express"
externalApi = require "./externalApi"
app = module.exports = express.createServer()
app.configure ->
app.set "views", __dirname + "/../views"
app.set "view engine", "ejs"
app.use express.static __dirname + "/../public"
app.get "/", (req, res) ->
res.render "index", {}
app.get "/fragment/:fragmentName", (req, res) ->
query = req.params.fragmentName
externalApi.slowFind query, (err, result) ->
res.render "fragment",
fragmentId: result
fragmentContent: result
layout: false
app.listen 3000
console.log "Express server listening on port %d", app.address().port
Our express app sets up two simple routes.
The ‘/’ route serves the skeleton html shown in the last section. The ‘/fragment’ route makes a slow “external api” call and then renders the fragment view. Let’s look at that view now.
<div id="<%= fragmentId %>">
<p>Content -- <%= fragmentContent %></p>
</div>
In the previous section, we showed the client side code that was making the requests to our server’s ‘/fragment’ route. The simple div shown above will be the contents returned to those ajax requets and will, in turn, be displayed on the page.
Next, let’s take a closer look at what’s happening in our external api call.
cacher = require './cacher'
performSlowExternalCall = (query, callback) ->
timeout = Math.floor(Math.random() * 3001)
setTimeout ->
callback("result for #{query}")
, timeout
slowFind = (query, callback) ->
cacher.performCached
query: query
hit: (value) ->
callback null, value
miss: (cacheResultCallback) ->
performSlowExternalCall query, (result) ->
cacheResultCallback(result)
exports.slowFind = slowFind
Basically, we’re using a timeout to represent a slow external call. We first call the performCached method on the cacher object. If the query is not present in the cache, we call the performSlowExternalCall method which sleeps for a random number of seconds and then returns a result string. If the query is present in the cache, we simply return the value from the cache directly to our callback, avoiding the costly api call.
Caching
Finally, let’s take a quick look at how we’re caching the results of our “external api” calls to avoid duplication of effort. Here’s the cacher code:
EventEmitter = require('events').EventEmitter
redis = require "./redisClient"
client = redis.namespacedClient "worker"
class Cacher extends EventEmitter
constructor: (options) ->
@query = options.query
@on 'hit', options.hit
@on 'miss', options.miss
perform: ->
client.get @query, (err, result) =>
if result?
@emit 'hit', result
else
@emit 'miss', (result) =>
client.setWithExpiration @query, result, (err, setResult) =>
@emit 'hit', result
exports.Cacher = Cacher
exports.performCached = (options) ->
cacher = new Cacher options
cacher.perform()
The code here is a bit complicated and probably warrants a post of its own, but the general idea should be clear. When calling perform, we first check to see if our query exists in the cache. If it does, we simply emit the ‘hit’ event with the value, which calls user-defined hit function. If the query is not in the cache, we emit the ‘miss’ event, calling the user-defined miss function. We pass the miss function a callback that, when called, stores the result into the cache and then emits the ‘hit’ event with our result.
Wrap Up
The worker pattern is a clever way to speed up page loads and leverage the concurrency of client side javascript. In an evented framework, such as node, this pattern becomes more elegant by eliminating the workers altogether. If you’d like to check out the complete proof-of-concept I threw together, you can find the code here.
Drew