Collecting Custom Data from your CDN

Getting visibility into the edge using server-timing headers

The use of a content delivery network (CDN) remains one of the most effective tools for improving your page performance. However, visibility into the inner workings or your CDN has not always been easy. Thankfully, most CDNs allow you to take advantage of server-timing headers as a way of understanding what's going on inside the black box. Here are some examples of what can be exposed by mainstream CDNs with relative ease.

Some of the more common use cases:

  • Cache status: Was the basepage served from the CDN (HIT) or did the request get routed to origin (MISS)
  • POP/Edge node: What datacenter was the request served from?

  • Edge time: How much elapsed time was spent on the CDN (edge)?
  • Origin time: How much elapsed time in ms was spent at the origin?
  • Round trip time (RTT): How long did it take the CDN to respond to the browser?

  • Request ID

Akamai

Akamai was the first CDN to expose server-timing headers out of the box for their customers. This data is available by enabling the mPulse behavior in property manager. With the behavior enabled, you will start seeing the following server-timing headers:

Server-Timing: cdn-cache; desc=<MISS|HIT>

Cache HIT/MISS from CDN. This would be best defined as a dimension.

Server-Timing: edge; dur=<# ms>

Time spent at the edge (CDN). Best defined as a timing metric.

Server-Timing: origin; dur=<# ms>

Time spent fetching from origin. Best defined as a timing metric.

Also, note that if you don't use the mPulse product you can still enable the headers without the snippet by modifying property settings. Another option would be using EdgeWorkers to add the headers to the request, similar to the example shown next.

Cloudflare

Using Cloudflare Workers, you can add values from existing headers such as CF-Cache-Status into server-timing headers. Other possible use cases are parsing the datacenter location from the CF-Ray header.

Here is an example:

/**
 * @param {Response} response
 * @returns {Response}
 */
function addServerTimingHeaders(response, startTime) {
  const serverTiming = [];

  const cfCache = response.headers.get('cf-cache-status');
  if (cfCache) {
    serverTiming.push(`cf_cache;desc=${cfCache}`);
  }

  serverTiming.push(`worker;dur=${Date.now() - startTime}`);

  response.headers.set('Server-Timing', serverTiming.join(', '));
}

Fastly

While you could likely use Compute@Edge to add server-timing headers via Fastly, using VCL is pretty straightforward as discussed in this post

TL;DR

To get the following:
Server-Timing: time-start-msec;dur=1544705663920,time-elapsed;dur=0,fastly-pop;desc=LCY,hit-state;desc=HIT

Use the following VCL:

set resp.http.Server-Timing = "time-start-msec;dur=" time.start.msec ",time-elapsed;dur=" time.elapsed.msec ",fastly-pop;desc=" server.datacenter ",hit-state;desc=" fastly_info.state;

There are a lot of VCL variables available that might be interesting candidates for custom data.

Cloudfront

You can add server-timing headers via opt-in via the AWS Console as mentioned in this post. Here is an example of server-timing headers provided with this opt-in taken from https://www.perfwork.com/

server-timing: cdn-upstream-layer;desc="EDGE",cdn-upstream-dns;dur=0,cdn-upstream-connect;dur=69,cdn-upstream-fbl;dur=562,cdn-cache-miss,cdn-pop;desc="DEN52-P3",cdn-rid;desc="5McHcGf1pCMEZKUtTuHH-UI7Co2qq-817CJu_cD7oVUo9BmxBtpIHQ==",cdn-downstream-fbl;dur=563

Shopify

Shopify provides the following server-timing headers for all Shopify sites. It's important to note that these are not considered public, so use at your own risk.

server-timing: processing;dur=15, db;dur=5, asn;desc="7922", edge;desc="DFW", country;desc="US", theme;desc="Prestige", pageType;desc="index", servedBy;desc="8jlx", requestID;desc="4ab33c3d-21e6-425a-9754-a6f42a27d36f"

server-timing: cfRequestDuration;dur=48.999786, earlyhints

As of the writing of this article, our understanding of each of the headers is as follows:

Timings:

cfRequestDuration = Duration from the time the request hits Cloudflare (CDN) until it is finished processing.

processing = Duration from the time the request reaches the Storefront until processing of the request is complete.

db = Duration of the request processing spent querying the database. (Subset of processing time)

Dimensions

asn = Autonomous System Number

edge = Location of CDN edge server

country = Country of CDN edge server

theme = Shopify theme used

pageType = page identifier

servedBy = ??

requestID = ??

earlyhints = Were early hints used for the request (if present, assume yes)