Polling is Evil, Start Pushing
It’s an exciting time to be working at Breezy right now. We’re past the initial rush to build our MVP and can finally loop back around and focus on making things more awesome. One of the biggest challenges we’ve had to solve is how we notify our Connector applications that documents are ready to print. A bit of context, the Connector is a piece of software that sits on desktop PC’s that have printers. When a user chooses to print to a printer that’s “connected” to this PC we notify the Connector and it does the work of actually sending the document to the printer.
It’s magic and it works. So what’s the problem? The problem is we currently solve this notification challenge by having the Connector poll our web service. This is less than optimal for a number of reasons: 1) This places a significant (seriously…) load on our backend infrastructure 2) This introduces latency because the Connector is polling on an interval. Finding a better solution is difficult because most of these Connector installations are sitting behind firewalls and are on a consumer’s home network. This means you can’t expect any additional setup other than installation of the software so any sort of direct connection initiating from the server is out of the question.
So what’s option 3? In a word Websockets. This nifty little protocol has silently made its way into web browsers and web servers over the last few years. It’s most common use case is allowing servers to send messages to web browsers, allowing the display of real time data and notifications. Web Sockets are similar to long lived HTTP connections except that they allow bi-directional messaging between both the client and the server. After the initial handshake the connection is “upgraded” after which time data is exchanged using a process known as Data Framing. Websockets also run over HTTP/S ports 80 and 443 so there’s no worry that they’ll be blocked by firewalls. For these reasons Websockets are also a great solution to our Connector’s polling problem.
Cool, so Websockets, now what? We could have built a Websocket server that’s capable of handling thousands of live connections at a time, implemented a nice security layer and defined our own messaging protocol but… that’s a lot of work. It’s a beautiful world we live in where people build these sorts of things for us and let us use them. That’s where Pusher came in.
Why did we choose Pusher?
Pusher didn’t just save us time it made refactoring our architecture possible. The work required to implement a notifications infrastructure that would handle our traffic requirements is simply intractable for us. Furthermore, out of the box it has many of the less obvious features we needed like security, presence information and a well documented messaging protocol.
What problems did Pusher solve?
Pusher allowed us to offload the vast majority of our backend traffic to their notifications infrastructure. This will result in significantly improved performance and dramatically lower costs as we scale. Furthermore, this adds value to our customers because we can print a document in “real-time” rather than forcing them to wait several seconds for the next poll.
What was missing from Pusher?
One feature I’d like to see added is the ability to receive callbacks when clients connect to a Channel. This would help us keep track of what Connectors are actively connected and ready to receive notifications. Currently our workaround is a process that is constantly checking with the Pusher API to see who’s active. This has been somewhat time consuming and challenging to develop, it’d be nice if we could remove this “moving part” in the future. I’ve talked to some of the Pusher guys and it sounds like this is on their near-term roadmap, definitely looking forward to seeing it.
Also, they seem to have their client libraries separated into Client and Server functionality. This provided a bit of a challenge for us because we needed some of the “client” functionality available from our backend which is written in Rails. This meant that we had to add this functionality to the Pusher gem, also a bit time consuming. For our scenario it would have been nice to see a single gem that encompassed both client and server functionality but frankly I’m not sure how common that is.