PMTiles: Range requests browser caching not working in Firefox (browser bug)

Hey folks, I noticed earlier in my experimentation with maplibre and pmtiles that range requests against a .pmtiles file are not getting cached by browsers (I’m talking about Firefox and Chrome specifically).

I just ran across @bdon’s https://bdon.github.io/overture-tiles/places.html#10.28/52.4901/13.3301 on mastodon and used this example to drill further down into what’s happening because I can reproduce the issue there.

The summary is that currently neither Firefox nor Chrome can cache the range requests against .pmtiles, not even Chrome even tho it sends an If-Range with an ETag and the ETag matches (Firefox sends a regular Range request).

Firefox

Check this out. On second page load (warm cache)

  1. In red: the individual .mvt tiles are getting cached because these are individual files
  2. In blue: the range requests against the single .pmtiles are not getting cached and the requests always go through

firefox-cache-1

Here are the request and response headers; notice

  1. Firefox sends a Range request
  2. The .pmtiles have an ETag attached to it

firefox-cache-2

Chrome

Check this out. On second page load (warm cache)

  1. In red: again the .mvt files are getting cached
  2. In blue: the range requests against the single .pmtiles are again not getting cached and requests always go through

chromium-cache-1

Here are the request and response headers; notice

  1. Chrome sends an If-Range request and attached the ETag
  2. The response returns the very same ETag, so even though the ETags are matching it’s not getting cached

chromium-cache-2


What’s happening here?

I looked around it and it seems like both Firefox as well as Chrome’s support for caching range requests is very limited.

For example for Firefox

they only allow to cache a range request for the very first byte range starting at byte zero.

// Don't cache byte range requests which are subranges, only cache 0-
// byte range requests.
if (IsSubRangeRequest(mRequestHead)) {
  return NS_OK;
}

How to move forward

With limited support of caching range requests in the browsers, I see the following ways forward

  1. We don’t care about caching and always re-run requests against the .pmtiles file
  2. We have a lambda or similar in front of the static file turning range requests into individual file get requests
  3. We implement caching ourselves in the pmtiles lib here in this repo
  4. We implement caching ourselves e.g. as a maplibre plugin

I wanted to flag this with you since you might have thoughts on this and might have encountered this before.

What I wanted to add: pmtiles is such an amazing format for dropping a single file onto a static host that not having caching working by default is a bit unfortunate, because otherwise it shines and is such a cool idea 👌

About this issue

  • Original URL
  • State: open
  • Created 8 months ago
  • Reactions: 1
  • Comments: 18 (14 by maintainers)

Most upvoted comments

JS 3.0.0-alpha.2 is on NPM implementing this, see changelog here: https://github.com/protomaps/PMTiles/blob/main/js/CHANGELOG.md

It may require you change from import pmtiles from "pmtiles" to named imports import {PMTiles} from "pmtiles"

This should be the final RC of 3.0.0, planning to publish the new major version this week.

Well, I spent today testing it out and I’m rolling back this change 😛

It turns out If-Match causes not only browser cache misses, but also a preflight CORS for every request, which can add 200ms+ to each tile. I’m implementing a different approach that will use cache: 'reload' in fetch conditionally only when an ETag change is detected. So v3 should be 100% as cacheable as v2, but correct the cases around in-place updates which are broken right now.

Here is the Firefox bug tracker where there’s discussion of how this affects Cloud Optimized GeoTIFF in the same way:

https://bugzilla.mozilla.org/show_bug.cgi?id=1615698