Execution modes¶
rypipe adapters can expose up to three execution strategies. Choosing the right one is usually the biggest single decision for memory and throughput.
| Mode | Best for | Memory | Parallelism | Output |
|---|---|---|---|---|
stream |
Huge files, unknown schema, row-at-a-time consumers | bounded by batch size | single-threaded parse | iterator / batched record batches |
columnar |
Medium files that fit in RAM, table output | holds full table | single-threaded parse, vectorized builders | one RecordBatch |
parallel |
Large files that fit in RAM, many cores | holds full table | chunked multi-threaded parse | one RecordBatch or Vec<RecordBatch> |
auto lets the adapter pick. A common heuristic is: files under ~8 MiB use columnar; larger files use parallel when memory allows; otherwise stream. Adapters should document their own heuristic because format split boundaries affect chunk safety.
Stream mode¶
Stream mode uses BoundedExecutor. It keeps a memory budget and parses the file in batches:
- Opens the file via
InputBuffer. - Estimates
bytes_per_rowfrom the splitter. - Computes
rows_per_batchfrom the budget. - Splits the file into at most 64 batches.
- Parses each batch into a
TableBuilder, exports it to aRecordBatch, and resets the builder. - Returns a
Vec<RecordBatch>; the caller concatenates or iterates.
Because the input buffer is dropped before the parse phase begins for bounded mode, mmap-backed pages are released before downstream work starts. This keeps peak memory close to the budget even for files much larger than RAM.
Use stream mode when:
- the file does not fit in RAM;
- the consumer is row-oriented or streaming (e.g., writing one row at a time);
- latency per batch matters more than total throughput;
- parallel merge overhead would dominate (very simple parsers).
Columnar mode¶
Columnar mode parses the whole file in one thread and builds one TableBuilder. It is the simplest path and avoids all chunking and synchronization overhead. The full table stays in memory until export.
Use columnar mode when:
- the file fits comfortably in RAM;
- the parser is fast enough that parallel overhead would not pay off;
- you need one contiguous
RecordBatchwithout a merge step; auto_dictor compare filters force a merge anyway, so parallelism adds overhead.
Columnar mode is often fastest for small files because there is no per-chunk setup and no rayon scheduling.
Parallel mode¶
Parallel mode uses ParallelExecutor:
- Calls
Splitter::find_split_points. - Converts points to non-empty
Range<usize>chunks. - Uses
rayon::par_iterto parse each chunk independently into aTableBuilder. - Fast path: if
auto_dictis false and there is noComparefilter, each builder is exported as its ownRecordBatchin parallel. No serial merge happens. - Merge path: if
auto_dictor aComparefilter is present, chunk builders are merged sequentially before export.
Use parallel mode when:
- the file fits in RAM or in the OS page cache;
- the parser is CPU-bound (heavy XML, complex field extraction, many columns);
- you can tolerate higher peak memory for shorter wall-clock time;
auto_dictand compare filters are off, so the fast path applies.
Auto engine selection¶
The Python Adapter layer usually exposes engine="auto". The heuristic is format-specific, but a common default is:
if file_size < 8 * 1024 * 1024:
engine = "columnar"
elif memory_available > 4 * file_size:
engine = "parallel"
else:
engine = "stream"
Adapters should expose the engine choice explicitly because the best default depends on split safety, row size variance, and downstream use. A format with expensive per-chunk setup (for example, one that must scan for a global header) may prefer columnar for much larger files than a simple newline-delimited format.
Trade-offs¶
| Concern | Prefer | Avoid | Why |
|---|---|---|---|
| Lowest memory | stream | parallel | Bounded batches keep peak RSS flat. |
| Lowest latency to first batch | stream | parallel | First batch is emitted before the whole file is read. |
| Highest throughput on large files | parallel | columnar | Many cores parse simultaneously. |
| Highest throughput on small files | columnar | parallel | Chunk overhead dominates. |
| Deterministic column order | any with schema_order |
inference | Chunk merges rely on a common schema. |
| Low cardinality string compression | columnar or parallel with dictionary_columns |
parallel with auto_dict |
Auto-dict forces the merge path. |
GIL behavior¶
All parse paths release the GIL during the heavy Rust work. The Arrow C Data Interface export re-acquires the GIL briefly. For read_path_par, the entire parallel parse runs outside the GIL.
This means parallel mode can saturate CPU from Python without multiprocessing, provided the adapter is implemented in Rust and exports Arrow.
Summary¶
- Use
streamfor huge files or row consumers. - Use
columnarfor small-to-medium files and when merge is unavoidable. - Use
parallelfor large cached files with a CPU-bound parser and no merge-forcing options. - Expose
engineexplicitly and document the adapter-specific heuristic.