lance_encoding/encodings/logical/primitive/
miniblock.rs1use crate::{buffer::LanceBuffer, data::DataBlock, format::metadata::CompressiveEncoding};
16
17use lance_core::Result;
18
19pub const MAX_MINIBLOCK_BYTES: u64 = 8 * 1024 - 6;
20
21const DEFAULT_MAX_MINIBLOCK_VALUES: u64 = 4096;
22pub(crate) const MAX_CONFIGURABLE_MINIBLOCK_VALUES: u64 = 32768;
24
25fn parse_max_miniblock_values() -> u64 {
26 let val = std::env::var("LANCE_MINIBLOCK_MAX_VALUES")
27 .ok()
28 .and_then(|v| v.parse().ok())
29 .unwrap_or(DEFAULT_MAX_MINIBLOCK_VALUES);
30 val.clamp(1, MAX_CONFIGURABLE_MINIBLOCK_VALUES)
31}
32
33pub static MAX_MINIBLOCK_VALUES: std::sync::LazyLock<u64> =
34 std::sync::LazyLock::new(parse_max_miniblock_values);
35
36pub fn max_repdef_levels_per_chunk(bits_per_level: u64) -> u64 {
39 debug_assert!(bits_per_level > 0);
40 const REPDEF_BUDGET_BITS: u64 = 16 * 1024 * 8;
41 let budgeted_levels = REPDEF_BUDGET_BITS / bits_per_level;
42 budgeted_levels.min(u16::MAX as u64)
43}
44
45#[derive(Debug)]
48pub struct MiniBlockCompressed {
49 pub data: Vec<LanceBuffer>,
51 pub chunks: Vec<MiniBlockChunk>,
53 pub num_values: u64,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct MiniBlockCompressionContext {
60 common_chunk_buffers: u64,
61 support_large_chunk: bool,
62 allow_generic_offsets: bool,
63}
64
65impl MiniBlockCompressionContext {
66 pub fn new(
68 common_chunk_buffers: u64,
69 support_large_chunk: bool,
70 allow_generic_offsets: bool,
71 ) -> Self {
72 Self {
73 common_chunk_buffers,
74 support_large_chunk,
75 allow_generic_offsets,
76 }
77 }
78
79 pub(crate) fn chunk_header_bytes(self, value_buffers: u64) -> u64 {
80 let value_buffer_size_bytes = if self.support_large_chunk { 4 } else { 2 };
81 2_u64
82 .saturating_add(self.common_chunk_buffers.saturating_mul(2))
83 .saturating_add(value_buffers.saturating_mul(value_buffer_size_bytes))
84 .next_multiple_of(8)
85 }
86
87 pub(crate) fn allows_generic_offsets(self) -> bool {
88 self.allow_generic_offsets
89 }
90}
91
92#[derive(Debug)]
107pub struct MiniBlockChunk {
108 pub buffer_sizes: Vec<u32>,
113 pub log_num_values: u8,
122}
123
124impl MiniBlockChunk {
125 pub fn num_values(&self, vals_in_prev_blocks: u64, total_num_values: u64) -> u64 {
132 if self.log_num_values == 0 {
133 total_num_values - vals_in_prev_blocks
134 } else {
135 1 << self.log_num_values
136 }
137 }
138}
139
140pub trait MiniBlockCompressor: std::fmt::Debug + Send + Sync {
145 fn compress(
152 &self,
153 context: MiniBlockCompressionContext,
154 page: DataBlock,
155 ) -> Result<(MiniBlockCompressed, CompressiveEncoding)>;
156}
157
158#[cfg(test)]
159mod tests {
160 use serial_test::serial;
161
162 use super::*;
163
164 #[test]
165 #[serial]
166 fn test_parse_default() {
167 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
168 assert_eq!(parse_max_miniblock_values(), 4096);
169 }
170
171 #[test]
172 #[serial]
173 fn test_parse_custom_value() {
174 unsafe { std::env::set_var("LANCE_MINIBLOCK_MAX_VALUES", "256") };
175 assert_eq!(parse_max_miniblock_values(), 256);
176 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
177 }
178
179 #[test]
180 #[serial]
181 fn test_parse_can_raise_to_32k() {
182 unsafe { std::env::set_var("LANCE_MINIBLOCK_MAX_VALUES", "32768") };
183 assert_eq!(parse_max_miniblock_values(), 32768);
184 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
185 }
186
187 #[test]
188 #[serial]
189 fn test_parse_clamps_zero_to_one() {
190 unsafe { std::env::set_var("LANCE_MINIBLOCK_MAX_VALUES", "0") };
191 assert_eq!(parse_max_miniblock_values(), 1);
192 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
193 }
194
195 #[test]
196 #[serial]
197 fn test_parse_clamps_above_max() {
198 unsafe { std::env::set_var("LANCE_MINIBLOCK_MAX_VALUES", "99999") };
199 assert_eq!(
200 parse_max_miniblock_values(),
201 MAX_CONFIGURABLE_MINIBLOCK_VALUES
202 );
203 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
204 }
205
206 #[test]
207 #[serial]
208 fn test_parse_invalid_falls_back_to_default() {
209 unsafe { std::env::set_var("LANCE_MINIBLOCK_MAX_VALUES", "not_a_number") };
210 assert_eq!(parse_max_miniblock_values(), DEFAULT_MAX_MINIBLOCK_VALUES);
211 unsafe { std::env::remove_var("LANCE_MINIBLOCK_MAX_VALUES") };
212 }
213}