Skip to content
aadhar.sh/garage/ranges
Address
aadhar.shGarageRanges, chunk by chunk

Ranges, chunk by chunk

On 2026-10-23 Cloudflare shipped Origin Range Requests, a Cache Rule switch for big files. When a client asks for a slice of a file the edge doesn't hold, the edge now fetches only the chunks that slice touches, keeps them, and builds later answers from whatever chunks it has. Three demos below: the chunk arithmetic, ten viewers sharing one fetch, and a live probe of the one place this site serves big files. That place can't use the switch, and why is half the story.

What the old cache did

A range request asks for bytes a through b of a file and gets back a 206 Partial Content. A video player seeking to minute 40 sends one. So does a download manager resuming at 61%.

Before this change, a zone that wanted ranges cached set one fixed range size and a list of file extensions, close to nginx's slice module. Every request mapped onto those fixed slices. R2, the biggest internal user, ran at 64 MiB, so a 1 KiB read that straddled a slice edge pulled two slices, 128 MiB, between data centers. A slice had to finish downloading before anyone could read from it. The size was part of the cache key, too, so changing it threw the cache away.

Now the slice is the chunk that Pingora already writes to disk, and customers don't pick its size. Metadata records which byte ranges landed. One response can stitch together bytes from disk, bytes from a download still in flight, and bytes from a new origin fetch. Before it stitches, the edge checks that every origin piece reports the same total size and the same ETag, so two versions of a file never share a response. Cloudflare reports that R2's p99 time to first byte roughly halved, and traffic between its cache tiers fell about 6% week over week.

Fill the chunks

Pick a file and a chunk size, then send reads. Click anywhere on the strip to read 1 MiB from that spot. Blue squares are cached, green ones arrived from the origin on the last read, and the outline marks what the last read touched.

Two costs pull against each other. A read of n bytes makes the origin send at most n plus two chunks, one partly wasted at each end, so smaller chunks waste less. Every chunk is also a cache entry to track. On the 500 MiB video, the 8 seeks ask for 8 MiB: at 64 KiB the origin sends exactly 8 MiB as 128 chunks, at 8 MiB it sends 80 MiB as 10 chunks, and at 64 MiB it sends the whole file. The post doesn't say what size Pingora's chunks are, so read the menu as a spread of guesses.

The photo shows the blunt end. At 64 MiB the file fits in one chunk, so the first read of any byte pulls all 9.27 MB.

Ten viewers, one fetch

A popular file misses in bursts. A video premieres, and the players all seek to the same opening seconds within a few hundred milliseconds of each other, before the first origin fetch has come back. Here, viewers arrive about 30 ms apart, each wants 2 MiB near the same spot of a cold file, and the origin takes 300 ms per fetch.

At ten viewers, sharing turns 20 origin fetches into 4 and cuts the median wait from 300 ms to 178 ms, because a viewer that attaches late waits only for the rest of a fetch already running. Past 300 ms the first fetches land, and from then on everyone hits the cache with or without sharing. The burst is the only window where sharing matters, and it's the window when the origin is busiest.

Why this site can't switch it on

The big files here are 165 photo originals: JPEG XL files from 7.2 MB to 30.8 MB, 3.26 GB in all. Everything else the site ships fits in 31 MB, and its largest single file is a 1.7 MB AV2 decoder.

A Cache Rule governs requests that pass through Cloudflare's cache on their way to an origin. On this site the Worker is the origin, and it reads originals through an R2 binding, which never passes a cache. No rule sees that read, so the switch has nothing to act on.

Full reads still come out of a cache, two of them in fact. Workers Cache sits in front of the Worker and answers a repeat GET of /images/full/ before the Worker runs, with cf-cache-status: HIT and an age. Behind it, the photo route keeps its own copy in the Cache API for the colo.

A range read skips both. The dispatcher sends any request with a Range header around Workers Cache, and the photo route skips the Cache API, because a 1 KiB body must never land in the slot that holds the whole file. The route then turns Range: bytes=a-b into an R2 read with an offset and a length, and writes the 206 and its Content-Range itself. So every range read runs the Worker and reads R2, even when both caches hold every byte it asks for.

Read a photo original, 32 bytes at a time

A JPEG XL file is a stack of boxes, and each opens with its length and a four-letter type. Read one box header, add the length, and you land on the next. The script below walks a real original that way, one range request per box. It also reads the image's width and height out of the first codestream bytes, so it learns the layout and the size of a 15 MB file from a few hundred bytes.

The live probe needs JavaScript and this site's photo list.

No hop carries cf-cache-status, which means none of them touched a cache: each one ran the Worker and read R2. The race puts the two paths side by side: a full request, cut off once its headers arrive, against a 1 KiB range of the same file. On 2026-10-24 from New York, 5 rounds each, the full request's headers took a median 27 ms, all from Workers Cache, and the range's took 69 ms.

One trap cost me a wrong draft of this section. The photo route stamps x-photo-cache: hit or miss on what it returns, and Workers Cache stores that response whole, header included. A repeat request then gets x-photo-cache: miss out of a cache hit with an age of nine minutes. Four of 13 originals I sampled answered that way on every request. Read cf-cache-status and age first.

What would close the gap

  • Let ranges into Workers Cache. The front tier is Cloudflare's cache, and the Worker behind it already answers ranges, which is the shape Origin Range Requests wants. Two things I haven't tested decide it: whether Workers Cache answers a range out of a stored full response, and whether a Cache Rule reaches Workers Cache at all.
  • Answer ranges from the Cache API. If cache.match() can serve a range out of a stored full response, the fix is a few lines in the photo route, and the read stops reaching R2.
  • Leave it. As far as I know, browsers don't send ranges for an <img>. Video players and resumable downloads do, and this site serves neither. I haven't counted how many range requests /images/full/ gets, and that count decides whether either fix earns a change.

watching · the understanding check is part of the page, not a gate

garagewatching2026-10-24