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 insert_generic(&self, key: &[u8], value: &[u8]);
|
||||||
fn get_generic(&self, key: &[u8]) -> Option<Self::READ>;
|
fn get_generic(&self, key: &[u8]) -> Option<Self::READ>;
|
||||||
fn flush_generic(&self);
|
fn flush_generic(&self);
|
||||||
|
fn print_stats(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Databench for Db {
|
impl Databench for Db {
|
||||||
|
@ -57,6 +58,9 @@ impl Databench for Db {
|
||||||
fn flush_generic(&self) {
|
fn flush_generic(&self) {
|
||||||
self.flush().unwrap();
|
self.flush().unwrap();
|
||||||
}
|
}
|
||||||
|
fn print_stats(&self) {
|
||||||
|
dbg!(self.stats());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -497,6 +501,8 @@ fn bench<D: Databench>() -> Stats {
|
||||||
|
|
||||||
let remove_stats = removes(&store);
|
let remove_stats = removes(&store);
|
||||||
|
|
||||||
|
store.print_stats();
|
||||||
|
|
||||||
Stats {
|
Stats {
|
||||||
post_insert_disk_space,
|
post_insert_disk_space,
|
||||||
post_remove_disk_space: du(D::PATH.as_ref()).unwrap(),
|
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::io::{self, Read};
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
use std::sync::atomic::{AtomicPtr, AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ebr::{Ebr, Guard};
|
use ebr::{Ebr, Guard};
|
||||||
|
@ -18,6 +18,8 @@ use crate::{CollectionId, Config, DeferredFree, MetadataStore, ObjectId};
|
||||||
|
|
||||||
const WARN: &str = "DO_NOT_PUT_YOUR_FILES_HERE";
|
const WARN: &str = "DO_NOT_PUT_YOUR_FILES_HERE";
|
||||||
pub(crate) const N_SLABS: usize = 78;
|
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] = [
|
const SLAB_SIZES: [usize; N_SLABS] = [
|
||||||
64, // 0x40
|
64, // 0x40
|
||||||
|
@ -137,6 +139,7 @@ pub struct Stats {
|
||||||
pub heap_slots_freed: u64,
|
pub heap_slots_freed: u64,
|
||||||
pub compacted_heap_slots: u64,
|
pub compacted_heap_slots: u64,
|
||||||
pub tree_leaves_merged: u64,
|
pub tree_leaves_merged: u64,
|
||||||
|
pub truncated_file_bytes: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -336,7 +339,11 @@ pub(crate) fn recover<P: AsRef<Path>>(
|
||||||
|
|
||||||
let file = fallible!(slab_opts.open(slab_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);
|
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),
|
directory_lock: Arc::new(directory_lock),
|
||||||
free_ebr: Ebr::default(),
|
free_ebr: Ebr::default(),
|
||||||
write_batch_atomicity_mutex: Default::default(),
|
write_batch_atomicity_mutex: Default::default(),
|
||||||
|
truncated_file_bytes: Arc::default(),
|
||||||
},
|
},
|
||||||
recovered_nodes,
|
recovered_nodes,
|
||||||
was_recovered,
|
was_recovered,
|
||||||
|
@ -508,6 +516,7 @@ mod sys_io {
|
||||||
struct Slab {
|
struct Slab {
|
||||||
file: fs::File,
|
file: fs::File,
|
||||||
slot_size: usize,
|
slot_size: usize,
|
||||||
|
max_allocated_slot_since_last_truncation: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Slab {
|
impl Slab {
|
||||||
|
@ -676,6 +685,7 @@ pub(crate) struct Heap {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
directory_lock: Arc<fs::File>,
|
directory_lock: Arc<fs::File>,
|
||||||
write_batch_atomicity_mutex: Arc<Mutex<()>>,
|
write_batch_atomicity_mutex: Arc<Mutex<()>>,
|
||||||
|
truncated_file_bytes: Arc<AtomicU64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Heap {
|
impl fmt::Debug for Heap {
|
||||||
|
@ -715,7 +725,9 @@ impl Heap {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stats(&self) -> Stats {
|
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>>> {
|
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);
|
drop(atomicity_mu);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crossbeam_queue::SegQueue;
|
use crossbeam_queue::SegQueue;
|
||||||
use fnv::FnvHashSet;
|
use fnv::FnvHashSet;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::{Mutex, MutexGuard};
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub(crate) struct Allocator {
|
pub(crate) struct Allocator {
|
||||||
|
@ -94,6 +94,9 @@ impl Allocator {
|
||||||
while let Some(free_id) = self.free_queue.pop() {
|
while let Some(free_id) = self.free_queue.pop() {
|
||||||
free.insert(free_id);
|
free.insert(free_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compact(&mut free, &self.next_to_allocate);
|
||||||
|
|
||||||
let pop_attempt = free.pop_first();
|
let pop_attempt = free.pop_first();
|
||||||
|
|
||||||
if let Some(id) = pop_attempt {
|
if let Some(id) = pop_attempt {
|
||||||
|
@ -110,6 +113,8 @@ impl Allocator {
|
||||||
free.insert(free_id);
|
free.insert(free_id);
|
||||||
}
|
}
|
||||||
free.insert(id);
|
free.insert(id);
|
||||||
|
|
||||||
|
compact(&mut free, &self.next_to_allocate);
|
||||||
} else {
|
} else {
|
||||||
self.free_queue.push(id);
|
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(crate) struct DeferredFree {
|
||||||
pub allocator: Arc<Allocator>,
|
pub allocator: Arc<Allocator>,
|
||||||
pub freed_slot: u64,
|
pub freed_slot: u64,
|
||||||
|
|
|
@ -269,7 +269,7 @@ impl concurrent_map::Minimum for ObjectId {
|
||||||
Eq,
|
Eq,
|
||||||
Hash,
|
Hash,
|
||||||
)]
|
)]
|
||||||
struct CollectionId(u64);
|
pub struct CollectionId(u64);
|
||||||
|
|
||||||
impl concurrent_map::Minimum for CollectionId {
|
impl concurrent_map::Minimum for CollectionId {
|
||||||
const MIN: CollectionId = CollectionId(u64::MIN);
|
const MIN: CollectionId = CollectionId(u64::MIN);
|
||||||
|
|
|
@ -107,6 +107,18 @@ impl ObjectLocationMapper {
|
||||||
ret
|
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 {
|
pub(crate) fn stats(&self) -> Stats {
|
||||||
let (objects_allocated, objects_freed) =
|
let (objects_allocated, objects_freed) =
|
||||||
self.object_id_allocator.counters();
|
self.object_id_allocator.counters();
|
||||||
|
@ -129,6 +141,7 @@ impl ObjectLocationMapper {
|
||||||
compacted_heap_slots: 0,
|
compacted_heap_slots: 0,
|
||||||
tree_leaves_merged: 0,
|
tree_leaves_merged: 0,
|
||||||
flushes: 0,
|
flushes: 0,
|
||||||
|
truncated_file_bytes: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue