use linkrs_core::types::{EdgeStrategy, Timestamp};
use std::collections::BTreeMap;
use std::fmt;
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
use super::{CsrVariant, MappedFrozen, RecordForm};
pub(crate) mod address;
pub(crate) mod append_log;
pub(crate) mod compaction;
pub(crate) mod core;
pub(crate) mod dirt;
pub(crate) mod freeze;
pub(crate) mod iter;
pub(crate) mod manifest;
pub(crate) mod read;
pub(crate) mod stats;
pub(crate) mod trait_impl;
pub use address::{
calibrator_max_density, calibrator_tree_height, group_base, group_id_for, group_size,
local_vid, region_id_for_local, region_local_range, region_tail_gap, regions_per_group,
validate_group_bits, DEFAULT_NODE_GROUP_BITS, LEAF_HIGH_CSR_DENSITY, LEAF_REGION_ROWS,
};
pub use compaction::{RegionMergeScope, GROUP_MERGE_MIN_DENSITY, REGION_MERGE_MIN_DENSITY};
pub use dirt::{EdgeCheckpointKind, GroupDirty, RegionDirty};
pub use freeze::{FreezeBlockReason, FreezeFeasibility};
pub use iter::ShardCsrIterator;
pub use manifest::TableShardManifest;
pub use stats::NodeGroupStats;
pub(crate) use append_log::{decode_append_ops, encode_append_ops, ShardAppendLog};
#[derive(Debug, Clone)]
struct Shard {
variant: CsrVariant,
mapped: Option<MappedFrozen>,
dirty: GroupDirty,
regions: Vec<RegionDirty>,
append: ShardAppendLog,
reclaim_hint: bool,
dead_entries: usize,
}
const ROUTE_CACHE_SLOTS: usize = 128;
const ROUTE_CACHE_EMPTY: u32 = u32::MAX;
struct RouteCache {
vids: Box<[AtomicU32]>,
gids: Box<[AtomicUsize]>,
locals: Box<[AtomicU32]>,
hits: AtomicUsize,
misses: AtomicUsize,
collisions: AtomicUsize,
}
impl RouteCache {
fn new() -> Self {
Self {
vids: (0..ROUTE_CACHE_SLOTS)
.map(|_| AtomicU32::new(ROUTE_CACHE_EMPTY))
.collect(),
gids: (0..ROUTE_CACHE_SLOTS)
.map(|_| AtomicUsize::new(0))
.collect(),
locals: (0..ROUTE_CACHE_SLOTS).map(|_| AtomicU32::new(0)).collect(),
hits: AtomicUsize::new(0),
misses: AtomicUsize::new(0),
collisions: AtomicUsize::new(0),
}
}
#[inline]
fn slot(vid: u32) -> usize {
vid as usize & (ROUTE_CACHE_SLOTS - 1)
}
fn lookup(&self, vid: u32) -> Option<(usize, u32)> {
let slot = Self::slot(vid);
let resident = self.vids[slot].load(Ordering::Relaxed);
if resident != vid {
self.misses.fetch_add(1, Ordering::Relaxed);
if resident != ROUTE_CACHE_EMPTY {
self.collisions.fetch_add(1, Ordering::Relaxed);
}
return None;
}
self.hits.fetch_add(1, Ordering::Relaxed);
Some((
self.gids[slot].load(Ordering::Relaxed),
self.locals[slot].load(Ordering::Relaxed),
))
}
fn insert(&self, vid: u32, gid: usize, local: u32) {
let slot = Self::slot(vid);
self.gids[slot].store(gid, Ordering::Relaxed);
self.locals[slot].store(local, Ordering::Relaxed);
self.vids[slot].store(vid, Ordering::Relaxed);
}
fn invalidate_gid(&self, gid: usize) {
for slot in 0..ROUTE_CACHE_SLOTS {
if self.vids[slot].load(Ordering::Relaxed) != ROUTE_CACHE_EMPTY
&& self.gids[slot].load(Ordering::Relaxed) == gid
{
self.vids[slot].store(ROUTE_CACHE_EMPTY, Ordering::Relaxed);
}
}
}
fn clear(&self) {
for slot in 0..ROUTE_CACHE_SLOTS {
self.vids[slot].store(ROUTE_CACHE_EMPTY, Ordering::Relaxed);
}
}
fn hit_rate(&self) -> f32 {
let hits = self.hits.load(Ordering::Relaxed) as f64;
let misses = self.misses.load(Ordering::Relaxed) as f64;
if hits + misses == 0.0 {
0.0
} else {
(hits / (hits + misses)) as f32
}
}
fn collision_count(&self) -> usize {
self.collisions.load(Ordering::Relaxed)
}
}
impl Clone for RouteCache {
fn clone(&self) -> Self {
Self::new()
}
}
impl fmt::Debug for RouteCache {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let live = (0..ROUTE_CACHE_SLOTS)
.filter(|slot| self.vids[*slot].load(Ordering::Relaxed) != ROUTE_CACHE_EMPTY)
.count();
f.debug_struct("RouteCache")
.field("slots", &ROUTE_CACHE_SLOTS)
.field("live", &live)
.field("hit_rate", &self.hit_rate())
.field("collisions", &self.collision_count())
.finish()
}
}
pub struct CsrShardSet {
strategy: EdgeStrategy,
group_bits: u32,
overflow_chunk_edges: usize,
record_form: RecordForm,
tombstone_reuse_cutoff: Timestamp,
shards: BTreeMap<usize, Shard>,
route_cache: RouteCache,
}
impl Clone for CsrShardSet {
fn clone(&self) -> Self {
Self {
strategy: self.strategy,
group_bits: self.group_bits,
overflow_chunk_edges: self.overflow_chunk_edges,
record_form: self.record_form,
tombstone_reuse_cutoff: self.tombstone_reuse_cutoff,
shards: self.shards.clone(),
route_cache: RouteCache::new(),
}
}
}
impl fmt::Debug for CsrShardSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CsrShardSet")
.field("strategy", &self.strategy)
.field("group_bits", &self.group_bits)
.field("overflow_chunk_edges", &self.overflow_chunk_edges)
.field("record_form", &self.record_form)
.field("tombstone_reuse_cutoff", &self.tombstone_reuse_cutoff)
.field("shards", &self.shards)
.field("route_cache", &self.route_cache)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests;