Skip to content

Commit bba089a

Browse files
committed
simplify versioned lru
1 parent c4952fc commit bba089a

File tree

3 files changed

+92
-137
lines changed

3 files changed

+92
-137
lines changed

src/cache.rs

Lines changed: 70 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use alloy_trie::Nibbles;
33
use std::{
44
collections::HashMap,
55
num::NonZeroUsize,
6-
sync::{Arc, RwLock, RwLockReadGuard},
6+
sync::{Arc, RwLock},
77
};
88

99
/// An entry in the versioned LRU cache with doubly-linked list indices.
@@ -115,7 +115,7 @@ impl VersionedLru {
115115
.iter()
116116
.position(|entry| entry.key == tail_key && entry.snapshot_id == smallest);
117117

118-
// Remove from entries hashmap
118+
// Remove from `entries` hashmap
119119
if let Some(evict_idx) = smallest_idx {
120120
if let Some(versions) = self.entries.get_mut(&tail_key) {
121121
versions.retain(|e| e.snapshot_id != smallest);
@@ -162,7 +162,7 @@ impl VersionedLru {
162162
self.tail = prev_idx;
163163
}
164164

165-
// Mark as removed (we could also compact the list, but this is simpler)
165+
// Mark as removed
166166
self.lru[lru_idx].lru_prev = None;
167167
self.lru[lru_idx].lru_next = None;
168168
}
@@ -184,7 +184,7 @@ impl VersionedLru {
184184
}
185185
}
186186

187-
// Remove obsolete entries from LRU list
187+
// Remove from LRU list
188188
self.lru.retain(|entry| entry.snapshot_id >= min_id);
189189

190190
// Rebuild LRU pointers after retention
@@ -215,68 +215,31 @@ impl CacheManager {
215215
CacheManager { cache: Arc::new(RwLock::new(VersionedLru::new(max_size.get()))) }
216216
}
217217

218-
/// Provides a reader handle to the cache.
219-
/// Multiple readers can exist concurrently.
220-
pub fn read(&self) -> Reader {
221-
Reader { guard: self.cache.read().unwrap() }
222-
}
223-
224-
/// Provides a writer handle to the cache.
225-
/// This briefly acquires a lock to clone the cache, then releases it.
226-
pub fn write(&self) -> Writer {
227-
Writer { cache: Arc::clone(&self.cache) }
228-
}
229-
230-
/// Sets the minimum snapshot ID for proactive cache purging.
231-
pub fn set_min_snapshot_id(&self, min_snapshot_id: SnapshotId) {
232-
let mut guard = self.cache.write().unwrap();
233-
guard.set_min_snapshot_id(min_snapshot_id);
234-
}
235-
}
236-
237-
/// A handle for reading from the cache.
238-
/// Dropping this struct releases the read lock.
239-
#[derive(Debug)]
240-
pub struct Reader<'a> {
241-
guard: RwLockReadGuard<'a, VersionedLru>,
242-
}
243-
244-
impl<'a> Reader<'a> {
245-
/// Gets a value for the given key and snapshot ID without updating LRU state.
218+
/// Gets a value for the given key and snapshot ID and updates LRU state.
246219
pub fn get(&self, snapshot_id: SnapshotId, key: &Nibbles) -> Option<(PageId, u8)> {
247-
let versions = self.guard.entries.get(key)?;
248-
versions
249-
.iter()
250-
.rev()
251-
.find(|entry| entry.snapshot_id <= snapshot_id)
252-
.and_then(|entry| entry.value)
220+
// Acquire write lock since we move the `Entry` to the front of the LRU list each time
221+
// This is helpful because we'll want to cache an account on read to accelerate
222+
// reading its contract state.
223+
let mut guard = self.cache.write().unwrap();
224+
guard.get(key, snapshot_id)
253225
}
254-
}
255226

256-
/// A handle for writing to the cache.
257-
/// Modifications are made directly to the shared cache under write lock.
258-
#[derive(Debug)]
259-
pub struct Writer {
260-
cache: Arc<RwLock<VersionedLru>>,
261-
}
262-
263-
impl Writer {
264227
/// Inserts or updates an entry in the cache.
265-
pub fn write(&mut self, snapshot_id: SnapshotId, key: Nibbles, value: Option<(PageId, u8)>) {
228+
pub fn insert(&self, snapshot_id: SnapshotId, key: Nibbles, value: Option<(PageId, u8)>) {
266229
let mut guard = self.cache.write().unwrap();
267230
guard.set(key, snapshot_id, value);
268231
}
269232

270233
/// Removes an entry from the cache by inserting a None value.
271-
pub fn remove(&mut self, snapshot_id: SnapshotId, key: Nibbles) {
234+
pub fn remove(&self, snapshot_id: SnapshotId, key: Nibbles) {
272235
let mut guard = self.cache.write().unwrap();
273236
guard.set(key, snapshot_id, None);
274237
}
275238

276-
/// Gets a value and updates LRU state.
277-
pub fn get(&mut self, snapshot_id: SnapshotId, key: &Nibbles) -> Option<(PageId, u8)> {
239+
/// Sets the minimum snapshot ID for proactive cache purging.
240+
pub fn set_min_snapshot_id(&self, min_snapshot_id: SnapshotId) {
278241
let mut guard = self.cache.write().unwrap();
279-
guard.get(key, snapshot_id)
242+
guard.set_min_snapshot_id(min_snapshot_id);
280243
}
281244
}
282245

@@ -291,62 +254,64 @@ mod tests {
291254
let shared_cache = Arc::new(cache);
292255

293256
// first writer
294-
let mut writer1 = shared_cache.write();
295-
writer1.write(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
296-
writer1.write(100, Nibbles::from_nibbles([2]), Some((PageId::new(12).unwrap(), 13)));
297-
writer1.write(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
298-
drop(writer1);
257+
shared_cache.insert(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
258+
shared_cache.insert(100, Nibbles::from_nibbles([2]), Some((PageId::new(12).unwrap(), 13)));
259+
shared_cache.insert(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
299260

300261
// have some concurrent readers
301262
let cache_reader1 = Arc::clone(&shared_cache);
302263
let reader1 = thread::spawn(move || {
303-
let reader = cache_reader1.read();
304-
let val1 = reader.get(100, &Nibbles::from_nibbles([1]));
305-
let val2 = reader.get(200, &Nibbles::from_nibbles([1]));
264+
let val1 = cache_reader1.get(100, &Nibbles::from_nibbles([1]));
265+
let val2 = cache_reader1.get(200, &Nibbles::from_nibbles([1]));
306266
assert_eq!(val1, Some((PageId::new(10).unwrap(), 11)));
307267
assert_eq!(val2, Some((PageId::new(20).unwrap(), 21)));
308268
thread::sleep(Duration::from_millis(50));
309269
});
310270

311271
let cache_reader2 = Arc::clone(&shared_cache);
312272
let reader2 = thread::spawn(move || {
313-
let reader = cache_reader2.read();
314-
let val = reader.get(100, &Nibbles::from_nibbles([2]));
273+
let val = cache_reader2.get(100, &Nibbles::from_nibbles([2]));
315274
assert_eq!(val, Some((PageId::new(12).unwrap(), 13)));
316275
thread::sleep(Duration::from_millis(100));
317276
});
318277

319-
// writer2 will be blocked until concurrent readers are done
278+
// writer2
320279
let cache_writer2 = Arc::clone(&shared_cache);
321280
let writer2 = thread::spawn(move || {
322-
let mut writer = cache_writer2.write();
323-
writer.write(101, Nibbles::from_nibbles([3]), Some((PageId::new(14).unwrap(), 15)));
324-
writer.write(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
281+
cache_writer2.insert(
282+
101,
283+
Nibbles::from_nibbles([3]),
284+
Some((PageId::new(14).unwrap(), 15)),
285+
);
286+
cache_writer2.insert(
287+
300,
288+
Nibbles::from_nibbles([1]),
289+
Some((PageId::new(30).unwrap(), 31)),
290+
);
325291
});
326292

327293
reader1.join().unwrap();
328294
reader2.join().unwrap();
329295
writer2.join().unwrap();
330296

331-
let final_reader = shared_cache.read();
332297
assert_eq!(
333-
final_reader.get(100, &Nibbles::from_nibbles([1])),
298+
shared_cache.get(100, &Nibbles::from_nibbles([1])),
334299
Some((PageId::new(10).unwrap(), 11))
335300
);
336301
assert_eq!(
337-
final_reader.get(100, &Nibbles::from_nibbles([2])),
302+
shared_cache.get(100, &Nibbles::from_nibbles([2])),
338303
Some((PageId::new(12).unwrap(), 13))
339304
);
340305
assert_eq!(
341-
final_reader.get(101, &Nibbles::from_nibbles([3])),
306+
shared_cache.get(101, &Nibbles::from_nibbles([3])),
342307
Some((PageId::new(14).unwrap(), 15))
343308
);
344309
assert_eq!(
345-
final_reader.get(200, &Nibbles::from_nibbles([1])),
310+
shared_cache.get(200, &Nibbles::from_nibbles([1])),
346311
Some((PageId::new(20).unwrap(), 21))
347312
);
348313
assert_eq!(
349-
final_reader.get(300, &Nibbles::from_nibbles([1])),
314+
shared_cache.get(300, &Nibbles::from_nibbles([1])),
350315
Some((PageId::new(30).unwrap(), 31))
351316
);
352317
}
@@ -356,41 +321,36 @@ mod tests {
356321
let cache = CacheManager::new(NonZeroUsize::new(10).unwrap());
357322
let shared_cache = Arc::new(cache);
358323

359-
let mut writer = shared_cache.write();
360-
writer.write(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
361-
writer.write(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
362-
writer.write(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
363-
drop(writer);
364-
365-
let reader = shared_cache.read();
324+
shared_cache.insert(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
325+
shared_cache.insert(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
326+
shared_cache.insert(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
366327

367-
// given the exact same snapshot
328+
// exact same snapshots
368329
assert_eq!(
369-
reader.get(100, &Nibbles::from_nibbles([1])),
330+
shared_cache.get(100, &Nibbles::from_nibbles([1])),
370331
Some((PageId::new(10).unwrap(), 11))
371332
);
372333
assert_eq!(
373-
reader.get(200, &Nibbles::from_nibbles([1])),
334+
shared_cache.get(200, &Nibbles::from_nibbles([1])),
374335
Some((PageId::new(20).unwrap(), 21))
375336
);
376337
assert_eq!(
377-
reader.get(300, &Nibbles::from_nibbles([1])),
338+
shared_cache.get(300, &Nibbles::from_nibbles([1])),
378339
Some((PageId::new(30).unwrap(), 31))
379340
);
380341

381-
// given different snapshots, but it should find the latest version <= target snapshot
342+
// different snapshots, but it should find the latest version <= target snapshot
382343
assert_eq!(
383-
reader.get(150, &Nibbles::from_nibbles([1])),
344+
shared_cache.get(150, &Nibbles::from_nibbles([1])),
384345
Some((PageId::new(10).unwrap(), 11))
385346
);
386347
assert_eq!(
387-
reader.get(250, &Nibbles::from_nibbles([1])),
348+
shared_cache.get(250, &Nibbles::from_nibbles([1])),
388349
Some((PageId::new(20).unwrap(), 21))
389350
);
390351

391-
// given snapshot too small, since snapshot < earliest
392-
assert_eq!(reader.get(50, &Nibbles::from_nibbles([1])), None);
393-
drop(reader);
352+
// snapshot too small, since snapshot < earliest
353+
assert_eq!(shared_cache.get(50, &Nibbles::from_nibbles([1])), None);
394354
}
395355

396356
#[test]
@@ -399,17 +359,13 @@ mod tests {
399359
let shared_cache = Arc::new(cache);
400360

401361
// insert a value
402-
let mut writer = shared_cache.write();
403-
writer.write(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
362+
shared_cache.insert(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
404363

405364
// invalidate it
406-
writer.write(100, Nibbles::from_nibbles([1]), None);
407-
drop(writer);
365+
shared_cache.insert(100, Nibbles::from_nibbles([1]), None);
408366

409367
// try reading it
410-
let reader = shared_cache.read();
411-
assert_eq!(reader.get(100, &Nibbles::from_nibbles([1])), None);
412-
drop(reader);
368+
assert_eq!(shared_cache.get(100, &Nibbles::from_nibbles([1])), None);
413369
}
414370

415371
#[test]
@@ -418,65 +374,58 @@ mod tests {
418374
let shared_cache = Arc::new(cache);
419375

420376
// insert entries with different snapshots
421-
let mut writer = shared_cache.write();
422-
writer.write(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
423-
writer.write(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
424-
writer.write(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
425-
drop(writer);
377+
shared_cache.insert(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
378+
shared_cache.insert(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
379+
shared_cache.insert(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
426380

427381
// set minimum snapshot ID to 250
428382
shared_cache.set_min_snapshot_id(250);
429-
let reader = shared_cache.read();
430383

431384
// purged the entries below min snapshot
432-
assert_eq!(reader.get(100, &Nibbles::from_nibbles([1])), None);
433-
assert_eq!(reader.get(200, &Nibbles::from_nibbles([1])), None);
385+
assert_eq!(shared_cache.get(100, &Nibbles::from_nibbles([1])), None);
386+
assert_eq!(shared_cache.get(200, &Nibbles::from_nibbles([1])), None);
434387

435388
// only keep entries above min snapshot
436389
assert_eq!(
437-
reader.get(300, &Nibbles::from_nibbles([1])),
390+
shared_cache.get(300, &Nibbles::from_nibbles([1])),
438391
Some((PageId::new(30).unwrap(), 31))
439392
);
440-
drop(reader);
441393
}
442394

443395
#[test]
444396
fn test_oldest_sibling_eviction() {
445397
let cache = CacheManager::new(NonZeroUsize::new(4).unwrap());
446398
let shared_cache = Arc::new(cache);
447399

448-
let mut writer = shared_cache.write();
449400
// multiple versions of key [1]
450-
writer.write(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
451-
writer.write(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
452-
writer.write(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
401+
shared_cache.insert(100, Nibbles::from_nibbles([1]), Some((PageId::new(10).unwrap(), 11)));
402+
shared_cache.insert(200, Nibbles::from_nibbles([1]), Some((PageId::new(20).unwrap(), 21)));
403+
shared_cache.insert(300, Nibbles::from_nibbles([1]), Some((PageId::new(30).unwrap(), 31)));
453404

454405
// one entry for key [2]
455-
writer.write(150, Nibbles::from_nibbles([2]), Some((PageId::new(15).unwrap(), 16)));
406+
shared_cache.insert(150, Nibbles::from_nibbles([2]), Some((PageId::new(15).unwrap(), 16)));
456407

457408
// since the cache is full, should evict oldest sibling of tail entry
458-
writer.write(400, Nibbles::from_nibbles([3]), Some((PageId::new(40).unwrap(), 41)));
459-
drop(writer);
409+
shared_cache.insert(400, Nibbles::from_nibbles([3]), Some((PageId::new(40).unwrap(), 41)));
460410

461-
let reader = shared_cache.read();
462-
// the oldest sibling (snapshot 100) should be evicted, NOT the tail entry
463-
assert_eq!(reader.get(100, &Nibbles::from_nibbles([1])), None);
411+
// snapshot 100 should be evicted
412+
assert_eq!(shared_cache.get(100, &Nibbles::from_nibbles([1])), None);
464413

465-
// test the rest should exist
414+
// rest should exist
466415
assert_eq!(
467-
reader.get(200, &Nibbles::from_nibbles([1])),
416+
shared_cache.get(200, &Nibbles::from_nibbles([1])),
468417
Some((PageId::new(20).unwrap(), 21))
469418
);
470419
assert_eq!(
471-
reader.get(300, &Nibbles::from_nibbles([1])),
420+
shared_cache.get(300, &Nibbles::from_nibbles([1])),
472421
Some((PageId::new(30).unwrap(), 31))
473422
);
474423
assert_eq!(
475-
reader.get(150, &Nibbles::from_nibbles([2])),
424+
shared_cache.get(150, &Nibbles::from_nibbles([2])),
476425
Some((PageId::new(15).unwrap(), 16))
477426
);
478427
assert_eq!(
479-
reader.get(400, &Nibbles::from_nibbles([3])),
428+
shared_cache.get(400, &Nibbles::from_nibbles([3])),
480429
Some((PageId::new(40).unwrap(), 41))
481430
);
482431
}

src/database.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub struct DatabaseOptions {
2929
create_new: bool,
3030
wipe: bool,
3131
meta_path: Option<PathBuf>,
32+
max_pages: u32,
3233
}
3334

3435
#[derive(Debug)]
@@ -79,6 +80,12 @@ impl DatabaseOptions {
7980
self
8081
}
8182

83+
/// Sets the maximum number of pages that can be allocated.
84+
pub fn max_pages(&mut self, max_pages: u32) -> &mut Self {
85+
self.max_pages = max_pages;
86+
self
87+
}
88+
8289
/// Opens the database file at the given path.
8390
pub fn open(&self, db_path: impl AsRef<Path>) -> Result<Database, OpenError> {
8491
let db_path = db_path.as_ref();

0 commit comments

Comments
 (0)