Skip to main content

dig_blockstore/cache/
sharded.rs

1//! Sharded LRU caches for hot dig-block values ([`CAC-001`](../../docs/requirements/domains/caching/specs/CAC-001_sharded_block_cache.md)).
2//!
3//! ## Types
4//!
5//! - [`ShardedBlockCache`] — [`dig_block::L2Block`] bodies ([`BLK-002`](../../docs/requirements/domains/block_storage/specs/BLK-002.md)).
6//! - [`ShardedHeaderCache`] — [`dig_block::L2BlockHeader`] rows ([`BLK-003`](../../docs/requirements/domains/block_storage/specs/BLK-003.md), [`CAC-002`](../../docs/requirements/domains/caching/specs/CAC-002_sharded_header_cache.md) precursor).
7//!
8//! Both are aliases over [`ShardedLruCache`], which centralizes shard math and eviction policy.
9//!
10//! ## Locking note vs CAC-001 prose
11//!
12//! The normative CAC-001 snippet uses [`parking_lot::RwLock`] **read** locks for `get`. The [`lru::LruCache`] API
13//! requires `&mut self` to promote entries on access, so this implementation uses **write** locks per shard for
14//! `get_clone` + `insert`. Contention is still reduced ~`num_shards`× versus one global LRU.
15//!
16//! ## Shard selection
17//!
18//! [`Bytes32`](chia_protocol::Bytes32) is uniform; we use `key[0]` like CAC-001. For power-of-two shard counts we
19//! apply a bitmask instead of `%`.
20
21use std::num::NonZeroUsize;
22
23use chia_protocol::Bytes32;
24use dig_block::{L2Block, L2BlockHeader};
25use lru::LruCache;
26use parking_lot::RwLock;
27
28/// Generic sharded LRU keyed by block hash ([`CAC-001`](../../docs/requirements/domains/caching/specs/CAC-001_sharded_block_cache.md)).
29pub struct ShardedLruCache<V: Clone> {
30    shards: Vec<RwLock<LruCache<Bytes32, V>>>,
31    num_shards: usize,
32}
33
34impl<V: Clone> ShardedLruCache<V> {
35    /// Build `num_shards` LRUs; each shard capacity is `max(1, total_capacity / num_shards)`.
36    ///
37    /// **`num_shards`:** Clamped to ≥ 1. If not a power of two, [`Self::shard_index`] uses modulo instead of bitmask.
38    pub fn new(total_capacity: usize, num_shards: usize) -> Self {
39        let num_shards = num_shards.max(1);
40        let per_shard = (total_capacity / num_shards).max(1);
41        let nz = NonZeroUsize::new(per_shard).expect("per-shard capacity is at least 1");
42        let shards = (0..num_shards)
43            .map(|_| RwLock::new(LruCache::new(nz)))
44            .collect();
45        Self { shards, num_shards }
46    }
47
48    #[inline]
49    fn shard_index(&self, key: &Bytes32) -> usize {
50        let b = key.as_ref()[0] as usize;
51        if self.num_shards.is_power_of_two() {
52            b & (self.num_shards - 1)
53        } else {
54            b % self.num_shards
55        }
56    }
57
58    /// Clone a cached value if present; promotes the entry inside the shard LRU.
59    pub fn get_clone(&self, key: &Bytes32) -> Option<V> {
60        let i = self.shard_index(key);
61        let mut guard = self.shards[i].write();
62        guard.get(key).cloned()
63    }
64
65    /// Insert / update; may evict LRU entry in this shard only.
66    pub fn insert(&self, key: Bytes32, value: V) {
67        let i = self.shard_index(&key);
68        let mut guard = self.shards[i].write();
69        guard.put(key, value);
70    }
71
72    /// Membership probe **without** LRU promotion ([`LruCache::peek`](lru::LruCache::peek)).
73    ///
74    /// **Rationale:** [`Self::get_clone`] must take a write lock because [`LruCache::get`] mutates recency; existence-only
75    /// checks for [`BLK-011`](../../docs/requirements/domains/block_storage/specs/BLK-011.md) should not reorder hot entries
76    /// when answering “is this hash cached?”.
77    #[inline]
78    #[must_use]
79    pub fn contains(&self, key: &Bytes32) -> bool {
80        let i = self.shard_index(key);
81        let guard = self.shards[i].read();
82        guard.peek(key).is_some()
83    }
84
85    /// Drop one entry — tests simulate eviction; future invalidation / PRN hooks may reuse this.
86    pub fn remove(&self, key: &Bytes32) {
87        let i = self.shard_index(key);
88        let mut guard = self.shards[i].write();
89        let _ = guard.pop(key);
90    }
91}
92
93/// Sharded LRU for block **bodies** ([`BLK-002`](../../docs/requirements/domains/block_storage/specs/BLK-002.md)).
94pub type ShardedBlockCache = ShardedLruCache<L2Block>;
95
96/// Sharded LRU for **headers** ([`BLK-003`](../../docs/requirements/domains/block_storage/specs/BLK-003.md)).
97pub type ShardedHeaderCache = ShardedLruCache<L2BlockHeader>;