Execution: Pipeline, Parallel, Bounded, Input¶
This page covers how bytes become batches. The same Splitter plus
RecordParser plus ExecutionPlan are shared across all modes; only the
driver differs.
See Data flow for diagrams of each mode.
Pipeline¶
S: Splitter + Clone and P: RecordParser + Clone so the pipeline can be
reused across files and modes.
Methods¶
new(splitter, parser): Creates with default plan.with_plan(plan): Replaces the plan (builder pattern).read_bytes(bytes): Single-threaded: oneTableBuilder, oneparse_chunk.read_bytes_par(bytes, num_chunks): Parallel viaParallelExecutor.read_bytes_stream(bytes, budget): Bounded-memory viaBoundedExecutor.read_path(path, use_mmap, prefault): Opens file, callsread_bytes.read_path_par(path, num_chunks, use_mmap, prefault): Opens file, calls parallel.read_path_stream(path, budget, prefault): Opens file, calls bounded.
All six methods share the same splitter, parser, and plan. The adapter
implements Splitter and RecordParser once; the engine handles the rest.
ParallelExecutor¶
pub fn parse<P>(
bytes: &[u8],
splitter: &dyn Splitter,
parser: P,
plan: Arc<ExecutionPlan>,
num_chunks: usize,
) -> Result<Vec<RecordBatch>>
where P: RecordParser + Clone + Send + Sync
Steps¶
- Split:
splitter.find_split_points(bytes, num_chunks)→split_points_to_ranges - Parse in parallel:
rayon::into_par_iterover ranges, each creating aTableBuilder, callingvalidate+parse_chunk, returning the builder. Panics are caught viacatch_unwind. - Fast path (no auto_dict, schemas consistent):
engines_to_record_batchesexports per-chunk batches with unified schema. - Merge path (auto_dict or inconsistent schemas):
Sequential
extendloop → single merged batch.
Fast path vs merge path¶
The fast path keeps one batch per chunk (chunked columns, no copy). It
unifies schema via unify_variants and promote_to_variant so all batches
share one Schema. Missing columns become null_array. rayon::par_iter
builds arrays in parallel.
The merge path (extend loop) returns a single merged batch and handles
auto_dict visibility (full cardinality) and irreconcilable type errors
with Error::Merge naming the column.
schemas_consistent¶
Checks that all engines agree on column variant keys. Mixed int64/float64
or string/dictionary falls to merge path for promotion.
BoundedExecutor¶
MemoryBudget¶
pub struct MemoryBudget { bytes: usize }
impl MemoryBudget {
pub fn new(bytes: usize) -> Self { Self { bytes } }
pub fn bytes(&self) -> usize { self.bytes }
}
plan_chunks¶
Estimates chunk sizes from budget:
bytes_per_row = splitter.estimate_bytes_per_row(bytes).max(1)total_rows_est = bytes.len() / bytes_per_rowrows_per_batch = (budget.bytes() / bytes_per_row).max(1).min(total_rows_est)num_batches = (total_rows_est / rows_per_batch).max(1)split_points = splitter.find_split_points(bytes, num_batches.min(MAX_SPLIT_CHUNKS))- Convert to ranges
MAX_SPLIT_CHUNKS = 100_000 caps split points to prevent pathological overhead.
run_bytes¶
For each chunk:
- Slice
&bytes[chunk.start..chunk.end] - Create per-chunk
TableBuilder validate+parse_chunkextendinto batch engine- Flush when
rows_in_batch >= rows_per_batch
run (file-based)¶
Opens InputBuffer. If Mmap:
plan_chunkson the mapped slice- Drop the mapping
- Reopen file,
seek+read_exactper chunk - Parse and accumulate
This keeps RSS low: mapping released before parse loop, only one chunk buffer live at a time.
If Owned (compressed or small file): delegates to run_bytes.
InputBuffer¶
MmapHandle¶
Maps the file. On Unix, does mmap.advise(WillNeed) if prefault,
else Sequential.
Compression detection¶
Reads first 4 bytes, matches magic:
- 1f 8b → gzip (feature gzip)
- 28 b5 2f fd → zstd (feature zstd)
- 04 22 4d 18 → lz4 frame (feature lz4)
If detected: Owned(decompress(path, codec)?) (read to end).
Decompressed bytes are served from memory for all modes.
open¶
open(path, use_mmap, prefault):
detect_compression(path)
→ Some(compressed) → Owned(decompress)
→ None + mmap enabled + use_mmap → Mmap
→ None → Owned(fs::read)
Cargo features¶
gzip = ["dep:flate2"]zstd = ["dep:zstd"]lz4 = ["dep:lz4_flex"]compress-all = ["gzip", "zstd", "lz4"]mmap = ["dep:memmap2"]
Merge¶
extend¶
Merges another TableBuilder into self:
- Create missing columns with null backfill
- For each column in order: check variant equality, promote if needed
(
int64→float64,string→dictionary), thenextend_owned - Update
row_count
engines_to_record_batches¶
Exports per-chunk builders without serial merge:
- Normalize and retain non-empty builders
- Unify schema via
unify_variants+promote_to_variant par_iterover engines to build arrays per unified order- Apply
apply_compare_filterper batch if filter is present
Arrow export¶
apply_compare_filter re-applies pure Compare and And trees using
Arrow compute kernels. Other filter trees are returned unchanged because
per-row evaluation is authoritative.
The filter works by:
- Checking
is_pure_compare_tree(no Or/Not/Equal/NotEqual) - Building a boolean mask via
compare_columns(cast to Float64 or Utf8, then gt/lt/eq/neq/and) - Applying
filter_record_batchto produce the filtered batch
See Storage and export for Arrow type mapping and null
handling details. See Engine for TableBuilder::finish
and the zero-copy Arrow export path.