Performance & the FullStory Script

The Short Answer:

FullStory will not slow down your website.

The Technical Details:

The FullStory recording script (fs.js) is carefully constructed and measured to ensure that it has negligible impact on the performance of any page on which it is embedded. To understand the potential points of impact and the steps we have taken to ensure maximal responsiveness, we have to consider the following interactions: page load, ui thread, and upload bandwidth. Further, it is critically important to avoid any errors in the client page, so we must also consider dom and script interactions.

Page Load

First and foremost, we will not allow the script to delay loading of your page. To this end, we employ the following techniques:

  • Keep the script small
    fs.js is approximately 16k, gzipped. This is smaller than most scripts and all but the smallest images (for comparison, jQuery 2.0.0 is roughly 30k, minified and gzipped).
  • Reduce fetch time
    The script is edge-cached on Google’s CDN, and typically takes under 20ms to fetch. This is particularly important because browsers will only allow a fixed number of outstanding resources requests at any given point in time. The faster this request completes, the faster it frees up slots for other resources.
  • Get out of the way
    Javascript’s semantics require that an inline script of the form <script src='fs.js'> block evaluation of the page until the script is loaded, which could have a noticeable impact on page load time. We sidestep this risk by installing a very small script stub that loads the full fs.js script dynamically, guaranteeing that it’s not loaded until the page is fully evaluated, and requests for all static resources (such as CSS and images) have already begun. Note that this is the same strategy as that employed by Google Analytics and similar products. The following is a network-load graph of a typical session. Note that fs.js loads quickly, and not until all static resource requests are in-flight (it’s the last request, at the bottom of the window):

Bottom line: The FullStory script will not slow your page-load time, and its unavailability will not impact your page.

UI Thread Utilization

After a page is loaded, the browser’s most precious resource is CPU time spent in the main UI thread. All Javascript is executed on this thread, and if it blocks for more than 50-100ms, it can result in user-visible “hangs”. We have taken careful steps to optimize the FullStory script to avoid this.

Event Handlers

In order to record all interactions on the page, fs.js captures most common event types (e.g., mouse, keyboard, resize, dom mutations, and so forth) at the top level. To avoid interfering with the page, these event handlers must do the minimum possible work and return control to the browser’s event loop. To this end, they all simply add events to an in-memory queue to be processed later. To get a sense for what this means in practice, see the following Chrome timeline graph:

Here we see a series of mousemove events being handled by the recording script. The relevant value here is “Self Time” at the bottom, which is 0.037ms (37µs), which is at least three orders-of-magnitude below anything a human can perceive.

Outgoing Event-Queue Processing

When it’s time to upload a set of events to the server, the recording script has to process the outgoing event queue, which can of course be a bit more expensive than just enqueueing an event. For the vast majority of cases, however, this takes very little time. Here’s a typical case:

Here we see two timers being fired (which is part of the throttling process, described below), the latter of which results in a request being made to the server. All told, this process takes approximately 1ms, again far below the user-visible threshold.

Compression and Upload

There are cases that can take a bit longer to process. This typically occurs because of large DOM mutations that must be compressed before being uploaded (more on the compression process below). But it’s notable that in these cases the recording script does significantly less work than the script that produced the mutation in the first place -- e.g., an existing 100ms event becomes a 110ms event. These large mutations are almost always associated with significant user actions such as paging a table or list view, where a few extra milliseconds is unnoticeable.

Upload Bandwidth

While it tends not to obviously create a user-visible performance impact, it is important that we not push more data upstream than strictly necessary. The recording script records the entire DOM, which can become fairly large (though not larger than the size of the downloaded HTML). To mitigate this, the script compresses all HTML before it’s sent to the server using an algorithm that’s specifically tailored to run efficiently in Javascript. This typically gives a compression ratio of 40-60%.

Technical note: The reason we can’t rely on HTTP compression when uploading data to the server is that, because of historical problems with the design of HTTP 1.1, almost no browser will use gzip compression when uploading data.

The script also does not send externally-fetched CSS or images over the network, which often form the bulk of a page’s data. These are instead fetched later by the FullStory servers.

Server Load

Because FullStory servers fetch external CSS and images just after a page is recorded, it must load these resources directly from the application’s server. In order to minimize these extra requests, FullStory employs two levels of caching. The first is a standard HTTP client cache, which both respects standard cache headers and includes rate-limiting to ensure that it doesn’t fetch too frequently even if the cache headers are incorrect. The second tier shares fetched resources across all sessions for a given customer. Practically speaking, this means that your servers will see very few extra requests.

DOM and Script Interaction

The following identifiers are exposed to the global scope in fs.js:

FS, _fs_host, _fs_org, _fs_ready

The script is careful to ensure that none of its event listeners are attached in such a way that they are visible to other scripts on the page. It also uses _fsid as an expando on recorded DOM nodes for bookkeeping.

Future Work

We continue to monitor performance of the recording script, and to deploy optimizations to both the recording process and the encoded wire format.

Was this article helpful?

/

Can’t find what you’re looking for?

The FullStory team awaits your every question.

Contact Us