Blocks & reads
Every object in a namespace is divided into blocks of one fixed size. A block is the unit of caching, of origin fetching and of coalescing. A read never touches bytes outside the blocks covering its range, and never holds more than a bounded window of blocks in memory.
Block size
Block size is a per-namespace power of two between 64 KiB and 16 MiB, 1 MiB by default. Block i covers bytes [i * B, (i + 1) * B), the last block of an object is short. Alignment is what lets every reader of an object agree on block boundaries without coordination: any request for offset n in the object resolves to block n / B for everyone.
The cache stores whole blocks. The bytes a caller gets back are slices of those blocks, so a 4 KiB read of a cold block costs one 1 MiB fetch and every later read within that block is a hit. Larger blocks mean fewer origin requests for sequential readers, smaller blocks mean less over-fetch for random ones. The trade-off is discussed in Tuning.
Ranges
A read is a ReadRange: the whole object, a bounded start..end, an open start.. or a suffix of the last n bytes. Bounded ranges resolve to blocks immediately. The other three need the object size, which the read learns from the metadata cache or from the first origin response, see Origin fetches. A range past the end of the object fails with the object size attached, which the S3 endpoint turns into a 416.
The read path
A get returns a ReadStream that yields Bytes in block order. Behind it a reader walks the block range with a window of blocks in flight.
For each block in the window the fetcher does one of three things.
- Hit. The block is in RAM or on disk. The handle resolves at once.
- Join. Another read is already fetching the block. The handle waits on that fetch. The origin sees one request however many readers arrive.
- Own. Nothing has the block. The read registers itself as owner and the block joins a fetch group.
Consecutive owned blocks are grouped into one origin GET of up to fetch_window blocks, aligned to fetch_window boundaries so two readers moving through the same object produce identical groups and coalesce. The response body is consumed as a stream, each block is inserted into the cache and its waiters are resolved as soon as its bytes arrive, before the rest of the group has downloaded.
Windows
Two settings bound how much a read has in motion.
| Setting | Default | Meaning |
|---|---|---|
fetch_window | 8 | Maximum blocks in one origin GET. With 1 MiB blocks a miss costs one 8 MiB request. |
read_window | 16 | Maximum blocks a single stream has in flight, hits included. Bounds memory per reader. |
A stream yields blocks in order and only pulls new blocks into its window as the caller consumes. A slow consumer holds at most read_window blocks, a fast one keeps read_window / fetch_window origin requests in flight, which is what makes a sequential scan approach origin throughput without any explicit prefetch.
Readahead is separate and covered with the fetch scheduler. It extends the window past what the caller asked for when access looks sequential, and runs at a lower priority so it never delays a foreground miss.
Whole-object writes
insert puts a whole object into the cache without an origin round trip, splitting it into blocks and recording its metadata. invalidate drops the metadata and, for immutable namespaces, the blocks. The S3 endpoint uses both on PUT and DELETE, see S3 endpoint. Objects larger than a configured limit are not populated on write, since a multi-gigabyte upload is not worth holding in RAM while it uploads.