Skip to content

Commit 51321da

Browse files
Make the block cache optional (#8066)
Address contention on the store's `block_cache` by allowing it to be disabled when `--block-cache-size 0` is provided, and also making this the default. Co-Authored-By: Michael Sproul <[email protected]>
1 parent 92f60b8 commit 51321da

File tree

5 files changed

+133
-84
lines changed

5 files changed

+133
-84
lines changed

beacon_node/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ pub fn cli_app() -> Command {
779779
.long("block-cache-size")
780780
.value_name("SIZE")
781781
.help("Specifies how many blocks the database should cache in memory")
782-
.default_value("5")
782+
.default_value("0")
783783
.action(ArgAction::Set)
784784
.display_order(0)
785785
)

beacon_node/store/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub const DEFAULT_BACKEND: DatabaseBackend = DatabaseBackend::LevelDb;
1919
pub const PREV_DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 2048;
2020
pub const DEFAULT_SLOTS_PER_RESTORE_POINT: u64 = 8192;
2121
pub const DEFAULT_EPOCHS_PER_STATE_DIFF: u64 = 8;
22-
pub const DEFAULT_BLOCK_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(64);
22+
pub const DEFAULT_BLOCK_CACHE_SIZE: usize = 0;
2323
pub const DEFAULT_STATE_CACHE_SIZE: NonZeroUsize = new_non_zero_usize(128);
2424
pub const DEFAULT_STATE_CACHE_HEADROOM: NonZeroUsize = new_non_zero_usize(1);
2525
pub const DEFAULT_COMPRESSION_LEVEL: i32 = 1;
@@ -34,7 +34,7 @@ pub const DEFAULT_BLOB_PUNE_MARGIN_EPOCHS: u64 = 0;
3434
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
3535
pub struct StoreConfig {
3636
/// Maximum number of blocks to store in the in-memory block cache.
37-
pub block_cache_size: NonZeroUsize,
37+
pub block_cache_size: usize,
3838
/// Maximum number of states to store in the in-memory state cache.
3939
pub state_cache_size: NonZeroUsize,
4040
/// Minimum number of states to cull from the state cache upon fullness.

beacon_node/store/src/hot_cold_store.rs

Lines changed: 115 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub struct HotColdDB<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> {
7070
/// The hot database also contains all blocks.
7171
pub hot_db: Hot,
7272
/// LRU cache of deserialized blocks and blobs. Updated whenever a block or blob is loaded.
73-
block_cache: Mutex<BlockCache<E>>,
73+
block_cache: Option<Mutex<BlockCache<E>>>,
7474
/// Cache of beacon states.
7575
///
7676
/// LOCK ORDERING: this lock must always be locked *after* the `split` if both are required.
@@ -229,7 +229,9 @@ impl<E: EthSpec> HotColdDB<E, MemoryStore<E>, MemoryStore<E>> {
229229
cold_db: MemoryStore::open(),
230230
blobs_db: MemoryStore::open(),
231231
hot_db: MemoryStore::open(),
232-
block_cache: Mutex::new(BlockCache::new(config.block_cache_size)),
232+
block_cache: NonZeroUsize::new(config.block_cache_size)
233+
.map(BlockCache::new)
234+
.map(Mutex::new),
233235
state_cache: Mutex::new(StateCache::new(
234236
config.state_cache_size,
235237
config.state_cache_headroom,
@@ -281,7 +283,9 @@ impl<E: EthSpec> HotColdDB<E, BeaconNodeBackend<E>, BeaconNodeBackend<E>> {
281283
blobs_db: BeaconNodeBackend::open(&config, blobs_db_path)?,
282284
cold_db: BeaconNodeBackend::open(&config, cold_path)?,
283285
hot_db,
284-
block_cache: Mutex::new(BlockCache::new(config.block_cache_size)),
286+
block_cache: NonZeroUsize::new(config.block_cache_size)
287+
.map(BlockCache::new)
288+
.map(Mutex::new),
285289
state_cache: Mutex::new(StateCache::new(
286290
config.state_cache_size,
287291
config.state_cache_headroom,
@@ -488,14 +492,17 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
488492
pub fn register_metrics(&self) {
489493
let hsc_metrics = self.historic_state_cache.lock().metrics();
490494

491-
metrics::set_gauge(
492-
&metrics::STORE_BEACON_BLOCK_CACHE_SIZE,
493-
self.block_cache.lock().block_cache.len() as i64,
494-
);
495-
metrics::set_gauge(
496-
&metrics::STORE_BEACON_BLOB_CACHE_SIZE,
497-
self.block_cache.lock().blob_cache.len() as i64,
498-
);
495+
if let Some(block_cache) = &self.block_cache {
496+
let cache = block_cache.lock();
497+
metrics::set_gauge(
498+
&metrics::STORE_BEACON_BLOCK_CACHE_SIZE,
499+
cache.block_cache.len() as i64,
500+
);
501+
metrics::set_gauge(
502+
&metrics::STORE_BEACON_BLOB_CACHE_SIZE,
503+
cache.blob_cache.len() as i64,
504+
);
505+
}
499506
let state_cache = self.state_cache.lock();
500507
metrics::set_gauge(
501508
&metrics::STORE_BEACON_STATE_CACHE_SIZE,
@@ -553,7 +560,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
553560
let block = self.block_as_kv_store_ops(block_root, block, &mut ops)?;
554561
self.hot_db.do_atomically(ops)?;
555562
// Update cache.
556-
self.block_cache.lock().put_block(*block_root, block);
563+
self.block_cache
564+
.as_ref()
565+
.inspect(|cache| cache.lock().put_block(*block_root, block));
557566
Ok(())
558567
}
559568

@@ -605,7 +614,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
605614
metrics::inc_counter(&metrics::BEACON_BLOCK_GET_COUNT);
606615

607616
// Check the cache.
608-
if let Some(block) = self.block_cache.lock().get_block(block_root) {
617+
if let Some(cache) = &self.block_cache
618+
&& let Some(block) = cache.lock().get_block(block_root)
619+
{
609620
metrics::inc_counter(&metrics::BEACON_BLOCK_CACHE_HIT_COUNT);
610621
return Ok(Some(DatabaseBlock::Full(block.clone())));
611622
}
@@ -630,8 +641,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
630641

631642
// Add to cache.
632643
self.block_cache
633-
.lock()
634-
.put_block(*block_root, full_block.clone());
644+
.as_ref()
645+
.inspect(|cache| cache.lock().put_block(*block_root, full_block.clone()));
635646

636647
DatabaseBlock::Full(full_block)
637648
} else if !self.config.prune_payloads {
@@ -902,7 +913,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
902913

903914
/// Delete a block from the store and the block cache.
904915
pub fn delete_block(&self, block_root: &Hash256) -> Result<(), Error> {
905-
self.block_cache.lock().delete(block_root);
916+
self.block_cache
917+
.as_ref()
918+
.inspect(|cache| cache.lock().delete(block_root));
906919
self.hot_db
907920
.key_delete(DBColumn::BeaconBlock, block_root.as_slice())?;
908921
self.hot_db
@@ -917,7 +930,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
917930
block_root.as_slice(),
918931
&blobs.as_ssz_bytes(),
919932
)?;
920-
self.block_cache.lock().put_blobs(*block_root, blobs);
933+
self.block_cache
934+
.as_ref()
935+
.inspect(|cache| cache.lock().put_blobs(*block_root, blobs));
921936
Ok(())
922937
}
923938

@@ -945,9 +960,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
945960
self.blobs_db
946961
.put(&DATA_COLUMN_CUSTODY_INFO_KEY, &data_column_custody_info)?;
947962

948-
self.block_cache
949-
.lock()
950-
.put_data_column_custody_info(Some(data_column_custody_info));
963+
self.block_cache.as_ref().inspect(|cache| {
964+
cache
965+
.lock()
966+
.put_data_column_custody_info(Some(data_column_custody_info))
967+
});
951968

952969
Ok(())
953970
}
@@ -964,8 +981,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
964981
&data_column.as_ssz_bytes(),
965982
)?;
966983
self.block_cache
967-
.lock()
968-
.put_data_column(*block_root, data_column);
984+
.as_ref()
985+
.inspect(|cache| cache.lock().put_data_column(*block_root, data_column));
969986
}
970987
Ok(())
971988
}
@@ -1399,7 +1416,7 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
13991416

14001417
// Update database whilst holding a lock on cache, to ensure that the cache updates
14011418
// atomically with the database.
1402-
let mut guard = self.block_cache.lock();
1419+
let guard = self.block_cache.as_ref().map(|cache| cache.lock());
14031420

14041421
let blob_cache_ops = blobs_ops.clone();
14051422
// Try to execute blobs store ops.
@@ -1446,57 +1463,68 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
14461463
return Err(e);
14471464
}
14481465

1449-
for op in hot_db_cache_ops {
1466+
// Delete from the state cache.
1467+
for op in &hot_db_cache_ops {
14501468
match op {
1451-
StoreOp::PutBlock(block_root, block) => {
1452-
guard.put_block(block_root, (*block).clone());
1469+
StoreOp::DeleteBlock(block_root) => {
1470+
self.state_cache.lock().delete_block_states(block_root);
14531471
}
1472+
StoreOp::DeleteState(state_root, _) => {
1473+
self.state_cache.lock().delete_state(state_root)
1474+
}
1475+
_ => (),
1476+
}
1477+
}
14541478

1455-
StoreOp::PutBlobs(_, _) => (),
1479+
// If the block cache is enabled, also delete from the block cache.
1480+
if let Some(mut guard) = guard {
1481+
for op in hot_db_cache_ops {
1482+
match op {
1483+
StoreOp::PutBlock(block_root, block) => {
1484+
guard.put_block(block_root, (*block).clone());
1485+
}
14561486

1457-
StoreOp::PutDataColumns(_, _) => (),
1487+
StoreOp::PutBlobs(_, _) => (),
14581488

1459-
StoreOp::PutState(_, _) => (),
1489+
StoreOp::PutDataColumns(_, _) => (),
14601490

1461-
StoreOp::PutStateSummary(_, _) => (),
1491+
StoreOp::PutState(_, _) => (),
14621492

1463-
StoreOp::DeleteBlock(block_root) => {
1464-
guard.delete_block(&block_root);
1465-
self.state_cache.lock().delete_block_states(&block_root);
1466-
}
1493+
StoreOp::PutStateSummary(_, _) => (),
14671494

1468-
StoreOp::DeleteState(state_root, _) => {
1469-
self.state_cache.lock().delete_state(&state_root)
1470-
}
1495+
StoreOp::DeleteBlock(block_root) => {
1496+
guard.delete_block(&block_root);
1497+
}
14711498

1472-
StoreOp::DeleteBlobs(_) => (),
1499+
StoreOp::DeleteState(_, _) => (),
14731500

1474-
StoreOp::DeleteDataColumns(_, _) => (),
1501+
StoreOp::DeleteBlobs(_) => (),
14751502

1476-
StoreOp::DeleteExecutionPayload(_) => (),
1503+
StoreOp::DeleteDataColumns(_, _) => (),
14771504

1478-
StoreOp::DeleteSyncCommitteeBranch(_) => (),
1505+
StoreOp::DeleteExecutionPayload(_) => (),
14791506

1480-
StoreOp::KeyValueOp(_) => (),
1481-
}
1482-
}
1507+
StoreOp::DeleteSyncCommitteeBranch(_) => (),
14831508

1484-
for op in blob_cache_ops {
1485-
match op {
1486-
StoreOp::PutBlobs(block_root, blobs) => {
1487-
guard.put_blobs(block_root, blobs);
1509+
StoreOp::KeyValueOp(_) => (),
14881510
}
1511+
}
14891512

1490-
StoreOp::DeleteBlobs(block_root) => {
1491-
guard.delete_blobs(&block_root);
1492-
}
1513+
for op in blob_cache_ops {
1514+
match op {
1515+
StoreOp::PutBlobs(block_root, blobs) => {
1516+
guard.put_blobs(block_root, blobs);
1517+
}
14931518

1494-
_ => (),
1519+
StoreOp::DeleteBlobs(block_root) => {
1520+
guard.delete_blobs(&block_root);
1521+
}
1522+
1523+
_ => (),
1524+
}
14951525
}
14961526
}
14971527

1498-
drop(guard);
1499-
15001528
Ok(())
15011529
}
15021530

@@ -2425,21 +2453,23 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
24252453
/// If custody info doesn't exist in the cache,
24262454
/// try to fetch from the DB and prime the cache.
24272455
pub fn get_data_column_custody_info(&self) -> Result<Option<DataColumnCustodyInfo>, Error> {
2428-
let Some(data_column_custody_info) = self.block_cache.lock().get_data_column_custody_info()
2429-
else {
2430-
let data_column_custody_info = self
2431-
.blobs_db
2432-
.get::<DataColumnCustodyInfo>(&DATA_COLUMN_CUSTODY_INFO_KEY)?;
2456+
if let Some(cache) = &self.block_cache
2457+
&& let Some(data_column_custody_info) = cache.lock().get_data_column_custody_info()
2458+
{
2459+
return Ok(Some(data_column_custody_info));
2460+
}
2461+
let data_column_custody_info = self
2462+
.blobs_db
2463+
.get::<DataColumnCustodyInfo>(&DATA_COLUMN_CUSTODY_INFO_KEY)?;
24332464

2434-
// Update the cache
2435-
self.block_cache
2465+
// Update the cache
2466+
self.block_cache.as_ref().inspect(|cache| {
2467+
cache
24362468
.lock()
2437-
.put_data_column_custody_info(data_column_custody_info.clone());
2438-
2439-
return Ok(data_column_custody_info);
2440-
};
2469+
.put_data_column_custody_info(data_column_custody_info.clone())
2470+
});
24412471

2442-
Ok(Some(data_column_custody_info))
2472+
Ok(data_column_custody_info)
24432473
}
24442474

24452475
/// Fetch all columns for a given block from the store.
@@ -2460,9 +2490,13 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
24602490
/// Fetch blobs for a given block from the store.
24612491
pub fn get_blobs(&self, block_root: &Hash256) -> Result<BlobSidecarListFromRoot<E>, Error> {
24622492
// Check the cache.
2463-
if let Some(blobs) = self.block_cache.lock().get_blobs(block_root) {
2493+
if let Some(blobs) = self
2494+
.block_cache
2495+
.as_ref()
2496+
.and_then(|cache| cache.lock().get_blobs(block_root).cloned())
2497+
{
24642498
metrics::inc_counter(&metrics::BEACON_BLOBS_CACHE_HIT_COUNT);
2465-
return Ok(blobs.clone().into());
2499+
return Ok(blobs.into());
24662500
}
24672501

24682502
match self
@@ -2481,8 +2515,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
24812515
{
24822516
let blobs = BlobSidecarList::new(blobs, max_blobs_per_block as usize)?;
24832517
self.block_cache
2484-
.lock()
2485-
.put_blobs(*block_root, blobs.clone());
2518+
.as_ref()
2519+
.inspect(|cache| cache.lock().put_blobs(*block_root, blobs.clone()));
24862520

24872521
Ok(BlobSidecarListFromRoot::Blobs(blobs))
24882522
} else {
@@ -2515,8 +2549,8 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
25152549
// Check the cache.
25162550
if let Some(data_column) = self
25172551
.block_cache
2518-
.lock()
2519-
.get_data_column(block_root, column_index)
2552+
.as_ref()
2553+
.and_then(|cache| cache.lock().get_data_column(block_root, column_index))
25202554
{
25212555
metrics::inc_counter(&metrics::BEACON_DATA_COLUMNS_CACHE_HIT_COUNT);
25222556
return Ok(Some(data_column));
@@ -2528,9 +2562,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
25282562
)? {
25292563
Some(ref data_column_bytes) => {
25302564
let data_column = Arc::new(DataColumnSidecar::from_ssz_bytes(data_column_bytes)?);
2531-
self.block_cache
2532-
.lock()
2533-
.put_data_column(*block_root, data_column.clone());
2565+
self.block_cache.as_ref().inspect(|cache| {
2566+
cache
2567+
.lock()
2568+
.put_data_column(*block_root, data_column.clone())
2569+
});
25342570
Ok(Some(data_column))
25352571
}
25362572
None => Ok(None),
@@ -3264,11 +3300,11 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
32643300
}
32653301

32663302
// Remove deleted blobs from the cache.
3267-
let mut block_cache = self.block_cache.lock();
3268-
for block_root in removed_block_roots {
3269-
block_cache.delete_blobs(&block_root);
3303+
if let Some(mut block_cache) = self.block_cache.as_ref().map(|cache| cache.lock()) {
3304+
for block_root in removed_block_roots {
3305+
block_cache.delete_blobs(&block_root);
3306+
}
32703307
}
3271-
drop(block_cache);
32723308

32733309
let new_blob_info = BlobInfo {
32743310
oldest_blob_slot: Some(end_slot + 1),

book/src/help_bn.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Options:
2222
Data directory for the blobs database.
2323
--block-cache-size <SIZE>
2424
Specifies how many blocks the database should cache in memory
25-
[default: 5]
25+
[default: 0]
2626
--boot-nodes <ENR/MULTIADDR LIST>
2727
One or more comma-delimited base64-encoded ENR's to bootstrap the p2p
2828
network. Multiaddr is also supported.

0 commit comments

Comments
 (0)