[new file format] use crc on frame lengths in metadata store

This commit is contained in:
Tyler Neely 2024-04-14 19:35:49 +02:00
parent 795a2217df
commit cb14155b89
6 changed files with 67 additions and 28 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "sled"
version = "1.0.0-alpha.120"
version = "1.0.0-alpha.121"
edition = "2021"
authors = ["Tyler Neely <tylerneely@gmail.com>"]
documentation = "https://docs.rs/sled/"

View File

@ -409,7 +409,7 @@ pub(crate) fn recover<P: AsRef<Path>>(
slabs.push(Slab {
slot_size,
file,
max_allocated_slot_since_last_truncation: AtomicU64::new(0),
max_live_slot_since_last_truncation: AtomicU64::new(0),
})
}
@ -586,7 +586,7 @@ mod sys_io {
struct Slab {
file: fs::File,
slot_size: usize,
max_allocated_slot_since_last_truncation: AtomicU64,
max_live_slot_since_last_truncation: AtomicU64,
}
impl Slab {
@ -990,40 +990,44 @@ impl Heap {
let before_truncate = Instant::now();
let mut truncated_files = 0;
let mut truncated_bytes = 0;
for (i, current_allocated) in self.table.get_max_allocated_per_slab() {
for (i, max_live_slot) in self.table.get_max_allocated_per_slab() {
let slab = &self.slabs[i];
let last_max = slab
.max_allocated_slot_since_last_truncation
.fetch_max(current_allocated, Ordering::SeqCst);
.max_live_slot_since_last_truncation
.fetch_max(max_live_slot, Ordering::SeqCst);
let max_since_last_truncation = last_max.max(current_allocated);
let max_since_last_truncation = last_max.max(max_live_slot);
let currently_occupied =
(current_allocated + 1) * slab.slot_size as u64;
let currently_occupied_bytes =
(max_live_slot + 1) * slab.slot_size as u64;
let max_occupied =
let max_occupied_bytes =
(max_since_last_truncation + 1) * slab.slot_size as u64;
let ratio = currently_occupied * 100 / max_occupied;
let ratio = currently_occupied_bytes * 100 / max_occupied_bytes;
if ratio < FILE_TARGET_FILL_RATIO {
let target_len = currently_occupied * FILE_RESIZE_MARGIN / 100;
let target_len = if max_live_slot < 16 {
currently_occupied_bytes
} else {
currently_occupied_bytes * FILE_RESIZE_MARGIN / 100
};
assert!(target_len < max_occupied);
assert!(target_len < max_occupied_bytes);
assert!(
target_len > currently_occupied,
target_len >= currently_occupied_bytes,
"target_len of {} is above actual occupied len of {}",
target_len,
currently_occupied
currently_occupied_bytes
);
if slab.file.set_len(target_len).is_ok() {
slab.max_allocated_slot_since_last_truncation
.store(current_allocated, Ordering::SeqCst);
slab.max_live_slot_since_last_truncation
.store(max_live_slot, Ordering::SeqCst);
let file_truncated_bytes =
currently_occupied.saturating_sub(target_len);
currently_occupied_bytes.saturating_sub(target_len);
self.truncated_file_bytes
.fetch_add(file_truncated_bytes, Ordering::Release);

View File

@ -481,14 +481,20 @@ fn read_frame(
file: &mut fs::File,
reusable_frame_buffer: &mut Vec<u8>,
) -> io::Result<Vec<UpdateMetadata>> {
let mut frame_size_buf: [u8; 8] = [0; 8];
let mut frame_size_with_crc_buf: [u8; 8] = [0; 8];
// TODO only break if UnexpectedEof, otherwise propagate
fallible!(file.read_exact(&mut frame_size_buf));
fallible!(file.read_exact(&mut frame_size_with_crc_buf));
let expected_len_hash_buf = [frame_size_buf[6], frame_size_buf[7]];
let expected_len_hash_buf =
[frame_size_with_crc_buf[6], frame_size_with_crc_buf[7]];
let actual_len_hash_buf: [u8; 2] =
(crc32fast::hash(&frame_size_buf[..6]) as u16).to_le_bytes();
(crc32fast::hash(&frame_size_with_crc_buf[..6]) as u16).to_le_bytes();
// clear crc bytes before turning into usize
let mut frame_size_buf = frame_size_with_crc_buf;
frame_size_buf[6] = 0;
frame_size_buf[7] = 0;
if actual_len_hash_buf != expected_len_hash_buf {
return Err(annotate!(io::Error::new(
@ -498,7 +504,6 @@ fn read_frame(
}
let len_u64: u64 = u64::from_le_bytes(frame_size_buf);
// TODO make sure len < max len
let len: usize = usize::try_from(len_u64).unwrap();
reusable_frame_buffer.clear();
@ -506,7 +511,7 @@ fn read_frame(
unsafe {
reusable_frame_buffer.set_len(len + 12);
}
reusable_frame_buffer[..8].copy_from_slice(&frame_size_buf);
reusable_frame_buffer[..8].copy_from_slice(&frame_size_with_crc_buf);
fallible!(file.read_exact(&mut reusable_frame_buffer[8..]));

View File

@ -1403,6 +1403,8 @@ impl<const LEAF_FANOUT: usize> Tree<LEAF_FANOUT> {
leaf.insert(key, value);
merges.remove(lo);
merges.remove(&leaf.lo);
if let Some((split_key, rhs_node)) = leaf.split_if_full(
new_epoch,
&self.cache,
@ -1418,7 +1420,8 @@ impl<const LEAF_FANOUT: usize> Tree<LEAF_FANOUT> {
leaf.remove(&key);
if leaf.is_empty() {
merges.insert(lo.clone(), object.clone());
assert_eq!(leaf.lo, lo);
merges.insert(leaf.lo.clone(), object.clone());
}
}
}

View File

@ -1,8 +1,31 @@
mod common;
mod tree;
use std::alloc::{Layout, System};
use tree::{prop_tree_matches_btreemap, Key, Op::*};
#[global_allocator]
static ALLOCATOR: ShredAllocator = ShredAllocator;
#[derive(Default, Debug, Clone, Copy)]
struct ShredAllocator;
unsafe impl std::alloc::GlobalAlloc for ShredAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
assert!(layout.size() < 1_000_000_000);
let ret = System.alloc(layout);
assert_ne!(ret, std::ptr::null_mut());
std::ptr::write_bytes(ret, 0xa1, layout.size());
ret
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
std::ptr::write_bytes(ptr, 0xde, layout.size());
System.dealloc(ptr, layout)
}
}
#[allow(dead_code)]
const INTENSITY: usize = 10;

View File

@ -1238,9 +1238,13 @@ fn tree_gc() {
"{stats:?}"
);
// TODO test this after we implement file truncation
// let expected_max_size = size_on_disk_after_inserts / 100;
// assert!(size_on_disk_after_deletes <= expected_max_size);
let expected_max_size = size_on_disk_after_inserts / 15;
assert!(
size_on_disk_after_deletes <= expected_max_size,
"expected file truncation to take size under {expected_max_size} \
but it was {size_on_disk_after_deletes}"
);
// TODO assert!(stats.cache.heap.truncated_file_bytes > 0);
println!(
"after writing {N} items and removing them, disk size went \