You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf: optimize object store requests when reading CSV (apache#22962)
## Which issue does this PR close?
- Closesapache#21419
## Rationale for this change
The CSV scanner currently uses `calculate_range` which issues two extra
`get_opts` requests per byte range to find newline boundaries (one for
the start boundary, one for the end boundary), plus one GET for the
actual data. For a file split into 3 partitions, this results in 8 total
object store requests.
apache#20823 solved this same problem for the JSON scanner by introducing
`AlignedBoundaryStream`, which wraps the raw byte stream and lazily
aligns to newline boundaries as data is read, eliminating the extra
boundary-seeking requests entirely. This PR applies the same approach to
CSV.
## What changes are included in this PR?
Based on the approach from apache#20823:
Moved `AlignedBoundaryStream` from `datasource-json` to the shared
`datasource` crate so it can be reused by both JSON and CSV scanners.
Updated `CsvOpener` to use instead of `calculate_range`, and removed the
`calculate_range` & `find_first_newline` as they no longer had any
callers. Updated tests to reflect.
Public API changes include:
- Removal of the `RangeCalculation` enum
- Removal of the `calculate_change` function
- `boundary_stream` (containing `AlignedBoundaryStream`) moved from
`datafusion_datasource_json` to `datafusion_datasource`
## Are these changes tested?
Yes. The existing `AlignedBoundaryStream` unit tests (16 tests covering
boundary alignment edge cases) were moved along with the implementation
and continue to pass. The `query_csv_file_with_byte_range_partitions`
snapshot test in `object_store_access.rs` has been updated to verify the
new request pattern (4 requests instead of 8).
## Are there any user-facing changes?
No.
find_first_newline(store, location, end - 1, file_size, newline).await?
473
-
}else{
474
-
0
475
-
};
476
-
477
-
let range = start + start_delta..end + end_delta;
478
-
479
-
if range.start >= range.end{
480
-
returnOk(RangeCalculation::TerminateEarly);
481
-
}
482
-
483
-
Ok(RangeCalculation::Range(Some(range)))
484
-
}
485
-
}
486
-
}
487
-
488
-
/// Asynchronously finds the position of the first newline character in a specified byte range
489
-
/// within an object, such as a file, in an object store.
490
-
///
491
-
/// This function scans the contents of the object starting from the specified `start` position
492
-
/// up to the `end` position, looking for the first occurrence of a newline character.
493
-
/// It returns the position of the first newline relative to the start of the range.
494
-
///
495
-
/// Returns a `Result` wrapping a `usize` that represents the position of the first newline character found within the specified range. If no newline is found, it returns the length of the scanned data, effectively indicating the end of the range.
496
-
///
497
-
/// The function returns an `Error` if any issues arise while reading from the object store or processing the data stream.
498
-
asyncfnfind_first_newline(
499
-
object_store:&Arc<dynObjectStore>,
500
-
location:&Path,
501
-
start:u64,
502
-
end:u64,
503
-
newline:u8,
504
-
) -> Result<u64>{
505
-
let options = GetOptions{
506
-
range:Some(GetRange::Bounded(start..end)),
507
-
..Default::default()
508
-
};
509
-
510
-
let result = object_store.get_opts(location, options).await?;
0 commit comments