Properties of the Google Signed Exchange Cache

I work on the team that maintains both the Google AMP Cache and Google's recently launched cache for non-AMP signed exchanges (SXGs). I'm proud of the work we've done to enable advances in speed for mobile web publishers large and small. I've also done a lot of reading about what people dislike of AMP and, in particular, AMP caches, and I've taken it to heart. Regardless of whether I share a concern, or whether it is of a current aspect of the system or a hypothetical future aspect, I try to understand its axiomatic core and figure out what we can address.

As people discover more about the Google non-AMP SXG cache, I think it's only reasonable that they will begin to question whether their same concerns about AMP apply to non-AMP signed exchanges. I've thought a lot about it, and I think the new technology enables a deployment that guards against many of those concerns. I've tried to look at it through the lens of what properties people look for in HTTP caches.

In the following sections, I'll present my personal analysis of how the Google SXG cache deployment fares on each of these properties, and then synthesize that into a conclusion. I'm always trying to learn more, so I welcome any further discussion that adds more nuance to my own thinking.

Introduction πŸ”—

First, a quick refresher on the SXG specification. Quoting the article:

Signed Exchanges (SXGs) allow a site to cryptographically sign a request/response pair (an "HTTP exchange") in a way that makes it possible for the browser to verify the origin and integrity of the content independently of how the content was distributed. As a result, the browser can display the URL of the origin site in the address bar, rather than the URL of the server that delivered the content. Separating content attribution from content distribution advances a variety of use cases such as privacy-preserving prefetching, offline internet experiences, and serving content from third-party caches.

A short example:

$ curl -sH 'Accept: application/signed-exchange;v=b3' | cat -v
sxg1-b3^@^@3^@^Ai^@^@M-^Tlabel;cert-sha256=*Yovy1s8e5iMxifBErDnXPe/+bCwXqmhkaWT0fDbBxfo=*;cert-url="";date=1613088002;expires=1613174402;integrity="digest/mi-sha256-03";sig=*MEQCIFo7/B07Zu7bQv6knuU8PVrVhXNlXcJvzgCvVpvHT0v/AiA0tWchEDBP/UbId74wVA9vnTZPRoesH0sxlTSCoCvABA==*;validity-url=""M-$FdigestX9mi-sha256-03=LB7wdlHO51Z1Ze600lbYAEYsckWAJpdg8OW6P04MlDk=G:statusC200Lcontent-typeX^Xtext/html; charset=utf-8Pcontent-encodingLmi-sha256-03^@^@^@^@^@^@^P^@<!DOCTYPE html>...

When loading an SXG, the browser verifies that it hasn't expired, that the signature matches the rest of the SXG and its certificate, and that the certificate is valid. If verification fails, the browser fetches the resource from the original URL that was signed over.

To me, one important thing about this format is that it enables any distributor to serve it. In an ecosystem populated with signed exchanges, the speed and network resiliency benefits are potentially available to all referrers. But from here on, I'll limit my discussion to the Google SXG cache in particular, unless otherwise noted.

In the case of Google Search, when search results include an SXG result, that SXG is eligible for prefetching. Most search results cannot be prefetched because doing so would reveal the searcher's interest in a topic to a site before they navigate to that site; this is contrary to user expectations around sensitive information. However, using SXG, Google Search can prefetch its cached copy of the result. The request that Google issues in order to populate its cache is credentialless, from a Google egress IP, time-shifted, and possibly coalesced with neighboring requests. This SXG is then stored temporarily in the browser's prefetch cache, for use if the user subsequently clicks on the result link.

This technology enables two changes on Google Search:

Latency πŸ”—

Improving page speed is the primary reason to opt into the Google SXG cache. I think it's here that we provide the opportunity for a big net benefit to web publishers ("publishers" for short) and visitors, offsetting any of the potential costs outlined below.

Through the narrow lens of network latency, the cache does fairly well. However, through the broader lens of visitor-perceived latency (for example as measured by FCP and LCP), the cache greatly improves page speed on average, by enabling prefetch from Google Search.

If the only signed resource is the main HTML, this saves a network round trip (around 100ms) in discovering the first tier of the waterfall. However, there is the potential to save much more, by signing (and thus enabling prefetch for) all resources on the critical path to LCP. This may require some eng work to:

An early tester of this integration saw 300ms improvement in LCP at the 75th percentile on Android Chromium browsers, and expects that significantly more improvement is possible with further optimization.

In cases where the page is prefetched, this provides publishers a tool to enable near-instantaneous loading, by eliminating network latency as a factor in page speed. However, not all situations are the same. Publishers should measure the impact of the cache on their metrics to verify that prefetching brings their visitors a net benefit.

Cost πŸ”—

Cost has several components, including:

As an output format, signed exchanges require minimal divergence from the technologies that web developers are familiar with. They allow signing of almost any arbitrary resource (including HTML, JS, CSS, images, fonts) along with its URL and response headers. While there are some limits imposed by the spec and the Google SXG cache, it is possible to meet most of them via an automated transformation on top of an existing build pipeline. The end result should be that publishers have minimal ongoing cost in build pipeline maintenance and internal training, and minimal opportunity cost in terms of talent acquisition.

Pages opting into the cache will need to be fetched from the cache's egress points. These requests have an associated cost, as the origin needs to spend machine resources to serve it. The Google SXG cache is designed not to significantly affect the amount of traffic that sites need to serve.

Freshness πŸ”—

There are various strategies to caching content that is expected to change over time:

The Google SXG cache aims to conform to the cache-control specifications as closely as possible. However, the publisher needn't trust SXG caches' behavior. Signed exchanges provide an additional expiration mechanism that is enforced by the browser.

The cache additionally provides a heuristic asynchronous update mechanism for entries that have not yet expired. This provides an additional guard against cache-control misconfiguration or unexpected changes in circumstances that require more freshness for a particular resource.

Implementing a cache purge API is on our roadmap and we are eager to complete it.

The cache, in accepting arbitrary signed exchanges, allows use of JS to revalidate the freshness of the page, as a supplement to the above techniques. It is not a perfect solution; in the event the page needs a post-load update, this could result in a flash of stale content or a hit to page speed.

Personalization πŸ”—

On a per-resource basis, it is infeasible to cache a resource if it varies for each visitor.

However, at the page-level, a personalized experience can be delivered via cache through a few different mechanisms. For instance:

The Google SXG cache, in allowing arbitrary signed exchanges, enables both of these strategies.

Content negotiation πŸ”—

Proactive content negotiation requires knowing both what variants a user agent accepts and what variants a server provides for a given resource. The HTTP specification only provides for the former, leaving the latter up to the server to determine. Traditionally, this has meant that origin servers would perform proactive content negotiation. In order for an intermediary to cache these resources effectively, two problems need to be overcome:

However, there are various avenues to constrain the problem in order to make the solution cache-friendly.

We are working on implementing a supported-media spec to enable the common use case of varying by the user agent's form factor (e.g. mobile / desktop) without the need for responsive design.

Additionally, the Variants spec enables cache-side variance on a few different headers; if there is significant interest, it could be worthwhile for the Google SXG cache to add Variants support for Accept, Accept-Encoding, and Accept-Language.

Integrity πŸ”—

Both publishers and visitors want a guarantee that what visitors see is what the publisher intends. HTTPS provides some guarantees, but they are not without limitations, e.g.:

Any additional link in the chain from the author to the reader introduces some risk. Signed exchanges set a browser-enforced limit to the amount of risk introduced by cross-origin caching: the Google SXG cache is not able to serve a modification of a signed exchange and have a browser display it.

The remaining risk is that the cache may serve a previously-fetched version of the resource, as long as it is within the expiration window. I think that the options in Freshness provide sufficient mitigation for most use cases, but I am open to hearing of others.

Privacy πŸ”—

Visitors want their visits not to be tracked by unauthorized intermediaries, and publishers want their valuable business data not to be exfiltrated. While many pages involve lots of third parties, I believe that the Google SXG cache's usage is of very low risk to these properties.

The cache will be aware of the initial visit, as cache request URLs can easily be decoded to corresponding publisher URLs. We do not set any cookies on the cache domain.

When a browser requests a cache entry, the request is to the cache origin, and thus contains no credentials for the publisher origin. The cache may make a downstream request to the publisher for the corresponding signed content; this request is credentialless, IP-shifted, time-shifted, and possibly coalesced. Because the cache is the HTTPS client, it can read the contents of the signed exchange; SXG offers no additional encryption. However, I don't believe this decreases privacy, for the following reasons:

The Google SXG cache doesn't mandate the inclusion of any JS that could track visitor behavior on the page, and similarly doesn't require that any outgoing links point to the cache.

SXG guarantees that any client-side state associated with the page's origin or domain, such as cookies, localStorage, and IndexedDB, is assigned to the page’s origin, and not to the cache's.

Availability πŸ”—

Through a narrow lens of network availability, the Google SXG cache is running on much of the same infrastructure that serves many Google sites. It is thus very available.

However, it's worth looking at availability through a broader lens of content availability, and how opting into a cache affects a publisher's ability to meet their own availability goals:

Here, the Google SXG cache does well, but there is room for improvement. We are working to lessen the impact of malformed SXGs, by narrowing their definition, and by allowing some fallback behaviors.

Metrics πŸ”—

Publishers want to measure the usage and behavior (e.g. LCP) of their pages. The Google SXG cache allows SXGs to include any JS; this can include a client-side pingback of any relevant information to their origin, such as Navigation Timing metrics or a session ID.

Publication intermediaries (e.g. CDNs and hosted CMS frontends) may want to measure this usage too, e.g. in order to provide their customers a reporting service as a value-add. They may want to do so while also guaranteeing not to modify the publisher-provided HTML. HTTP server log analysis does not work well for SXGs intended for referrer prefetch; because the requests mentioned in Cost are credentialless and eligible for coalescence, server log analysis is made less accurate and precise by the introduction of caching.

One partial solution to the needs of such intermediaries would be to make the asynchronous fetch non-coalesced (albeit still credentialless, since it happens during prefetch). Another would be to provide domain owners with an API to query their metrics. Both of these require publishers to depend on the cache to deliver this information quickly and accurately.

I think the best approach may be the use of an HTTP header to induce a secondary load only on navigation. This way, publishers can depend on browser behavior, which is more easily verifiable than cache behavior, and more malleable through standards and open source processes.

Web ecosystem health πŸ”—

One concern I've heard is that web developers might spend less effort on the performance of non-cached content, as their needs are met by cross-origin prefetching. I believe that the impact of the Google SXG cache would be negligible:

I think there is potential for positive impact to web ecosystem health to offset the above potential risk. For instance:

There may be other concerns around web ecosystem health; I'm interested in learning more.

Conclusion πŸ”—

Mirroring of content enables publishers to achieve dramatic improvements in aggregate page speed, by allowing network latency to be eliminated in cases where the referrer predicts that prefetching is net beneficial. But mirroring of arbitrary unsigned content is impractical; there are hurdles to overcome in meeting publishers' and visitors' standards of usability, functionality, privacy, and security.

Signed exchanges make this practical, by enabling the browser to protect pages against many potential risks associated with mirroring of their content. They provide a useful check against potential misbehavior by caches. With their deployment, we believe we can deliver a service that meets the modern needs that publishers and visitors have of caches, while also enabling a novel, but complementary, approach to improving page speed.

However, these browser-enforced protections are not iron-clad. As I've enumerated above, delivering on modern expectations of HTTP caches depends in part on the SXG spec, and in part on proper deployment of the technology. Compared to caching of non-signed content, SXG reduces the amount of trust that publishers and visitors need to place in caches to deliver on these expectations (whether on-path or off-path). It is not a zero-trust system. I hope we can earn your trust through the sum of our actions.

The AMP Project has been working hard to break apart AMP's individual components and allow each publisher to adopt only the pieces that work for them, and we are beginning to see that come to fruition. For instance:

Google Search has also been working towards the goal of enabling publishers to adopt any combination of technologies, though a combination of:

Though a bit of an oversimplification, I like to think of this collection of technologies as part of a continuum between maximal performance and maximal flexibility. Most engineering decisions involve trade-offs, and I would rather publishers choose where to land in the trade-off space.

I am excited to be a part of this progress. I think a world in which these technologies are available for anybody to use is better than a world without them.

In the meantime, it's possible that I missed some concerns, or that the above concerns weren't sufficiently addressed. Where that's the case, I am eager to figure out the details, because I want to figure out how to address concerns and then advocate for that.

I'm also always on the lookout for new technologies that address the same needs. There are several existing and upcoming specifications in this same space, and while no one technology perfectly meets the needs of all of its users and stakeholders, some of them land at another useful point in the trade-off space. I hope to see further exploration into the set of technologies that best balances simplicity and flexibility of the web platform in the long run.