The Short Answer:
FullStory will have no discernible effect on website performance.
The Technical Details:
The FullStory data capture 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.
Page Rendering and Interactivity
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:
Keep the script smallfs.js is ~60 KB, gzipped. This is smaller than most scripts and all but the smallest images.
Reduce fetch timeThe 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.
<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 asynchronously, guaranteeing that it’s requested after any static resources such as CSS and images. 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 have no discernible effect on your perceivable page rendering time, and its unavailability will not impact your page.
UI Thread Utilization
In order to capture 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 data capture 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 data capture 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), 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 data capture 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 captured, 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 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 captured DOM nodes for bookkeeping.
We continue to monitor performance of the data capture script, and to deploy optimizations to both the data capture process and the encoded wire format.