use linkrs_core::columnar::MaterializedBatch;
use linkrs_core::error::QueryError;
use linkrs_core::Value;
use super::manager::SpillManager;
use super::run_format::fnv1a_64_update;
use super::run_format::SpilledRun;
use super::run_io::{RunReader, RunWriter};
pub const HASH_PARTITION_VERSION: u32 = 1;
pub const HASH_PARTITION_SEED: u64 = 0xdeadbeefcafe;
pub fn hash_row_partition(row: &[Value], num_partitions: u64) -> u64 {
let encoded = postcard::to_allocvec(row).unwrap_or_default();
let hash = fnv1a_64_update(HASH_PARTITION_SEED, &encoded);
hash % num_partitions
}
pub fn hash_bytes_partition(bytes: &[u8], num_partitions: u64) -> u64 {
if num_partitions == 0 {
return 0;
}
fnv1a_64_update(HASH_PARTITION_SEED, bytes) % num_partitions
}
pub fn hash_column_partition(
batch: &MaterializedBatch,
cols: &[usize],
num_partitions: u64,
) -> Vec<u64> {
batch
.hash_rows(cols)
.into_iter()
.map(|h| {
if num_partitions == 0 {
0
} else {
h % num_partitions
}
})
.collect()
}
#[derive(Debug, Clone)]
pub struct HashPartitionConfig {
pub num_partitions: u64,
pub max_rows_per_partition: u64,
pub max_recursion_depth: u32,
}
impl Default for HashPartitionConfig {
fn default() -> Self {
Self {
num_partitions: 16,
max_rows_per_partition: 1_000_000,
max_recursion_depth: 3,
}
}
}
#[derive(Debug)]
pub struct HashPartitionSpiller {
config: HashPartitionConfig,
pub(super) writers: Vec<Option<RunWriter>>,
counts: Vec<u64>,
recursion_depth: u32,
schema_fingerprint: u64,
}
impl HashPartitionSpiller {
pub fn new(
config: HashPartitionConfig,
manager: &SpillManager,
schema_fingerprint: u64,
) -> Result<Self, QueryError> {
let n = config.num_partitions as usize;
let mut writers = Vec::with_capacity(n);
for _ in 0..n {
writers.push(Some(manager.create_run_writer(schema_fingerprint)?));
}
Ok(Self {
config,
writers,
counts: vec![0; n],
recursion_depth: 0,
schema_fingerprint,
})
}
pub fn insert_row(&mut self, row: &[Value], manager: &SpillManager) -> Result<(), QueryError> {
let partition = hash_row_partition(row, self.config.num_partitions) as usize;
self.insert_row_to_partition(row, partition, manager)
}
pub fn insert_row_to_partition(
&mut self,
row: &[Value],
partition: usize,
manager: &SpillManager,
) -> Result<(), QueryError> {
if let Some(Some(writer)) = self.writers.get_mut(partition) {
writer.write_row(row)?;
self.counts[partition] += 1;
if self.counts[partition] > self.config.max_rows_per_partition
&& self.recursion_depth < self.config.max_recursion_depth
{
self.repartition(manager)?;
}
}
Ok(())
}
pub fn finalize_with_manager(
mut self,
manager: &SpillManager,
) -> Result<Vec<Option<SpilledRun>>, QueryError> {
let mut runs = Vec::with_capacity(self.writers.len());
for writer in self.writers.drain(..) {
match writer {
Some(w) => runs.push(Some(manager.finalize_run(w)?)),
None => runs.push(None),
}
}
Ok(runs)
}
fn repartition(&mut self, manager: &SpillManager) -> Result<(), QueryError> {
self.recursion_depth += 1;
let _old_count = self.config.num_partitions;
self.config.num_partitions = self.config.num_partitions.saturating_mul(2);
let old_runs = self.finalize_current(manager)?;
let mut new_writers = Vec::with_capacity(self.config.num_partitions as usize);
for _ in 0..self.config.num_partitions {
new_writers.push(Some(manager.create_run_writer(self.schema_fingerprint)?));
}
let mut new_counts = vec![0u64; self.config.num_partitions as usize];
for old_run in old_runs.into_iter().flatten() {
let byte_size = old_run.byte_size;
let mut reader = RunReader::open(&old_run)?;
while let Some(row) = reader.read_row()? {
let partition = hash_row_partition(&row, self.config.num_partitions) as usize;
if let Some(Some(writer)) = new_writers.get_mut(partition) {
writer.write_row(&row)?;
new_counts[partition] += 1;
}
}
let _ = std::fs::remove_file(&old_run.path);
manager.disk_quota().release(byte_size);
}
self.writers = new_writers;
self.counts = new_counts;
Ok(())
}
fn finalize_current(
&mut self,
manager: &SpillManager,
) -> Result<Vec<Option<SpilledRun>>, QueryError> {
let mut runs = Vec::with_capacity(self.writers.len());
for writer in self.writers.drain(..) {
match writer {
Some(w) => runs.push(Some(manager.finalize_run(w)?)),
None => runs.push(None),
}
}
Ok(runs)
}
pub fn partition_counts(&self) -> &[u64] {
&self.counts
}
pub fn num_partitions(&self) -> u64 {
self.config.num_partitions
}
}