From b70ff139698fb685fd2a5c056d9557d0786e9b76 Mon Sep 17 00:00:00 2001 From: 0xPause Date: Wed, 27 Nov 2024 09:13:37 +0800 Subject: [PATCH] Add object state cache for moveos (#2914) add cache in StateDBStore --- Cargo.lock | 13 +++++++++++ Cargo.toml | 1 + moveos/moveos-config/src/store_config.rs | 19 ++++++++++++++++ moveos/moveos-store/Cargo.toml | 3 ++- moveos/moveos-store/src/lib.rs | 6 +++-- .../moveos-store/src/state_store/statedb.rs | 22 ++++++++++++++++--- 6 files changed, 58 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b31fccaf64..46920239a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7088,6 +7088,7 @@ dependencies = [ "moveos-types", "once_cell", "prometheus", + "quick_cache", "rand 0.8.5", "raw-store", "smt", @@ -8688,6 +8689,18 @@ dependencies = [ "serde 1.0.215", ] +[[package]] +name = "quick_cache" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d7c94f8935a9df96bb6380e8592c70edf497a643f94bd23b2f76b399385dbf4" +dependencies = [ + "ahash 0.8.11", + "equivalent", + "hashbrown 0.14.5", + "parking_lot 0.12.3", +] + [[package]] name = "quinn" version = "0.11.5" diff --git a/Cargo.toml b/Cargo.toml index f5060b56c4..0e4e300e5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -268,6 +268,7 @@ tower_governor = { version = "0.4.3", features = ["tracing"] } pin-project = "1.1.7" mirai-annotations = "1.12.0" lru = "0.11.0" +quick_cache = "0.6.9" bs58 = "0.5.1" dirs-next = "2.0.0" anstream = { version = "0.3" } diff --git a/moveos/moveos-config/src/store_config.rs b/moveos/moveos-config/src/store_config.rs index 9c9e95d5ec..9b5bb08ffe 100644 --- a/moveos/moveos-config/src/store_config.rs +++ b/moveos/moveos-config/src/store_config.rs @@ -80,3 +80,22 @@ impl Default for RocksdbConfig { } } } + +#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, Serialize, Parser)] +#[serde(default, deny_unknown_fields)] +pub struct MoveOSStoreConfig { + #[clap( + name = "moveos-store-state-cache-size", + long, + help = "MoveOS store state cache size" + )] + pub state_cache_size: usize, +} + +impl Default for MoveOSStoreConfig { + fn default() -> Self { + Self { + state_cache_size: 10_000, + } + } +} diff --git a/moveos/moveos-store/Cargo.toml b/moveos/moveos-store/Cargo.toml index 9da87fc0f0..605f6856b8 100644 --- a/moveos/moveos-store/Cargo.toml +++ b/moveos/moveos-store/Cargo.toml @@ -31,4 +31,5 @@ moveos-types = { workspace = true } raw-store = { workspace = true } moveos-config = { workspace = true } accumulator = { workspace = true } -metrics = { workspace = true } \ No newline at end of file +metrics = { workspace = true } +quick_cache = { workspace = true } \ No newline at end of file diff --git a/moveos/moveos-store/src/lib.rs b/moveos/moveos-store/src/lib.rs index 56acf68823..82a06892ba 100644 --- a/moveos/moveos-store/src/lib.rs +++ b/moveos/moveos-store/src/lib.rs @@ -13,7 +13,7 @@ use accumulator::inmemory::InMemoryAccumulator; use anyhow::{Error, Result}; use bcs::to_bytes; use move_core_types::language_storage::StructTag; -use moveos_config::store_config::RocksdbConfig; +use moveos_config::store_config::{MoveOSStoreConfig, RocksdbConfig}; use moveos_config::DataDirPath; use moveos_types::genesis_info::GenesisInfo; use moveos_types::h256::H256; @@ -100,8 +100,10 @@ impl MoveOSStore { } pub fn new_with_instance(instance: StoreInstance, registry: &Registry) -> Result { + let store_config = MoveOSStoreConfig::default(); let node_store = NodeDBStore::new(instance.clone()); - let state_store = StateDBStore::new(node_store.clone(), registry); + let state_store = + StateDBStore::new(node_store.clone(), registry, store_config.state_cache_size); let store = Self { node_store, diff --git a/moveos/moveos-store/src/state_store/statedb.rs b/moveos/moveos-store/src/state_store/statedb.rs index 88a5fbccbc..56ff5e0532 100644 --- a/moveos/moveos-store/src/state_store/statedb.rs +++ b/moveos/moveos-store/src/state_store/statedb.rs @@ -18,6 +18,7 @@ use moveos_types::state_resolver::StateKV; use moveos_types::state_resolver::StateResolver; use moveos_types::state_resolver::StatelessResolver; use prometheus::Registry; +use quick_cache::sync::Cache; use smt::{SMTIterator, TreeChangeSet}; use smt::{SMTree, UpdateSet}; use std::collections::BTreeMap; @@ -31,14 +32,16 @@ pub struct StateDBStore { pub node_store: NodeDBStore, smt: SMTree, metrics: Arc, + cache: Arc>>, } impl StateDBStore { - pub fn new(node_store: NodeDBStore, registry: &Registry) -> Self { + pub fn new(node_store: NodeDBStore, registry: &Registry, cache_size: usize) -> Self { Self { node_store: node_store.clone(), smt: SMTree::new(node_store, registry), metrics: Arc::new(StateDBMetrics::new(registry)), + cache: Arc::new(Cache::new(cache_size)), } } @@ -97,6 +100,8 @@ impl StateDBStore { Op::Delete => { //TODO clean up the removed object fields update_set.remove(field_key); + let pre_state_root = obj_change.metadata.state_root(); + self.cache.remove(&(pre_state_root, field_key)); return Ok(()); } }, @@ -126,7 +131,8 @@ impl StateDBStore { let new_state_root = tree_change_set.state_root; obj.update_state_root(new_state_root); obj_change.update_state_root(new_state_root); - update_set.put(field_key, obj); + update_set.put(field_key, obj.clone()); + self.cache.insert((new_state_root, field_key), Some(obj)); Ok(()) } @@ -225,7 +231,14 @@ impl StatelessResolver for StateDBStore { if state_root == *GENESIS_STATE_ROOT { return Ok(None); } - let result = self.smt.get(state_root, *key)?; + + let result = if let Some(state) = self.cache.get(&(state_root, *key)) { + state + } else { + let state = self.smt.get(state_root, *key)?; + self.cache.insert((state_root, *key), state.clone()); + state + }; if tracing::enabled!(tracing::Level::TRACE) { let result_info = match &result { Some(state) => format!("Some({})", state.metadata.object_type), @@ -261,6 +274,9 @@ impl StatelessResolver for StateDBStore { .with_label_values(&[fn_name]) .start_timer(); let result = self.smt.list(state_root, cursor, limit)?; + for (key, state) in &result { + self.cache.insert((state_root, *key), Some(state.clone())); + } // Only statistics object value bytes to avoid performance loss caused by serialization let size = result