FullStory will not slow down your website.
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 rendering and interactivity, 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.
First and foremost, we will not allow the script to delay rendering or interactivity of your page. To this end, we employ the following techniques:
<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 rendering time, and its unavailability will not impact your page.
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.
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.
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.
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.
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.
The following identifiers are set on the global scope in fs.js:
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 an integer property
_fs as a marker on recorded DOM nodes for bookkeeping.
We continue to monitor performance of the recording script, and to deploy optimizations to both the recording process and the encoded wire format.
The FullStory team awaits your every question.Contact Us