Predictive Pre-fetch
(upbeat music) - Hi, my name is Divya Tagtachian.
I am a Developer Experience Engineer at Netlify, where my goal is to make developers work with Netlify, and deploy and host websites easily and efficiently. You can find me online and at most social media platforms @shortdiv, if that's something that you care about.
I currently live in the US where French fries is the favourite food in times of difficulty.
And when you order French fries in the US, generally speaking, you get two condiments, ketchup and mayo.
Ketchup usually is the most common one, mayo follows closely after.
And if you fall into the majority of Americans who really enjoy eating your fries with ketchup, it is a really delightful experience because you don't have to ask for ketchup.
And even though we're generally eating take out these days, you don't have to reach into your fridge.
You have a pre-packaged ketchup, a condiment available to you right in your bag of fries, which is really nice.
And ideally, we want a very similar experience When we navigate across the web.
We wanna make sure that users are able to instantaneously get access to resources, navigate websites without having to always ask. And this concept is something I like to call prefetching. It's this idea of a website, essentially getting resources ahead of time without a user having to request it.
And we'll go into detail as to how we can go about doing this, but in order to do that, I'd like to first cover some basics.
And we wanna to cover, in terms of the basics, I think I wanna to cover the most basic example of how a website renders onto a page.
So when you type a web address into the address bar of a browser, generally speaking, the browser doesn't actually know where that website lives.
And so it makes a DNS request to a server or CDN node. And that particular server will then reply in kind with an IP address.
And with this IP address, then the server is able to make this TCP-TLS dance, in which you can establish a connection for future requests. That's of course, one level in which it takes a bit of time, there can be about a couple of milliseconds. So a couple thousand milliseconds, depending on how far you are physically from your server. But that's one level of latency that comes into rendering a page.
The next level comes in terms of fetching the resources and rendering them to the page.
So, when the initial request happens, that's generally to an Index.html page, which is where all the content lives.
The server will respond with that particular page, which is great.
Except for the fact that that's not the only page or not the only resource that a website will need to render. It obviously needs CSS and JavaScript, we all know this. And so the browser will then have to make those requests separately.
And of course, those tend to happen as blocking requests. So it has to happen one after another.
And so when the browser does that, that adds extra time of course, to the actual page rendering that the user will eventually see.
And so all of this adds latency when it comes to how a page is rendered.
But this is generally the status quo as you may know optimizations.
And in this particular talk, we're not gonna talking about optimizations that browsers make, because browsers do have various techniques in which they make things a little faster so developers don't have to do certain things. But we will be talking about that in this talk. We're gonna be talking about specific techniques that developers use in order to make their websites faster. And specifically I'm gonna be focusing on the umbrella of prefetching and prerendering.
Specifically three concepts, DNS prefetch, link prefetch, and prerendering. DNS prefetch is the first level of prefetch. It's the most simple of all three.
The idea is that it establishes that connection and make that DNS resolution and establishes that connection for future requests to be made. So nothing actually happens.
There's no resources that get exchanged.
But it is a level that makes it easier for future requests that happen.
The next level is link prefetching, and link prefetching goes one step further where upon DNS resolution, it fetches priority resources based on what a developer has decided.
So if that is a JavaScript page, it fetches the JavaScript, and so on.
The next level of that following is prerendering, which is essentially, it's some sort of speak the extreme version of prefetching, which is this idea of establishing connection and fetching every single resource that a user will need. And the idea is that it's fetching the entire page. And not only that it's rendering it as well in a different virtual layer.
And so when a user actually navigates to that page, that has been prerendered, the navigation is instantaneous.
They don't notice any lag or latency at all. So for my user experience, this is fantastic. But of course as we evaluate these three throughout the course of this talk, I'd like to point us to this particular table, which covers the various concepts of prefetching. This idea that DNS prefetch, even though the cost is very low, the benefit is also really low.
Because from a user perspective, the differences is inperceptible, Generally speaking, they don't know when that resolution happens.
So they don't notice that unless the connection that they're on is incredibly slow.
On the extreme when you talk about prerendering, the user experience is super high because that navigation is instantaneous.
However, the cost is also very high because it takes up quite a lot of bandwidth in order to make those connections, fetch those resources and then render it in the background. And so I think it's useful for us to think about that as we navigate through this particular talk. A common scenario in which developers do use prefetch is in the case of search engines.
And I'm not talking about Google because they use AMP. But if you look at generic search engines like DuckDuckGo, what they do is they prefetch the first few links in the top of a search page, because the idea is that users tend to click on those pages over the ones on the bottom.
And so you can make the assumption that likely the first two links for example, is what people will click on, and so you might want to prefetch those resources. Another common scenario in which prefetch can be used is in the case of a login flow.
So with a login flow, we know that a user is going to go from a Login page to an Dashboard page, perhaps.
And so with that assumption, we can then assume that we might want to fetch resources that are shared across those two pages, whether that be a JavaScript file or style sheets or so on. But of course, all of these techniques assume that we as developers know what a user is going to do. And while in certain scenarios, there awesome logical conclusions that can be drawn. Generally speaking, we are still speculating. We are assuming that users are going to go from one page to another, and this can't be used. We can't make these assumptions all the time because we don't always know what users are going to do. And oftentimes users behave in a way that we don't fully understand or that we can't anticipate.
And so a better approach is to move to what something that's slightly more predictive, where based on general user behaviour, we are changing the behaviour in which a website prefectures links, for example. And in order to do this, I would like to illustrate the point of what predictions are and how they work.
A common use case or example for predictions is in the case of weather data, it's a bit contrived, but the example here is that generally speaking, if you want to guess what kind of day it is tomorrow, you'll look at past days and predict based on what the patterns were, what the day will be like tomorrow.
So let's assume we have a cloudy day and we want to predict what the day is going to be like following a cloudy day.
So we can look at a week's worth of data.
And we can see that following a cloudy day, there have been four instances of a cloudy day and one instance of a rainy day.
So, you know, 80% of the time, a cloudy day is gonna follow a cloudy day.
And 20% of the time, a rainy day is gonna a cloudy day.
And we can do a similar analysis with regards to figuring out what day is gonna follow a rainy day.
And in our limited dataset, we know that a cloudy day follows a rainy day. So we know that it's a bit of 100%.
So, here we're talking about basic probability, just figuring out based on data that we have, what patterns emerge.
And so we can do that with respect to webpages. So we know where user is coming from and where user is going.
So assuming we know this information, just a one level deep information, we already can draw conclusions.
So for instance, if I look at the About page and where users navigate to after the About page, I see that 50% of the time users navigated to a Menu page and 50% of the time uses navigated to a Contact page. And so it's hard to tell where exactly a users kind of go in this particular case, because it's about a 50 50 split, but this gives us a sense of what is possible if we take a prediction and probability context and apply it in the context of web navigation as well, which is really, really useful.
Mmm, one way in which we can grab this particular data is through Google Analytics, and Google Analytics does give you access to this data. If you look at the dashboard, this is fairly old, I believe they updated it recently.
You see this sense of where exactly a user came from and where user went to after that page.
So it gives you a sense of previous page and next page. So our previous patient current page, if you'd like to think about that.
I personally find it useful to look at raw data because it's clearer to me, rather than using a dashboard in which people have assumed what you think you want to know.
And so pulling directly from the Google Analytics API, I can get specific data and it gives me data in this format. You have this concept of dimensions.
Dimensions can be thought of as the first index is the page that you came from and the next is the current page or the next page you navigated to from the previous page. And then the values below that you see is essentially just how many page views each of them had.
So where exactly a user came from and so on. I'm gonna aggregate that data because again, a lot of this raw data is not super clear, so I wanna aggregate it so it's a bit clearer as to what is happening.
And so when I aggregate it, I can see that the way I'm doing it is I want to grab the page path.
So assuming the page path is route, I want to see where exactly is going.
So far users go from route to Posts, About and Contact. And I have a total number of next page views of six. So six uses navigated from root to page.
And so I can look at the next pages to get a sense of how many users moved from one page to another.
The other useful piece of data is exits, and exits might not seem as pertinent, but exits is also sort of a next page navigation. It's essentially people didn't go to a next page, they just left.
And I think that's a useful action as well, to take into consideration.
So I'm gonna take that into account as well. But page views and total takes exits and next page views. So it's essentially both of those numbers.
So if I look at posts and how many users went to posts, I know it's four out of six.
So four out of six or 2/3 of users went from the root page to the post page, which to me feels significant. And maybe I can use that data to my advantage, to prefetch the specific resources.
Whereas in the case of the About page, not so much because not many users go there and so on. One way in which I can take this concept of, okay, I now have data, where do I apply it? Is in the sense of how exactly it integrate it to my page.
So I do that through build, at build time, and I'm gonna do it through build automation. So what I'm doing is whenever I build my website, I'm making a call to Google Analytics, making these calculations, and then prefetching those links.
It's fairly hard-coded, but the idea is that it's much better than making those assumptions and hard coding them yourself. So with predictive build automation, what I will do is I'm gonna grab data from Google Analytics, and then I'm gonna pull that into 11ty, which is the framework I choose to use because it's fairly lightweight and it's really easy to use. So the first step to doing that is to authenticate. So this is standard in terms of how Google does authentication.
And then of course I want my query parameters and here is which I can decide the time period in which I want to pull the data.
I've chosen to do 30 days in this particular case. But if you want more fidelity or more data, you can go much further than that.
And so once I do that, Google authentication dance, I can then run an aggregator over it.
And again, I won't go into detail as to the aggregation portion, but if you are interested, I wrote a blog post about all the intricacies of how I did this on the PerfPlanet Calendar for the advent calendar last year, if you're interested, please check that out. Essentially what I come up with, when I run this particular function, go through Google Analytics and aggregate, is I get data that looks like this.
It's a giant JSON or an array of objects.
And each object tells me the page path, and the next page path.
So if I have an About, I know users went to the Contact page and then the next page certainty as well of how many users actually went there.
So how certain am I that users went from one page to another.
So that's really useful.
And in 11ty, there's a very specific way in which I can tie a lot of this data that I get into my templates.
And they do this through your HTML interpolation, and this is nunjucks.
So it's very specific to the framework, but the idea is that I am looping over that entire array of objects.
I'm trying to check whether the URL matches the current URL. So if the specific page that I'm building is that page path. And then I am also just hard coding that prefetch link as needed.
So in this case, I'm just writing that particular link prefetch and then I'm giving it that specific reference. You might notice in the reference on the href that there's entry, and then there's also this concept of prefetch next URL. And prefetch next URL, or the way in which I'm doing that is through filtering. So I'm filtering information to this particular filter called prefetch next URL. And 11ty allows you to add filters, which is really nice.
And in this particular filter, I'm adding a threshold. So I'm just saying that if myself certainty is above a specific threshold, then grab that next page path and so on.
So it's really useful because I can tweak that number, and based on that number, I can then actually render a prefetch specific page. So here's where I jumped into a specific demo where I show how this will work.
So if I jump into this page and I look at the network tab, and I want to zoom in a little bit as well. So I'm going to jump into my network tab, just to see where exactly the request come from. I go to About, nothing really happens.
I go to Menu, and I go to Contact and Menu. I think it's in About in Contact, hang on.
So you see this concept of how exactly the request goes through with regards to how a page navigates. So for instance, whenever I went to the Menu page from the About page, so About to Menu, you'll see this "Served from prefetch cache." So the idea is that the page was pre fetched instead of just served normally.
So if I went to Contact, you'll see that it's prefetched as well.
But if I go to Home, it is not prefetched.
It's essentially fetched directly.
So this is really nice because it's just the concept of being able to see when a page has been prefetched and so on.
This concept is not new.
I actually pulled from an example via Guess.js, which is a project from the Google Chrome team. And they spearheaded this project in terms of predictively prefetching links and serving them as needed.
So I want to give a shout out to that team just because they were the ones who came up with this concept. And a lot of the code that I'm using takes inspiration from that directly.
Guess.js is really nice because it has a webpack config automatically. So you don't have to write a lot of the code yourself. You can just include the npm package and automatically get the resources that you need. So far, what we have is fairly naive.
As I mentioned, there is hard-coded links in this, and hard coding is fine because it's still better than hard coding it ourselves.
So hard coding it from the build.
So the build is checking that and then adding those links ourselves.
But I think we could make this a bit more intelligent, right? Because the idea of intelligence is being able to pull more data and having more fidelity. Going back to the example with weather data, we looked at weather data and we saw that based on a week's worth of data, we can make some predictions.
However, I think we can make more predictions based on a month's worth of data.
So let's say four weeks of data, because we know more information as to what day follows a cloudy day.
And so in this case, I know that there are three out of six instances of cloudy days and three out of six instances of rainy days that follows cloudy days.
And so it's 50-50 instead of an 80-20 split. And similarly, we can be able to tell what is gonna happen in those cases.
One way in which we can do this is through cookie-based tracking.
So we can track a user to see where exactly they're going to go.
Because so far we looked at general user trends as to all users and where they all went.
But if we do cookie-based tracking, we can track a specific user and with that specific user went, and then tracking based on top level pages, as well as subpages.
So you can get much more granular in terms of how you're prefetching and so on. So if we look at one extra level of data, let's say we only looked at one path.
If we added one extra path, we can make more predictions.
So we know that the About page and the Menu page, it looked like there were three, mmm.
Three out of four users who went from the About to the Menu page.
But if we look one level further, we know that two of them came from the root page. And so we can sort of like make more levels of prediction as to where exactly these users go based on where they went before.
Similarly, it's based on like where they went from Menu to Contact.
So I know Menu to Contact was two out of four, well it's one actually, from Menu to Contact, but I can also kind of look at patterns that mage from this particular thing.
So in this instance, I see that there are instances of user going from root to Menu back to root, and it might indicate to me, while it might not, prefetch a resource and I might not be used in that instance.
It can give me a sense of this circular motion that users are taking and potentially give me some data as to use a confusion or whether or not user is not able to navigate a website efficient.
So, the thing to know with cookie-based tracking is that we have data protection laws.
So GDPR was sort of the first and now there's various others across the world. And so that's something to take into consideration whenever you want to do intelligent predictive modelling like this, because cookie-based tracking requires users to opt in. And so if a user doesn't opt in, you can necessarily take advantage of this. And so that's something to take into account. But the nice thing is that with this approach, you can essentially go from build time to something that's more realtime.
And so potentially make better predictions and better experiences for users.
I wanna return back to this concept of this threshold, because I think it's putting on in this discussion. And in order to do that, I want to look back at the example or that table that I showed before, but specifically to overlay bandwidth onto that and offer some suggestions as to when to use specific things, specific techniques. So for instance, if someone is on a slow 2G connection, prefetching might not actually offer much benefit because prefetching requires resources and so on. And so if you do want prefetching to some extent, just to help a little bit DNS and link prefetching might be the recommended approach. And in order to do that, I would put the threshold incredibly high.
So if you almost have a 100% certainty that a user's going to go go to that page, then I would prefetch it.
In other cases where it's 0.6, let's say, I would absolutely not, because it's a waste of bandwidth. And similarly, if you look right at the bottom of the page, if someone's on data saver mode, you absolutely do not wanna prefetch anything because you know that person cares a lot about their bandwidth usage.
And so you want to be cognizant of that and not prefetch automatically.
And then if you look at higher connections like 4G and so on, you have more benefits to doing link prefetching, if someone's on 5G perhaps, you might even want to do pre rendering if that's a thing. But again, you want to change that threshold depending on whether or not it's useful to the user, because after all that does take into consideration. And so it's useful to return to this data model, which does not have any numbers, but it just has a general sense of what we should do in these scenarios.
And this is a table that you have created in one of his talks.
And the idea is that prerendering has a really high benefit, but are very high cost. Link prefetch is sort of in the middle, and then DNS prefetching is sort of the lowest level. So you want to take those into consideration when doing your data modelling.
And with that, thank you so much for listening to the presentation.
Please feel free to ask me questions and reach out. My slides are noticed.
And the code for all of this that I showed is on GitHub.
Browser hints like prefetch enable you to get critical resources in advance and save valuable (next) render time. These speculative optimizations integrate the developers assumptions about the users route. Speculative pre-fetching can be wasteful due to incidences of fetching resources that will never be used.
Leaning on advances in machine learning and analytics data allows us to significantly increase the efficacy of our fetches. Let’s explore techniques that move predictive prefetching from idea to reality.
Predictive Pre-fetch
Divya Sasidharan, Developer Experience Engineer Netlify
Divya currently lives in the United States where people love their french fries… and you usually get two condiments: ketchup or mayo. Since most Americans prefer ketchup, you get it by default – you don’t have to ask for it.
It’s a frictionless, nice experience… and that’s what we want on the web too. If apply it to users browsing the web, they should just get the content they want without having to constantly ask. This is prefetching – having the browser fetch information before the user specifically requests it.
To set the scene, a run through of how websites load – from request, through the connection dance, so data can be transferred. That’s one level of latency. The next level is fetching the resources – HTML comes down first, then other resources. This all adds time to rendering that the user can see. This is basically the status quo.
Divya will be focusing on…
- DNS Prefetching
- Link Prefetching
- Prerendering
DNS Prefetching is the first and simplest level. It establishes a connection for future requests to use.
Link Prefetching goes one step further, fetching priority resources that the developer has specified are important to load ahead of time.
Prerendering is the most extreme form of prefetching – it fetches an entire page and rendering it ahead of time, in a different virtual layer. This makes navigation instantaneous… assuming they go to that page next.
Hint | Cost if wrong | Benefit if correct |
---|---|---|
DNS Prefetch | Very low | Low |
Link Prefect | Mid high | High |
Prerender | Very high | Very high |
It is useful for us to consider the both the cost and benefits of these techniques.
Some use cases give more predictability; eg. the top results on a search page, or the post-login screen of an application.
These techniques all assume that we as devs know what the user is going to do… but we are speculating. Users aren’t really that predictable.
So a predictive approach is better.
An illustrative example is predicting weather – you predict future days based on past days. If you know that 80% of the time a cloudy day is followed by another cloudy day, you can make a reasonable prediction if today is cloudy.
Translating this to a website, you can look at user statistics to find the most common sequences. On a restaurant website you might find 50% of users go to the menu.
Google Analytics gives you a navigation summary that is useful for this; but Divya finds viewing raw data is more useful than pre-processed data, where people have assumed they know what you wanted.
You can consider not just pages people visited; but also where people exited and didn’t load another page at all.
So how to integrate this with your page? Build automation – Divya has it set up to query GA during the build and update the values in 11ty. It’s better than permanently hard-coding them.
The result is a JSON asset that gives a set of paths and certainty values. So the prefetching can be set according to certainty thresholds.
(Demo showing that menu is served from prefetched cache.)
This isn’t a new concept, this was pulled from guess.js – a project by the Google Chrome team. It makes a lot of this much easier to implement.
This is still a naive implementation with some hard-coded paths. It would be better to have more data, to make better predictions.
- Thinking of the weather example, a month’s data will give different results than just a week’s data.
- Cookie based tracking can enable predictions customised to a specific user’s habits.
- Looking at more levels of navigation will reveal more detailed patterns.
It’s important to note with cookie-based tracking you might run afoul of things like the GDPR. You will need to ensure you handle all the required opt-ins and so on.
But if it is an option, it can shift from compile-time predictions to real-time predictions; which will be a better experience.
Another lens to help decisions is the user’s connection, including disabling prefetch if people are on data saver.
Bandwith | Threshold | Recommended |
---|---|---|
Slow 2G | 0.9 | DNS/Link Prefetch |
2G | 0.9 | DNS/Link Prefetch |
3G | 0.5 | DNS/Link Prefetch |
4G | 0.2 | Link Prefetch |
Data Saver | 0 | null |
Also referring back to the cost table from earlier provides good guidelines.