Chat

Cloud Computing: the Dream vs. Reality

Michael Carroll on Oct 12, 2016
Cloud Computing: the Dream vs. Reality

Waving Hand

Good News! We've launched an all new Chat Resource Center.

We recommend checking out our new Chat Resource Center, which includes overviews, tutorials, and design patterns for building and deploying mobile and web chat.

Take me to the Chat Resource Center →

The dream of cloud computing is wonderful: you never run your own server, pay for electricity, or worry about network failures. Sadly, the reality is something else all together. You still have to administer the software on servers. You still need to design failover systems. You still have to secure the servers, even if they are virtual. Cloud computing requires you to build your own distributed computing infrastructure, you just don’t have to mess with the actual hardware anymore. It’s a nice dream, but cloud computing providers only deliver half of it.

The real dream is that you write some code that lives in the network. A message comes in and your code processes it, sending the messages back out. If you need to store some data you can just store it. If you need to call an external webservice you can call it. You don’t have worry about the file system or database administration. You don’t have to worry about whether to spin up 5 or 500 instances of your service. You don’t have worry about co-locating your services near where the users are. In short your code becomes truly serverless.

Distributed Servers Make Simple Code Hard

Let’s consider a simple example. Suppose you want to build a chat app for junior high students with a profanity filter. This is conceptually very simple. It’s just an if statement. If the message contains any of a list of words, send it back to the student. In practice, however, this code is much harder to build.

Making a simple text filter like this is difficult to scale; it requires sending the message from the end user, to the data stream network, back to your server, performing the computation, then back again to the network before reaching the final destination. This ‘back and forth' adds latency and load to your server, for what is conceptually a simple if statement. And we haven’t even considered data storage and synchronization yet. Your server doesn’t even really want the messages, but it’s the only way to perform computation on data in motion.

Move the Computation to the Data

What if there was a better way? Instead of moving the data to the computation, what if you could move the computation to the data?

PubNub has always had a Data Stream Network (DSN). It’s like a CDN, but for data in motion. Up until now the DSN could route that data in motion quickly and reliably, but not actually change the data in any way. That means you can’t edit contents or perform any computation. If you want to mutate a message you’d have to take it out of the DSN to your own server, do the modifications, then send back to PubNub before it reaches the end user.

This roundabout method of real-time computing not only adds tremendous latency, but it also introduces a new single point of failure and completely destroys user locality. No matter where the data comes from, it has to go to a single server in a single location. If that server goes down, or is simply far away from the user, then the user experience massively suffers.

Now there is a better way: Functions. You really can write a simple text filter and send it into the network to be run. The code is put near the data it needs to operate on. When a message comes in the code is executed. No discrete servers. No downtime. Just simple logic running at scale. We like to say: Functions just works.

But enough about the architecture. Let’s write some code!

Building your first PubNub BLOCK

Getting started is easy. Just log into your free PubNub account.

Create a PubNub Account
Create a new app with a pre-generated keyset.

Create New App

Now enable Functions from the ‘application add-ons’ section.

Enable Functions Addon

Next go to the Functions screen and create a new block.

Create New Block

Now add an event handler to filter the rad words. The type is set to Before Publish or Fire so that we can filter messages before they are sent to other users.

Create Event Handler
The code in your block is simple promise-based JavaScript. Below is a simple chat filter that looks for bad words, except we will look for ‘rad’ words. We don’t want the next generation becoming too radical. ‘Excellent’ and ‘Cool’ are fine, and even an occasional ‘awesome’, but ‘gnarly’, ‘tubluar’, and ‘grody’ are right out.

export default (request) => { 
    var radwords = ['gnarly','tubular','grody'];
    // fill in the final code
    for(var i=0; i < radwords.length; i++) {
	  var rad = radwords[i];
        if (request.message.text.indexOf(rad) >= 0) {
        	request.message.rejected = true;
        	request.message.reason = 'the word ' + rad + ' is too radical!';
        	console.log("request rejected", request.message);
	        return request.ok();
        }
    }	
    return request.ok(); // Return a promise when you're done 
}

In a real application you’d want to delegate this filter to a 3rd party text analysis API, but for detecting radical speech this will work well enough. Now save and start your BLOCK by pressing the Save button and the ‘Start Block’ button.
Test the block out by putting in this test message, then watch the console.

{
	"text": "Functions are tubular"
}

Console output from a running block
It’s really that easy. You’ve built a filter that can scale to millions of annoying middle school students.

Of course this won’t be very useful without an actual chat app to go with it. PubNub makes that easy too.

Build an HTML Chat App

PubNub’s JavaScript API is very easy to use and can be embedded in any webpage (or NodeJS app, or ReactNative app, etc). Let’s start with a simple webpage:

<html>
<head>
  <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.0.8.js"></script>
  <style type="text/css">
      .hbox {
        display: -webkit-flex;
        display: flex;
        -webkit-flex-direction: row;
        flex-direction: row;
        width: 50em;
      }
      .grow {  flex: 1;   }
      textarea { height: 30em; }
      #error { color: red; }
  </style>
<body>
  <div class='hbox'>
    <textarea id='history' class='grow'></textarea>
  </div>
  <div class='hbox'>
    <input id='message' class='grow'/>
    <button id='send'>Send</button>
  </div>
  <div class='hbox' id='error'></div>
<script type="text/javascript">
</script>
</body>
</html>

The HTML above creates a simple chat view. There is a textarea for the history above an input field and a send button. I’m using Flexbox (now supported everywhere) to create hbox and grow classes which let us nicely align the different views. Notice the hbox with an id of error doesn’t have any content. We’ll dynamically add this in a moment.

It looks like this:

Simple HTML Chat Interface
Now we can connect to the network. From your PubNub admin dashboard, get your subscribe and publish keys. It is these keys which let your code access PubNub’s network.
In the script element, add this code:

// a simple JQuery like function to get elements by ID
function $(id) {
  return document.getElementById(id);
}
//configure pubnub
var pubnub = new PubNub({
    subscribeKey:"sub-c-f065409c-76af-11e6-86e5-02ee2ddab7fe",
    publishKey:"pub-c-2da9d382-65a4-4592-9804-aa2f2b28e12d"
});

Calling new PubNub() sets up a connection to the data stream network with your access keys. The JavaScript API has many parameters you can tweak, but the defaults generally suffice. Next add a listener

//when message comes in
pubnub.addListener({
  message: function(msg) {
    //if rejected, show error
    if(msg.message.rejected) {
      $('error').innerHTML = msg.message.reason;
      return;
    }
    //append to history
    $('history').value += msg.message.text + '
'; } });

Every time a message comes in this listener will be called. It will check if the message was rejected. If it was then it sents the error message and returns. If the message was not rejected it appends it to the history.

//listen to the 'radchat' channel
pubnub.subscribe({channels:['radchat']});

This code subscribes to the channel called radchat. This is the same channel our block is using. You can have as many channels as you want, but a block can only operate on one channel at a time.

//which click the send button
$('send').addEventListener('click',function() {
    //send the message
    pubnub.publish({
      channel:'radchat',
      message:{text: $('message').value}
    });
    $('error').innerHTML = '';
});

This sends a new message to the radchat channel every time the user presses the send button. Now check that the block is still running and try sending a message. That’s it! You’ve just made a massively scalable chat app with rad word filtering.

The chat app running with live filtering

How it works

When you write a block it is running in live in the Data Stream Network. You can send messages to the block through the dashboard or from your own client side code. When you press the start button the code for your block is instantly sent to every part of the PubNub network around the globe. Now any messages sent to your channel will be handled by an instance of your block running in the data center nearest to the message.

When you update the code for your block, the update is applied to the entire network at once. There is no down time between the old block and the new one. No messages will ever be dropped. Every message will be handled by your code.

For maximum safety and reliability, Functions uses multiple levels of security. Each block is run inside of a NodeJS virtual machine with access to a very restricted set of APIs. Block code can never access the filesystem or host operating system. Next the entire Node instance is run using cgroups to isolate each process from the others and the host operating system. The use of cgroups ensures a badly written or malicious block can’t consume all resources on the physical machine or access data from another developer’s block. It also provides extra hooks for monitoring and management.

Functions is built on top of the existing secure Data Stream Network so all communication with the outside world is secure and sensitive payloads can be further encrypted on the end device for extra end-to-end security. The Access Manager APIs provide additional access control at the channel level, to further protect sensitive data streams.

For storage, Functions provides a built in key value store called KV Store. You can store and retrieve chat messages or any other JSON-like object in the datastore using the Promise based API. The datastore provides an eventually consistent data model so computation can continue without waiting for locks or synchronization.

One of Functions overarching goals is to maintain low-latency response to real-time messages. We do this by moving computation to where the data is instead of the other way around. The code for your block is sent to multiple edge nodes around the globe simultaneously then messages are routed to the closest edge node.

Additionally, alll APIs are designed to minimize latency through the use of promises and lack of data locking. If your code needs to invoke an external webservice that might take a while to respond, the event handler can both wait for the webservice callback and also send a message to the end user immediately. The immediate message gives the user the information they need right now. When the callback completes your event handler can send an additional followup message with additional information.

Scaling with Microservices

Imagine you built a real-time voting app. 1000 students on a campus can vote on their favorite topic instantly, with a real-time vote tally. You could build this with a quick and dirty NodeJS server to calculate the totals and handle the messaging with websockets. It’s easy. Now imagine you want to scale it to next season’s audience for Dancing with the Stars. Ten million viewers at once. Oops. Not so easy.

Scaling computation is hard. Really hard. And annoying. The problem you want to solve is making a good looking voting app, not distributed synchronization and computation. Functions makes the problem simple again. It removes the single point of failure of cloud computing. No patching and rebooting the operating system. No syncing. Get back to writing useful code, not wasting time on server headaches. Every second you have to spend on infrastructure is a second you can’t spend on making your application better.

So go get started now. What will you build?