Periodic Refresh - Ajax Patterns

Periodic Refresh

From Ajax Patterns

Evidence: 3/3

Tags: Auto-Update Polling Sync Synchronise Sychronize Real-Time



Contents

In A Blink

ChatRoom client receives message from remote machine (spoken balloon transfers down the line).

Initial State

The server is volatile: information relevant to the user changes with time.


Goal Story

Frank is surveying factory activity through a graphical web application which shows a floor plan with the current power usage of every machine. The information's updated every five seconds. All of a sudden, one of the machine displays goes red, indicating overload, and Frank heads over to investigate.


Problem

How can the application keep users informed of changes occurring on the server?


Forces

  • The state of many web applications is inherently volatile. Changes can come from numerous sources, such as other users, external news and data, results of complex calculations, triggers based on the current time and date.
  • HTTP requests can only emerge from the client. When a state change occurs, there's no way for a server to open connections to interested clients.
  • One way to keep the browser updated is HTTP Streaming, but, as the Alternatives section for that pattern explains, it's not always ideal. In particular, it's not very scaleable.


Solution

The browser periodically issues an XMLHttpRequest Call to gain new information, e.g. one call every five seconds. The solution makes use of the browser's Event Scheduling capabilities to provide a means of keeping the user informed of latest changes.

In its simplest form, a loop can be established to run the refresh indefinitely, by continuously issuing XMLHttpRequest Calls.

 setInterval(callServer, REFRESH_PERIOD_MILLIS);

Here, the callServer function will invoke the server, having registered a callback function to get the new information. That callback function will be responsible for updating the DOM according to the server's latest report. Conventional web applications, even most of those using XMLHttpRequest Calls, operate under a paradigm of one-way communication: the client can initiate communication with the server, but not vice-versa. Periodic Refresh fakes a back-channel: it approximates a situation where the server pushes data to the client, so the server can effectively send messages down to the browser. Indeed, as some of the examples show, the server can also mediate between users in almost real-time. So, Periodic Refresh can be used for peer-to-peer communication too.

But before we get too carried away with Periodic Refresh, it's important to note that it's a serious compromise, for two key reasons:

  • The period between refreshes would ideally be zero, meaning instant updates. But due to latency effects, the browser will always lag behind. Latency is particularly problematic when the user is interacting with a representation of volatile server-side data. For instance, a user might be editing an object without knowing that another user has already deleted it.
  • There is a significant cost attached to periodic refreshing. Each request, no matter how tiny, demands resources at both ends, all the way down to operating system level. Traffic-wise, each request also entails some bandwidth cost, and that can add up if refreshes are occurring once every few seconds.

So, a key design aim must be to increase the average refresh period and reduce the content per refresh, while maintaining a happy user experience. One common optimisation is a timeout: the refreshes cease when the system detects the user is no longer active, according to a Heartbeat protocol. You also want to make sure each refresh counts: it's wasteful to demand lots of updates if the network isn't capable of delivering them. Thus, the browser script can do some monitoring and dynamically adjust the period so it's at least long enough to cope with all incoming updates. Many of the Performance Optimisation can be applied to Periodic Refresh - see the Decisions below.

This solution might not apply for you if you are looking for a minimum latency response. To reduce latency, you should

  • issue a new request immediately after a response has been received and
  • have the server wait and defer responses until new data has to be sent.

To this effect the request semantic has to be devised in order to only send updates for information already available on the client.

Decisions

What sort of responses will be used?

As with any design problem involving XMLHttpRequest Calls, you need to decide if responses will be plain-text or XML, application-specific or generic, user-specific or common, HTML-based or semantic. Compared to many other remoting problems, Periodic Refresh responses have a great impact on performance due to their frequency, and this is likely to push their design towards lower-cost responses. Where performance is critical, the following options are likely to be superior:

  • Plain-text responses, using a custom encoding format for complex data, can be used to reduce the overhead of XML tags. However, the performance gain is probably small, especially when the web server compresses data using the gzip protocol.
  • Application-specific responses make sense because they can be be tailored to provide exactly what the browser needs for each refresh, and no more. Especially useful for performance, they can provide only recent changes rather then the entire current state.
  • User-specific responses make even more sense for maximum performance: by tracking what each browser knows, the server can trim responses even further. This does represent a use of stateful conversation, with all the headaches that go with it: it's expensive for the server, more difficult to develop against, and relies on cookies. A good compromise is for each request to attach some information about its current state, so the server can determine what, if anything, the browser needs to know. Lace, described in the Code Examples, does exactly that.


How long will the refresh period be?

by Mike Lu and Angelo Alejo

The refresh period can differ widely, depending on usage context. Broadly speaking, refreshes are required for three types of activity:

  • Interaction: The user relies on the server state at time of input. For example: a chat user needs to see what others are saying, a trader needs to see current prices, a game player needs to see the state of the game. Here, the interval is likely to be 10ms-5s, with the lower end only feasible on Personal Web Servers or intranets.
  • Active monitoring: The user relies on the server state for work outside the system. For example: a security officer watches sensor displays for suspicious activity, a manager occasionally watches the fluctuation of sales figures during a particular window of time. In some cases, timeliness may be critical, making sub-second responses desirable. In other cases, a few seconds is sufficient feedback.
  • Casual monitoring: Some applications are designed for the user to leave in a window or separate browser tab and view throughout the day. The information does not change often, and it's no drama if the user finds out a little later. Prime candidates here are portals and RSS aggregators. Refresh periods of 10 minutes or more are often acceptable for such content. A manual "Refresh Now" mechanism is worth including where the refresh period is longer than a few seconds. It can relate to the entire application or to specific components.

Sometimes, it's feasible to have multiple Periodic Refresh cycles occurring in parallel, each with a frequency reflecting the user's needs. An interactive wiki, for example, might update news headlines every 10 minutes, online statuses of other users every minute, and content being edited every second.


Real-World Examples

White Chat and Lace Chat

Instant messaging (or "online chat") applications pre-date the web, and, unlike e-mail and other services, web interfaces have never quite worked out. There have certainly been some attempts. A slashdot commenter recently pointed out that Japanese chat was first conducted over the web, using a Periodic Refresh mechanism to check for new messages from other users. Apparently, it was easier to render Japanese characters in web browsers than on desktop clients. White Chat is an example, and has been online since 1998. In White Chat, the entire page is refreshed every few seconds.

Ajax makes it possible to avoid a complete refresh by pulling down messages with an XMLHttpRequest Call. Only the new messages need to be sent in each periodic response. In fact, there are several applications under development. Lace Chat, for example, is only a proof-of-concept, but provides good evidence that web-based chat is feasible. Every few seconds the messages update to show any new messages other users may have entered. When you post a message, it's also handled as an XMLHttpRequest Call.


JotSpot Live Wiki

Conventional wikis suffer from the problem of stale data: you can't tell if the page you're looking at has been edited by another user. That's particularly problematic on a wiki, where you might edit some data yourself, only to discover someone else has been working on the same text.

It's likely that many Ajaxian wikis will appear to address this concern, and the Jotspot wiki is the first I know of (I was fortunate enough to test a preview edition). The browser polls the server every TODO seconds, highlighting any changes that have occurred since the last refresh. In providing immediate feedback, the concepts of wiki and chat begin to morph.

Magnetic Poetry

Like a wiki, magnetic poetry involves a shared workspace. In this case, users move tiles through the space, and the application updates once a second to reflect the new space. As of version 1.7, the entire tile set is sent each time, but there's the potential to compress the information by only sending recent tile positions. This enables two users to work on the area simultaneously, and one can even see a tile being dragged by a different user, like a low-frequency animation.

Claude Hussnet's Portal

Portals, by definition, display various kinds of information. And often that information is of a dynamic nature, requiring periodic updates. Claude Hussenet's portal contains several portlets:

  • World news headlines (taken from Moreover and Yahoo! News). The server appears to generate these from an RSS feed.
  • New online articles appearing (at TheServerSide.com and http://DevX.com DevX.com. Again, these are taken from RSS feeds.
  • Customised stock portal. The server appears to maintain a record of all current stock prices, and is capable of

delivering those requested by the browser.

In each case, the information is volatile and needs to be periodically updated, as is typical for many portlet. Also characteristic of portal applications is the relatively long refresh period, 15 minutes in this case. Each portlet contains a manual refresh too, for immediate results.


Code Examples

Lace

(version 0.1.12) Lace handles Periodic Refreshes to show all users' chat messages.

The timer is set on startup to periodically call the get() function, which initiates the XMLHttpRequest Call for new messages.

 this.timerID = setInterval(function () { thisObj.get(true); }, interval);

 [lace.js]

get() performs a straightforward query of the server:

 this.httpGetObj.open('POST', this.url, true);
 this.httpGetObj.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
 var thisObj = this;
 this.httpGetObj.onreadystatechange = function () { thisObj.handleGet(system); };
 this.httpGetObj.send(param);

 [lace.js]

If the server has changed at all, the entire contents are returned. How does Lace know if the server has changed? Each time the server responds, it generates a hash for the contents. And when the browser next calls for a refresh, it includes the last hash as a parameter to the call. The server only sends a response if the current hash differs from that specified by the browser.

 function getMessageHash() {
   ...
   return md5(implode(file(LACE_FILE)));
 }

 [lib_lace.php]

 $_hash = getMessageHash(); if ($_hash == $hash) exit; // no change exit($_hash.'||||'.getFileContentsRaw());

 [lace.php]

You can also download the lace chat source. But be prepared! The code is just rarely commented.

Refactoring Illustration

The Basic Time Demo requires the user to manually update the time display by clicking a button. We'll perform a quick refactoring to re-fetch the time from the server every 5 seconds.

First, we'll create the function that initiatiates the server calls. Here, we already have two functions like that, one for each display. So, let's ensure we call both of those periodically:

 function requestBothTimes() { requestDefaultTime(); requestCustomTime(); }

Then, the Periodic Refresh simply a matter of running the request every five seconds:

 function onLoad() { ...  setInterval(requestBothTimes, 5000); ...  }

One more niceity: the function in setTimeout only begins to run after the initial delay period. So we're left with empty time displays for five seconds. To rectify that, requestBothTimes is also called on startup:

 function onLoad() { ...  requestBothTimes(); setInterval(requestBothTimes, 5000); ...  }

Now, the user sees the time almost as soon as the page is loaded.


Alternatives

HTTP Streaming

One of the forces for this pattern is that HTTP connections tend to be short-lived. That's a tendency, not a requirement. As HTTP Streaming explains, there are some circumstances where it's actually feasible to leave the connection open. Streaming allows for a sequence of messages to be downloaded into the browser without the need for explicit polling. (Low-level polling still happens at the operating system and network levels, but that doesn't have to harga iphone 6 be handled by the web developer, and there's no overhead of starting and stopping an HTTP connection for each refresh, nor of starting and stopping the web service.)


Related Patterns

Distributed Events

You can use Distributed Events to co-ordinate browser activity following a response.

Performance Optimisation Patterns

A little thoughtwork about performance issues can help make the system feel more responsive without significant downsides for the user. Some of the performance optmisation patterns help:

  • Browser-Side Interaction reduces the need for server processing by pushing functionality into the browser.
  • Browser-Side Cache reduces queries by retaining query data locally.
  • Guesstimate gives the user a sense of what's happening without actually performing any query.

Submission Throttling

Submission throttling also involves a periodic call to the server. The emphasis there is on uploading browser-side changes, whereas the present pattern harga laptop asus focuses on downloading server-side changes. The two may be combined to form a general-purpose "synchronise" operation, as long as the update frequency is sufficient in both directions. This would improve performance by reducing the amount of overall traffic. The Wiki Demo takes this approach.

Heartbeat

Friday, 5pm: An office worker surfs over to your website to check the weather forecast in Barcelona, then promptly heads over there for a two-week vacation, leaving the computer on and the browser pointing at your auto-refreshing weather portal. This ten-second interaction just cost your server several thousand refresh queries.

It's inevitable that users will leave their browser pointing at websites which they're not actually using. The rising popularity of tabbed browsing - now supported by all the major browsers - only exarcebates the problem. You don't want to keep refreshing the page for idle users, so use Heartbeat to detect if the user is still paying attention.

Visual Metaphor

A movie is refreshed at sub-second intervals to provide the illusion of real-time activity.

<script type="text/javascript">
var page = "online.php";
function ajax(url,target) {
  // native XMLHttpRequest object
  document.getElementById(target).innerHTML = 'Loading...';
  if (window.XMLHttpRequest) {
    req = new XMLHttpRequest();
    req.onreadystatechange = function() {ajaxDone(target);};
    req.open("GET", url, true);
    req.send(null);
    // IE/Windows ActiveX version
  } else if (window.ActiveXObject) {
    req = new ActiveXObject("Microsoft.XMLDOM");
    if (req) {
      req.onreadystatechange = function() {ajaxDone(target);};
      req.open("GET", url, true);
      req.send(null);
    }
  }
  setTimeout("ajax(page,'scriptoutput')", 5000);
}

function ajaxDone(target) {
  // only if req is "loaded"
  if (req.readyState == 4) {
    // only if "OK"
    if (req.status == 200 || req.status == 304) {
      results = req.responseText;
      document.getElementById(target).innerHTML = results;
    } else {
      document.getElementById(target).innerHTML="ajax error:\n" +
      req.statusText;
    }
  }
}
</script>