|
11 | 11 | //! Import this module's [`ScanBuilder`] to select V2 explicitly. It accepts a [`ScanFile`] |
12 | 12 | //! directly and supports projection, row ranges, and selections. Unsupported layouts return an |
13 | 13 | //! error; filters and limits are not part of this builder's API. Work is partitioned into fixed |
14 | | -//! 65,536-row chunks, independently of the stored layout's chunk boundaries. |
| 14 | +//! chunks of up to 65,536 rows, independently of the stored layout's chunk boundaries. |
| 15 | +//! Full-file projections use smaller 8,192-row multiples when serialized bytes per row are high. |
15 | 16 |
|
16 | 17 | mod io; |
17 | 18 | mod split; |
@@ -63,19 +64,34 @@ use crate::scan::v2::io::segment_ranges; |
63 | 64 | use crate::scan::v2::split::SplitPlan; |
64 | 65 | use crate::segments::SegmentSource; |
65 | 66 |
|
66 | | -/// Eight of the default writer's 8,192-row blocks per scan task. |
67 | | -const ROWS_PER_CHUNK: usize = 8 * 8192; |
| 67 | +const ROW_BLOCK_SIZE: usize = 8192; |
| 68 | +const MAX_ROWS_PER_CHUNK: usize = 8 * ROW_BLOCK_SIZE; |
| 69 | +const TARGET_BYTES_PER_CHUNK: u64 = 8 * 1024 * 1024; |
| 70 | + |
| 71 | +/// Limit estimated bytes per task without changing the writer-aligned row granularity. |
| 72 | +fn rows_per_chunk(row_count: u64, locations: &[SegmentLocation]) -> usize { |
| 73 | + if row_count <= MAX_ROWS_PER_CHUNK as u64 { |
| 74 | + return MAX_ROWS_PER_CHUNK; |
| 75 | + } |
| 76 | + let bytes: u64 = locations |
| 77 | + .iter() |
| 78 | + .map(|location| u64::from(location.length)) |
| 79 | + .sum(); |
| 80 | + let bytes_per_row = bytes.div_ceil(row_count).max(1); |
| 81 | + let blocks = TARGET_BYTES_PER_CHUNK / bytes_per_row / ROW_BLOCK_SIZE as u64; |
| 82 | + blocks.clamp(1, 8) as usize * ROW_BLOCK_SIZE |
| 83 | +} |
68 | 84 |
|
69 | 85 | /// Fixed boundaries in the file's row domain, clipped to the requested range. |
70 | | -fn row_chunks(rows: Range<u64>) -> Vec<Range<u64>> { |
| 86 | +fn row_chunks(rows: Range<u64>, rows_per_chunk: usize) -> Vec<Range<u64>> { |
71 | 87 | if rows.is_empty() { |
72 | 88 | return Vec::new(); |
73 | 89 | } |
74 | | - let first = rows.start / ROWS_PER_CHUNK as u64 * ROWS_PER_CHUNK as u64; |
| 90 | + let first = rows.start / rows_per_chunk as u64 * rows_per_chunk as u64; |
75 | 91 | (first..rows.end) |
76 | | - .step_by(ROWS_PER_CHUNK) |
| 92 | + .step_by(rows_per_chunk) |
77 | 93 | .map(|start| { |
78 | | - start.max(rows.start)..start.saturating_add(ROWS_PER_CHUNK as u64).min(rows.end) |
| 94 | + start.max(rows.start)..start.saturating_add(rows_per_chunk as u64).min(rows.end) |
79 | 95 | }) |
80 | 96 | .collect() |
81 | 97 | } |
@@ -282,7 +298,12 @@ impl<A: 'static + Send> PreparedScan<A> { |
282 | 298 | } else { |
283 | 299 | optimize(plan_row_idx_expression(builder.projection, root)?)? |
284 | 300 | }; |
285 | | - let splits = row_chunks(builder.row_range.unwrap_or(0..row_count)); |
| 301 | + let chunk_rows = if identity { |
| 302 | + rows_per_chunk(row_count, &file.locations) |
| 303 | + } else { |
| 304 | + MAX_ROWS_PER_CHUNK |
| 305 | + }; |
| 306 | + let splits = row_chunks(builder.row_range.unwrap_or(0..row_count), chunk_rows); |
286 | 307 | let io = Arc::new(SegmentScanIo::new( |
287 | 308 | file.segments, |
288 | 309 | segment_ranges(&file.locations), |
|
0 commit comments