Perform file truncation when a slab is detected to be at 80% of its previous peak capacity

This commit is contained in:
Tyler Neely 2023-12-25 18:00:24 -05:00
parent ca270abf25
commit ecc717a3a0
5 changed files with 87 additions and 5 deletions

View File

@ -24,6 +24,7 @@ trait Databench: Clone + Send {
fn insert_generic(&self, key: &[u8], value: &[u8]);
fn get_generic(&self, key: &[u8]) -> Option<Self::READ>;
fn flush_generic(&self);
fn print_stats(&self);
}
impl Databench for Db {
@ -57,6 +58,9 @@ impl Databench for Db {
fn flush_generic(&self) {
self.flush().unwrap();
}
fn print_stats(&self) {
dbg!(self.stats());
}
}
/*
@ -497,6 +501,8 @@ fn bench<D: Databench>() -> Stats {
let remove_stats = removes(&store);
store.print_stats();
Stats {
post_insert_disk_space,
post_remove_disk_space: du(D::PATH.as_ref()).unwrap(),

View File

@ -3,7 +3,7 @@ use std::fs;
use std::io::{self, Read};
use std::num::NonZeroU64;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
use std::sync::Arc;
use ebr::{Ebr, Guard};
@ -18,6 +18,8 @@ use crate::{CollectionId, Config, DeferredFree, MetadataStore, ObjectId};
const WARN: &str = "DO_NOT_PUT_YOUR_FILES_HERE";
pub(crate) const N_SLABS: usize = 78;
const FILE_TARGET_FILL_RATIO: u64 = 80;
const FILE_RESIZE_MARGIN: u64 = 115;
const SLAB_SIZES: [usize; N_SLABS] = [
64, // 0x40
@ -137,6 +139,7 @@ pub struct Stats {
pub heap_slots_freed: u64,
pub compacted_heap_slots: u64,
pub tree_leaves_merged: u64,
pub truncated_file_bytes: u64,
}
#[derive(Debug)]
@ -336,7 +339,11 @@ pub(crate) fn recover<P: AsRef<Path>>(
let file = fallible!(slab_opts.open(slab_path));
slabs.push(Slab { slot_size, file })
slabs.push(Slab {
slot_size,
file,
max_allocated_slot_since_last_truncation: AtomicU64::new(0),
})
}
log::info!("recovery of Heap at {:?} complete", path);
@ -351,6 +358,7 @@ pub(crate) fn recover<P: AsRef<Path>>(
directory_lock: Arc::new(directory_lock),
free_ebr: Ebr::default(),
write_batch_atomicity_mutex: Default::default(),
truncated_file_bytes: Arc::default(),
},
recovered_nodes,
was_recovered,
@ -508,6 +516,7 @@ mod sys_io {
struct Slab {
file: fs::File,
slot_size: usize,
max_allocated_slot_since_last_truncation: AtomicU64,
}
impl Slab {
@ -676,6 +685,7 @@ pub(crate) struct Heap {
#[allow(unused)]
directory_lock: Arc<fs::File>,
write_batch_atomicity_mutex: Arc<Mutex<()>>,
truncated_file_bytes: Arc<AtomicU64>,
}
impl fmt::Debug for Heap {
@ -715,7 +725,9 @@ impl Heap {
}
pub fn stats(&self) -> Stats {
self.table.stats()
let truncated_file_bytes =
self.truncated_file_bytes.load(Ordering::Acquire);
Stats { truncated_file_bytes, ..self.table.stats() }
}
pub fn read(&self, object_id: ObjectId) -> Option<io::Result<Vec<u8>>> {
@ -819,6 +831,40 @@ impl Heap {
}
}
// truncate files that are now too fragmented
for (i, current_allocated) 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);
let max_since_last_truncation =
last_max.max(current_allocated).max(1);
let ratio =
current_allocated.max(1) * 100 / max_since_last_truncation;
if ratio < FILE_TARGET_FILL_RATIO {
let current_len =
slab.slot_size as u64 * max_since_last_truncation;
let target_len =
slab.slot_size as u64 * max_since_last_truncation * 100
/ FILE_RESIZE_MARGIN;
assert!(target_len > current_allocated * slab.slot_size as u64);
if slab.file.set_len(target_len).is_ok() {
slab.max_allocated_slot_since_last_truncation
.store(current_allocated, Ordering::SeqCst);
let truncated_bytes =
current_len.saturating_sub(target_len);
self.truncated_file_bytes
.fetch_add(truncated_bytes, Ordering::Release);
}
}
}
drop(atomicity_mu);
Ok(())

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use crossbeam_queue::SegQueue;
use fnv::FnvHashSet;
use parking_lot::Mutex;
use parking_lot::{Mutex, MutexGuard};
#[derive(Default, Debug)]
pub(crate) struct Allocator {
@ -94,6 +94,9 @@ impl Allocator {
while let Some(free_id) = self.free_queue.pop() {
free.insert(free_id);
}
compact(&mut free, &self.next_to_allocate);
let pop_attempt = free.pop_first();
if let Some(id) = pop_attempt {
@ -110,6 +113,8 @@ impl Allocator {
free.insert(free_id);
}
free.insert(id);
compact(&mut free, &self.next_to_allocate);
} else {
self.free_queue.push(id);
}
@ -124,6 +129,18 @@ impl Allocator {
}
}
fn compact(
free: &mut MutexGuard<'_, BTreeSet<u64>>,
next_to_allocate: &AtomicU64,
) {
let mut next = next_to_allocate.load(Ordering::Acquire);
while next > 1 && free.contains(&(next - 1)) {
free.remove(&(next - 1));
next = next_to_allocate.fetch_sub(1, Ordering::SeqCst) - 1;
}
}
pub(crate) struct DeferredFree {
pub allocator: Arc<Allocator>,
pub freed_slot: u64,

View File

@ -269,7 +269,7 @@ impl concurrent_map::Minimum for ObjectId {
Eq,
Hash,
)]
struct CollectionId(u64);
pub struct CollectionId(u64);
impl concurrent_map::Minimum for CollectionId {
const MIN: CollectionId = CollectionId(u64::MIN);

View File

@ -107,6 +107,18 @@ impl ObjectLocationMapper {
ret
}
pub(crate) fn get_max_allocated_per_slab(&self) -> Vec<(usize, u64)> {
let mut ret = vec![];
for (i, slab) in self.slab_tenancies.iter().enumerate() {
if let Some(max_allocated) = slab.slot_allocator.max_allocated() {
ret.push((i, max_allocated));
}
}
ret
}
pub(crate) fn stats(&self) -> Stats {
let (objects_allocated, objects_freed) =
self.object_id_allocator.counters();
@ -129,6 +141,7 @@ impl ObjectLocationMapper {
compacted_heap_slots: 0,
tree_leaves_merged: 0,
flushes: 0,
truncated_file_bytes: 0,
}
}