diff --git a/README.md b/README.md index 204d39d..ba6269f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Generic value log implementation for key-value separated storage, inspired by Ro - 100% safe & stable Rust - Supports generic KV-index structures (LSM-tree, ...) - Built-in per-blob compression (LZ4) -- In-memory blob cache for hot data +- In-memory blob cache for hot data, that can be shared between multiple value logs Keys are limited to 65536 bytes, values are limited to 2^32 bytes. diff --git a/src/blob_cache.rs b/src/blob_cache.rs index 341601c..045c9cb 100644 --- a/src/blob_cache.rs +++ b/src/blob_cache.rs @@ -1,9 +1,23 @@ -use crate::{value::UserValue, ValueHandle}; -use quick_cache::{sync::Cache, Weighter}; +use crate::{value::UserValue, value_log::ValueLogId, ValueHandle}; +use quick_cache::{sync::Cache, Equivalent, Weighter}; -type CacheKey = ValueHandle; type Item = UserValue; +#[derive(Eq, std::hash::Hash, PartialEq)] +pub struct CacheKey(ValueLogId, ValueHandle); + +impl Equivalent for (ValueLogId, ValueHandle) { + fn equivalent(&self, key: &CacheKey) -> bool { + self.0 == key.0 && self.1 == key.1 + } +} + +impl From<(ValueLogId, ValueHandle)> for CacheKey { + fn from((vid, handle): (ValueLogId, ValueHandle)) -> Self { + Self(vid, handle) + } +} + #[derive(Clone)] struct BlobWeighter; @@ -42,15 +56,15 @@ impl BlobCache { } } - pub(crate) fn insert(&self, handle: CacheKey, value: UserValue) { + pub(crate) fn insert(&self, key: CacheKey, value: UserValue) { if self.capacity > 0 { - self.data.insert(handle, value); + self.data.insert(key, value); } } - pub(crate) fn get(&self, handle: &CacheKey) -> Option { + pub(crate) fn get(&self, key: &CacheKey) -> Option { if self.capacity > 0 { - self.data.get(handle) + self.data.get(key) } else { None } diff --git a/src/config.rs b/src/config.rs index a2b0cdf..a3f4006 100644 --- a/src/config.rs +++ b/src/config.rs @@ -20,7 +20,10 @@ impl Default for Config { impl Config { /// Sets the blob cache. /// - /// Defaults to a blob cache with 16 MiB of capacity. + /// You can create a global [`BlobCache`] and share it between multiple + /// value logs to cap global cache memory usage. + /// + /// Defaults to a blob cache with 16 MiB of capacity *per value log*. #[must_use] pub fn blob_cache(mut self, blob_cache: Arc) -> Self { self.blob_cache = blob_cache; diff --git a/src/value_log.rs b/src/value_log.rs index 02e977d..e19247c 100644 --- a/src/value_log.rs +++ b/src/value_log.rs @@ -15,9 +15,19 @@ use std::{ fs::File, io::{BufReader, Read, Seek}, path::PathBuf, - sync::{Arc, Mutex}, + sync::{atomic::AtomicU64, Arc, Mutex}, }; +/// Unique value log ID +#[allow(clippy::module_name_repetitions)] +pub type ValueLogId = u64; + +/// Hands out a unique (monotonically increasing) value log ID +pub fn get_next_vlog_id() -> ValueLogId { + static VLOG_ID_COUNTER: AtomicU64 = AtomicU64::new(0); + VLOG_ID_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) +} + /// A disk-resident value log #[derive(Clone)] pub struct ValueLog(Arc); @@ -32,6 +42,8 @@ impl std::ops::Deref for ValueLog { #[allow(clippy::module_name_repetitions)] pub struct ValueLogInner { + id: u64, + config: Config, path: PathBuf, @@ -100,6 +112,7 @@ impl ValueLog { let manifest = SegmentManifest::create_new(&path)?; Ok(Self(Arc::new(ValueLogInner { + id: get_next_vlog_id(), config, path, blob_cache, @@ -138,6 +151,7 @@ impl ValueLog { .unwrap_or_default(); Ok(Self(Arc::new(ValueLogInner { + id: get_next_vlog_id(), config, path, blob_cache, @@ -172,7 +186,7 @@ impl ValueLog { return Ok(None); }; - if let Some(value) = self.blob_cache.get(handle) { + if let Some(value) = self.blob_cache.get(&((self.id, handle.clone()).into())) { return Ok(Some(value)); } @@ -193,7 +207,8 @@ impl ValueLog { let val: UserValue = val.into(); - self.blob_cache.insert(handle.clone(), val.clone()); + self.blob_cache + .insert((self.id, handle.clone()).into(), val.clone()); Ok(Some(val)) }