From d063a3833ab35d7615dc283549a0ead3513e4e4e Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Fri, 7 Aug 2026 13:01:08 +0800 Subject: [PATCH 1/7] Add Sans I/O analytic file formats blog post --- ...026-08-07-analytic-file-formats-sans-io.md | 366 ++++++++++++++++++ img/sans-io-analytic-formats/architecture.png | Bin 0 -> 41705 bytes .../parquet-state-machine.png | Bin 0 -> 61646 bytes 3 files changed, 366 insertions(+) create mode 100644 _posts/2026-08-07-analytic-file-formats-sans-io.md create mode 100644 img/sans-io-analytic-formats/architecture.png create mode 100644 img/sans-io-analytic-formats/parquet-state-machine.png diff --git a/_posts/2026-08-07-analytic-file-formats-sans-io.md b/_posts/2026-08-07-analytic-file-formats-sans-io.md new file mode 100644 index 000000000000..b9aafc89542b --- /dev/null +++ b/_posts/2026-08-07-analytic-file-formats-sans-io.md @@ -0,0 +1,366 @@ +--- +layout: post +title: "Implementing Analytic File Formats Sans I/O" +description: "Why separating Parquet decoding from storage I/O makes analytic file readers more composable, and how arrow-rs puts the design into practice" +date: "2026-08-07 00:00:00" +author: "Andrew Lamb, Xudong Wang, Neil Conway, and Daniël Heres" +categories: [parquet, arrow] +--- + + +Opening a file, constructing a reader, and calling `next()` is a wonderfully +simple interface. It is also where many analytic file libraries accidentally +make a much larger architectural decision: **the decoder, rather than the +application, controls I/O**. + +That choice works well until the decoder is embedded in a system with its own +storage runtime, buffer manager, cache, or scheduler. Such a system may want to +race object-store reads, use `io_uring`, account for every buffered byte, or +reuse pages that are already resident in memory. If the file reader performs +I/O internally, the application is limited to the policies anticipated by the +library author. + +This post presents a different foundation for analytic file readers: **Sans +I/O**. A Sans I/O decoder contains all the knowledge needed to interpret a file, +but never reads the file itself. It reports which byte ranges it needs, and the +application decides how to obtain them. + +> **The core idea:** format knowledge belongs in the decoder; I/O policy belongs +> in the application. + +This post distills our CIDR 2027 paper, *Implementing Analytic File Formats Sans +I/O*, and describes the production implementation in the [Apache Arrow Rust +Parquet reader][arrow-rs-parquet]. + +## A Parquet reader is a small query engine + +[Apache Parquet] is a good example of why the I/O boundary matters. Reading a +Parquet file is not a single sequential operation. Even a simplified decoder +alternates between I/O and CPU work across at least nine states. + +The two diagrams in this post are reproduced from the paper under [CC BY 4.0]. + +
+ Parquet decoding alternates between I/O and CPU stages for the file footer, metadata, optional pruning structures, and data pages +
Figure 1 (paper Figure 2): The Parquet decoding state machine. Pink states perform I/O, yellow states perform CPU work, and dashed states are optional.
+
+ +The footer reveals where the file metadata lives. The metadata identifies row +groups and column chunks. Bloom filters and page indexes may eliminate some of +those chunks, and only then does the decoder know which data pages survive. The +answer to "what bytes are needed next?" therefore depends on information +decoded in earlier states, along with the query's projection, filters, and +limit. + +There are several valid ways to orchestrate these reads: + +- Prefetch the next predicted range while the CPU decodes the current one. +- Coalesce nearby ranges to reduce request count, at the cost of reading and + buffering extra bytes. +- Interleave or race remote reads to reduce tail latency. +- Skip indexes that cannot help the current query or maintenance task. +- Reuse pages already owned by the application's buffer manager. + +The best choice depends on the environment. Local NVMe, remote object storage, +an in-memory cache, `mmap`, RDMA, `O_DIRECT`, and `io_uring` have very different +latency, concurrency, alignment, and memory requirements. There is no single +read policy that is best for all of them. + +## The hidden policy inside a pull API + +Most analytic file libraries expose a pull interface modeled after a database +iterator. For example, the main Parquet Java API is conceptually as simple as: + +```java +ParquetReader reader = ParquetReader.builder( + new GroupReadSupport(), path +).build(); + +Group record; +while ((record = reader.read()) != null) { + process(record); +} +``` + +The caller sees records, not reads. When the decoder needs another page, it +internally calls methods such as `seek` and `readFully` on a +`SeekableInputStream`. This is convenient for both the library implementer and +applications whose own execution model is also an iterator. + +However, a replaceable storage interface is not the same thing as Sans I/O. A +callback such as `read_at(offset, length)` lets the application choose *where* +bytes come from, but the decoder still chooses *which* ranges to read, *when* +to issue each request, and often *where* to buffer the result. + +This has two important consequences: + +1. **I/O optimization is limited to what the reader implements.** Adding a + new policy such as read racing or a workload-specific prefetcher requires a + change inside the format library. +2. **Memory ownership becomes blurred.** Either the reader maintains a sizable + private buffer pool, or the application adapts its own buffer manager to the + reader's I/O abstraction. Both make global memory accounting harder. + +### Async is useful, but it is not Sans I/O + +An asynchronous reader can return control while an I/O operation is pending, +so a thread does not need to sit idle. That changes *how execution waits*. It +does not necessarily change *who owns the I/O policy*. + +In most async readers, a future or stream still computes and issues reads +internally. The caller can await the result, but cannot inspect the decoder's +future byte requirements or substitute a different schedule for those reads. +By contrast, a Sans I/O decoder reports those requirements as data. It can be +driven by synchronous code, async code, a thread pool, a custom runtime, or no +runtime at all. + +## The Sans I/O contract + +We call an analytic file decoder Sans I/O if it performs no I/O, directly or +indirectly, and communicates all byte requirements to its caller. + +
+ Side-by-side comparison of an integrated decoder, where I/O is orchestrated by the library and performed through a storage callback, and a Sans I/O decoder, where I/O is orchestrated and performed by the application +
Figure 2 (paper Figure 1): An integrated reader accepts a storage callback but owns the read schedule. A Sans I/O reader leaves both orchestration and execution of I/O to the application.
+
+ +The contract has a small feedback loop. Each time the application asks the +decoder to advance, the decoder does one of three things: + +1. Produces a decoded batch. +2. Reports that decoding is finished. +3. Returns the precise byte ranges needed to make progress. + +For the third outcome, the application fetches those ranges using any strategy +and pushes the resulting buffers into the decoder. It may supply more data than +was requested, which permits prefetching. The decoder keeps all format +semantics, including pruning and the order in which metadata must be decoded, +while the host owns concurrency, caching, request coalescing, retries, memory, +and cost. + +In simplified Rust, the interaction looks like this: + +```rust +loop { + match decoder.try_decode()? { + DecodeResult::NeedsData(ranges) => { + // Any storage API, cache, runtime, or policy can be used here. + let buffers = io_policy.fetch(&ranges)?; + decoder.push_ranges(ranges, buffers)?; + } + DecodeResult::Data(batch) => consume(batch)?, + DecodeResult::Finished => break, + } +} +``` + +This is a push-style storage API: the application asks the decoder to advance, +then pushes bytes into it when requested. "Push" here describes the boundary +between storage and decoding, not the execution model used by the rest of the +query engine. + +## Putting the design into arrow-rs + +The [`ParquetPushDecoder`] implementation was released in `arrow-rs` `57.1.0`. +It was motivated by two related problems: maintaining parallel synchronous and +asynchronous readers, and repeated requests from users who needed more control +over Parquet I/O. + +A Sans I/O Parquet decoder cannot simply pause in the middle of a normal +function and wait for a read. It must preserve enough state to explain what it +needs and later resume after the caller supplies the bytes. The arrow-rs +implementation therefore models decoding as an explicit state machine. Its +[`try_decode`] method advances until it produces a batch, finishes, or +returns `NeedsData(Vec>)`. [`push_ranges`] adds reference-counted +`Bytes` buffers without prescribing how they were fetched. + +The explicit state machine pays off in reuse. The existing asynchronous +[`ParquetRecordBatchStream`] was [rewritten as a thin adapter][pr-8159] around +the push decoder: it fetches each `NeedsData` request through +`AsyncFileReader`, pushes +the buffers back, and exposes the familiar stream API. Applications that do +not need custom I/O still get a convenient reader, while format behavior lives +in one decoder core. + +The decoding loop shown earlier starts after the file metadata is available. Footer and metadata +decoding follow the same pattern through [`ParquetMetaDataPushDecoder`], so the +application can control I/O from the first footer read through the final data +page. + +## One decoder, many I/O policies + +To demonstrate what the separation enables, our [prototype driver] uses the +same Arrow Rust decoder with four deliberately different policies: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PolicyPhysical readsPrimary benefitPrimary cost
Exact rangeRead exactly the requested rangesMinimum over-read and bufferingPotentially many requests
Request coalescingMerge adjacent or nearby rangesFewer physical requestsExtra bytes and buffering
Read-aheadExtend requested ranges and retain unused bytesFewer future stalls and more cache hitsMore bandwidth and memory
Full-file cacheFetch the file once and return zero-copy slicesZero-copy hits on subsequent requests (models a buffer manager or hot cache)Buffers the entire file
+
+ +These policies optimize different things; the point is not that one always +wins. The point is that none requires a second Parquet implementation. A local +backend can use `io_uring`; an object-store backend can coalesce or race reads; +a database cache can return already-resident buffers by reference. The decoder +continues to request the same logical ranges and decode the same format. + +This separation also makes measurement clearer. The application can account +independently for logical ranges requested by the decoder, physical storage +requests after policy decisions, bytes over-read, and peak buffered memory. + +## What existing readers expose + +We surveyed eight production-grade open-source analytic file readers. The +snapshot below reflects the versions examined for the paper in mid-2026. Every +reader offered an integrated I/O path; only Arrow Rust offered a production +public Sans I/O API, while cuDF exposed an experimental hybrid path. + +
+ + + + + + + + + + + + + + + + + + + +
ImplementationFormatPublic Sans I/O pathPrefetch / coalescing
Apache Arrow C++ParquetNoInternal and external
Apache Parquet JavaParquetNoInternal
Apache Arrow GoParquetNoNone
Apache Arrow RustParquetYesExternal
cuDF (RAPIDS)ParquetExperimentalInternal; experimental external path
hyparquetParquetNoInternal
VortexVortexNoInternal and external
LanceLanceNoInternal and external
+
+ +Many of these are well-designed readers with extensible storage interfaces, +zero-copy buffers, and sophisticated prefetchers. The distinction is about +control: even when the byte source is pluggable, an integrated decoder still +decides the read schedule. Some systems, including DuckDB and Polars, go +further and maintain embedded readers tightly coupled to their own runtimes and +buffer managers. Those readers can be very effective, but their format logic +is not available as a reusable public library. + +## Why streaming CSV and JSON already look familiar + +Sans I/O is not a new idea. The term came from the Python networking community, +where protocol libraries such as `h11` and `h2` operate as state machines over +caller-provided bytes. Streaming CSV and JSON parsers commonly use the same +shape: accept a byte chunk, emit records or tokens, and report when more input +is needed. + +These formats have an easier answer to "what bytes are needed next?": usually, +the next bytes in the stream. Analytic formats such as Parquet must request +non-contiguous footers, metadata blocks, indexes, and selected pages. The idea +is the same, but the API must communicate byte ranges rather than simply ask +for another sequential chunk. + +## Tradeoffs and design guidance + +Sans I/O moves complexity to an explicit boundary; it does not make that +complexity disappear. + +- **The decoder is harder to implement.** State that was implicit in a call + stack or async future must become an explicit, testable state machine. +- **The host must drive the feedback loop.** Applications with no special I/O + requirements should not have to write that loop themselves, so libraries + should ship synchronous and asynchronous adapters. +- **Performance is not automatic.** Sans I/O enables a system-specific policy; + the application still has to choose and implement a good one. +- **The boundary must preserve zero-copy operation.** Callers should be able to + provide reference-counted or otherwise borrowed resident buffers, including + data fetched speculatively. + +For new analytic formats, we recommend making the Sans I/O state machine the +decoder core, then layering blocking, async, memory-mapped, and prefetching +readers on top. Format-specific choices such as pruning remain inside that +core. Storage-specific choices such as request size, concurrency, caching, and +buffer ownership remain outside. + +## Conclusion + +A file reader API is more than a convenience wrapper. It determines whether a +database can combine shared, well-tested format logic with the I/O and memory +architecture that makes the database distinctive. + +Tightly integrated readers are easy to start with and remain useful as +adapters. But when they are the only API, applications must either accept the +reader's policy or build another decoder. Sans I/O provides a third option: one +reusable implementation of the format, with I/O orchestration owned by each +host system. That is a better fit for the increasingly composable data systems +being built around Arrow and Parquet. + +The paper *Implementing Analytic File Formats Sans I/O* is by Andrew Lamb, +Xudong Wang, Neil Conway, and Daniël Heres and appears at CIDR 2027. The paper +is licensed under [CC BY 4.0]. We thank InfluxData, Massive, and Coralogix for +supporting this work. + +[Apache Parquet]: https://parquet.apache.org/ +[arrow-rs-parquet]: https://github.com/apache/arrow-rs/tree/57.1.0/parquet +[`ParquetPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html +[`try_decode`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_decode +[`push_ranges`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.push_ranges +[`ParquetRecordBatchStream`]: https://docs.rs/parquet/57.1.0/parquet/arrow/async_reader/struct.ParquetRecordBatchStream.html +[pr-8159]: https://github.com/apache/arrow-rs/pull/8159 +[`ParquetMetaDataPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/file/metadata/struct.ParquetMetaDataPushDecoder.html +[prototype driver]: https://github.com/xudong963/push_decoder_policies +[CC BY 4.0]: https://creativecommons.org/licenses/by/4.0/ diff --git a/img/sans-io-analytic-formats/architecture.png b/img/sans-io-analytic-formats/architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..36f1590bda2b9b9ca9cf71c11d4bf4be7a42e99a GIT binary patch literal 41705 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 z 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

Qh%v2%9bTgv_M5}IOot^4 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 F{|8G4*>?Z{ literal 0 HcmV?d00001 From 46293c42e30c3dda4029da98df50cf81a2801c40 Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Mon, 12 Oct 2026 13:15:44 +0800 Subject: [PATCH 2/7] Revise Sans I/O blog based on reviewer feedback --- ...026-08-07-analytic-file-formats-sans-io.md | 547 ++++++++++-------- 1 file changed, 291 insertions(+), 256 deletions(-) diff --git a/_posts/2026-08-07-analytic-file-formats-sans-io.md b/_posts/2026-08-07-analytic-file-formats-sans-io.md index b9aafc89542b..6d2fca39352c 100644 --- a/_posts/2026-08-07-analytic-file-formats-sans-io.md +++ b/_posts/2026-08-07-analytic-file-formats-sans-io.md @@ -26,146 +26,51 @@ limitations under the License. --> Opening a file, constructing a reader, and calling `next()` is a wonderfully -simple interface. It is also where many analytic file libraries accidentally -make a much larger architectural decision: **the decoder, rather than the -application, controls I/O**. - -That choice works well until the decoder is embedded in a system with its own -storage runtime, buffer manager, cache, or scheduler. Such a system may want to -race object-store reads, use `io_uring`, account for every buffered byte, or -reuse pages that are already resident in memory. If the file reader performs -I/O internally, the application is limited to the policies anticipated by the -library author. - -This post presents a different foundation for analytic file readers: **Sans -I/O**. A Sans I/O decoder contains all the knowledge needed to interpret a file, -but never reads the file itself. It reports which byte ranges it needs, and the -application decides how to obtain them. - -> **The core idea:** format knowledge belongs in the decoder; I/O policy belongs -> in the application. - -This post distills our CIDR 2027 paper, *Implementing Analytic File Formats Sans -I/O*, and describes the production implementation in the [Apache Arrow Rust -Parquet reader][arrow-rs-parquet]. - -## A Parquet reader is a small query engine - -[Apache Parquet] is a good example of why the I/O boundary matters. Reading a -Parquet file is not a single sequential operation. Even a simplified decoder -alternates between I/O and CPU work across at least nine states. - -The two diagrams in this post are reproduced from the paper under [CC BY 4.0]. - -

- Parquet decoding alternates between I/O and CPU stages for the file footer, metadata, optional pruning structures, and data pages -
Figure 1 (paper Figure 2): The Parquet decoding state machine. Pink states perform I/O, yellow states perform CPU work, and dashed states are optional.
-
- -The footer reveals where the file metadata lives. The metadata identifies row -groups and column chunks. Bloom filters and page indexes may eliminate some of -those chunks, and only then does the decoder know which data pages survive. The -answer to "what bytes are needed next?" therefore depends on information -decoded in earlier states, along with the query's projection, filters, and -limit. - -There are several valid ways to orchestrate these reads: - -- Prefetch the next predicted range while the CPU decodes the current one. -- Coalesce nearby ranges to reduce request count, at the cost of reading and - buffering extra bytes. -- Interleave or race remote reads to reduce tail latency. -- Skip indexes that cannot help the current query or maintenance task. -- Reuse pages already owned by the application's buffer manager. - -The best choice depends on the environment. Local NVMe, remote object storage, -an in-memory cache, `mmap`, RDMA, `O_DIRECT`, and `io_uring` have very different -latency, concurrency, alignment, and memory requirements. There is no single -read policy that is best for all of them. - -## The hidden policy inside a pull API - -Most analytic file libraries expose a pull interface modeled after a database -iterator. For example, the main Parquet Java API is conceptually as simple as: - -```java -ParquetReader reader = ParquetReader.builder( - new GroupReadSupport(), path -).build(); - -Group record; -while ((record = reader.read()) != null) { - process(record); -} -``` - -The caller sees records, not reads. When the decoder needs another page, it -internally calls methods such as `seek` and `readFully` on a -`SeekableInputStream`. This is convenient for both the library implementer and -applications whose own execution model is also an iterator. - -However, a replaceable storage interface is not the same thing as Sans I/O. A -callback such as `read_at(offset, length)` lets the application choose *where* -bytes come from, but the decoder still chooses *which* ranges to read, *when* -to issue each request, and often *where* to buffer the result. - -This has two important consequences: - -1. **I/O optimization is limited to what the reader implements.** Adding a - new policy such as read racing or a workload-specific prefetcher requires a - change inside the format library. -2. **Memory ownership becomes blurred.** Either the reader maintains a sizable - private buffer pool, or the application adapts its own buffer manager to the - reader's I/O abstraction. Both make global memory accounting harder. - -### Async is useful, but it is not Sans I/O - -An asynchronous reader can return control while an I/O operation is pending, -so a thread does not need to sit idle. That changes *how execution waits*. It -does not necessarily change *who owns the I/O policy*. - -In most async readers, a future or stream still computes and issues reads -internally. The caller can await the result, but cannot inspect the decoder's -future byte requirements or substitute a different schedule for those reads. -By contrast, a Sans I/O decoder reports those requirements as data. It can be -driven by synchronous code, async code, a thread pool, a custom runtime, or no -runtime at all. +simple interface. The reader handles fetching, buffering, and decoding, and +many applications need nothing more. The boundary becomes harder to work with +when the application already has a storage runtime, buffer manager, cache, or +scheduler of its own. + +Consider a query engine scanning several Parquet files under a shared memory +budget. It wants to serve cached ranges immediately, prioritize reads for one +query, and overlap decoding with remote I/O. To coordinate that work, it needs +to know both **what bytes the decoder needs** and **when decoding can proceed**. +A storage callback exposes some of this information, but the rest may remain +inside the reader's control loop. + +A **Sans I/O** decoder makes that boundary explicit. It interprets bytes but +never fetches them. When it cannot continue, it returns the byte ranges it +needs; the application supplies buffers and decides when to resume decoding. +Familiar blocking and asynchronous readers can be adapters around this core. + +> **The core idea:** the decoder determines its byte dependencies; the +> application schedules the I/O that satisfies them. + +Sans I/O is an established pattern in [network protocol libraries][sans-io]. +This post explores its application to analytic file formats, using the +[Apache Arrow Rust Parquet reader][arrow-rs-parquet]. The benefit is a reusable +decoder that can fit different execution and storage policies. Performance +still depends on the policy, workload, and implementation. ## The Sans I/O contract -We call an analytic file decoder Sans I/O if it performs no I/O, directly or -indirectly, and communicates all byte requirements to its caller. +The decoder performs no I/O, including through callbacks. Each time the +application asks it to advance, it does one of three things: -
- Side-by-side comparison of an integrated decoder, where I/O is orchestrated by the library and performed through a storage callback, and a Sans I/O decoder, where I/O is orchestrated and performed by the application -
Figure 2 (paper Figure 1): An integrated reader accepts a storage callback but owns the read schedule. A Sans I/O reader leaves both orchestration and execution of I/O to the application.
-
- -The contract has a small feedback loop. Each time the application asks the -decoder to advance, the decoder does one of three things: - -1. Produces a decoded batch. +1. Produces decoded data. 2. Reports that decoding is finished. -3. Returns the precise byte ranges needed to make progress. +3. Returns the byte ranges needed to make progress. -For the third outcome, the application fetches those ranges using any strategy -and pushes the resulting buffers into the decoder. It may supply more data than -was requested, which permits prefetching. The decoder keeps all format -semantics, including pruning and the order in which metadata must be decoded, -while the host owns concurrency, caching, request coalescing, retries, memory, -and cost. - -In simplified Rust, the interaction looks like this: +For the third outcome, the application obtains the bytes from storage or a +cache, supplies them to the decoder, and tries again. Errors propagate through +the API as usual. In simplified Rust, with application-provided `fetch` and +`consume` helpers, the interaction looks like this: ```rust loop { match decoder.try_decode()? { DecodeResult::NeedsData(ranges) => { - // Any storage API, cache, runtime, or policy can be used here. + // Fetch or reuse one buffer for each logical range, in order. let buffers = io_policy.fetch(&ranges)?; decoder.push_ranges(ranges, buffers)?; } @@ -175,43 +80,164 @@ loop { } ``` +The logical ranges describe the decoder's requirements, not necessarily the +physical reads. A policy can merge nearby ranges into one storage request and +return slices of the resulting buffer. It can also retain extra bytes in a +cache for later requests. The decoder does not need to know which happened. + +
+ An integrated reader invokes a storage callback from its decoding loop; a Sans I/O decoder returns byte requirements for the application to schedule and supply +
Figure 1: Moving byte requests out of the decoder's control loop gives the application an explicit boundary for scheduling I/O and decoding.
+
+ This is a push-style storage API: the application asks the decoder to advance, -then pushes bytes into it when requested. "Push" here describes the boundary -between storage and decoding, not the execution model used by the rest of the -query engine. +then pushes bytes into it. "Push" describes the boundary between storage and +decoding, not the execution model of the rest of the query engine. + +## A Parquet reader is a small query engine + +Streaming CSV and JSON parsers already use a similar pattern: accept a byte +chunk, emit records or tokens, and ask for more input. Their next input is +usually the next chunk in the stream. [Apache Parquet] requires a more complex +conversation because its reads are non-sequential and depend on earlier data. + +
+ Parquet decoding alternates between I/O and CPU stages for the file footer, metadata, optional pruning structures, and data pages +
Figure 2: A simplified Parquet decoding state machine. Pink states perform I/O, yellow states perform CPU work, and dashed states are optional.
+
+ +The file's trailing footer tells the reader how much metadata to fetch. That +metadata locates row groups and column chunks. Optional Bloom filters and page +indexes can help eliminate row groups or pages before reading their data. +Evaluating a predicate on one column can further reduce which pages are needed +from another. The next byte requirements therefore depend on previously +decoded information, as well as the query's projection and filters. + +A reusable decoder must preserve these dependencies while letting the host +choose how to satisfy them. Query planning still supplies choices such as +columns, row selections, and predicates; the decoder handles the format logic +needed to execute that selection. Separating I/O does not require giving up +filter pushdown or moving Parquet parsing into the application. + +## Why not just use a storage callback or async reader? + +A replaceable storage interface is already powerful. A `read_at(offset, +length)` callback can serve cache hits, retry failures, or race reads against +multiple replicas. An async implementation can queue requests in a shared +scheduler and return futures for their results. These optimizations do not +inherently require Sans I/O; the [Arrow Rust design discussion][async-callback] +explicitly considered such an arrangement. + +The distinction is which decisions the public API exposes. A storage callback +sees requests when the reader issues them. It does not, by itself, expose a +separate operation to advance format processing, obtain ready-to-decode work, +or discover requirements beyond the reader's current suspension point. A +batched or asynchronous callback can offer substantial scheduling control, but +that control still depends on the reader's buffering and execution contract. + +Async changes how execution waits. Sans I/O makes the decoder's byte +dependencies and resumption points explicit, independently of any particular +I/O interface. The host can keep pending requirements as ordinary data and +advance each decoder when its scheduler is ready. Reproducing this boundary +with callbacks is possible, but can require coordinating channels, futures, +and polling around the reader's control loop. + +If a reader's existing callbacks and buffering policy fit the application, +there is no need to replace them. Sans I/O is useful when the application needs +the lower-level boundary as a supported library API. + +## Where the boundary helps in practice + +Two discussions that motivated the Arrow Rust work show why that boundary +matters. + +In [the original design issue][io-motivation], an InfluxDB developer described +buffering entire files in memory or on local SSD because the reader did not +expose enough information to coordinate more selective prefetching. The +alternative was a series of smaller reads interleaved with decoding, a poor +fit for high-latency remote storage. Whole-file buffering was a workable +integration strategy, but it consumed resources and gave up some of the +benefits of reading only selected columns and pages. + +A [separate discussion about incremental decoding][incremental-decoding] +described the memory cost of buffering the pages for all selected columns in a +row group. A later [production report][wide-column-report] highlighted the +problem for columns containing very large values. A storage callback can +change how the bytes arrive, but it cannot change a decoder's requirement to +buffer that set of pages before making progress. This case also illustrates a +limit of the abstraction: exposing byte requests alone does not make a decoder +incremental at every page boundary. + +### Overlapping decoding and reads + +Arrow Rust provides a concrete example of a richer boundary through +[`try_next_reader`]. Once the required data is available, this method returns a +`ParquetRecordBatchReader` over buffered data. The application can then: + +1. Send that reader to a CPU worker to produce batches. +2. Advance the push decoder to discover the next set of byte requirements. +3. Schedule those reads while the worker decodes the preceding data. +4. Limit the number of active readers and pending reads according to its + memory and CPU budgets. + +This gives the scheduler separate handles for batch decoding and further I/O. +An ordinary storage callback cannot create that handoff on its own: the reader +also needs to expose a suitable unit of decoding work. Our [example +driver][prototype driver] demonstrates the handoff; a production scheduler +would also bound the worker queue and preserve batch order where required. + +A decoder integrated into a database can implement the same overlap. The +reason to expose it in a shared library is to let other systems use that +format implementation with their own scheduling policy. ## Putting the design into arrow-rs -The [`ParquetPushDecoder`] implementation was released in `arrow-rs` `57.1.0`. -It was motivated by two related problems: maintaining parallel synchronous and -asynchronous readers, and repeated requests from users who needed more control -over Parquet I/O. - -A Sans I/O Parquet decoder cannot simply pause in the middle of a normal -function and wait for a read. It must preserve enough state to explain what it -needs and later resume after the caller supplies the bytes. The arrow-rs -implementation therefore models decoding as an explicit state machine. Its -[`try_decode`] method advances until it produces a batch, finishes, or -returns `NeedsData(Vec>)`. [`push_ranges`] adds reference-counted -`Bytes` buffers without prescribing how they were fetched. - -The explicit state machine pays off in reuse. The existing asynchronous -[`ParquetRecordBatchStream`] was [rewritten as a thin adapter][pr-8159] around -the push decoder: it fetches each `NeedsData` request through -`AsyncFileReader`, pushes -the buffers back, and exposes the familiar stream API. Applications that do -not need custom I/O still get a convenient reader, while format behavior lives -in one decoder core. - -The decoding loop shown earlier starts after the file metadata is available. Footer and metadata -decoding follow the same pattern through [`ParquetMetaDataPushDecoder`], so the -application can control I/O from the first footer read through the final data -page. +The APIs described here are available in `arrow-rs` `57.1.0`. +[`ParquetPushDecoder`] preserves decoding state between calls to [`try_decode`] +and returns `NeedsData(Vec>)` when bytes are missing. [`push_ranges`] +accepts reference-counted `Bytes` buffers, allowing the caller to share +resident input without copying it at the API boundary. Decompression and +construction of Arrow arrays can still require allocations. + +The earlier loop starts after file metadata is available. Footer and metadata +decoding use the same pattern through [`ParquetMetaDataPushDecoder`], so the +application can control fetching from the first footer read onward. + +The existing asynchronous [`ParquetRecordBatchStream`] was [rewritten as an +adapter][pr-8159] around the push decoder. It fetches `NeedsData` requests +through `AsyncFileReader`, supplies the buffers, and exposes the familiar +stream API. This consolidates decoding control flow while retaining a +convenient entry point for applications that do not need custom orchestration. + +## What the decoder can tell a prefetcher + +`NeedsData` describes what is missing at the current decoding step. It is not +a complete plan for all future reads. If a predicate has not yet been +evaluated, the decoder may not know which pages of another column will survive. +A range-only interface also does not tell the host whether a request is for +metadata, a predicate column, or output data. + +There are several levels of prefetching. A host can read ahead around a known +range, or speculatively fetch a larger file tail to avoid a second metadata +read. With `try_next_reader`, it can discover later requirements while earlier +batches are being decoded. More selective lookahead may need additional +format-aware planning APIs that expose candidate ranges and their dependencies. +Those candidates can change after pruning; a scheduler must distinguish them +from ranges required to make progress now. + +Sans I/O therefore gives a prefetcher a place to operate, but does not supply +all the information a sophisticated prefetcher needs. Useful format knowledge +must remain accessible through the library's API rather than be reconstructed +in every storage adapter. ## One decoder, many I/O policies -To demonstrate what the separation enables, our [prototype driver] uses the -same Arrow Rust decoder with four deliberately different policies: +Our [example driver][prototype driver] uses the same decoder with four I/O +policies. They illustrate different choices at this boundary:
@@ -219,148 +245,157 @@ same Arrow Rust decoder with four deliberately different policies: - + - - + + - - + + - - - + + + - - - + + +
Policy Physical readsPrimary benefitWhen to consider it Primary cost
Exact rangeRead exactly the requested rangesMinimum over-read and bufferingRead only the requested rangesLow-latency storage or expensive over-reading Potentially many requests
Request coalescingMerge adjacent or nearby rangesFewer physical requestsMerge adjacent or nearby ranges, then return slicesHigh per-request overhead and small gaps between ranges Extra bytes and buffering
Read-aheadExtend requested ranges and retain unused bytesFewer future stalls and more cache hitsMore bandwidth and memoryExtend requested ranges and cache unused bytesLikely reuse of nearby bytes and available memoryWasted bandwidth and memory when predictions miss
Full-file cacheFetch the file once and return zero-copy slicesZero-copy hits on subsequent requests (models a buffer manager or hot cache)Buffers the entire fileFetch once and return shared slicesSmall files, repeated scans, or already-resident dataRetains the entire file
-These policies optimize different things; the point is not that one always -wins. The point is that none requires a second Parquet implementation. A local -backend can use `io_uring`; an object-store backend can coalesce or race reads; -a database cache can return already-resident buffers by reference. The decoder -continues to request the same logical ranges and decode the same format. - -This separation also makes measurement clearer. The application can account -independently for logical ranges requested by the decoder, physical storage -requests after policy decisions, bytes over-read, and peak buffered memory. +The tradeoff is between request overhead, extra bytes read, and retained +memory. Coalescing is attractive when the avoided request costs more than +reading the intervening bytes. Read-ahead depends on those bytes being useful +soon enough to justify retaining them. Full-file caching can be appropriate +for a small hot file and wasteful for a large selective scan. -## What existing readers expose - -We surveyed eight production-grade open-source analytic file readers. The -snapshot below reflects the versions examined for the paper in mid-2026. Every -reader offered an integrated I/O path; only Arrow Rust offered a production -public Sans I/O API, while cuDF exposed an experimental hybrid path. - -
- - - - - - - - - - - - - - - - - - - -
ImplementationFormatPublic Sans I/O pathPrefetch / coalescing
Apache Arrow C++ParquetNoInternal and external
Apache Parquet JavaParquetNoInternal
Apache Arrow GoParquetNoNone
Apache Arrow RustParquetYesExternal
cuDF (RAPIDS)ParquetExperimentalInternal; experimental external path
hyparquetParquetNoInternal
VortexVortexNoInternal and external
LanceLanceNoInternal and external
-
+These are composable choices: a host might satisfy cache hits first and +coalesce only the misses. The examples show that policies can change without +changing the decoder; they do not establish a universally faster policy. -Many of these are well-designed readers with extensible storage interfaces, -zero-copy buffers, and sophisticated prefetchers. The distinction is about -control: even when the byte source is pluggable, an integrated decoder still -decides the read schedule. Some systems, including DuckDB and Polars, go -further and maintain embedded readers tightly coupled to their own runtimes and -buffer managers. Those readers can be very effective, but their format logic -is not available as a reusable public library. +## Building on existing designs -## Why streaming CSV and JSON already look familiar +The separation has substantial precedent. Beyond [networking Sans I/O][sans-io] +and streaming parsers, [`parquet2`][parquet2] separates reading compressed pages +from decompression and deserialization, leaving parallelism to its users. +[Polars' Parquet implementation][polars-parquet] also exposes distinct reading, +compression, and page modules. These are useful forms of separation, with +boundaries at different levels of the read pipeline. -Sans I/O is not a new idea. The term came from the Python networking community, -where protocol libraries such as `h11` and `h2` operate as state machines over -caller-provided bytes. Streaming CSV and JSON parsers commonly use the same -shape: accept a byte chunk, emit records or tokens, and report when more input -is needed. +cuDF's [experimental hybrid scan reader][cudf-hybrid] exposes methods to obtain +page-index and column-chunk byte ranges and to supply buffers for processing. +It illustrates how explicit byte dependencies can support a staged scan API, +even when the interface is specific to a particular execution environment. -These formats have an easier answer to "what bytes are needed next?": usually, -the next bytes in the stream. Analytic formats such as Parquet must request -non-contiguous footers, metadata blocks, indexes, and selected pages. The idea -is the same, but the API must communicate byte ranges rather than simply ask -for another sequential chunk. +Page-level building blocks let the host assemble its own scan control loop. +The push decoder keeps that format-dependent state machine in the library, +exposing its byte requirements across metadata, filtering, and data decoding. +This packages established separation-of-concerns ideas into a reusable API for +a format whose future reads depend on earlier results. Integrated readers can +also provide sophisticated scheduling; the useful boundary depends on which +parts of the scan the host needs to control. -## Tradeoffs and design guidance +## Costs and open challenges Sans I/O moves complexity to an explicit boundary; it does not make that complexity disappear. -- **The decoder is harder to implement.** State that was implicit in a call - stack or async future must become an explicit, testable state machine. -- **The host must drive the feedback loop.** Applications with no special I/O - requirements should not have to write that loop themselves, so libraries - should ship synchronous and asynchronous adapters. -- **Performance is not automatic.** Sans I/O enables a system-specific policy; - the application still has to choose and implement a good one. -- **The boundary must preserve zero-copy operation.** Callers should be able to - provide reference-counted or otherwise borrowed resident buffers, including - data fetched speculatively. - -For new analytic formats, we recommend making the Sans I/O state machine the -decoder core, then layering blocking, async, memory-mapped, and prefetching -readers on top. Format-specific choices such as pruning remain inside that -core. Storage-specific choices such as request size, concurrency, caching, and -buffer ownership remain outside. +- **State-machine complexity.** A decoder must preserve partially completed + work, buffer references, and error state across resumptions. A refactor must + retain the existing reader's handling of nested data, dictionaries, filters, + and optional indexes. Shared conformance tests help keep adapters consistent. +- **Buffer ownership and accounting.** Reference-counted input avoids copies, + but a small slice can keep a large allocation alive. The host must account + for caches, outstanding reads, decoder inputs, decompression buffers, and + output batches. Arrow Rust's [`buffered_bytes`] reports unconsumed input + bytes, not total memory usage. +- **Backpressure and read granularity.** The host can stop advancing decoders + or issuing speculative reads when its budget is full. It must still allow + enough memory to satisfy a decoder's next dependency set. In the version + described here, a step can require pages across the selected columns of a + row group; reducing output batch size alone does not bound input buffering. +- **Concurrency and cancellation.** The host must track outstanding reads and + their destination decoders, handle completions, and serialize mutation of + each decoder. On cancellation, it must cancel or disregard pending results + and release its buffers. Dropping the decoder alone does not cancel I/O + owned by the application. +- **Preserving performance.** Matching a tuned integrated reader requires + preserving effective pruning, coalescing, CPU/I/O overlap, and buffer reuse. + Extra allocations, tiny requests, or excessive task handoffs can erase the + benefits. The boundary makes these policies replaceable, not automatically + efficient. + +High-level adapters remain essential. Most users should be able to select a +file and iterate over batches without implementing a scheduler. + +## A shared boundary across formats + +The same architectural pattern could help hosts integrate Parquet, Lance, +Vortex, and future formats with a common cache, request scheduler, and memory +budget. Each decoder would explain its byte dependencies; the host would reuse +its storage machinery across them. + +That is an architectural direction, not a claim that one standardized API +already fits every format. A common interface would need to specify file +identity, range and buffer lifetimes, errors, and cancellation. It would also +need room for optional lookahead and format-specific capabilities such as +pruning and decoding work on accelerators. Flattening everything to an +unannotated list of ranges could hide information the scheduler needs. + +For libraries intended to serve several host systems, a practical starting +point is an I/O-free decoder core with useful planning hooks and convenient +adapters. Existing libraries can expose that boundary incrementally while +keeping their optimized high-level readers. ## Conclusion -A file reader API is more than a convenience wrapper. It determines whether a -database can combine shared, well-tested format logic with the I/O and memory -architecture that makes the database distinctive. +A file reader API determines how easily an application can combine shared +format logic with its own storage and execution model. Sans I/O makes byte +dependencies explicit and lets the host decide how to satisfy them. Arrow +Rust shows how this can coexist with a familiar asynchronous reader and expose +additional control to applications that need it. -Tightly integrated readers are easy to start with and remain useful as -adapters. But when they are the only API, applications must either accept the -reader's policy or build another decoder. Sans I/O provides a third option: one -reusable implementation of the format, with I/O orchestration owned by each -host system. That is a better fit for the increasingly composable data systems -being built around Arrow and Parquet. +The goal is to make a well-tested decoder reusable across systems with +different scheduling, caching, and memory requirements, while preserving the +format knowledge that makes selective reads effective. -The paper *Implementing Analytic File Formats Sans I/O* is by Andrew Lamb, -Xudong Wang, Neil Conway, and Daniël Heres and appears at CIDR 2027. The paper -is licensed under [CC BY 4.0]. We thank InfluxData, Massive, and Coralogix for -supporting this work. +This post is based on *Implementing Analytic File Formats Sans I/O*, by Andrew +Lamb, Xudong Wang, Neil Conway, and Daniël Heres. The two diagrams are reproduced +from that manuscript under [CC BY 4.0]. We thank InfluxData, Massive, and +Coralogix for supporting this work. [Apache Parquet]: https://parquet.apache.org/ [arrow-rs-parquet]: https://github.com/apache/arrow-rs/tree/57.1.0/parquet +[sans-io]: https://sans-io.readthedocs.io/ [`ParquetPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html [`try_decode`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_decode +[`try_next_reader`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_next_reader [`push_ranges`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.push_ranges +[`buffered_bytes`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.buffered_bytes [`ParquetRecordBatchStream`]: https://docs.rs/parquet/57.1.0/parquet/arrow/async_reader/struct.ParquetRecordBatchStream.html [pr-8159]: https://github.com/apache/arrow-rs/pull/8159 [`ParquetMetaDataPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/file/metadata/struct.ParquetMetaDataPushDecoder.html +[io-motivation]: https://github.com/apache/arrow-rs/issues/7983 +[async-callback]: https://github.com/apache/arrow-rs/issues/7983#issuecomment-3165065017 +[incremental-decoding]: https://github.com/apache/arrow-rs/issues/6946 +[wide-column-report]: https://github.com/apache/arrow-rs/issues/6946#issuecomment-5061172503 [prototype driver]: https://github.com/xudong963/push_decoder_policies +[parquet2]: https://docs.rs/crate/parquet2/0.17.2 +[polars-parquet]: https://github.com/pola-rs/polars/tree/1b83c09eed30fd2fb4c59f26a1bc2d8c09cad22b/crates/polars-parquet/src/parquet +[cudf-hybrid]: https://github.com/NVIDIA/cudf/blob/4f4de11b87d5f639d51f1ace71dbb3c19eaa15dc/cpp/include/cudf/io/experimental/hybrid_scan.hpp [CC BY 4.0]: https://creativecommons.org/licenses/by/4.0/ From 17c19455adbb39fcc6e048b37ab06b320c710bb3 Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Mon, 12 Oct 2026 13:25:21 +0800 Subject: [PATCH 3/7] Name Andrew in the Sans I/O design example --- _posts/2026-08-07-analytic-file-formats-sans-io.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2026-08-07-analytic-file-formats-sans-io.md b/_posts/2026-08-07-analytic-file-formats-sans-io.md index 6d2fca39352c..9285a120d87a 100644 --- a/_posts/2026-08-07-analytic-file-formats-sans-io.md +++ b/_posts/2026-08-07-analytic-file-formats-sans-io.md @@ -155,7 +155,7 @@ the lower-level boundary as a supported library API. Two discussions that motivated the Arrow Rust work show why that boundary matters. -In [the original design issue][io-motivation], an InfluxDB developer described +In [the original design issue][io-motivation], Andrew described buffering entire files in memory or on local SSD because the reader did not expose enough information to coordinate more selective prefetching. The alternative was a series of smaller reads interleaved with decoding, a poor From 0dee9f38bb6d990225a316f6309c46b96b6dec39 Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Mon, 12 Oct 2026 13:33:16 +0800 Subject: [PATCH 4/7] Restore original Sans I/O tradeoffs section --- ...026-08-07-analytic-file-formats-sans-io.md | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/_posts/2026-08-07-analytic-file-formats-sans-io.md b/_posts/2026-08-07-analytic-file-formats-sans-io.md index 9285a120d87a..14be3ab438f6 100644 --- a/_posts/2026-08-07-analytic-file-formats-sans-io.md +++ b/_posts/2026-08-07-analytic-file-formats-sans-io.md @@ -310,38 +310,27 @@ a format whose future reads depend on earlier results. Integrated readers can also provide sophisticated scheduling; the useful boundary depends on which parts of the scan the host needs to control. -## Costs and open challenges +## Tradeoffs and design guidance Sans I/O moves complexity to an explicit boundary; it does not make that complexity disappear. -- **State-machine complexity.** A decoder must preserve partially completed - work, buffer references, and error state across resumptions. A refactor must - retain the existing reader's handling of nested data, dictionaries, filters, - and optional indexes. Shared conformance tests help keep adapters consistent. -- **Buffer ownership and accounting.** Reference-counted input avoids copies, - but a small slice can keep a large allocation alive. The host must account - for caches, outstanding reads, decoder inputs, decompression buffers, and - output batches. Arrow Rust's [`buffered_bytes`] reports unconsumed input - bytes, not total memory usage. -- **Backpressure and read granularity.** The host can stop advancing decoders - or issuing speculative reads when its budget is full. It must still allow - enough memory to satisfy a decoder's next dependency set. In the version - described here, a step can require pages across the selected columns of a - row group; reducing output batch size alone does not bound input buffering. -- **Concurrency and cancellation.** The host must track outstanding reads and - their destination decoders, handle completions, and serialize mutation of - each decoder. On cancellation, it must cancel or disregard pending results - and release its buffers. Dropping the decoder alone does not cancel I/O - owned by the application. -- **Preserving performance.** Matching a tuned integrated reader requires - preserving effective pruning, coalescing, CPU/I/O overlap, and buffer reuse. - Extra allocations, tiny requests, or excessive task handoffs can erase the - benefits. The boundary makes these policies replaceable, not automatically - efficient. - -High-level adapters remain essential. Most users should be able to select a -file and iterate over batches without implementing a scheduler. +- **The decoder is harder to implement.** State that was implicit in a call + stack or async future must become an explicit, testable state machine. +- **The host must drive the feedback loop.** Applications with no special I/O + requirements should not have to write that loop themselves, so libraries + should ship synchronous and asynchronous adapters. +- **Performance is not automatic.** Sans I/O enables a system-specific policy; + the application still has to choose and implement a good one. +- **The boundary must preserve zero-copy operation.** Callers should be able to + provide reference-counted or otherwise borrowed resident buffers, including + data fetched speculatively. + +For new analytic formats, we recommend making the Sans I/O state machine the +decoder core, then layering blocking, async, memory-mapped, and prefetching +readers on top. Format-specific choices such as pruning remain inside that +core. Storage-specific choices such as request size, concurrency, caching, and +buffer ownership remain outside. ## A shared boundary across formats @@ -386,7 +375,6 @@ Coralogix for supporting this work. [`try_decode`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_decode [`try_next_reader`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_next_reader [`push_ranges`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.push_ranges -[`buffered_bytes`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.buffered_bytes [`ParquetRecordBatchStream`]: https://docs.rs/parquet/57.1.0/parquet/arrow/async_reader/struct.ParquetRecordBatchStream.html [pr-8159]: https://github.com/apache/arrow-rs/pull/8159 [`ParquetMetaDataPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/file/metadata/struct.ParquetMetaDataPushDecoder.html From 9252bf599150ea152f8ebd56394d65b2c6ed6d79 Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Mon, 12 Oct 2026 13:36:46 +0800 Subject: [PATCH 5/7] Update Sans I/O blog publication date --- ...s-sans-io.md => 2026-10-12-analytic-file-formats-sans-io.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename _posts/{2026-08-07-analytic-file-formats-sans-io.md => 2026-10-12-analytic-file-formats-sans-io.md} (99%) diff --git a/_posts/2026-08-07-analytic-file-formats-sans-io.md b/_posts/2026-10-12-analytic-file-formats-sans-io.md similarity index 99% rename from _posts/2026-08-07-analytic-file-formats-sans-io.md rename to _posts/2026-10-12-analytic-file-formats-sans-io.md index 14be3ab438f6..ca9088876292 100644 --- a/_posts/2026-08-07-analytic-file-formats-sans-io.md +++ b/_posts/2026-10-12-analytic-file-formats-sans-io.md @@ -2,7 +2,7 @@ layout: post title: "Implementing Analytic File Formats Sans I/O" description: "Why separating Parquet decoding from storage I/O makes analytic file readers more composable, and how arrow-rs puts the design into practice" -date: "2026-08-07 00:00:00" +date: "2026-10-12 00:00:00" author: "Andrew Lamb, Xudong Wang, Neil Conway, and Daniël Heres" categories: [parquet, arrow] --- From 1361189cd46e609241ec62c6fa526084772e967a Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Wed, 14 Oct 2026 08:54:46 +0800 Subject: [PATCH 6/7] Focus Sans I/O blog on the push decoder API --- ...026-10-12-analytic-file-formats-sans-io.md | 393 ++++++------------ 1 file changed, 117 insertions(+), 276 deletions(-) diff --git a/_posts/2026-10-12-analytic-file-formats-sans-io.md b/_posts/2026-10-12-analytic-file-formats-sans-io.md index ca9088876292..c0035311dd06 100644 --- a/_posts/2026-10-12-analytic-file-formats-sans-io.md +++ b/_posts/2026-10-12-analytic-file-formats-sans-io.md @@ -1,7 +1,7 @@ --- layout: post title: "Implementing Analytic File Formats Sans I/O" -description: "Why separating Parquet decoding from storage I/O makes analytic file readers more composable, and how arrow-rs puts the design into practice" +description: "How the Arrow Rust Parquet push decoder exposes byte requirements so applications can control storage I/O" date: "2026-10-12 00:00:00" author: "Andrew Lamb, Xudong Wang, Neil Conway, and Daniël Heres" categories: [parquet, arrow] @@ -25,32 +25,16 @@ limitations under the License. {% endcomment %} --> -Opening a file, constructing a reader, and calling `next()` is a wonderfully -simple interface. The reader handles fetching, buffering, and decoding, and -many applications need nothing more. The boundary becomes harder to work with -when the application already has a storage runtime, buffer manager, cache, or -scheduler of its own. - -Consider a query engine scanning several Parquet files under a shared memory -budget. It wants to serve cached ranges immediately, prioritize reads for one -query, and overlap decoding with remote I/O. To coordinate that work, it needs -to know both **what bytes the decoder needs** and **when decoding can proceed**. -A storage callback exposes some of this information, but the rest may remain -inside the reader's control loop. - -A **Sans I/O** decoder makes that boundary explicit. It interprets bytes but -never fetches them. When it cannot continue, it returns the byte ranges it -needs; the application supplies buffers and decides when to resume decoding. -Familiar blocking and asynchronous readers can be adapters around this core. - -> **The core idea:** the decoder determines its byte dependencies; the -> application schedules the I/O that satisfies them. +A **Sans I/O** decoder interprets bytes but never fetches them. When it cannot +continue, it returns the byte ranges it needs; the application supplies buffers +and decides when to resume decoding. This exposes a boundary where an +application can use its own cache, storage scheduler, and memory policy. Sans I/O is an established pattern in [network protocol libraries][sans-io]. -This post explores its application to analytic file formats, using the -[Apache Arrow Rust Parquet reader][arrow-rs-parquet]. The benefit is a reusable -decoder that can fit different execution and storage policies. Performance -still depends on the policy, workload, and implementation. +This post introduces the [Arrow Rust Parquet reader's][arrow-rs-parquet] push +API, shows how an application drives it, and explains what control that API +provides. We plan to cover prefetching and its measured performance in a +follow-up post. ## The Sans I/O contract @@ -61,16 +45,15 @@ application asks it to advance, it does one of three things: 2. Reports that decoding is finished. 3. Returns the byte ranges needed to make progress. -For the third outcome, the application obtains the bytes from storage or a -cache, supplies them to the decoder, and tries again. Errors propagate through -the API as usual. In simplified Rust, with application-provided `fetch` and -`consume` helpers, the interaction looks like this: +[`ParquetPushDecoder`] exposes this interaction through [`try_decode`] and +[`push_ranges`]. In simplified Rust, with application-provided `fetch` and +`consume` helpers: ```rust loop { match decoder.try_decode()? { DecodeResult::NeedsData(ranges) => { - // Fetch or reuse one buffer for each logical range, in order. + // Return one buffer for each logical range, in order. let buffers = io_policy.fetch(&ranges)?; decoder.push_ranges(ranges, buffers)?; } @@ -80,28 +63,26 @@ loop { } ``` -The logical ranges describe the decoder's requirements, not necessarily the -physical reads. A policy can merge nearby ranges into one storage request and -return slices of the resulting buffer. It can also retain extra bytes in a -cache for later requests. The decoder does not need to know which happened. +These APIs are available in `arrow-rs` `57.1.0`, the version linked here. +The loop starts after file metadata is available; footer and metadata decoding +use the same request-and-supply pattern through [`ParquetMetaDataPushDecoder`]. +Errors propagate through the API as usual.
An integrated reader invokes a storage callback from its decoding loop; a Sans I/O decoder returns byte requirements for the application to schedule and supply -
Figure 1: Moving byte requests out of the decoder's control loop gives the application an explicit boundary for scheduling I/O and decoding.
+
Figure 1: The decoder determines its byte dependencies; the application schedules the I/O that satisfies them.
-This is a push-style storage API: the application asks the decoder to advance, -then pushes bytes into it. "Push" describes the boundary between storage and -decoding, not the execution model of the rest of the query engine. +"Push" describes the application supplying bytes to the decoder. It does not +prescribe the execution model of the rest of the query engine. -## A Parquet reader is a small query engine +## Why Parquet needs a conversation -Streaming CSV and JSON parsers already use a similar pattern: accept a byte -chunk, emit records or tokens, and ask for more input. Their next input is -usually the next chunk in the stream. [Apache Parquet] requires a more complex -conversation because its reads are non-sequential and depend on earlier data. +Streaming CSV and JSON parsers can accept chunks and ask for more input, usually +at the next position in the stream. [Apache Parquet] reads are non-sequential +and depend on earlier data.
Figure 2: A simplified Parquet decoding state machine. Pink states perform I/O, yellow states perform CPU work, and dashed states are optional.
-The file's trailing footer tells the reader how much metadata to fetch. That -metadata locates row groups and column chunks. Optional Bloom filters and page -indexes can help eliminate row groups or pages before reading their data. -Evaluating a predicate on one column can further reduce which pages are needed -from another. The next byte requirements therefore depend on previously -decoded information, as well as the query's projection and filters. - -A reusable decoder must preserve these dependencies while letting the host -choose how to satisfy them. Query planning still supplies choices such as -columns, row selections, and predicates; the decoder handles the format logic -needed to execute that selection. Separating I/O does not require giving up -filter pushdown or moving Parquet parsing into the application. - -## Why not just use a storage callback or async reader? - -A replaceable storage interface is already powerful. A `read_at(offset, -length)` callback can serve cache hits, retry failures, or race reads against -multiple replicas. An async implementation can queue requests in a shared -scheduler and return futures for their results. These optimizations do not -inherently require Sans I/O; the [Arrow Rust design discussion][async-callback] -explicitly considered such an arrangement. - -The distinction is which decisions the public API exposes. A storage callback -sees requests when the reader issues them. It does not, by itself, expose a -separate operation to advance format processing, obtain ready-to-decode work, -or discover requirements beyond the reader's current suspension point. A -batched or asynchronous callback can offer substantial scheduling control, but -that control still depends on the reader's buffering and execution contract. - -Async changes how execution waits. Sans I/O makes the decoder's byte -dependencies and resumption points explicit, independently of any particular -I/O interface. The host can keep pending requirements as ordinary data and -advance each decoder when its scheduler is ready. Reproducing this boundary -with callbacks is possible, but can require coordinating channels, futures, -and polling around the reader's control loop. - -If a reader's existing callbacks and buffering policy fit the application, -there is no need to replace them. Sans I/O is useful when the application needs -the lower-level boundary as a supported library API. - -## Where the boundary helps in practice - -Two discussions that motivated the Arrow Rust work show why that boundary -matters. - -In [the original design issue][io-motivation], Andrew described -buffering entire files in memory or on local SSD because the reader did not -expose enough information to coordinate more selective prefetching. The -alternative was a series of smaller reads interleaved with decoding, a poor -fit for high-latency remote storage. Whole-file buffering was a workable -integration strategy, but it consumed resources and gave up some of the -benefits of reading only selected columns and pages. - -A [separate discussion about incremental decoding][incremental-decoding] -described the memory cost of buffering the pages for all selected columns in a -row group. A later [production report][wide-column-report] highlighted the -problem for columns containing very large values. A storage callback can -change how the bytes arrive, but it cannot change a decoder's requirement to -buffer that set of pages before making progress. This case also illustrates a -limit of the abstraction: exposing byte requests alone does not make a decoder -incremental at every page boundary. - -### Overlapping decoding and reads - -Arrow Rust provides a concrete example of a richer boundary through -[`try_next_reader`]. Once the required data is available, this method returns a -`ParquetRecordBatchReader` over buffered data. The application can then: - -1. Send that reader to a CPU worker to produce batches. -2. Advance the push decoder to discover the next set of byte requirements. -3. Schedule those reads while the worker decodes the preceding data. -4. Limit the number of active readers and pending reads according to its - memory and CPU budgets. - -This gives the scheduler separate handles for batch decoding and further I/O. -An ordinary storage callback cannot create that handoff on its own: the reader -also needs to expose a suitable unit of decoding work. Our [example -driver][prototype driver] demonstrates the handoff; a production scheduler -would also bound the worker queue and preserve batch order where required. - -A decoder integrated into a database can implement the same overlap. The -reason to expose it in a shared library is to let other systems use that -format implementation with their own scheduling policy. - -## Putting the design into arrow-rs - -The APIs described here are available in `arrow-rs` `57.1.0`. -[`ParquetPushDecoder`] preserves decoding state between calls to [`try_decode`] -and returns `NeedsData(Vec>)` when bytes are missing. [`push_ranges`] -accepts reference-counted `Bytes` buffers, allowing the caller to share -resident input without copying it at the API boundary. Decompression and -construction of Arrow arrays can still require allocations. - -The earlier loop starts after file metadata is available. Footer and metadata -decoding use the same pattern through [`ParquetMetaDataPushDecoder`], so the -application can control fetching from the first footer read onward. - -The existing asynchronous [`ParquetRecordBatchStream`] was [rewritten as an -adapter][pr-8159] around the push decoder. It fetches `NeedsData` requests -through `AsyncFileReader`, supplies the buffers, and exposes the familiar -stream API. This consolidates decoding control flow while retaining a -convenient entry point for applications that do not need custom orchestration. - -## What the decoder can tell a prefetcher - -`NeedsData` describes what is missing at the current decoding step. It is not -a complete plan for all future reads. If a predicate has not yet been -evaluated, the decoder may not know which pages of another column will survive. -A range-only interface also does not tell the host whether a request is for -metadata, a predicate column, or output data. - -There are several levels of prefetching. A host can read ahead around a known -range, or speculatively fetch a larger file tail to avoid a second metadata -read. With `try_next_reader`, it can discover later requirements while earlier -batches are being decoded. More selective lookahead may need additional -format-aware planning APIs that expose candidate ranges and their dependencies. -Those candidates can change after pruning; a scheduler must distinguish them -from ranges required to make progress now. - -Sans I/O therefore gives a prefetcher a place to operate, but does not supply -all the information a sophisticated prefetcher needs. Useful format knowledge -must remain accessible through the library's API rather than be reconstructed -in every storage adapter. - -## One decoder, many I/O policies - -Our [example driver][prototype driver] uses the same decoder with four I/O -policies. They illustrate different choices at this boundary: - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PolicyPhysical readsWhen to consider itPrimary cost
Exact rangeRead only the requested rangesLow-latency storage or expensive over-readingPotentially many requests
Request coalescingMerge adjacent or nearby ranges, then return slicesHigh per-request overhead and small gaps between rangesExtra bytes and buffering
Read-aheadExtend requested ranges and cache unused bytesLikely reuse of nearby bytes and available memoryWasted bandwidth and memory when predictions miss
Full-file cacheFetch once and return shared slicesSmall files, repeated scans, or already-resident dataRetains the entire file
-
- -The tradeoff is between request overhead, extra bytes read, and retained -memory. Coalescing is attractive when the avoided request costs more than -reading the intervening bytes. Read-ahead depends on those bytes being useful -soon enough to justify retaining them. Full-file caching can be appropriate -for a small hot file and wasteful for a large selective scan. - -These are composable choices: a host might satisfy cache hits first and -coalesce only the misses. The examples show that policies can change without -changing the decoder; they do not establish a universally faster policy. - -## Building on existing designs - -The separation has substantial precedent. Beyond [networking Sans I/O][sans-io] -and streaming parsers, [`parquet2`][parquet2] separates reading compressed pages -from decompression and deserialization, leaving parallelism to its users. -[Polars' Parquet implementation][polars-parquet] also exposes distinct reading, -compression, and page modules. These are useful forms of separation, with -boundaries at different levels of the read pipeline. - -cuDF's [experimental hybrid scan reader][cudf-hybrid] exposes methods to obtain -page-index and column-chunk byte ranges and to supply buffers for processing. -It illustrates how explicit byte dependencies can support a staged scan API, -even when the interface is specific to a particular execution environment. - -Page-level building blocks let the host assemble its own scan control loop. -The push decoder keeps that format-dependent state machine in the library, -exposing its byte requirements across metadata, filtering, and data decoding. -This packages established separation-of-concerns ideas into a reusable API for -a format whose future reads depend on earlier results. Integrated readers can -also provide sophisticated scheduling; the useful boundary depends on which -parts of the scan the host needs to control. +The trailing footer locates metadata, which locates row groups and column +chunks. Optional Bloom filters and page indexes can help eliminate row groups +or pages. Evaluating a predicate on one column can further reduce the pages +needed from another. The decoder retains this format-dependent state machine: +the application supplies the projection, row selections, and predicates, then +satisfies the byte requests that result. + +`NeedsData` describes what is missing **now**, not every range that might be +needed later. In particular, a predicate that has not yet run may eliminate +future reads. Selective prefetching therefore needs additional planning +information; the basic push loop does not provide a complete future read plan. + +## From logical ranges to physical reads + +The ranges in `NeedsData` specify the decoder's requirements. They need not map +one-to-one to storage requests. Consider an illustrative request for +`100..200` and `220..300`, using exclusive end offsets: + +| Policy | Physical reads | Bytes fetched | Buffers supplied to the decoder | +| --- | --- | --- | --- | +| Exact ranges | `100..200`, `220..300` | 180 | The two fetched buffers | +| Coalesced range | `100..300` | 200 | Slices `0..100` and `120..200` of the fetched buffer | + +For this particular request, a coalescing policy could use the following code. +`read_range` is an application helper returning a reference-counted `Bytes` +buffer: + +```rust +let buffer = read_range(100..300)?; +decoder.push_ranges( + vec![100..200, 220..300], + vec![buffer.slice(0..100), buffer.slice(120..200)], +)?; +``` + +The decoder receives the same logical bytes in both cases. Coalescing replaces +two storage reads with one by fetching the 20-byte gap. The slices share the +allocation without copying their contents. Whether saving a request outweighs +reading extra bytes depends on the storage system; this example demonstrates +the mechanism, not a measured speedup. Decoding and decompression can still +allocate output buffers. + +A cache fits at the same boundary. If `100..300` is already resident, the host +can supply those slices without another read. If it fetches beyond the current +request, it can retain the extra bytes in its own cache and use them to satisfy +later requests. That also makes the host responsible for retaining and evicting +those bytes. Our [example driver][prototype driver] implements several such +policies around the same decoder. + +## Scheduling decoding and I/O + +A storage callback can already implement caching, retries, and request +coalescing. An async callback can submit requests to a shared scheduler. These +capabilities do not inherently require Sans I/O, as the [original design +discussion][async-callback] also considered. + +The push API makes advancing the decoder a separate operation from satisfying +its requests. A host can keep missing ranges as ordinary data and resume each +decoder when its scheduler is ready. Additional control requires additional API +support. For example, [`try_next_reader`] returns a +`ParquetRecordBatchReader` once the input needed for that reader is available. +The host can then: + +1. Send that reader to a CPU worker to produce batches from buffered input. +2. Advance the push decoder to discover the next byte requirements. +3. Fetch those bytes while the worker decodes the preceding data. + +The independent reader is what makes this handoff possible. The host must still +bound active readers and pending reads, and preserve output order where +required. Merely using an async fetch callback does not define that handoff. +Likewise, reducing the output batch size does not by itself reduce the input +that must be buffered before a reader is ready. + +This separation builds on existing designs: [`parquet2`][parquet2] separates +page reading from decompression and deserialization, while cuDF's +[experimental hybrid scan reader][cudf-hybrid] exposes staged byte-range and +buffer operations. The push decoder packages Parquet's dependent read steps +into a state machine that the host can drive through one request-and-supply +interface. ## Tradeoffs and design guidance @@ -332,36 +193,19 @@ readers on top. Format-specific choices such as pruning remain inside that core. Storage-specific choices such as request size, concurrency, caching, and buffer ownership remain outside. -## A shared boundary across formats - -The same architectural pattern could help hosts integrate Parquet, Lance, -Vortex, and future formats with a common cache, request scheduler, and memory -budget. Each decoder would explain its byte dependencies; the host would reuse -its storage machinery across them. - -That is an architectural direction, not a claim that one standardized API -already fits every format. A common interface would need to specify file -identity, range and buffer lifetimes, errors, and cancellation. It would also -need room for optional lookahead and format-specific capabilities such as -pruning and decoding work on accelerators. Flattening everything to an -unannotated list of ranges could hide information the scheduler needs. - -For libraries intended to serve several host systems, a practical starting -point is an I/O-free decoder core with useful planning hooks and convenient -adapters. Existing libraries can expose that boundary incrementally while -keeping their optimized high-level readers. - -## Conclusion +## What comes next -A file reader API determines how easily an application can combine shared -format logic with its own storage and execution model. Sans I/O makes byte -dependencies explicit and lets the host decide how to satisfy them. Arrow -Rust shows how this can coexist with a familiar asynchronous reader and expose -additional control to applications that need it. +The API gives applications explicit byte requirements and control over when to +resume decoding. Effective prefetching also needs information about future +reads and a policy for spending bandwidth and memory. -The goal is to make a well-tested decoder reusable across systems with -different scheduling, caching, and memory requirements, while preserving the -format knowledge that makes selective reads effective. +Recent work addresses those pieces separately: [`scan_plan()`][scan-plan] has +merged into `arrow-rs` main to expose candidate future ranges, while +[batch-granular decoding][batch-decoding] and a [DataFusion read-ahead +prototype][datafusion-read-ahead] are under review. These additions are separate +from the `57.1.0` examples above. We plan to use a follow-up post to explain the +prefetching policy and evaluate it on object storage alongside a local-storage +baseline. This post is based on *Implementing Analytic File Formats Sans I/O*, by Andrew Lamb, Xudong Wang, Neil Conway, and Daniël Heres. The two diagrams are reproduced @@ -375,15 +219,12 @@ Coralogix for supporting this work. [`try_decode`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_decode [`try_next_reader`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.try_next_reader [`push_ranges`]: https://docs.rs/parquet/57.1.0/parquet/arrow/push_decoder/struct.ParquetPushDecoder.html#method.push_ranges -[`ParquetRecordBatchStream`]: https://docs.rs/parquet/57.1.0/parquet/arrow/async_reader/struct.ParquetRecordBatchStream.html -[pr-8159]: https://github.com/apache/arrow-rs/pull/8159 [`ParquetMetaDataPushDecoder`]: https://docs.rs/parquet/57.1.0/parquet/file/metadata/struct.ParquetMetaDataPushDecoder.html -[io-motivation]: https://github.com/apache/arrow-rs/issues/7983 [async-callback]: https://github.com/apache/arrow-rs/issues/7983#issuecomment-3165065017 -[incremental-decoding]: https://github.com/apache/arrow-rs/issues/6946 -[wide-column-report]: https://github.com/apache/arrow-rs/issues/6946#issuecomment-5061172503 [prototype driver]: https://github.com/xudong963/push_decoder_policies [parquet2]: https://docs.rs/crate/parquet2/0.17.2 -[polars-parquet]: https://github.com/pola-rs/polars/tree/1b83c09eed30fd2fb4c59f26a1bc2d8c09cad22b/crates/polars-parquet/src/parquet [cudf-hybrid]: https://github.com/NVIDIA/cudf/blob/4f4de11b87d5f639d51f1ace71dbb3c19eaa15dc/cpp/include/cudf/io/experimental/hybrid_scan.hpp +[scan-plan]: https://github.com/apache/arrow-rs/pull/10555 +[batch-decoding]: https://github.com/apache/arrow-rs/pull/11238 +[datafusion-read-ahead]: https://github.com/apache/datafusion/pull/24086 [CC BY 4.0]: https://creativecommons.org/licenses/by/4.0/ From 8938a4dd5f5721ff6fd9915b77ccb92a11668204 Mon Sep 17 00:00:00 2001 From: "xudong.w" Date: Wed, 14 Oct 2026 10:29:24 +0800 Subject: [PATCH 7/7] Clarify byte-range example and improve blog formatting --- ...026-10-12-analytic-file-formats-sans-io.md | 108 +++++++++++++----- 1 file changed, 81 insertions(+), 27 deletions(-) diff --git a/_posts/2026-10-12-analytic-file-formats-sans-io.md b/_posts/2026-10-12-analytic-file-formats-sans-io.md index c0035311dd06..998eb57a059b 100644 --- a/_posts/2026-10-12-analytic-file-formats-sans-io.md +++ b/_posts/2026-10-12-analytic-file-formats-sans-io.md @@ -25,6 +25,14 @@ limitations under the License. {% endcomment %} --> + + A **Sans I/O** decoder interprets bytes but never fetches them. When it cannot continue, it returns the byte ranges it needs; the application supplies buffers and decides when to resume decoding. This exposes a boundary where an @@ -49,11 +57,12 @@ application asks it to advance, it does one of three things: [`push_ranges`]. In simplified Rust, with application-provided `fetch` and `consume` helpers: -```rust +
+{% highlight rust %} loop { match decoder.try_decode()? { DecodeResult::NeedsData(ranges) => { - // Return one buffer for each logical range, in order. + // One buffer per logical range, in order. let buffers = io_policy.fetch(&ranges)?; decoder.push_ranges(ranges, buffers)?; } @@ -61,7 +70,8 @@ loop { DecodeResult::Finished => break, } } -``` +{% endhighlight %} +
These APIs are available in `arrow-rs` `57.1.0`, the version linked here. The loop starts after file metadata is available; footer and metadata decoding @@ -105,33 +115,77 @@ information; the basic push loop does not provide a complete future read plan. ## From logical ranges to physical reads -The ranges in `NeedsData` specify the decoder's requirements. They need not map -one-to-one to storage requests. Consider an illustrative request for -`100..200` and `220..300`, using exclusive end offsets: - -| Policy | Physical reads | Bytes fetched | Buffers supplied to the decoder | -| --- | --- | --- | --- | -| Exact ranges | `100..200`, `220..300` | 180 | The two fetched buffers | -| Coalesced range | `100..300` | 200 | Slices `0..100` and `120..200` of the fetched buffer | - -For this particular request, a coalescing policy could use the following code. -`read_range` is an application helper returning a reference-counted `Bytes` -buffer: - -```rust +Suppose `NeedsData` asks for two parts of a file: `100..200` (100 bytes) +and `220..300` (80 bytes). The end offset is excluded in this notation. +The 20-byte gap at `200..220` is not needed by the decoder. + +The application could issue two reads, fetching exactly 180 bytes. Alternatively, +it could **combine them into one read of `100..300`**, fetching 200 bytes. +This saves one storage request at the cost of reading the 20-byte gap. + +After the combined read, the returned buffer starts at index **0**, which +corresponds to file offset **100**. To locate a file byte in that buffer, +subtract 100 from its file offset: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
File offsets and buffer indices for a combined read of bytes 100 through 299.
Part of the readFile offsetsBuffer indices
First requested range100..2000..100
Unneeded gap200..220100..120
Second requested range220..300120..200
+
+ +The application then supplies just the two requested parts to the decoder. +Here, `read_range` is an application helper returning a reference-counted +`Bytes` buffer: + +
+{% highlight rust %} +// 1. One storage read returns 200 bytes, including the gap. let buffer = read_range(100..300)?; +// +// 2. Slice with BUFFER indices, which start at zero. +// File offset 100 -> index 0; offset 220 -> index 120. +let first = buffer.slice(0..100); // File bytes 100..200 +let second = buffer.slice(120..200); // File bytes 220..300 +// +// 3. Match FILE ranges to slices, in the same order. +// Omit the gap at buffer indices 100..120. decoder.push_ranges( - vec![100..200, 220..300], - vec![buffer.slice(0..100), buffer.slice(120..200)], + vec![100..200, 220..300], // Original file offsets + vec![first, second], // Matching slices )?; -``` - -The decoder receives the same logical bytes in both cases. Coalescing replaces -two storage reads with one by fetching the 20-byte gap. The slices share the -allocation without copying their contents. Whether saving a request outweighs -reading extra bytes depends on the storage system; this example demonstrates -the mechanism, not a measured speedup. Decoding and decompression can still -allocate output buffers. +{% endhighlight %} +
+ +The decoder receives the same two byte ranges as it would with separate reads. +Both slices share the fetched allocation without copying their contents; +decoding and decompression can still allocate output buffers. Whether saving +a request outweighs reading extra bytes depends on the storage system. This +example shows how the application controls that tradeoff, not a measured +speedup. A cache fits at the same boundary. If `100..300` is already resident, the host can supply those slices without another read. If it fetches beyond the current