mirror of https://github.com/spacejam/sled
Perform file truncation when a slab is detected to be at 80% of its previous peak capacity
This commit is contained in:
parent
ca270abf25
commit
ecc717a3a0
|
@ -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(),
|
||||
|
|
52
src/heap.rs
52
src/heap.rs
|
@ -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(())
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue