#[cfg(test)]
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use quick_cache::sync::Cache as QCache;
use quick_cache::{Equivalent, Weighter};
use crate::sstable::block::Block;
use crate::Value;
const KIND_DATA: u8 = 0;
const KIND_INDEX: u8 = 1;
const KIND_VLOG: u8 = 2;
const KIND_DATA_HISTORY: u8 = 3;
#[derive(Clone)]
pub(crate) enum Item {
Data(Arc<Block>),
Index(Arc<Block>),
VLog(Value),
}
#[derive(Eq, std::hash::Hash, PartialEq)]
pub(crate) struct CacheKey {
kind: u8,
id: u64,
offset: u64,
}
impl From<(u8, u64, u64)> for CacheKey {
fn from((kind, id, offset): (u8, u64, u64)) -> Self {
Self {
kind,
id,
offset,
}
}
}
impl Equivalent<CacheKey> for (u8, u64, &u64) {
fn equivalent(&self, key: &CacheKey) -> bool {
self.0 == key.kind && self.1 == key.id && *self.2 == key.offset
}
}
#[derive(Clone)]
struct BlockWeighter;
impl Weighter<CacheKey, Item> for BlockWeighter {
fn weight(&self, _: &CacheKey, item: &Item) -> u64 {
match item {
Item::Data(block) => block.size() as u64,
Item::Index(block) => block.size() as u64,
Item::VLog(value) => value.len() as u64,
}
}
}
pub(crate) struct BlockCache {
data: QCache<CacheKey, Item, BlockWeighter>,
#[cfg(test)]
data_hits: AtomicU64,
#[cfg(test)]
data_misses: AtomicU64,
#[cfg(test)]
index_hits: AtomicU64,
#[cfg(test)]
index_misses: AtomicU64,
#[cfg(test)]
vlog_hits: AtomicU64,
#[cfg(test)]
vlog_misses: AtomicU64,
#[cfg(test)]
data_history_hits: AtomicU64,
#[cfg(test)]
data_history_misses: AtomicU64,
}
impl BlockCache {
pub(crate) fn with_capacity_bytes(bytes: u64) -> Self {
Self {
data: QCache::with_weighter(10_000, bytes, BlockWeighter),
#[cfg(test)]
data_hits: AtomicU64::new(0),
#[cfg(test)]
data_misses: AtomicU64::new(0),
#[cfg(test)]
index_hits: AtomicU64::new(0),
#[cfg(test)]
index_misses: AtomicU64::new(0),
#[cfg(test)]
vlog_hits: AtomicU64::new(0),
#[cfg(test)]
vlog_misses: AtomicU64::new(0),
#[cfg(test)]
data_history_hits: AtomicU64::new(0),
#[cfg(test)]
data_history_misses: AtomicU64::new(0),
}
}
pub(crate) fn insert_data_block(&self, table_id: u64, offset: u64, block: Arc<Block>) {
self.data.insert((KIND_DATA, table_id, offset).into(), Item::Data(block));
}
pub(crate) fn insert_data_block_history(&self, table_id: u64, offset: u64, block: Arc<Block>) {
self.data.insert((KIND_DATA_HISTORY, table_id, offset).into(), Item::Data(block));
}
pub(crate) fn insert_index_block(&self, table_id: u64, offset: u64, block: Arc<Block>) {
self.data.insert((KIND_INDEX, table_id, offset).into(), Item::Index(block));
}
pub(crate) fn insert_vlog(&self, file_id: u32, offset: u64, value: Value) {
self.data.insert((KIND_VLOG, file_id as u64, offset).into(), Item::VLog(value));
}
pub(crate) fn get_data_block(&self, table_id: u64, offset: u64) -> Option<Arc<Block>> {
let key = (KIND_DATA, table_id, &offset);
let item = self.data.get(&key);
#[cfg(test)]
{
if item.is_some() {
self.data_hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
} else {
self.data_misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
match item.as_ref()? {
Item::Data(block) => Some(Arc::clone(block)),
_ => None,
}
}
pub(crate) fn get_data_block_history(&self, table_id: u64, offset: u64) -> Option<Arc<Block>> {
let key = (KIND_DATA_HISTORY, table_id, &offset);
let item = self.data.get(&key);
#[cfg(test)]
{
if item.is_some() {
self.data_history_hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
} else {
self.data_history_misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
match item.as_ref()? {
Item::Data(block) => Some(Arc::clone(block)),
_ => None,
}
}
pub(crate) fn get_index_block(&self, table_id: u64, offset: u64) -> Option<Arc<Block>> {
let key = (KIND_INDEX, table_id, &offset);
let item = self.data.get(&key);
#[cfg(test)]
{
if item.is_some() {
self.index_hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
} else {
self.index_misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
match item.as_ref()? {
Item::Index(block) => Some(Arc::clone(block)),
_ => None,
}
}
pub(crate) fn get_vlog(&self, file_id: u32, offset: u64) -> Option<Value> {
let key = (KIND_VLOG, file_id as u64, &offset);
let item = self.data.get(&key);
#[cfg(test)]
{
if item.is_some() {
self.vlog_hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
} else {
self.vlog_misses.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
match item.as_ref()? {
Item::VLog(value) => Some(value.clone()),
_ => None,
}
}
#[cfg(test)]
pub(crate) fn get_stats(&self) -> CacheStats {
CacheStats {
data_hits: self.data_hits.load(std::sync::atomic::Ordering::Relaxed),
data_misses: self.data_misses.load(std::sync::atomic::Ordering::Relaxed),
index_hits: self.index_hits.load(std::sync::atomic::Ordering::Relaxed),
index_misses: self.index_misses.load(std::sync::atomic::Ordering::Relaxed),
vlog_hits: self.vlog_hits.load(std::sync::atomic::Ordering::Relaxed),
vlog_misses: self.vlog_misses.load(std::sync::atomic::Ordering::Relaxed),
data_history_hits: self.data_history_hits.load(std::sync::atomic::Ordering::Relaxed),
data_history_misses: self
.data_history_misses
.load(std::sync::atomic::Ordering::Relaxed),
}
}
#[cfg(test)]
pub(crate) fn reset_stats(&self) {
self.data_hits.store(0, std::sync::atomic::Ordering::Relaxed);
self.data_misses.store(0, std::sync::atomic::Ordering::Relaxed);
self.index_hits.store(0, std::sync::atomic::Ordering::Relaxed);
self.index_misses.store(0, std::sync::atomic::Ordering::Relaxed);
self.vlog_hits.store(0, std::sync::atomic::Ordering::Relaxed);
self.vlog_misses.store(0, std::sync::atomic::Ordering::Relaxed);
self.data_history_hits.store(0, std::sync::atomic::Ordering::Relaxed);
self.data_history_misses.store(0, std::sync::atomic::Ordering::Relaxed);
}
}
#[cfg(test)]
#[derive(Debug, Clone, Copy)]
pub(crate) struct CacheStats {
pub data_hits: u64,
pub data_misses: u64,
pub index_hits: u64,
pub index_misses: u64,
pub vlog_hits: u64,
pub vlog_misses: u64,
pub data_history_hits: u64,
pub data_history_misses: u64,
}
#[cfg(test)]
impl CacheStats {
pub fn total_hits(&self) -> u64 {
self.data_hits + self.index_hits + self.vlog_hits + self.data_history_hits
}
pub fn total_misses(&self) -> u64 {
self.data_misses + self.index_misses + self.vlog_misses + self.data_history_misses
}
pub fn total_accesses(&self) -> u64 {
self.total_hits() + self.total_misses()
}
pub fn hit_ratio(&self) -> f64 {
let total = self.total_accesses();
if total == 0 {
0.0
} else {
self.total_hits() as f64 / total as f64
}
}
}