Heartbeat - Ajax Patterns

Heartbeat

From Ajax Patterns

Evidence: 0/3

Tags: ACK Announcement Flash Heartbeat Monitor Signal


Contents

In A Blink

List of current players in a MUD game (back-end). With "last heartbeat" field.


Goal Story

For auditing purposes, Frank must be running the factory monitoring system at least 50% of the time, so the server records the time he's running it. But the client is fat, with most activity only occurring inside the browser and not following through to the server. Thus, the browser explicitly uploads a heartbeat message every 10 minutes, to tell the server Frank's logged in and actively working with the system.


Problem

How do you know the user still has an application in the browser, and is actively working with it?


Forces

  • It's often useful for the server to track if the user is active, with several applications highlighted in the Solution of Timeout.
  • Due to the state-less nature of HTTP, the server doesn't know when the user has quit the browser, experienced a browser crash, or surfed away to a different URL. Note that you could use the Javascript onunload event to catch the last of these, but it won't work for the first two. In tracking the user's activity, the server may be assuming the user's still active when they've in fact abandoned the application.
  • The user sometimes spends a long time working in the browser, but in a manner which yields no calls to the server. For example, filling out a long form or playing a game. The server will have no way to know the user's still around if no calls are made.


Solution

Have the browser periodically upload heartbeat messages to indicate the application is still loaded in the browser and the user is still active. The server keeps track of each user's "last heartbeat". If the heartbeat interval is 10 minutes, and the user's last heartbeat was longer than 10 minutes ago, the server knows the user is no longer active. The objective is to help the server track which users are active; note that

TODO graph against time

Heartbeat messages are uploaded to a special "heartbeat service" using XMLHttpRequest Calls. As they affect server state, they should be POSTed in. The heartbeat service will update the user's last heartbeat record, but how does it associate a message with a user? Heartbeat relies on some form of session management. One approach is to use cookies, either directly or via a cookie-based session framework. However, if you do that, the conversation will be stateful, thus violating a fundamental RESTFul Service principle. A cleaner approach is to explicitly include the session ID in the heartbeat body. Note that you shouldn't just upload the user ID, as others could easily fake the user ID.

Heartbeat is closely related Timeout, but they work in different ways. You can use either independently, but Heartbeat works best as a supplement to Timeout. I'll tell you why. The main purpose of Timeout is to stop the browser application upon timeout for security and bandwidth reduction. Notifying the server of timeouts yields extra benefits, but it's optional. Moreover, it won't work especially well anyway, because it relies on the application still sitting in the browser and working fine. That's why we use Heartbeat messages, which are the inverse of Timeouts, and therefore a good supplement. Where Timeout has the browser announce when a timeout has occurred, Heartbeat has it continuously announce that a timeout hasn't occurred. As soon as the server detects a missed heartbeat, it can assume the application is no longer running.

As a variant of Heartbeat, you can maintain a "last seen" or "last request" field, to track the last time the user issues a request. Thus, it's not just heartbeat messages, but any other messages that will refresh the user's record. The heartbeat is just a backup Doing so might paint a more meaningful picture if you're showing the timestamp to other users or feeding it into analysis.

This pattern is purely speculative in the context of Ajax applications. However, Heartbeats are commonplace in enterprise messaging systems, where they are used to monitor the status of components throughout a network.


Decisions

How will you maintain user records?

There are two main options for maintaining user records:

  • In memory, which means the data is not persisted and will be lost once the user is timed out. In most environments, this is what will happen if you rely on standard session objects.
  • Directly in the database, against persistent user records. There's some extra storage and data maintenance involved, but the benefit is that you always know when the user was last seen.

What, if anything, will cause the browser application to stop sending heartbeats?

One option is for the browser application to always send heartbeats. This lets the server track if the application is still sitting in the browser, which may be useful for analysis purposes. More likely, though, you probably want to send heartbeats only if the user is actively working with the browser. Thus, as mentioned in the solution, the heartbeat is effectively a message that says "the user has not yet timed out".

How much time between heartbeats? How much delay until a user is declared inactive?

You need to decide on the period between heartbeats. If it's too long, the information will not be very useful - imagine discovering that A.User is assumed active because his last heartbeat was 12 hours ago. If it's too short, you'll be placing strain on the network, as well as impacting on browser and server performance.

The right figure could vary from sub-second up to 30 minutes or more. It depends on the following factors:

  • Application: Some applications make up-to-the-second information more critical. In a multi-user system, for example, users' work may be dictated by whoever else is present. If you wait 10 minutes to tell Alice that Bob has quit the chess game, she might be annoyed that she's wasted the last 10 minutes thinking about her next move.
  • Available Resources: Ideally, the period should be as short as possible, but you can't always justify it. For an Intranet application, you're likely to use a shorter period in recognition of better resources per user.


Real-World Examples

As this pattern is speculative, there are no real-world examples at this time.


Refactoring Illustration

The Timeout pattern refactored the wiki to produce the Timeout Wiki Demo, and two further refactorings from there. This illustration creates a third refactoring of the basic Timeout demo, introducing a Heartbeat. Note that Heartbeat can work independently of Timeout, but works better in tandem, as this pattern demonstrates.

To illustrate Heartbeat, one of its main applications is shown: to let users see who else is "currently online", i.e. who else has a recent heartbeat registered. Thus, each user is assigned a random ID and a Heartbeat mechanism is used to maintain the "Currently Online" list.

But first, let's look at the heartbeat mechanism. Server-side, the Author class contains a lastRequest property. (lastAction is not used in this demo.)

 class Author {
   private $id;
   private $lastAction;
   private $lastRequest;
   ...
 }

session.php is the heartbeat service - it accepts a message with an author ID and updates the user's lastRequest. Note that a production system should accept a session ID, rather than a user ID. The service extracts the author from the database and updates its lastRequest to the present. If the author does not yet exist, a new record is created with lastRequest set to the present. Finally, the record is persisted.

 if (isset($_POST['renew']) && $_POST['renew']=="true") {
   $authorId = $_POST['author'];
   $now = time();
   $author = $authorDAO->fetch($authorId);
   logInfo("Got author record for $authorId");
   if ($author) {
     logInfo("Updating timestamp for user $authorId");
     $author->setLastRequest($now);
   } else {
     logInfo("Adding author $authorId");
     $author = new Author($authorId, $now, $now);
   }
   $authorDAO->persist($author);
 }

To access the heartbeat service, a periodic loop is set up. Because the heartbeat call is a "fire-and-forget", an empty callback function is used.

 window.onload = function() {
   ...
   heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_PERIOD);
   ...
 }
 function sendHeartbeat() {
   vars = {
     renew: true,
     author: authorId
   };
   ajaxCaller.postForPlainText("session.php", vars, function() {});
 }

And that's the basic heartbeat pattern. But there's a bit more here, because we're integrating it into Timeout. We want to stop sending heartbeats when a timeout has occurred in the browser. Above, we created a variable, heartbeatTimer, to track the periodic heartbeat calls. Since we have a handle on the periodic process, we are able to cancel it upon timeout.

 function onTimeout() {
   ...
   clearInterval(heartbeatTimer);
   ...
 }

The other feature here is the list of online users, which illustrates one way to use the heartbeat data. session.php exposes an XML list of currently online users. The fetchRecentlyActive method accepts a parameter indicating just how recently active the records should be. In this case, we ask for users with a request in the past 10 seconds.

 ... if ($_GET="current") {
       header("Content-type: text/xml");
       echo "<authors>";
       foreach ($authorDAO->fetchRecentlyActive(10) as $author) {
         echo "<author>{$author->getId()}</author>";
       }
       echo "</authors>";

In the browser, there's a div to contain the list.

    <h1>Who's Online?</h1>
    <div id="currentlyOnline"></div>


A timer is established to perform a Periodic Refresh on the current data. Every few seconds, the XML is requested and the callback function transforms it into HTML for display.

  currentlyOnlineTimer = setInterval(updateCurrentlyOnline, CURRENTLY_ONLINE_PERIOD);
 
  function updateCurrentlyOnline() {
    ajaxCaller.getXML("session.php?current", function(xml) {
      $("currentlyOnline").innerHTML = "<ul>";
      var authors = xml.getElementsByTagName("author");
      for (var i=0; i<authors.length; i++) {
        var authorName = authors[i].firstChild.nodeValue;
        $("currentlyOnline").innerHTML += "<li> " + authorName + "</li>";
      }
      $("currentlyOnline").innerHTML += "</ul>";
    });
  }

As with the heartbeat timer, the "currently online" timer is cancelled on timeout, so no more Periodic Refreshes will occur.

 function onTimeout() {
   ...
   clearInterval(currentlyOnlineTimer);
   ...
 }


Alternatives


Related Patterns

Timeout

Timeout is a companion pattern, as discussed in the Solution above.

Submission Throttling

Heartbeat resembles Submission Throttling and Periodic Refresh insofar as all have a continuous XMLHttpRequest Call cycle. However, Heartbeat has a more specific aim, namely informing the server of browser state.


Visual Metaphor

Heartbeat, of course, is based on a biological metaphor.


Want to Know More?

Brief summary of Enterprise Java "Heartbeat" pattern. See Mark Grand's "Java Enterprise Design Patterns" for the full pattern.