nodedb_columnar/compaction/
segment.rs1use nodedb_mem::ScopedMemory;
6use nodedb_types::columnar::{ColumnType, ColumnarSchema};
7use nodedb_types::value::Value;
8
9use crate::delete_bitmap::DeleteBitmap;
10use crate::error::ColumnarError;
11use crate::memtable::ColumnarMemtable;
12use crate::reader::{DecodedColumn, SegmentReader, decoded_cell_value};
13use crate::writer::SegmentWriter;
14
15pub const DEFAULT_DELETE_RATIO_THRESHOLD: f64 = 0.2;
17
18pub struct CompactionResult {
20 pub segment: Option<Vec<u8>>,
22 pub live_rows: usize,
24 pub removed_rows: usize,
26}
27
28pub fn compact_segment(
41 segment_data: &[u8],
42 deletes: &DeleteBitmap,
43 schema: &ColumnarSchema,
44 profile_tag: u8,
45 memory: &ScopedMemory,
46 kek: Option<&nodedb_wal::crypto::WalEncryptionKey>,
47) -> Result<CompactionResult, ColumnarError> {
48 let reader = SegmentReader::open(segment_data)?;
49 let total_rows = reader.row_count() as usize;
50 let deleted = deletes.deleted_count() as usize;
51 let live = total_rows.saturating_sub(deleted);
52
53 if live == 0 {
54 return Ok(CompactionResult {
55 segment: None,
56 live_rows: 0,
57 removed_rows: total_rows,
58 });
59 }
60
61 let col_count = reader.column_count();
63 let _cols_guard = memory.reserve(col_count * std::mem::size_of::<usize>() * 3)?;
65 let mut decoded_cols = Vec::with_capacity(col_count);
66 for i in 0..col_count {
67 decoded_cols.push(reader.read_column(i)?);
68 }
69
70 let mut memtable = ColumnarMemtable::new(schema);
72 let col_len = schema.columns.len();
73 let _row_guard = memory.reserve(col_len * std::mem::size_of::<usize>() * 3)?;
74 let mut row_values = Vec::with_capacity(col_len);
75
76 for row_idx in 0..total_rows {
77 if deletes.is_deleted(row_idx as u32) {
78 continue;
79 }
80
81 row_values.clear();
82 for (col_idx, decoded) in decoded_cols.iter().enumerate() {
83 let col = &schema.columns[col_idx];
84 row_values.push(carried_cell(decoded, row_idx, &col.column_type)?);
85 }
86
87 memtable.append_row(&row_values)?;
88 }
89
90 let (schema, columns, row_count) = memtable.drain();
91 let writer = SegmentWriter::new(profile_tag, memory.clone());
92 let new_segment = writer.write_segment(&schema, &columns, row_count, kek)?;
93
94 Ok(CompactionResult {
95 segment: Some(new_segment),
96 live_rows: row_count,
97 removed_rows: deleted,
98 })
99}
100
101fn carried_cell(
108 decoded: &DecodedColumn,
109 row_idx: usize,
110 declared: &ColumnType,
111) -> Result<Value, ColumnarError> {
112 let value = decoded_cell_value(decoded, row_idx, declared)?;
113 if !matches!(declared, ColumnType::Json) || value == Value::Null {
114 return Ok(value);
115 }
116 let DecodedColumn::Binary { data, offsets, .. } = decoded else {
117 return Ok(value);
118 };
119 let start = offsets[row_idx] as usize;
121 let end = offsets[row_idx + 1] as usize;
122 Ok(Value::Bytes(data[start..end].to_vec()))
123}
124
125#[cfg(test)]
126mod tests {
127 use nodedb_types::columnar::{ColumnDef, ColumnType, ColumnarSchema};
128 use nodedb_types::value::Value;
129
130 use crate::delete_bitmap::DeleteBitmap;
131 use crate::memtable::ColumnarMemtable;
132 use crate::reader::{DecodedColumn, SegmentReader};
133 use crate::test_support::test_memory;
134 use crate::writer::SegmentWriter;
135
136 use super::compact_segment;
137
138 fn test_schema() -> ColumnarSchema {
139 ColumnarSchema::new(vec![
140 ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
141 ColumnDef::required("name", ColumnType::String),
142 ColumnDef::nullable("score", ColumnType::Float64),
143 ])
144 .expect("valid")
145 }
146
147 fn write_segment(rows: usize) -> Vec<u8> {
148 let schema = test_schema();
149 let mut mt = ColumnarMemtable::new(&schema);
150 for i in 0..rows {
151 mt.append_row(&[
152 Value::Integer(i as i64),
153 Value::String(format!("user_{i}")),
154 if i % 3 == 0 {
155 Value::Null
156 } else {
157 Value::Float(i as f64 * 0.5)
158 },
159 ])
160 .expect("append");
161 }
162 let (schema, columns, row_count) = mt.drain();
163 SegmentWriter::new(crate::writer::PROFILE_PLAIN, test_memory())
164 .write_segment(&schema, &columns, row_count, None)
165 .expect("write")
166 }
167
168 #[test]
169 fn compact_removes_deleted_rows() {
170 let segment = write_segment(100);
171 let mut deletes = DeleteBitmap::new();
172
173 for i in (0..100).step_by(10) {
175 deletes.mark_deleted(i);
176 }
177
178 let result = compact_segment(&segment, &deletes, &test_schema(), 0, &test_memory(), None)
179 .expect("compact");
180
181 assert_eq!(result.live_rows, 90);
182 assert_eq!(result.removed_rows, 10);
183 assert!(result.segment.is_some());
184
185 let new_seg = result.segment.as_ref().expect("segment");
187 let reader = SegmentReader::open(new_seg).expect("open");
188 assert_eq!(reader.row_count(), 90);
189
190 let col = reader.read_column(0).expect("read id");
193 match col {
194 DecodedColumn::Int64 { values, valid } => {
195 assert_eq!(values[0], 1); assert!(valid[0]);
197 assert_eq!(values[8], 9);
199 }
200 _ => panic!("expected Int64"),
201 }
202 }
203
204 #[test]
205 fn compact_all_deleted() {
206 let segment = write_segment(10);
207 let mut deletes = DeleteBitmap::new();
208 for i in 0..10 {
209 deletes.mark_deleted(i);
210 }
211
212 let result = compact_segment(&segment, &deletes, &test_schema(), 0, &test_memory(), None)
213 .expect("compact");
214
215 assert_eq!(result.live_rows, 0);
216 assert_eq!(result.removed_rows, 10);
217 assert!(result.segment.is_none());
218 }
219
220 #[test]
221 fn compact_no_deletes() {
222 let segment = write_segment(50);
223 let deletes = DeleteBitmap::new();
224
225 let result = compact_segment(&segment, &deletes, &test_schema(), 0, &test_memory(), None)
226 .expect("compact");
227
228 assert_eq!(result.live_rows, 50);
229 assert_eq!(result.removed_rows, 0);
230 assert!(result.segment.is_some());
231 }
232
233 #[test]
234 fn compact_preserves_string_data() {
235 let segment = write_segment(20);
236 let mut deletes = DeleteBitmap::new();
237 deletes.mark_deleted(0); let result = compact_segment(&segment, &deletes, &test_schema(), 0, &test_memory(), None)
240 .expect("compact");
241 let new_seg = result.segment.as_ref().expect("segment");
242 let reader = SegmentReader::open(new_seg).expect("open");
243
244 let col = reader.read_column(1).expect("read name");
246 match col {
247 DecodedColumn::Binary {
248 data,
249 offsets,
250 valid,
251 } => {
252 let start = offsets[0] as usize;
254 let end = offsets[1] as usize;
255 let first_name = std::str::from_utf8(&data[start..end]).expect("utf8");
256 assert_eq!(first_name, "user_1");
257 assert!(valid[0]);
258 }
259 _ => panic!("expected Binary"),
260 }
261 }
262
263 #[test]
267 fn compact_preserves_typed_cells() {
268 let schema = ColumnarSchema::new(vec![
269 ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
270 ColumnDef::nullable("u", ColumnType::Uuid),
271 ColumnDef::nullable("ul", ColumnType::Ulid),
272 ColumnDef::nullable("dec", ColumnType::Decimal(None)),
273 ColumnDef::nullable("b", ColumnType::Bool),
274 ColumnDef::nullable("geo", ColumnType::Geometry),
275 ColumnDef::nullable("j", ColumnType::Json),
276 ])
277 .expect("valid");
278 let mut mt = ColumnarMemtable::new(&schema);
279 for i in 0..6u128 {
280 let json = if i % 2 == 0 {
281 Value::String("\"scalar\"".into())
282 } else {
283 Value::Array(vec![Value::Integer(i as i64)])
284 };
285 mt.append_row(&[
286 Value::Integer(i as i64),
287 Value::Uuid(uuid::Uuid::from_u128(i + 1).to_string()),
288 Value::Ulid(ulid::Ulid(i + 1).to_string()),
289 Value::Decimal(rust_decimal::Decimal::new(i as i64 * 5, 1)),
290 Value::Bool(i % 3 == 0),
291 Value::String(format!("POINT({i} 2)")),
292 json,
293 ])
294 .expect("append");
295 }
296 let expected: Vec<Vec<Value>> = (1..6)
297 .map(|i| mt.get_row(i).expect("read").expect("row"))
298 .collect();
299 let (schema, columns, row_count) = mt.drain();
300 let segment = SegmentWriter::new(0, test_memory())
301 .write_segment(&schema, &columns, row_count, None)
302 .expect("write");
303
304 let mut deletes = DeleteBitmap::new();
305 deletes.mark_deleted(0);
306 let result =
307 compact_segment(&segment, &deletes, &schema, 0, &test_memory(), None).expect("compact");
308 let new_seg = result.segment.as_ref().expect("segment");
309 let reader = SegmentReader::open(new_seg).expect("open");
310 let indices: Vec<usize> = (0..schema.columns.len()).collect();
311 let decoded = reader.read_columns(&indices, &[]).expect("read");
312
313 for (row, want_row) in expected.iter().enumerate() {
314 for ((col, def), want) in decoded.iter().zip(&schema.columns).zip(want_row) {
315 let got = crate::reader::decoded_cell_value(col, row, &def.column_type)
316 .expect("decodable cell");
317 assert_eq!(&got, want, "row {row} column '{}'", def.name);
318 }
319 }
320 }
321}