1use crate::sst::parquet::DEFAULT_READ_BATCH_SIZE;
18
19pub(crate) const TARGET_BATCH_BYTES: usize = 64 * 1024 * 1024;
21
22pub fn estimate_batch_size(sources: impl IntoIterator<Item = (u64, u64)>) -> usize {
28 let max_row_width = sources
29 .into_iter()
30 .filter_map(|(rows, bytes)| estimate_row_width(rows, bytes))
31 .max();
32
33 let Some(row_width) = max_row_width else {
34 return DEFAULT_READ_BATCH_SIZE;
35 };
36
37 (TARGET_BATCH_BYTES as u64 / row_width).clamp(1, DEFAULT_READ_BATCH_SIZE as u64) as usize
38}
39
40fn estimate_row_width(rows: u64, bytes: u64) -> Option<u64> {
42 if rows == 0 || bytes == 0 {
43 return None;
44 }
45
46 Some(bytes / rows + u64::from(!bytes.is_multiple_of(rows)))
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn test_estimate_batch_size_without_stats() {
55 assert_eq!(DEFAULT_READ_BATCH_SIZE, estimate_batch_size([]));
56 assert_eq!(
57 DEFAULT_READ_BATCH_SIZE,
58 estimate_batch_size([(0, 100), (100, 0)])
59 );
60 }
61
62 #[test]
63 fn test_estimate_batch_size_for_narrow_and_wide_rows() {
64 assert_eq!(DEFAULT_READ_BATCH_SIZE, estimate_batch_size([(100, 100)]));
65 assert_eq!(256, estimate_batch_size([(1, 256 * 1024)]));
66 assert_eq!(1, estimate_batch_size([(1, TARGET_BATCH_BYTES as u64 + 1)]));
67 }
68
69 #[test]
70 fn test_estimate_batch_size_uses_widest_known_source() {
71 assert_eq!(
72 256,
73 estimate_batch_size([(100, 0), (100, 100), (4, 1024 * 1024)])
74 );
75 }
76
77 #[test]
78 fn test_estimate_batch_size_saturates() {
79 assert_eq!(
80 DEFAULT_READ_BATCH_SIZE,
81 estimate_batch_size([(u64::MAX, u64::MAX)])
82 );
83 assert_eq!(1, estimate_batch_size([(1, u64::MAX)]));
84 assert_eq!(1, estimate_batch_size([(2, u64::MAX)]));
85 }
86}