#![forbid(unsafe_code)]
use std::collections::HashMap;
use crate::core::extent::ChunkId;
use crate::format::version::RecordTag;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Location {
pub segment_seq: u64,
pub offset: u64,
pub stored_len: u64,
pub materialized_len: Option<u64>,
pub tag: RecordTag,
}
impl Location {
pub fn total_size(&self) -> u64 {
crate::format::record::HEADER_SIZE + self.stored_len
}
}
#[derive(Debug)]
pub struct ObjectIndex {
shards: Box<[std::sync::RwLock<HashMap<ChunkId, Location>>]>,
}
const SHARDS: usize = 64;
impl Default for ObjectIndex {
fn default() -> Self {
let mut shards = Vec::with_capacity(SHARDS);
for _ in 0..SHARDS {
shards.push(std::sync::RwLock::new(HashMap::new()));
}
Self {
shards: shards.into_boxed_slice(),
}
}
}
impl ObjectIndex {
pub fn new() -> Self {
Self::default()
}
fn shard_of(id: &ChunkId) -> usize {
(u64::from_le_bytes(id.as_bytes()[..8].try_into().expect("8 bytes")) as usize)
& (SHARDS - 1)
}
pub fn insert(&self, id: ChunkId, loc: Location) {
self.shards[Self::shard_of(&id)]
.write()
.expect("object index shard poisoned")
.insert(id, loc);
}
pub fn get(&self, id: &ChunkId) -> Option<Location> {
self.shards[Self::shard_of(id)]
.read()
.expect("object index shard poisoned")
.get(id)
.copied()
}
pub fn contains(&self, id: &ChunkId) -> bool {
self.shards[Self::shard_of(id)]
.read()
.expect("object index shard poisoned")
.contains_key(id)
}
pub fn len(&self) -> usize {
self.shards
.iter()
.map(|s| s.read().expect("object index shard poisoned").len())
.sum()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter(&self) -> Vec<(ChunkId, Location)> {
let mut out = Vec::new();
for s in self.shards.iter() {
let guard = s.read().expect("object index shard poisoned");
out.extend(guard.iter().map(|(k, v)| (*k, *v)));
}
out
}
pub fn remove(&self, id: &ChunkId) -> Option<Location> {
self.shards[Self::shard_of(id)]
.write()
.expect("object index shard poisoned")
.remove(id)
}
pub fn clear(&self) {
for s in self.shards.iter() {
s.write().expect("object index shard poisoned").clear();
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct StoreStats {
pub physical_capacity: u64,
pub physical_used: u64,
pub logical_bytes: u64,
pub reachable_bytes: u64,
pub unreachable_bytes: u64,
pub snapshot_pinned_bytes: u64,
pub gc_reserve_bytes: u64,
pub object_count: u64,
pub data_record_count: u64,
}
impl StoreStats {
pub fn effective_ratio(&self) -> f64 {
if self.reachable_bytes == 0 {
0.0
} else {
self.logical_bytes as f64 / self.reachable_bytes as f64
}
}
pub fn reclaimable(&self) -> u64 {
self.unreachable_bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_basics() {
let idx = ObjectIndex::new();
let id = ChunkId::of(b"obj");
let loc = Location {
segment_seq: 0,
offset: 64,
stored_len: 16,
materialized_len: Some(16),
tag: RecordTag::Data,
};
assert!(!idx.contains(&id));
idx.insert(id, loc);
assert!(idx.contains(&id));
assert_eq!(idx.get(&id), Some(loc));
assert_eq!(idx.len(), 1);
assert_eq!(idx.remove(&id), Some(loc));
assert!(idx.is_empty());
}
}