#[cfg(feature = "std")]
use super::ordered_pipeline::{OrderedJob, OrderedPipeline};
#[cfg(feature = "std")]
use crate::{
CompressionType, TableId,
table::block::{Block, BlockIdentity, BlockTransform, BlockType, PreparedBlock},
};
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(feature = "std")]
use std::sync::Arc;
#[cfg(all(feature = "std", zstd_any))]
use crate::compression::ZstdDictionary;
#[cfg(feature = "std")]
use crate::encryption::EncryptionProvider;
pub trait CompactionSpawner: Send + Sync {
fn spawn(&self, task: Box<dyn FnOnce() + Send + 'static>);
}
#[cfg(feature = "parallel")]
pub struct RayonSpawner {
pool: Arc<rayon::ThreadPool>,
}
#[cfg(feature = "parallel")]
impl RayonSpawner {
pub fn with_threads(threads: usize) -> crate::Result<Self> {
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.thread_name(|i| format!("lsm-compress-{i}"))
.build()
.map_err(|e| crate::Error::Io(crate::io::Error::other(e.to_string())))?;
Ok(Self {
pool: Arc::new(pool),
})
}
#[must_use]
pub fn from_pool(pool: Arc<rayon::ThreadPool>) -> Self {
Self { pool }
}
}
#[cfg(feature = "parallel")]
impl CompactionSpawner for RayonSpawner {
fn spawn(&self, task: Box<dyn FnOnce() + Send + 'static>) {
self.pool.spawn(task);
}
}
#[cfg(feature = "std")]
struct BlockJob {
encoded: Vec<u8>,
extra_flags: u8,
}
#[cfg(feature = "std")]
struct BlockSettings {
table_id: TableId,
compression: CompressionType,
encryption: Option<Arc<dyn EncryptionProvider>>,
#[cfg(zstd_any)]
zstd_dict: Option<Arc<ZstdDictionary>>,
#[cfg(zstd_any)]
two_pass_seed: bool,
ecc: Option<crate::table::block::EccParams>,
}
#[cfg(feature = "std")]
impl OrderedJob for BlockJob {
type Context = BlockSettings;
type Output = crate::Result<PreparedBlock<'static>>;
fn run(self, settings: &BlockSettings) -> Self::Output {
prepare_owned(
&self.encoded,
settings.table_id,
TransformParams {
compression: settings.compression,
encryption: settings.encryption.as_deref(),
#[cfg(zstd_any)]
zstd_dict: settings.zstd_dict.as_deref(),
#[cfg(zstd_any)]
two_pass_seed: settings.two_pass_seed,
ecc: settings.ecc,
},
self.extra_flags,
)
}
}
#[cfg(feature = "std")]
#[derive(Clone)]
pub struct ParallelCompression {
pub spawner: Arc<dyn CompactionSpawner>,
pub threads: usize,
pub inline_below: Option<u32>,
}
#[cfg(feature = "std")]
pub struct BlockCompressor {
pipeline: OrderedPipeline<BlockJob>,
inline_below: u64,
}
#[cfg(all(feature = "std", feature = "lz4"))]
const LZ4_INLINE_BELOW: u64 = 1_024;
#[cfg(feature = "std")]
const TRANSFORM_ONLY_INLINE_BELOW: u64 = 8 * 1_024;
#[cfg(feature = "std")]
pub fn default_inline_below(compression: CompressionType) -> u64 {
match compression {
CompressionType::None => TRANSFORM_ONLY_INLINE_BELOW,
#[cfg(feature = "lz4")]
CompressionType::Lz4 => LZ4_INLINE_BELOW,
#[cfg(zstd_any)]
CompressionType::Zstd(_) | CompressionType::ZstdDict { .. } => 0,
}
}
#[cfg(feature = "std")]
impl BlockCompressor {
#[expect(
clippy::too_many_arguments,
reason = "the table's transform settings, each feature-gated, plus the pipeline's shape"
)]
pub fn new(
spawner: Arc<dyn CompactionSpawner>,
threads: usize,
in_flight: usize,
inline_below: u64,
table_id: TableId,
compression: CompressionType,
encryption: Option<Arc<dyn EncryptionProvider>>,
#[cfg(zstd_any)] zstd_dict: Option<Arc<ZstdDictionary>>,
#[cfg(zstd_any)] two_pass_seed: bool,
ecc: Option<crate::table::block::EccParams>,
) -> Self {
let settings = BlockSettings {
table_id,
compression,
encryption,
#[cfg(zstd_any)]
zstd_dict,
#[cfg(zstd_any)]
two_pass_seed,
ecc,
};
Self {
pipeline: OrderedPipeline::new(spawner, settings, threads, in_flight),
inline_below,
}
}
pub fn pending(&self) -> usize {
self.pipeline.pending()
}
pub fn submit(&mut self, encoded: Vec<u8>, extra_flags: u8) {
let inline = (encoded.len() as u64) < self.inline_below;
let job = BlockJob {
encoded,
extra_flags,
};
if inline {
self.pipeline.submit_inline(job);
} else {
self.pipeline.submit(job);
}
}
pub fn take_next(&mut self) -> Option<crate::Result<PreparedBlock<'static>>> {
self.pipeline.take_next()
}
}
#[cfg(feature = "std")]
#[derive(Clone, Copy)]
struct TransformParams<'a> {
compression: CompressionType,
encryption: Option<&'a dyn EncryptionProvider>,
#[cfg(zstd_any)]
zstd_dict: Option<&'a ZstdDictionary>,
#[cfg(zstd_any)]
two_pass_seed: bool,
ecc: Option<crate::table::block::EccParams>,
}
#[cfg(feature = "std")]
fn prepare_owned(
encoded: &[u8],
table_id: TableId,
params: TransformParams<'_>,
extra_flags: u8,
) -> crate::Result<PreparedBlock<'static>> {
let TransformParams {
compression,
encryption,
#[cfg(zstd_any)]
zstd_dict,
#[cfg(zstd_any)]
two_pass_seed,
ecc,
} = params;
let transform = BlockTransform::from_parts(
compression,
encryption,
#[cfg(zstd_any)]
zstd_dict,
)?;
#[cfg(zstd_any)]
let transform = transform.with_two_pass_seed(two_pass_seed);
let transform = if let Some(ecc) = ecc {
transform.with_ecc(ecc)
} else {
transform
};
let identity = BlockIdentity {
table_id,
block_type: BlockType::Data,
dict_id: compression.dict_id(),
window_log: 0,
};
Ok(Block::prepare_with_flags(encoded, identity, &transform, extra_flags)?.into_owned())
}
#[cfg(test)]
mod tests;