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>;