use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use crate::segment::{ColumnMeta, SegmentHeader};
use chronix_core::SegmentId;
#[derive(Debug, Clone)]
pub struct CachedSegmentMeta {
pub segment_id: SegmentId,
pub header: SegmentHeader,
pub columns: Vec<ColumnMeta>,
}
pub struct MetadataCache {
entries: Arc<RwLock<HashMap<SegmentId, Arc<CachedSegmentMeta>>>>,
bytes: Arc<RwLock<usize>>,
}
fn entry_bytes(meta: &CachedSegmentMeta) -> usize {
std::mem::size_of::<CachedSegmentMeta>()
+ meta.columns.capacity() * std::mem::size_of::<ColumnMeta>()
+ meta
.columns
.iter()
.map(|c| c.name.capacity() + c.bloom_filter.as_ref().map_or(0, Vec::capacity))
.sum::<usize>()
}
impl MetadataCache {
#[must_use]
pub fn new() -> Self {
Self {
entries: Arc::new(RwLock::new(HashMap::new())),
bytes: Arc::new(RwLock::new(0)),
}
}
pub fn insert(&self, meta: CachedSegmentMeta) {
let added = entry_bytes(&meta);
let mut map = self.entries.write();
let mut bytes = self.bytes.write();
let replaced = map.insert(meta.segment_id, Arc::new(meta));
*bytes = bytes.saturating_sub(replaced.as_deref().map_or(0, entry_bytes)) + added;
}
#[must_use]
pub fn get(&self, segment_id: SegmentId) -> Option<Arc<CachedSegmentMeta>> {
self.entries.read().get(&segment_id).cloned()
}
pub fn remove(&self, segment_id: SegmentId) {
let mut map = self.entries.write();
let mut bytes = self.bytes.write();
if let Some(removed) = map.remove(&segment_id) {
*bytes = bytes.saturating_sub(entry_bytes(&removed));
}
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
*self.bytes.read()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.read().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.read().is_empty()
}
pub fn clear(&self) {
let mut map = self.entries.write();
let mut bytes = self.bytes.write();
map.clear();
*bytes = 0;
}
#[must_use]
pub fn columns_for(&self, segment_id: SegmentId) -> Option<Vec<ColumnMeta>> {
self.entries
.read()
.get(&segment_id)
.map(|m| m.columns.clone())
}
}
impl Default for MetadataCache {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for MetadataCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetadataCache")
.field("entries", &self.entries.read().len())
.field("bytes", &*self.bytes.read())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::segment::ColumnStats;
fn make_header() -> SegmentHeader {
SegmentHeader {
version: 1,
flags: 0,
created_at: 0,
min_timestamp: 100,
max_timestamp: 200,
row_count: 1000,
column_count: 3,
series_count: 5,
compression: 0,
sort_order: 1,
}
}
fn make_column_meta(name: &str) -> ColumnMeta {
ColumnMeta {
name: name.to_string(),
data_type: 2,
role: 2,
default_encoding: 1,
stats: ColumnStats::empty(),
bloom_filter: None,
row_group_blooms: None,
encrypted: false,
key_id: None,
decimal_scale: None,
}
}
#[test]
fn insert_and_get() {
let cache = MetadataCache::new();
cache.insert(CachedSegmentMeta {
segment_id: SegmentId(1),
header: make_header(),
columns: vec![make_column_meta("cpu")],
});
let result = cache.get(SegmentId(1));
assert!(result.is_some());
assert_eq!(result.unwrap().columns.len(), 1);
}
#[test]
fn get_nonexistent_returns_none() {
let cache = MetadataCache::new();
assert!(cache.get(SegmentId(99)).is_none());
}
#[test]
fn remove_entry() {
let cache = MetadataCache::new();
cache.insert(CachedSegmentMeta {
segment_id: SegmentId(1),
header: make_header(),
columns: Vec::new(),
});
assert_eq!(cache.len(), 1);
cache.remove(SegmentId(1));
assert_eq!(cache.len(), 0);
}
#[test]
fn columns_for_segment() {
let cache = MetadataCache::new();
cache.insert(CachedSegmentMeta {
segment_id: SegmentId(1),
header: make_header(),
columns: vec![make_column_meta("cpu"), make_column_meta("mem")],
});
let cols = cache.columns_for(SegmentId(1)).unwrap();
assert_eq!(cols.len(), 2);
assert_eq!(cols[0].name, "cpu");
}
}