---
title: "aadhar.sh/garage/ranges: Ranges, chunk by chunk"
description: "Cloudflare's cache now pulls big files from the origin one chunk at a time. The chunk arithmetic, ten viewers sharing one fetch, and a live probe of why this site's 3.26 GB of photos can't use it."
path: "/garage/ranges"
section: "garage"
kind: "content"
updated: "2026-10-24"
source: "https://aadhar.sh/garage/ranges"
---

> Site index: https://aadhar.sh/llms.txt
> Section index: https://aadhar.sh/garage/llms.txt
> This is the Markdown twin of a page on aadhar.sh. The HTML at the source
> URL below is the original, and is hand-written and unminified on purpose.

# Ranges, chunk by chunk

On 2026-10-23 Cloudflare shipped [Origin Range Requests](https://blog.cloudflare.com/origin-range-requests/), a [Cache Rule][cache-rules] 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][http-range-requests] 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][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][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][etag], so two versions of a file never share a response. Cloudflare reports that R2's p99 [time to first byte][ttfb] 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.

started an origin fetch waited on one already running served from cache origin fetch

The track spans the first second after the first viewer arrives.

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][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][cloudflare-workers] 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][workers-cache-api] for the [colo][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

[cache-rules]: https://developers.cloudflare.com/cache/how-to/cache-rules/ "A Cloudflare zone setting that changes how the cache treats requests matching a filter, such as whether to cache them and for how long."
[http-range-requests]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Range_requests "An HTTP request with a Range header that asks for only some bytes of a file. The server answers 206 Partial Content with just those bytes."
[r2]: https://developers.cloudflare.com/r2/ "Cloudflare's object storage for files. It speaks Amazon S3's API and charges nothing for downloading data back out."
[pingora]: https://github.com/cloudflare/pingora "Cloudflare's open-source Rust framework for building network proxies. Cloudflare's own cache is built on it."
[etag]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag "An HTTP response header that names one exact version of a file, so a cache can ask whether its copy is still current."
[ttfb]: https://en.wikipedia.org/wiki/Time_to_first_byte "Time to first byte: how long a browser waits after sending a request before the first byte of the response arrives."
[jpeg-xl]: https://en.wikipedia.org/wiki/JPEG_XL "A newer image format from the JPEG committee. It compresses well, decodes in parallel, and can repack an existing JPEG losslessly into a smaller file."
[cloudflare-workers]: https://developers.cloudflare.com/workers/ "Code that runs on Cloudflare's servers around the world and answers each request directly, with no server of the site's own."
[workers-cache-api]: https://developers.cloudflare.com/workers/runtime-apis/cache/ "The interface a Cloudflare Worker uses to store responses in the local data center's cache and serve them on later requests."
[colo]: https://en.wikipedia.org/wiki/Colocation_centre "Short for colocation center, a shared data center. At Cloudflare it means one of the data centers that answers visitors nearby."

Source: https://aadhar.sh/garage/ranges
