Umur Karagöz
Umur Karagöz

Reputation: 3190

Service Worker slow response times

In Windows and Android Google Chrome browser, (haven't tested for others yet) response time from a service worker increases linearly to number of items stored in that specific cache storage when you use Cache.match() function with following option;

ignoreSearch = true

Dividing items in multiple caches helps but not always convenient to do so. Plus even a small amount of increase in items stored makes a lot of difference in response times. According to my measurements response time is roughly doubled for every tenfold increase in number of items in the cache.

Upvotes: 13

Views: 6323

Answers (3)

Umur Karagöz
Umur Karagöz

Reputation: 3190

Official answer to my question in chromium issue tracker reveals that the problem is a known performance issue with Cache Storage implementation in Chrome which only happens when you use Cache.match() with ignoreSearch parameter set to true.

As you might know ignoreSearch is used to disregard query parameters in URL while matching the request against responses in cache. Quote from MDN:

...whether to ignore the query string in the url. For example, if set to true the ?value=bar part of http://example.com/?value=bar would be ignored when performing a match.

Since it is not really convenient to stop using query parameter match, I have come up with following workaround, and I am posting it here in hopes of it will save time for someone;

// if the request has query parameters, `hasQuery` will be set to `true`
var hasQuery = event.request.url.indexOf('?') != -1;

event.respondWith(
  caches.match(event.request, {
    // ignore query section of the URL based on our variable
    ignoreSearch: hasQuery,
  })
  .then(function(response) {
    // handle the response
  })
);

This works great because it handles every request with a query parameter correctly while handling others still at lightning speed. And you do not have to change anything else in your application.

Upvotes: 16

marcos_xv
marcos_xv

Reputation: 11

I had the same issue, and previous approaches caused some errors with requests that should be ignoreSearch:false. An easy approach that worked for me was to simply apply ignoreSearch:true to a certain requests by using url.contains('A') && ... See example below:

    self.addEventListener("fetch", function(event) {
         
      var ignore
    
      if(event.request.url.includes('A') && event.request.url.includes('B') && event.request.url.includes('C')){
        ignore = true
      }else{
        ignore = false
      }
      event.respondWith(
        caches.match(event.request,{
            ignoreSearch:ignore, 
          })
          .then(function(cached) {
            ...
           }

Upvotes: 1

Hashbrown
Hashbrown

Reputation: 13013

According to the guy in that bug report, the issue was tied to the number of items in a cache. I made a solution and took it to the extreme, giving each resource its own cache:

var cachedUrls = [
    /* CACHE INJECT FROM GULP */
];

//update the cache
//don't worry StackOverflow, I call this only when the site tells the SW to update
function fetchCache() {
    return Promise.all(
        //for all urls
        cachedUrls.map(function(url) {
            //add a cache
            return caches.open('resource:'url).then(function(cache) {
                //add the url
                return cache.add(url);
            });
        });
    );
}

In the project we have here, there are static resources served with high cache expirations set, and we use query parameters (repository revision numbers, injected into the html) only as a way to manage the [browser] cache.
It didn't really work to use your solution to selectively use ignoreSearch, since we'd have to use it for all static resources anyway so that we could get cache hits!

However, not only did I dislike this hack, but it still performed very slowly.


Okay, so, given that it was only a specific set of resources I needed to ignoreSearch on, I decided to take a different route;
just remove the parameters from the url requests manually, instead of relying on ignoreSearch.

self.addEventListener('fetch', function(event) {
    //find urls that only have numbers as parameters
    //yours will obviously differ, my queries to ignore were just repo revisions
    var shaved = event.request.url.match(/^([^?]*)[?]\d+$/);
    //extract the url without the query
    shaved = shaved && shaved[1];

    event.respondWith(
        //try to get the url from the cache.
        //if this is a resource, use the shaved url,
        //otherwise use the original request
        //(I assume it [can] contain post-data and stuff)
        caches.match(shaved || event.request).then(function(response) {
            //respond
            return response || fetch(event.request);
        })
    );
});

Upvotes: 1

Related Questions