linkrs_query/executor/streaming/spill/
hash_partition.rs1use linkrs_core::columnar::MaterializedBatch;
2use linkrs_core::error::QueryError;
3use linkrs_core::Value;
4
5use super::manager::SpillManager;
6use super::run_format::fnv1a_64_update;
7use super::run_format::SpilledRun;
8use super::run_io::{RunReader, RunWriter};
9
10pub const HASH_PARTITION_VERSION: u32 = 1;
18pub const HASH_PARTITION_SEED: u64 = 0xdeadbeefcafe;
19
20pub fn hash_row_partition(row: &[Value], num_partitions: u64) -> u64 {
25 let encoded = postcard::to_allocvec(row).unwrap_or_default();
27 let hash = fnv1a_64_update(HASH_PARTITION_SEED, &encoded);
28 hash % num_partitions
29}
30
31pub fn hash_bytes_partition(bytes: &[u8], num_partitions: u64) -> u64 {
36 if num_partitions == 0 {
37 return 0;
38 }
39 fnv1a_64_update(HASH_PARTITION_SEED, bytes) % num_partitions
40}
41
42pub fn hash_column_partition(
48 batch: &MaterializedBatch,
49 cols: &[usize],
50 num_partitions: u64,
51) -> Vec<u64> {
52 batch
53 .hash_rows(cols)
54 .into_iter()
55 .map(|h| {
56 if num_partitions == 0 {
57 0
58 } else {
59 h % num_partitions
60 }
61 })
62 .collect()
63}
64
65#[derive(Debug, Clone)]
67pub struct HashPartitionConfig {
68 pub num_partitions: u64,
70 pub max_rows_per_partition: u64,
72 pub max_recursion_depth: u32,
74}
75
76impl Default for HashPartitionConfig {
77 fn default() -> Self {
78 Self {
79 num_partitions: 16,
80 max_rows_per_partition: 1_000_000,
81 max_recursion_depth: 3,
82 }
83 }
84}
85
86#[derive(Debug)]
91pub struct HashPartitionSpiller {
92 config: HashPartitionConfig,
93 pub(super) writers: Vec<Option<RunWriter>>,
94 counts: Vec<u64>,
95 recursion_depth: u32,
96 schema_fingerprint: u64,
97}
98
99impl HashPartitionSpiller {
100 pub fn new(
102 config: HashPartitionConfig,
103 manager: &SpillManager,
104 schema_fingerprint: u64,
105 ) -> Result<Self, QueryError> {
106 let n = config.num_partitions as usize;
107 let mut writers = Vec::with_capacity(n);
108 for _ in 0..n {
109 writers.push(Some(manager.create_run_writer(schema_fingerprint)?));
110 }
111 Ok(Self {
112 config,
113 writers,
114 counts: vec![0; n],
115 recursion_depth: 0,
116 schema_fingerprint,
117 })
118 }
119
120 pub fn insert_row(&mut self, row: &[Value], manager: &SpillManager) -> Result<(), QueryError> {
122 let partition = hash_row_partition(row, self.config.num_partitions) as usize;
123 self.insert_row_to_partition(row, partition, manager)
124 }
125
126 pub fn insert_row_to_partition(
131 &mut self,
132 row: &[Value],
133 partition: usize,
134 manager: &SpillManager,
135 ) -> Result<(), QueryError> {
136 if let Some(Some(writer)) = self.writers.get_mut(partition) {
137 writer.write_row(row)?;
138 self.counts[partition] += 1;
139
140 if self.counts[partition] > self.config.max_rows_per_partition
141 && self.recursion_depth < self.config.max_recursion_depth
142 {
143 self.repartition(manager)?;
144 }
145 }
146 Ok(())
147 }
148
149 pub fn finalize_with_manager(
151 mut self,
152 manager: &SpillManager,
153 ) -> Result<Vec<Option<SpilledRun>>, QueryError> {
154 let mut runs = Vec::with_capacity(self.writers.len());
155 for writer in self.writers.drain(..) {
156 match writer {
157 Some(w) => runs.push(Some(manager.finalize_run(w)?)),
158 None => runs.push(None),
159 }
160 }
161 Ok(runs)
162 }
163
164 fn repartition(&mut self, manager: &SpillManager) -> Result<(), QueryError> {
171 self.recursion_depth += 1;
172 let _old_count = self.config.num_partitions;
173 self.config.num_partitions = self.config.num_partitions.saturating_mul(2);
174
175 let old_runs = self.finalize_current(manager)?;
177
178 let mut new_writers = Vec::with_capacity(self.config.num_partitions as usize);
180 for _ in 0..self.config.num_partitions {
181 new_writers.push(Some(manager.create_run_writer(self.schema_fingerprint)?));
182 }
183 let mut new_counts = vec![0u64; self.config.num_partitions as usize];
184
185 for old_run in old_runs.into_iter().flatten() {
187 let byte_size = old_run.byte_size;
188 let mut reader = RunReader::open(&old_run)?;
189 while let Some(row) = reader.read_row()? {
190 let partition = hash_row_partition(&row, self.config.num_partitions) as usize;
191 if let Some(Some(writer)) = new_writers.get_mut(partition) {
192 writer.write_row(&row)?;
193 new_counts[partition] += 1;
194 }
195 }
196 let _ = std::fs::remove_file(&old_run.path);
199 manager.disk_quota().release(byte_size);
200 }
201
202 self.writers = new_writers;
203 self.counts = new_counts;
204 Ok(())
205 }
206
207 fn finalize_current(
212 &mut self,
213 manager: &SpillManager,
214 ) -> Result<Vec<Option<SpilledRun>>, QueryError> {
215 let mut runs = Vec::with_capacity(self.writers.len());
216 for writer in self.writers.drain(..) {
217 match writer {
218 Some(w) => runs.push(Some(manager.finalize_run(w)?)),
219 None => runs.push(None),
220 }
221 }
222 Ok(runs)
223 }
224
225 pub fn partition_counts(&self) -> &[u64] {
227 &self.counts
228 }
229
230 pub fn num_partitions(&self) -> u64 {
232 self.config.num_partitions
233 }
234}