Merge 'btree: Coalesce free blocks in page_free_array()' from Mohamed Hossam

Coalesce adjacent free blocks during `page_free_array()` in
`core/storage/btree`.
Instead of immediately passing free cells to `free_cell_range()`, buffer
up to 10 free cells and try to merge adjacent free blocks. Break on the
first merge to avoid time complexity, `free_cell_range()` coalesces
blocks afterwards anyways. This follows SQLite's [`pageFreeArray()`](htt
ps://github.com/sqlite/sqlite/blob/d7324103b196c572a98724a5658970b4000b8
c39/src/btree.c#L7729) implementation.
Removed this TODO:
```rust
fn page_free_array( . . )
    .
    .
    // TODO: implement fancy smart free block coalescing procedure instead of dumb free to
    // then defragment
```

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #1448
This commit is contained in:
Jussi Saurio 2025-05-12 16:52:11 +03:00
commit 2b6b09d435

View file

@ -4699,8 +4699,9 @@ fn page_free_array(
let buf = &mut page.as_ptr()[page.offset..usable_space as usize];
let buf_range = buf.as_ptr_range();
let mut number_of_cells_removed = 0;
// TODO: implement fancy smart free block coalescing procedure instead of dumb free to
// then defragment
let mut number_of_cells_buffered = 0;
let mut buffered_cells_offsets: [u16; 10] = [0; 10];
let mut buffered_cells_ends: [u16; 10] = [0; 10];
for i in first..first + count {
let cell = &cell_array.cells[i];
let cell_pointer = cell.as_ptr_range();
@ -4713,11 +4714,61 @@ fn page_free_array(
// TODO: remove pointer too
let offset = (cell_pointer.start as usize - buf_range.start as usize) as u16;
let len = (cell_pointer.end as usize - cell_pointer.start as usize) as u16;
free_cell_range(page, offset, len, usable_space)?;
page.write_u16(offset::BTREE_CELL_COUNT, page.cell_count() as u16 - 1);
assert!(len > 0, "cell size should be greater than 0");
let end = offset + len;
/* Try to merge the current cell with a contiguous buffered cell to reduce the number of
* `free_cell_range()` operations. Break on the first merge to avoid consuming too much time,
* `free_cell_range()` will try to merge contiguous cells anyway. */
let mut j = 0;
while j < number_of_cells_buffered {
// If the buffered cell is immediately after the current cell
if buffered_cells_offsets[j] == end {
// Merge them by updating the buffered cell's offset to the current cell's offset
buffered_cells_offsets[j] = offset;
break;
// If the buffered cell is immediately before the current cell
} else if buffered_cells_ends[j] == offset {
// Merge them by updating the buffered cell's end offset to the current cell's end offset
buffered_cells_ends[j] = end;
break;
}
j += 1;
}
// If no cells were merged
if j >= number_of_cells_buffered {
// If the buffered cells array is full, flush the buffered cells using `free_cell_range()` to empty the array
if number_of_cells_buffered >= buffered_cells_offsets.len() {
for j in 0..number_of_cells_buffered {
free_cell_range(
page,
buffered_cells_offsets[j],
buffered_cells_ends[j] - buffered_cells_offsets[j],
usable_space,
)?;
}
number_of_cells_buffered = 0; // Reset array counter
}
// Buffer the current cell
buffered_cells_offsets[number_of_cells_buffered] = offset;
buffered_cells_ends[number_of_cells_buffered] = end;
number_of_cells_buffered += 1;
}
number_of_cells_removed += 1;
}
}
for j in 0..number_of_cells_buffered {
free_cell_range(
page,
buffered_cells_offsets[j],
buffered_cells_ends[j] - buffered_cells_offsets[j],
usable_space,
)?;
}
page.write_u16(
offset::BTREE_CELL_COUNT,
page.cell_count() as u16 - number_of_cells_removed as u16,
);
Ok(number_of_cells_removed)
}
fn page_insert_array(