How skipping ahead in a video made Cloudflare fetch 128 MiB, and why it deleted the range-size setting

When a video player skips ahead it sends an HTTP range request: an ordinary GET with a Range header asking for a few megabytes around that point, answered…

Loading
cloudflare··PT2M17.5S

video loads only when you press play

When a video player skips ahead it sends an HTTP range request: an ordinary GET with a Range header asking for a few megabytes around that point, answered…

When a video player skips ahead it sends an HTTP range request: an ordinary GET with a Range header asking for a few megabytes around that point, answered with 206 Partial Content. Cloudflare's old range caching, much like nginx's cache slicing, rounded every request out to a fixed range size that each customer configured per zone. Sizes ran from tens of kilobytes to tens of gigabytes, and none worked for every request.

  • Cloudflare's old range caching rounded every HTTP range request out to a fixed range size each customer configured. R2 used 64 MiB, so a small read across a range boundary pulled 128 MiB between data centers, while small sizes turned one request into a long line of tiny origin fetches.
  • Its new Pingora-based cache already stores every file on disk in fixed-size chunks, so the range size stopped being a setting: a request fetches only the chunks its bytes live in and still returns exactly the bytes asked for.
  • A coordinator stitches one response from chunks on disk, downloads already in flight for other clients and fresh chunk-aligned origin fetches, after checking size and ETag agree. For R2, p99 time to first byte roughly halved and tiered-cache traffic fell about 6% week over week.

What an HTTP range request is

A range request asks a server for part of a file instead of all of it. It is an ordinary GET with one extra header naming the bytes the client wants, counted from zero and inclusive at both ends:

GET /movie.mp4 HTTP/1.1
Host: example.com
Range: bytes=1048576-2097151

A server that supports ranges answers with status 206 Partial Content and says which bytes it sent, out of how many in total:

HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-2097151/524288000
Content-Length: 1048576

This is how most large files move on the web. A video player that skips ahead asks for the bytes around the new position rather than everything before it. A download manager resumes an interrupted file from where it stopped. A package manager or an AI tool reads one region of a large archive or model file, and some clients read only the end of a file, where formats often keep their index. The client decides when to send a range request; a CDN in the middle does not change the request, only how it gets the bytes to answer it.

Why a CDN cannot simply cache whatever slice it is asked for

A CDN cache sits between viewers and the origin, the customer's own server, and keeps copies of files close to viewers so the origin is asked as rarely as possible. Whole files are easy to cache: one key, one body. Arbitrary slices are not. If one viewer asks for bytes 1,000,000 to 1,900,000 and the next for 1,200,000 to 2,400,000, storing each slice as its own entry means overlapping copies that never quite match the next request, so almost nothing gets reused.

The usual answer, and the one Cloudflare used for years, is to cut every file into fixed-size ranges and cache those. nginx calls this cache slicing: each client request is mapped onto the fixed ranges that cover it, the cache fetches and stores whole ranges from the origin, and the response is cut back down to what the client asked for. In Cloudflare's old implementation the range size, and the file extensions it applied to, were configured per zone, which meant every customer serving large files had to pick a number.

Why no range size worked

The range size is a trade, and both ends of it hurt.

  • Large ranges fetch far more than anyone asked for. R2, Cloudflare's object storage and the largest internal user of the old feature, was configured with 64 MiB ranges. A small read that happened to straddle a boundary between two ranges needed both, so the cache pulled 128 MiB from upstream for it. Cloudflare describes requests too small to fill a floppy disk causing a few DVDs' worth of transfer, moved across several data centers, sometimes after the client had already given up and left.
  • Small ranges multiply requests. One client request turns into a long line of small origin requests, and Cloudflare held each of them in memory while coalescing them, so a burst of traffic became memory pressure on the cache.
  • The waste compounds across hops. Extra bytes do not travel once. They go from the origin into one data center, between Cloudflare's cache tiers, and then toward the client, and each hop repeats the cost.
  • Changing the size threw the cache away. The range size was part of the cache key, so tuning it invalidated everything already cached under the old size.

Customers' configured sizes ran from tens of kilobytes to tens of gigabytes, and Cloudflare says both the large and the small patterns caused real operational incidents. No single value suits a small seek, a sequential download, a large media file and every origin and cache topology at once, and customers had no way to know the right one without understanding the cache's internals.

The old design had two more problems the video left out. Concurrent requests that fell inside the same range could block each other, however far apart the bytes they actually wanted were. And the nginx-based cache could not combine range caching with concurrent streaming, Cloudflare's feature that lets several clients read a file from cache while it is still being written. Turning both on together cut responses short, so each range had to finish downloading before anyone could read it, and a popular file needed a separate origin fetch for every range.

The chunk becomes the range

The fix came from moving Cloudflare's cache from nginx to a caching server built on Pingora, Cloudflare's open-source Rust framework for proxies. To make interrupted downloads easy to resume, it already stored every cached file on disk in fixed-size chunks. If the cache already works in chunks, the range size does not need to be a setting: it can just be the chunk.

For a range request, the cache now checks which of the requested bytes it already holds and serves those directly. For the missing bytes it fetches origin ranges aligned to its own chunk boundaries, stores them in the same chunks it uses for ordinary caching, and returns only the bytes the client asked for. A request for bytes 150 to 250 can cause the cache to fetch the whole chunk containing them, and the client still receives exactly 150 to 250.

This does not remove all extra work. A request smaller than a chunk still fetches the whole chunk, and ranges that do not line up with chunk boundaries cost Cloudflare some additional work. The difference is that the overhead is now bounded by one chunk, predictable, and no longer decided by a number a customer guessed. Cloudflare does not publish the chunk size, which is why the video never labels a cell with one.

How one response is stitched together

Parsing the Range header is the easy part. The harder problem is producing one correct response when the requested bytes are spread across data already on disk, a download that is still in progress, and bytes nobody has fetched yet. Cloudflare describes the edge's work in five steps:

1. Look up the file's headers and its range-coverage metadata, the record of which ranges have been written successfully. 2. Read whatever part of the request it can, from disk or from a download still streaming in memory. 3. Fetch the missing coverage from the origin, using chunk-aligned ranges. 4. Write the new bytes and record the completed ranges in the metadata. 5. Combine the pieces and set Content-Range and Content-Length for the client's response.

Cloudflare calls the component that does this a coordinator rather than a single cache lookup. One request can use a cached prefix, an in-flight download and a newly fetched suffix. The origin response it triggers can be larger than what the client receives, but the status and headers the client sees describe only the range it asked for.

The in-flight case is what helps popular files. If a download already under way covers another request's bytes, the second request attaches to that download instead of starting its own origin fetch. During a burst on one large file, many viewers end up sharing a handful of downloads. The general technique is often called request coalescing or request collapsing; Cloudflare's post does not use either name.

The design reuses the cache's existing data model: headers identify which version of the file is stored, the body sits in bounded chunks, and metadata records which ranges were written successfully.

Making sure every piece is the same file

A stitched response is only valid if every byte comes from the same version of the file. Splicing the start of yesterday's build onto the end of today's would hand the client a file that never existed. So before composing, the coordinator checks that each origin response matches the range it asked for, that the total size of the file stays the same, and that the ETag, the version identifier the origin attaches to a response, is consistent across every subrequest.

Clients can ask for the same guarantee with the If-Range header, which requests a range only if the file still matches a known ETag or date. If it does not match, Cloudflare returns the whole current file instead of combining bytes from different versions.

There is one more case where Cloudflare refuses to compose ranges: a file it is about to decompress for the client. A byte range over compressed data selects different content than the same range over the decompressed result, so Cloudflare serves the full transformed response rather than advertise a range it cannot honour. The outcomes are ordinary HTTP:

  • a range the cache can satisfy gets 206 Partial Content with an accurate Content-Range
  • a range outside the file gets 416 Range Not Satisfiable
  • a response that cannot safely be ranged is served whole

What the numbers measure

Before opening the feature to everyone, Cloudflare moved existing users of the old range caching onto the new implementation, starting with the workloads that hurt most: many concurrent range requests against very large files, often with very spiky traffic. R2 was the largest.

  • p99 time to first byte roughly halved across R2's storage regions, on both cache hits and cache misses. p99 is the latency that 99% of requests beat, so this is the slow tail, not the average. Cloudflare gives no millisecond figures, which is why the video's bars are relative.
  • About 6% less tiered-cache traffic, week over week, for R2 and similar workloads, which Cloudflare puts at petabytes less data moving between its data centers every day. This number comes from a specific, later change: teaching the coordinator to combine bytes already on disk with bytes fetched from upstream in one response. Before that change, a request was served either entirely from cache or entirely from upstream, so a request with even one missing chunk went entirely upstream, including bytes already on disk.

Turning it on

The feature is called Origin Range Requests and is self-service on all plans, with no range size to tune and no support ticket. It is enabled with a Cache Rule that matches large-file traffic, either in the dashboard under Caching, Cache Rules, or through the API with origin_range_requests: { "mode": "on" }. Cloudflare says it intends to make this the default way certain file types are served, adjustable by cache rules.

Cloudflare lists the traffic it is meant for: progressive and seekable video such as MP4, resumable large downloads, software distributions and package repositories, disk images and archives, AI training and inference workloads that move large model files or read regions of them, and clients that fetch metadata or a small region of a larger file.

Two limits are worth knowing. Enabling it does not make an otherwise uncacheable response cacheable; normal cache eligibility still applies. And the origin has to support byte ranges itself, or there are no partial origin fetches to align.

What the video simplified

The strip in the video has 24 equal cells as an illustration; the real chunk size is unpublished and real files have far more chunks. The 64 MiB window spanning six cells is a drawing choice, not a ratio Cloudflare states. The hook shows bytes crossing from one Cloudflare data center to another as a stand-in for the several hops the post describes (origin to a data center, between cache tiers, toward the client). The crowd scene uses four downloads and eleven viewers to show attaching to in-flight downloads; Cloudflare gives no such counts. Everything numeric that is labelled (64 MiB, 128 MiB, tens of KB to tens of GB, about half, about 6%, petabytes a day) is Cloudflare's.

Terms

Range request
An HTTP request with a Range header asking for specific bytes of a file rather than the whole file.
206 Partial Content
The status a server returns when it answers a range request with the requested part of the file.
Content-Range
The response header that says which bytes were sent and the file's total size, for example bytes 1048576-2097151/524288000.
416 Range Not Satisfiable
The status for a range that falls outside the file.
If-Range
A request header that makes a range request conditional on the file still matching a known version; if it has changed, the whole file comes back instead.
ETag
An identifier the origin attaches to a specific version of a file, used to tell whether two responses came from the same version.
Origin
The customer's own server behind the CDN, where the authoritative copy of the file lives.
Cache key
The value a cache uses to decide whether two requests are for the same stored object. Anything in it, such as the old range size, splits the cache when it changes.
Cache slicing
Caching a large file as fixed-size ranges and mapping each client request onto the ranges that cover it; nginx's slice module is the well-known implementation.
Pingora
Cloudflare's open-source Rust framework for building proxies. Cloudflare's current caching server is built on it, replacing an nginx-based cache.
Fixed-size chunks
The equal-sized pieces Pingora's cache stores every file in on disk, originally to make interrupted downloads easy to resume, and now the unit of range caching too.
Coordinator
Cloudflare's name for the component that builds one range response from cached chunks, in-flight downloads and new origin fetches, and checks they all belong to one version.
Request coalescing
Letting several requests for the same data share one upstream fetch instead of each making its own.
Tiered cache
A cache arrangement where data centers near viewers fetch from other Cloudflare data centers before going to the origin, so traffic between data centers is part of the cost.
R2
Cloudflare's object storage service, and the largest internal user of range caching.
p99 time to first byte
How long the slowest 1% of requests wait before the first byte of the response arrives.

Questions

What does a CDN fetch when a video player skips ahead?

The player sends a range request for a few megabytes around the new position. With Origin Range Requests, Cloudflare serves whatever of those bytes it already has cached and fetches only the missing fixed-size chunks from the origin, then returns exactly the requested bytes. Under the old configuration it fetched whole fixed ranges set by the customer, up to tens of gigabytes each, and two of them if the request crossed a boundary.

Why did changing the range size empty Cloudflare's cache?

The configured range size was part of the cache key. Entries stored under one size were not found under another, so any change meant refetching everything from the origin.

How big is a chunk in Cloudflare's cache?

Cloudflare has not published the chunk size. It says the overhead for a small or unaligned request is bounded by the chunk, which is what replaced the unbounded overhead of a large configured range.

Does Origin Range Requests make uncacheable files cacheable?

No. Normal cache eligibility still applies, and the origin must support byte ranges for chunk-aligned origin fetches to work.

What happens if the file changes halfway through a stitched response?

The coordinator checks the file's total size and ETag across every piece. If the versions disagree, or a client's If-Range no longer matches, Cloudflare serves the whole current file rather than combine bytes from two versions.

Is this the same as nginx's slice module?

The old Cloudflare implementation resembled it: fixed ranges, sized per zone. The new one keeps the idea of fixed pieces but takes their size from the cache's own storage chunks, so there is nothing to configure, and it can compose a response from cached, in-flight and newly fetched pieces.

Skipping ahead in a video could make Cloudflare fetch 128 MiB. The player only wanted a few, and the rest got shipped between data centers, sometimes after the viewer had left. Cloudflare fixed it by deleting a setting, because the right size was already sitting on its disks. Skipping ahead sends a range request, a GET for just the bytes around that point in the file, and Cloudflare's cache serves it from a stored copy or from the customer's own server. A cache can't reuse arbitrary slices, so the old system rounded each request out to a fixed range size, set by each customer. Customers picked anything from tens of kilobytes to tens of gigabytes, and every choice hurt somewhere. R2, Cloudflare's object storage, used 64 MiB, so a small read that crossed a range boundary pulled in both, that same 128 MiB. Small sizes failed the other way, turning one request into a long line of tiny fetches that each held memory while it waited. The answer was already inside Cloudflare's new caching server, which stores every file on disk in fixed-size chunks. If the cache already thinks in chunks, the range size doesn't need to be a setting, because it can just be the chunk. So bytes 150 to 250 fetch only the chunk they live in, and the player gets back just those bytes. A tiny request can still pull a whole chunk, but that waste now has a fixed ceiling. A request across three chunks is harder, because each can be in a different state. One is already on disk, so it's read straight from there, one is mid-download for another viewer, so the request joins that download, and only the chunk nobody has yet comes from the customer's server, then the three are stitched into one response holding exactly the requested bytes. A crowd skipping around the same popular file shares a handful of downloads that way, instead of each viewer starting their own. After R2 moved over, its p99 time to first byte fell by about half, on cache hits and misses, and stitching cached bytes with fetched ones cut data-center traffic for R2 and workloads like it by about six percent, which is petabytes a day. So before you hand someone a number to tune, it's worth checking whether your system already chose one.

This explainer is based on Origin Range Requests: Serving large files without range-request tuning by Cloudflare ↗. The original reporting and technical work belong to its publisher.