My app loads a video here: .mp4
It loads the video from another origin in chunks of 16Kb using the Range
request header. My server has set the response header Access-Control-Max-Age
to 10 minutes to prevent redundant OPTIONS requests.
Visit the link (its an HTML page), open the network tools, and observe that it takes around 1s to fetch a 16Kb chunk.
Now check "Disable cache", and you should see the requests finish a LOT faster, and it looks like there are more concurrent requests. This is the opposite of expected behavior, because each request has to fire and wait for an OPTIONS request.
If the cache is enabled, requests should be faster, no? What's going on here?
Here's a reproduction in GIF form: .mp4
NOTE: The chunks are stored in IndexedDB, so if you refresh the page, you'll need to clear IndexedDB to force all the requests to fire.
EDIT: Filter out any firestore requests, I'm not sure why those are firing but it should be unrelated to my question.
EDIT: A new clue! .png
When "Disable Cache" is checked, Chrome will reuse the same TCP connection as seen in the "Connection ID" column. Ostensibly this should make requests faster.
My app loads a video here: https://core.arc.io/guanzo/VideoOfPeopleWalking.mp4
It loads the video from another origin in chunks of 16Kb using the Range
request header. My server has set the response header Access-Control-Max-Age
to 10 minutes to prevent redundant OPTIONS requests.
Visit the link (its an HTML page), open the network tools, and observe that it takes around 1s to fetch a 16Kb chunk.
Now check "Disable cache", and you should see the requests finish a LOT faster, and it looks like there are more concurrent requests. This is the opposite of expected behavior, because each request has to fire and wait for an OPTIONS request.
If the cache is enabled, requests should be faster, no? What's going on here?
Here's a reproduction in GIF form: https://i.gyazo./ec5941829031cdd4dc684a3b53ec6c39.mp4
NOTE: The chunks are stored in IndexedDB, so if you refresh the page, you'll need to clear IndexedDB to force all the requests to fire.
EDIT: Filter out any firestore requests, I'm not sure why those are firing but it should be unrelated to my question.
EDIT: A new clue! https://i.gyazo./bd887533a42868f748564ccda4451881.png
When "Disable Cache" is checked, Chrome will reuse the same TCP connection as seen in the "Connection ID" column. Ostensibly this should make requests faster.
Share Improve this question edited Sep 16, 2018 at 23:47 sideshowbarker♦ 88.6k30 gold badges215 silver badges212 bronze badges asked Sep 16, 2018 at 0:36 Eric GuanEric Guan 16k10 gold badges52 silver badges62 bronze badges 3- Is there any way to reproduce what you're doing in the gif? – Arend Commented Sep 16, 2018 at 0:39
- 1 You should be able to visit the link I posted (it leads to an HTML page), and perform the same steps I describe in my question. – Eric Guan Commented Sep 16, 2018 at 0:44
- 1 Excellent self answered question. This is really useful. – Boaz Commented Sep 16, 2018 at 23:35
1 Answer
Reset to default 10Taking deep breaths while poking my head out of the debugging rabbit hole.
This is the kind of problem that has many solutions.
The problem:
Chrome thinks each range request is requesting the "same" resource because the URL is the same. It's technically NOT the "same" resource because a different range of bytes is requested as specified in the Content-Range
header.
Here's a quote from the chromium docs on how the http cache treats requests with the same URL.
Enforce the cache lock.
The cache implements a single writer - multiple reader lock so that only one network request for the same resource is in flight at any given time.
Note that the existence of the cache lock means that no bandwidth is wasted re-fetching the same resource simultaneously. On the other hand, it forces requests to wait until a previous request finishes downloading a resource...
If you opened the "Timing" tab in each network request in my example, you'd see that each request was waiting for the previous request to finish.
The Solution:
Force Chrome to recognize that the range requests are returning different resources.
This can be done by setting the header Cache-Control: no-cache, no-store
on the request or the response. The performance improvement was highest when setting it on the request. Other header values will probably work, I only tested no-cache, no-store
. This explains why checking "Disable cache" made my requests so much faster, each request was able to use a different TCP connection and wasn't stalled by the cache lock.
I'd like to give props to this SO question for giving me the "Aha!" moment, and to Firefox for not having this problem.