A long long poll ago in an AJAX call far far away.

Fun with long polling in Node and Redis

Kyle
7 min readApr 12, 2016

This is part 22 of my Node / Redis series. The previous part was There is something rotten in the state of geohash.

I’ve written a little bit about Firebase in the past. It’s definitely a fun platform for writing apps . One of the things that makes it a breath of fresh air is that you escape from the cycle of loading — No AJAX calls to fetch data from a server you just setup .on(‘value’,…) and you’re in business. Unexpectedly, it really changes how you write scripts — the server seems very close. You find yourself writing to a server with .set(..) then just letting .on(‘value’,…) take care of updating your view. Firebase isn’t all fun-and-games, though. I’m used to Redis and the power that comes from a handful of special commands — Firebase is more limited. Oh, and Firebase is expensive. Like first-born-child-as-collateral expensive.

Can we mix a little Redis with Node.js and come up with that cool client side auto-updating magic? Let’s find out!

There are a few different technologies that can be used to keep a web browser up-to-date. Websockets are a favourite, but they come with some issues:

And (maybe a bad reason) it’s a decent sized shift from AJAX for many front-enders to absorb. That being said, let’s look at long polling. Long polling is just an AJAX call. That’s it. It’s not a different protocol, yet it can provide instant communication with a server. The ‘long’ in long polling is how long the server keeps the connection open. The goal for traditional AJAX calls on the server is to close them as quickly as possible — get an HTTP request, respond to the request and close the connection. In long polling, you keep the connection open for a long time, usually measured in minutes, without doing anything. When new information comes in, send it to the client and the connection is closed. The client immediately makes another request starting the process over again. It’s not perfect either — it has some overhead on both the client and the server and, on mobile devices, it keeps the radio active (which can thrash a battery in quick order). But, in the right environment, long polling is all you need. And you can make it work in a little over 100 lines of Node.js.

In our implementation, we’re going to need to make a few tweaks to the standard long polling sequence. We’ll have two identical routes — one is a short poll (/poll/:key) that will immediately respond back with the data at a particular Redis key and another that long polls (/long-poll/:key), keeping the connection open for a while and, prior to closing, sends the key’s current value. This is a departure from most long polling systems — they tend to just close the connection if nothing new comes in. I’ll cover why I made this tweak a bit further down.

So, now that we know how we want the server to behave, let’s think about how to accomplish it. Let’s start with Redis keyspace notifications. With keyspace notifications, the pub/sub system publishes a message every time a key is touched. This is a simplified explanation, so head over to the official documentation for the complete run-down.

Node has a similar built-in pub/sub system — the events module. The events module is actually what drives a lot of the internal asynchronous behaviours of Node. With the events module, you create a new instance (var someEvents = new EventEmitter()) then you can publish events to a channel (someEvents.emit(‘mychannel’,‘myvalue’)) to which any listeners attached will execute a callback (someEvents.on(‘mychannel’,function(‘mychannel’,function(value) {…}).

We’re going to use both the Redis pub/sub system and the Node event emitter to make our server. Why do we need both? Well, we’ll be using the event emitter to relay events from Redis pub/sub. By having the internal event emitter, we can effectively scope the message to last for only the length of a single HTTP request using the once method rather than the on method. In addition, we can alter the message coming from Redis (literally message) into something more useful and relevant for keys.

Another important detail is that we can manage the subscriptions to a particular key — so we are only doing notifications for that key while a HTTP request(s) are active. If you have multiple HTTP requests active pointed at the same key, the one Redis notification is relayed to all the HTTP requests. We use a counter to determine the number of active connections, so there are no requests active, then we can unsubscribe from the Redis subscription.

With our front end code, we’re just making a connection first to the /poll/:key route to immediately get the data and then change the url for each subsequent request to /long-poll/:key. When the connection ends, we immediately start it over and request again. Lather-rinse-repeat. So, while the browser page is loaded we can go to redis-cli and start modifying the key (in our example “always-up-to-date”). You should see that the browser immediately changes. That’s because the keyspace notification is sent, the event is emitted, signaling the server to fetch the latest value for the key, send it out, and close the connection.

So, why then do we always send out the current value before closing? It compensates for dropped keyspace notifications. With Redis, the keyspace notifications are fire-and-forget. Meaning that there is no backup or log of these events. If no subscriber is listening to the event then it is lost. In our situation, we could have the following series of events occur if it didn’t send the message at the end of a request:

  1. The browser window is opened and the latest is retrieved from the /poll/:key route. Let’s say the key is “c3p0” and it has a value of “gold arm”
  2. The Node process goes down for some unknown reason
  3. While the Node process is restarting, another client changes the value at key “c3po” to “red arm.”
  4. The keyspace notification is published, but since the Node process is busy restarting, there is no subscriber.
  5. The Node process is now accepting connections and the browser makes connection. No further changes are made.
  6. The Node process sends and empty response to the connection, so the script doesn’t do anything.

In this case, the browser would still think the value for the key “c3po” is “gold arm.” By sending the value at the end of every request, you can only ever be out of date by the duration of one timeout.

Here is the whole server:

Before you run the server, you’ll need to enable Redis to use keyspace notifications either through the redis.conf file or by running CONFIG SET AKE.

Let’s do a small test to illustrate what is going on. First I’ll open three terminal windows and use curl to go to http://localhost:2600/long-poll/testing . When testing this, curl should just sit at idle — the connection hasn’t closed. In a fourth window, I’ll use redis-cli to modify the key testing. This triggers the keyspace notification that in turn tells our Express server to finally return the value. Here it is in action:

Three clients connected to our sample server all with the HTTP connection left open. I change the value and bam all the connections end.

Now for our next test, let’s do something similar, but point the middle window at a different key / url (c3po). First, set the value of the key testing — you’ll see the two end windows close connection as they’ve just got a value. Then I set the value of the key c3po and the middle window closes.

Juggling different keys and notifications

So, now we know our little server can handle closing the connection and multiple keys. Let’s see how we can use this on the front-end.

From a front-end perspective, we’re going to do something that looks a little like recursion. We’re going to make a AJAX request and set a callback on a successful completion. In this callback, we’ll do something with the result and immediately make the callback again. Remember, since this is all asynchronous, it’s not really recursive. The other call is over and done with by the time the second is being processed.

I’m using jQuery here, but the same thing can be accomplished with Angular. Actually, with Angular you can do a great deal more. Imagine using this same method but instead of taking the value of a simple string and throwing it into a DOM element, we set the Angular $scope with the value being returned. Then you have a three-way data binding — your controller data, your model data and your server will aways have the same data.

The implications of this are pretty big, although my implementation is pretty basic and naïve. With a little more work you could create a real-time chat, an always-up-to-date news ticker, web app notification, game server, the scrolling opening credits to a block buster movie — really anything that needs up-to-the-moment updates that would be wasteful to poll.

--

--

Kyle

Developer of things. Node.js + all the frontend jazz. Also, not from Stockholm, don’t do UX. Long story.