use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use bytes::Bytes;
use oxistore_blob::{BlobError, BlobMeta, BlobStore};
use crate::lru::LruCache;
use crate::sync::SyncCache;
#[derive(Debug, Default)]
pub struct BlobCacheStats {
hits: AtomicU64,
misses: AtomicU64,
}
impl BlobCacheStats {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn hits(&self) -> u64 {
self.hits.load(Ordering::Relaxed)
}
#[must_use]
pub fn misses(&self) -> u64 {
self.misses.load(Ordering::Relaxed)
}
#[must_use]
pub fn hit_rate(&self) -> f64 {
let h = self.hits();
let m = self.misses();
let total = h + m;
if total == 0 {
0.0
} else {
h as f64 / total as f64
}
}
pub fn reset(&self) {
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
}
}
type BlobLruSync = SyncCache<String, Bytes, LruCache<String, Bytes>>;
pub struct BlobCache<B: BlobStore> {
inner: B,
cache: Arc<BlobLruSync>,
stats: Arc<BlobCacheStats>,
}
impl<B: BlobStore> BlobCache<B> {
pub fn new(inner: B, capacity: usize) -> Self {
Self {
inner,
cache: Arc::new(SyncCache::new(LruCache::new(capacity))),
stats: Arc::new(BlobCacheStats::new()),
}
}
pub fn stats(&self) -> Arc<BlobCacheStats> {
Arc::clone(&self.stats)
}
}
impl<B: BlobStore> BlobStore for BlobCache<B> {
fn get(&self, key: &str) -> impl std::future::Future<Output = Result<Bytes, BlobError>> + Send {
let cache = Arc::clone(&self.cache);
let stats = Arc::clone(&self.stats);
let key_owned = key.to_string();
async move {
if let Some(cached) = cache.get(&key_owned) {
stats.hits.fetch_add(1, Ordering::Relaxed);
return Ok(cached);
}
stats.misses.fetch_add(1, Ordering::Relaxed);
let data = self.inner.get(&key_owned).await?;
cache.put(key_owned, data.clone());
Ok(data)
}
}
fn put(
&self,
key: &str,
data: Bytes,
) -> impl std::future::Future<Output = Result<(), BlobError>> + Send {
let cache = Arc::clone(&self.cache);
let key_owned = key.to_string();
async move {
cache.remove(&key_owned);
self.inner.put(&key_owned, data).await
}
}
fn delete(&self, key: &str) -> impl std::future::Future<Output = Result<(), BlobError>> + Send {
let cache = Arc::clone(&self.cache);
let key_owned = key.to_string();
async move {
cache.remove(&key_owned);
self.inner.delete(&key_owned).await
}
}
fn head(
&self,
key: &str,
) -> impl std::future::Future<Output = Result<BlobMeta, BlobError>> + Send {
let key_owned = key.to_string();
async move { self.inner.head(&key_owned).await }
}
fn list(
&self,
prefix: &str,
) -> impl std::future::Future<Output = Result<Vec<String>, BlobError>> + Send {
let prefix_owned = prefix.to_string();
async move { self.inner.list(&prefix_owned).await }
}
}