diff --git a/accounts-bench/src/main.rs b/accounts-bench/src/main.rs index 608db2be371142..e32c01f1d821a5 100644 --- a/accounts-bench/src/main.rs +++ b/accounts-bench/src/main.rs @@ -6,6 +6,7 @@ use rayon::prelude::*; use solana_measure::measure::Measure; use solana_runtime::{ accounts::{create_test_accounts, update_accounts_bench, Accounts}, + accounts_db::AccountShrinkThreshold, accounts_index::AccountSecondaryIndexes, ancestors::Ancestors, }; @@ -64,6 +65,7 @@ fn main() { &ClusterType::Testnet, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); println!("Creating {} accounts", num_accounts); let mut create_time = Measure::start("create accounts"); diff --git a/core/src/tvu.rs b/core/src/tvu.rs index 1ec03d843bcdad..a7ba605b6d9a84 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -37,6 +37,7 @@ use solana_runtime::{ accounts_background_service::{ AbsRequestHandler, AbsRequestSender, AccountsBackgroundService, SnapshotRequestHandler, }, + accounts_db::AccountShrinkThreshold, bank_forks::{BankForks, SnapshotConfig}, commitment::BlockCommitmentCache, vote_sender_types::ReplayVoteSender, @@ -88,6 +89,7 @@ pub struct TvuConfig { pub rocksdb_compaction_interval: Option, pub rocksdb_max_compaction_jitter: Option, pub wait_for_vote_to_start_leader: bool, + pub accounts_shrink_ratio: AccountShrinkThreshold, } impl Tvu { diff --git a/core/src/validator.rs b/core/src/validator.rs index 3d1068de4a49ae..122e658480932a 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -52,6 +52,7 @@ use solana_rpc::{ transaction_status_service::TransactionStatusService, }; use solana_runtime::{ + accounts_db::AccountShrinkThreshold, accounts_index::AccountSecondaryIndexes, bank::Bank, bank_forks::{BankForks, SnapshotConfig}, @@ -138,6 +139,7 @@ pub struct ValidatorConfig { pub tpu_coalesce_ms: u64, pub validator_exit: Arc>, pub no_wait_for_vote_to_start_leader: bool, + pub accounts_shrink_ratio: AccountShrinkThreshold, } impl Default for ValidatorConfig { @@ -194,6 +196,7 @@ impl Default for ValidatorConfig { tpu_coalesce_ms: DEFAULT_TPU_COALESCE_MS, validator_exit: Arc::new(RwLock::new(Exit::default())), no_wait_for_vote_to_start_leader: true, + accounts_shrink_ratio: AccountShrinkThreshold::default(), } } } @@ -717,6 +720,7 @@ impl Validator { rocksdb_compaction_interval: config.rocksdb_compaction_interval, rocksdb_max_compaction_jitter: config.rocksdb_compaction_interval, wait_for_vote_to_start_leader, + accounts_shrink_ratio: config.accounts_shrink_ratio, }, &max_slots, ); @@ -1093,6 +1097,7 @@ fn new_banks_from_ledger( debug_keys: config.debug_keys.clone(), account_indexes: config.account_indexes.clone(), accounts_db_caching_enabled: config.accounts_db_caching_enabled, + shrink_ratio: config.accounts_shrink_ratio, ..blockstore_processor::ProcessOptions::default() }; diff --git a/core/tests/snapshots.rs b/core/tests/snapshots.rs index f989a4256bb72a..04cb77426acf41 100644 --- a/core/tests/snapshots.rs +++ b/core/tests/snapshots.rs @@ -106,6 +106,7 @@ mod tests { None, AccountSecondaryIndexes::default(), false, + accounts_db::AccountShrinkThreshold::default(), false, ); bank0.freeze(); @@ -167,6 +168,7 @@ mod tests { AccountSecondaryIndexes::default(), false, None, + accounts_db::AccountShrinkThreshold::default(), check_hash_calculation, ) .unwrap(); diff --git a/ledger/src/bank_forks_utils.rs b/ledger/src/bank_forks_utils.rs index da9cd1eed0ba27..bc416f8795e83d 100644 --- a/ledger/src/bank_forks_utils.rs +++ b/ledger/src/bank_forks_utils.rs @@ -142,6 +142,7 @@ fn load_from_snapshot( process_options.account_indexes.clone(), process_options.accounts_db_caching_enabled, process_options.limit_load_slot_count_from_snapshot, + process_options.shrink_ratio, process_options.accounts_db_test_hash_calculation, ) .expect("Load from snapshot failed"); diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index d2ecc9384dbaf5..5a49f7f4dc6a7a 100644 --- a/ledger/src/blockstore_processor.rs +++ b/ledger/src/blockstore_processor.rs @@ -16,6 +16,7 @@ use solana_measure::measure::Measure; use solana_metrics::{datapoint_error, inc_new_counter_debug}; use solana_rayon_threadlimit::get_thread_count; use solana_runtime::{ + accounts_db::AccountShrinkThreshold, accounts_index::AccountSecondaryIndexes, bank::{ Bank, ExecuteTimings, InnerInstructionsList, RentDebits, TransactionBalancesSet, @@ -374,6 +375,7 @@ pub struct ProcessOptions { pub limit_load_slot_count_from_snapshot: Option, pub allow_dead_slots: bool, pub accounts_db_test_hash_calculation: bool, + pub shrink_ratio: AccountShrinkThreshold, } pub fn process_blockstore( @@ -401,6 +403,7 @@ pub fn process_blockstore( Some(&crate::builtins::get(opts.bpf_jit)), opts.account_indexes.clone(), opts.accounts_db_caching_enabled, + opts.shrink_ratio, false, ); let bank0 = Arc::new(bank0); @@ -3097,6 +3100,7 @@ pub mod tests { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), false, ); *bank.epoch_schedule() diff --git a/local-cluster/src/validator_configs.rs b/local-cluster/src/validator_configs.rs index fbfc885ce6edb2..e267008d33acbe 100644 --- a/local-cluster/src/validator_configs.rs +++ b/local-cluster/src/validator_configs.rs @@ -56,6 +56,7 @@ pub fn safe_clone_config(config: &ValidatorConfig) -> ValidatorConfig { validator_exit: Arc::new(RwLock::new(Exit::default())), poh_hashes_per_batch: config.poh_hashes_per_batch, no_wait_for_vote_to_start_leader: config.no_wait_for_vote_to_start_leader, + accounts_shrink_ratio: config.accounts_shrink_ratio, } } diff --git a/runtime/benches/accounts.rs b/runtime/benches/accounts.rs index 63c3981f4bc736..0eab6e639c69c0 100644 --- a/runtime/benches/accounts.rs +++ b/runtime/benches/accounts.rs @@ -8,6 +8,7 @@ use rand::Rng; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use solana_runtime::{ accounts::{create_test_accounts, AccountAddressFilter, Accounts}, + accounts_db::AccountShrinkThreshold, accounts_index::AccountSecondaryIndexes, ancestors::Ancestors, bank::*, @@ -59,6 +60,7 @@ fn test_accounts_create(bencher: &mut Bencher) { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), false, ); bencher.iter(|| { @@ -79,6 +81,7 @@ fn test_accounts_squash(bencher: &mut Bencher) { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), false, )); let mut pubkeys: Vec = vec![]; @@ -105,6 +108,7 @@ fn test_accounts_hash_bank_hash(bencher: &mut Bencher) { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut pubkeys: Vec = vec![]; let num_accounts = 60_000; @@ -131,6 +135,7 @@ fn test_update_accounts_hash(bencher: &mut Bencher) { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut pubkeys: Vec = vec![]; create_test_accounts(&accounts, &mut pubkeys, 50_000, 0); @@ -148,6 +153,7 @@ fn test_accounts_delta_hash(bencher: &mut Bencher) { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut pubkeys: Vec = vec![]; create_test_accounts(&accounts, &mut pubkeys, 100_000, 0); @@ -164,6 +170,7 @@ fn bench_delete_dependencies(bencher: &mut Bencher) { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut old_pubkey = Pubkey::default(); let zero_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); @@ -197,6 +204,7 @@ fn store_accounts_with_possible_contention( &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), )); let num_keys = 1000; let slot = 0; @@ -326,6 +334,7 @@ fn setup_bench_dashmap_iter() -> (Arc, DashMap, cluster_type: &ClusterType) -> Self { + pub fn new( + paths: Vec, + cluster_type: &ClusterType, + shrink_ratio: AccountShrinkThreshold, + ) -> Self { Self::new_with_config( paths, cluster_type, AccountSecondaryIndexes::default(), false, + shrink_ratio, ) } @@ -131,6 +137,7 @@ impl Accounts { cluster_type: &ClusterType, account_indexes: AccountSecondaryIndexes, caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { Self { accounts_db: Arc::new(AccountsDb::new_with_config( @@ -138,6 +145,7 @@ impl Accounts { cluster_type, account_indexes, caching_enabled, + shrink_ratio, )), account_locks: Mutex::new(AccountLocks::default()), } @@ -1125,6 +1133,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); for ka in ka.iter() { accounts.store_slow_uncached(0, &ka.0, &ka.1); @@ -1662,6 +1671,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); // Load accounts owned by various programs into AccountsDb @@ -1690,6 +1700,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut error_counters = ErrorCounters::default(); let ancestors = vec![(0, 0)].into_iter().collect(); @@ -1713,6 +1724,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); accounts.bank_hash_at(1); } @@ -1734,6 +1746,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0); accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1); @@ -1860,6 +1873,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); accounts.store_slow_uncached(0, &keypair0.pubkey(), &account0); accounts.store_slow_uncached(0, &keypair1.pubkey(), &account1); @@ -2010,6 +2024,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); { accounts @@ -2062,6 +2077,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut old_pubkey = Pubkey::default(); let zero_account = AccountSharedData::new(0, 0, AccountSharedData::default().owner()); @@ -2109,6 +2125,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let instructions_key = solana_sdk::sysvar::instructions::id(); @@ -2394,6 +2411,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let collected_accounts = accounts.collect_accounts_to_store( txs.iter(), @@ -2513,6 +2531,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let collected_accounts = accounts.collect_accounts_to_store( txs.iter(), @@ -2547,6 +2566,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let pubkey0 = Pubkey::new_unique(); diff --git a/runtime/src/accounts_db.rs b/runtime/src/accounts_db.rs index 3d04506504db34..89fc50db4fd2e0 100644 --- a/runtime/src/accounts_db.rs +++ b/runtime/src/accounts_db.rs @@ -82,7 +82,6 @@ const SCAN_SLOT_PAR_ITER_THRESHOLD: usize = 4000; pub const DEFAULT_FILE_SIZE: u64 = PAGE_SIZE * 1024; pub const DEFAULT_NUM_THREADS: u32 = 8; pub const DEFAULT_NUM_DIRS: u32 = 4; -pub const SHRINK_RATIO: f64 = 0.80; // A specially reserved storage id just for entries in the cache, so that // operations that take a storage entry can maintain a common interface @@ -114,6 +113,31 @@ lazy_static! { pub static ref FROZEN_ACCOUNT_PANIC: Arc = Arc::new(AtomicBool::new(false)); } +#[derive(Debug, Clone, Copy)] +pub enum AccountShrinkThreshold { + /// Measure the total space sparseness across all candididates + /// And select the candidiates by using the top sparse account storage entries to shrink. + /// The value is the overall shrink threshold measured as ratio of the total live bytes + /// over the total bytes. + TotalSpace { shrink_ratio: f64 }, + /// Use the following option to shrink all stores whose alive ratio is below + /// the specified threshold. + IndividalStore { shrink_ratio: f64 }, +} +pub const DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE: bool = true; +pub const DEFAULT_ACCOUNTS_SHRINK_RATIO: f64 = 0.80; +// The default extra account space in percentage from the ideal target +const DEFAULT_ACCOUNTS_SHRINK_THRESHOLD_OPTION: AccountShrinkThreshold = + AccountShrinkThreshold::TotalSpace { + shrink_ratio: DEFAULT_ACCOUNTS_SHRINK_RATIO, + }; + +impl Default for AccountShrinkThreshold { + fn default() -> AccountShrinkThreshold { + DEFAULT_ACCOUNTS_SHRINK_THRESHOLD_OPTION + } +} + pub enum ScanStorageResult { Cached(Vec), Stored(B), @@ -896,6 +920,8 @@ pub struct AccountsDb { /// by `remove_unrooted_slot()`. Used to ensure `remove_unrooted_slots(slots)` /// can safely clear the set of unrooted slots `slots`. remove_unrooted_slots_synchronization: RemoveUnrootedSlotsSynchronization, + + shrink_ratio: AccountShrinkThreshold, } #[derive(Debug, Default)] @@ -1339,6 +1365,7 @@ impl Default for AccountsDb { load_limit: AtomicU64::default(), is_bank_drop_callback_enabled: AtomicBool::default(), remove_unrooted_slots_synchronization: RemoveUnrootedSlotsSynchronization::default(), + shrink_ratio: AccountShrinkThreshold::default(), } } } @@ -1353,6 +1380,7 @@ impl AccountsDb { cluster_type, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ) } @@ -1361,6 +1389,7 @@ impl AccountsDb { cluster_type: &ClusterType, account_indexes: AccountSecondaryIndexes, caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { let mut new = if !paths.is_empty() { Self { @@ -1369,6 +1398,7 @@ impl AccountsDb { cluster_type: Some(*cluster_type), account_indexes, caching_enabled, + shrink_ratio, ..Self::default() } } else { @@ -1381,6 +1411,7 @@ impl AccountsDb { cluster_type: Some(*cluster_type), account_indexes, caching_enabled, + shrink_ratio, ..Self::default() } }; @@ -2316,15 +2347,105 @@ impl AccountsDb { self.accounts_index.all_roots() } + /// Given the input `ShrinkCandidates`, this function sorts the stores by their alive ratio + /// in increasing order with the most sparse entries in the front. It will then simulate the + /// shrinking by working on the most sparse entries first and if the overall alive ratio is + /// achieved, it will stop and return the filtered-down candidates. + fn select_candidates_by_total_usage( + &self, + shrink_slots: &ShrinkCandidates, + shrink_ratio: f64, + ) -> ShrinkCandidates { + struct StoreUsageInfo { + slot: Slot, + alive_ratio: f64, + store: Arc, + } + let mut measure = Measure::start("select_top_sparse_storage_entries-ms"); + let mut store_usage: Vec = Vec::with_capacity(shrink_slots.len()); + let mut total_alive_bytes: u64 = 0; + let mut candidates_count: usize = 0; + let mut total_bytes: u64 = 0; + for (slot, slot_shrink_candidates) in shrink_slots { + candidates_count += slot_shrink_candidates.len(); + for store in slot_shrink_candidates.values() { + total_alive_bytes += Self::page_align(store.alive_bytes() as u64); + total_bytes += store.total_bytes(); + let alive_ratio = Self::page_align(store.alive_bytes() as u64) as f64 + / store.total_bytes() as f64; + store_usage.push(StoreUsageInfo { + slot: *slot, + alive_ratio, + store: store.clone(), + }); + } + } + store_usage.sort_by(|a, b| { + a.alive_ratio + .partial_cmp(&b.alive_ratio) + .unwrap_or(std::cmp::Ordering::Equal) + }); + + // Working from the beginning of store_usage which are the most sparse and see when we can stop + // shrinking while still achieving the overall goals. + let mut shrink_slots: ShrinkCandidates = HashMap::new(); + for usage in &store_usage { + let alive_ratio = (total_alive_bytes as f64) / (total_bytes as f64); + if alive_ratio > shrink_ratio { + // we have reached our goal, stop + debug!( + "Shrinking goal can be achieved at slot {:?}, total_alive_bytes: {:?} \ + total_bytes: {:?}, alive_ratio: {:}, shrink_ratio: {:?}", + usage.slot, total_alive_bytes, total_bytes, alive_ratio, shrink_ratio + ); + break; + } + let store = &usage.store; + let current_store_size = store.total_bytes(); + let after_shrink_size = Self::page_align(store.alive_bytes() as u64); + let bytes_saved = current_store_size.saturating_sub(after_shrink_size); + total_bytes -= bytes_saved; + shrink_slots + .entry(usage.slot) + .or_default() + .insert(store.append_vec_id(), store.clone()); + } + measure.stop(); + inc_new_counter_info!( + "select_top_sparse_storage_entries-ms", + measure.as_ms() as usize + ); + inc_new_counter_info!("select_top_sparse_storage_entries-seeds", candidates_count); + shrink_slots + } + pub fn shrink_candidate_slots(&self) -> usize { - let shrink_slots = std::mem::take(&mut *self.shrink_candidate_slots.lock().unwrap()); + let shrink_candidates_slots = + std::mem::take(&mut *self.shrink_candidate_slots.lock().unwrap()); + let shrink_slots = { + if let AccountShrinkThreshold::TotalSpace { shrink_ratio } = self.shrink_ratio { + self.select_candidates_by_total_usage(&shrink_candidates_slots, shrink_ratio) + } else { + shrink_candidates_slots + } + }; + + let mut measure_shrink_all_candidates = Measure::start("shrink_all_candidate_slots-ms"); let num_candidates = shrink_slots.len(); + let mut shrink_candidates_count: usize = 0; for (slot, slot_shrink_candidates) in shrink_slots { + shrink_candidates_count += slot_shrink_candidates.len(); let mut measure = Measure::start("shrink_candidate_slots-ms"); self.do_shrink_slot_stores(slot, slot_shrink_candidates.values(), false); measure.stop(); inc_new_counter_info!("shrink_candidate_slots-ms", measure.as_ms() as usize); } + measure_shrink_all_candidates.stop(); + inc_new_counter_info!( + "shrink_all_candidate_slots-ms", + measure_shrink_all_candidates.as_ms() as usize + ); + inc_new_counter_info!("shrink_all_candidate_slots-count", shrink_candidates_count); num_candidates } @@ -4968,6 +5089,18 @@ impl AccountsDb { true } + fn is_candidate_for_shrink(&self, store: &Arc) -> bool { + match self.shrink_ratio { + AccountShrinkThreshold::TotalSpace { shrink_ratio: _ } => { + Self::page_align(store.alive_bytes() as u64) < store.total_bytes() + } + AccountShrinkThreshold::IndividalStore { shrink_ratio } => { + (Self::page_align(store.alive_bytes() as u64) as f64 / store.total_bytes() as f64) + < shrink_ratio + } + } + } + fn remove_dead_accounts( &self, reclaims: SlotSlice, @@ -5003,9 +5136,7 @@ impl AccountsDb { dead_slots.insert(*slot); } else if self.caching_enabled && Self::is_shrinking_productive(*slot, &[store.clone()]) - && (Self::page_align(store.alive_bytes() as u64) as f64 - / store.total_bytes() as f64) - < SHRINK_RATIO + && self.is_candidate_for_shrink(&store) { // Checking that this single storage entry is ready for shrinking, // should be a sufficient indication that the slot is ready to be shrunk @@ -7484,6 +7615,7 @@ pub mod tests { &ClusterType::Development, spl_token_mint_index_enabled(), false, + AccountShrinkThreshold::default(), ); let pubkey1 = solana_sdk::pubkey::new_rand(); let pubkey2 = solana_sdk::pubkey::new_rand(); @@ -9289,6 +9421,99 @@ pub mod tests { ); } + #[test] + fn test_select_candidates_by_total_usage() { + solana_logger::setup(); + + // case 1: no candidates + let accounts = AccountsDb::new_single(); + + let mut candidates: ShrinkCandidates = HashMap::new(); + let output_candidates = + accounts.select_candidates_by_total_usage(&candidates, DEFAULT_ACCOUNTS_SHRINK_RATIO); + + assert_eq!(0, output_candidates.len()); + + // case 2: two candidates, only one selected + let dummy_path = Path::new(""); + let dummy_slot = 12; + let dummy_size = 2 * PAGE_SIZE; + + let dummy_id1 = 22; + let entry1 = Arc::new(AccountStorageEntry::new( + &dummy_path, + dummy_slot, + dummy_id1, + dummy_size, + )); + entry1.alive_bytes.store(8000, Ordering::Relaxed); + + candidates + .entry(dummy_slot) + .or_default() + .insert(entry1.append_vec_id(), entry1.clone()); + + let dummy_id2 = 44; + let entry2 = Arc::new(AccountStorageEntry::new( + &dummy_path, + dummy_slot, + dummy_id2, + dummy_size, + )); + entry2.alive_bytes.store(3000, Ordering::Relaxed); + candidates + .entry(dummy_slot) + .or_default() + .insert(entry2.append_vec_id(), entry2.clone()); + + let output_candidates = + accounts.select_candidates_by_total_usage(&candidates, DEFAULT_ACCOUNTS_SHRINK_RATIO); + assert_eq!(1, output_candidates.len()); + assert_eq!(1, output_candidates[&dummy_slot].len()); + assert!(output_candidates[&dummy_slot].contains(&entry2.append_vec_id())); + + // case 3: two candidates, both are selected + candidates.clear(); + let dummy_size = 4 * PAGE_SIZE; + let dummy_id1 = 22; + let entry1 = Arc::new(AccountStorageEntry::new( + &dummy_path, + dummy_slot, + dummy_id1, + dummy_size, + )); + entry1.alive_bytes.store(3500, Ordering::Relaxed); + + candidates + .entry(dummy_slot) + .or_default() + .insert(entry1.append_vec_id(), entry1.clone()); + + let dummy_id2 = 44; + let dummy_slot2 = 44; + let entry2 = Arc::new(AccountStorageEntry::new( + &dummy_path, + dummy_slot2, + dummy_id2, + dummy_size, + )); + entry2.alive_bytes.store(3000, Ordering::Relaxed); + + candidates + .entry(dummy_slot2) + .or_default() + .insert(entry2.append_vec_id(), entry2.clone()); + + let output_candidates = + accounts.select_candidates_by_total_usage(&candidates, DEFAULT_ACCOUNTS_SHRINK_RATIO); + assert_eq!(2, output_candidates.len()); + assert_eq!(1, output_candidates[&dummy_slot].len()); + assert_eq!(1, output_candidates[&dummy_slot2].len()); + + assert!(output_candidates[&dummy_slot].contains(&entry1.append_vec_id())); + assert!(output_candidates[&dummy_slot2].contains(&entry2.append_vec_id())); + } + #[test] fn test_shrink_stale_slots_skipped() { solana_logger::setup(); @@ -9787,6 +10012,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); @@ -9834,6 +10060,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); @@ -9882,6 +10109,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let zero_lamport_account_key = Pubkey::new_unique(); @@ -10013,6 +10241,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); let account_key2 = Pubkey::new_unique(); @@ -10117,6 +10346,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let slot: Slot = 0; let num_keys = 10; @@ -10171,6 +10401,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let slots: Vec<_> = (0..num_slots as Slot).into_iter().collect(); let stall_slot = num_slots as Slot; @@ -10569,6 +10800,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let account_key1 = Pubkey::new_unique(); let account_key2 = Pubkey::new_unique(); @@ -10831,6 +11063,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); db.load_delay = RACY_SLEEP_MS; let db = Arc::new(db); @@ -10902,6 +11135,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); db.load_delay = RACY_SLEEP_MS; let db = Arc::new(db); @@ -10977,6 +11211,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let db = Arc::new(db); let num_cached_slots = 100; @@ -11249,4 +11484,34 @@ pub mod tests { let stores = vec![Arc::new(s1), Arc::new(s2)]; assert!(AccountsDb::is_shrinking_productive(0, &stores)); } + + #[test] + fn test_is_candidate_for_shrink() { + solana_logger::setup(); + + let mut accounts = AccountsDb::new_single(); + let dummy_path = Path::new(""); + let dummy_size = 2 * PAGE_SIZE; + let entry = Arc::new(AccountStorageEntry::new(&dummy_path, 0, 1, dummy_size)); + match accounts.shrink_ratio { + AccountShrinkThreshold::TotalSpace { shrink_ratio } => { + assert_eq!( + (DEFAULT_ACCOUNTS_SHRINK_RATIO * 100.) as u64, + (shrink_ratio * 100.) as u64 + ) + } + AccountShrinkThreshold::IndividalStore { shrink_ratio: _ } => { + panic!("Expect the default to be TotalSpace") + } + } + entry.alive_bytes.store(3000, Ordering::Relaxed); + assert!(accounts.is_candidate_for_shrink(&entry)); + entry.alive_bytes.store(5000, Ordering::Relaxed); + assert!(!accounts.is_candidate_for_shrink(&entry)); + accounts.shrink_ratio = AccountShrinkThreshold::TotalSpace { shrink_ratio: 0.3 }; + entry.alive_bytes.store(3000, Ordering::Relaxed); + assert!(accounts.is_candidate_for_shrink(&entry)); + accounts.shrink_ratio = AccountShrinkThreshold::IndividalStore { shrink_ratio: 0.3 }; + assert!(!accounts.is_candidate_for_shrink(&entry)); + } } diff --git a/runtime/src/bank.rs b/runtime/src/bank.rs index 1e8ad1a3ff89dd..d21d66b8567592 100644 --- a/runtime/src/bank.rs +++ b/runtime/src/bank.rs @@ -38,7 +38,7 @@ use crate::{ AccountAddressFilter, Accounts, TransactionAccountDeps, TransactionAccounts, TransactionLoadResult, TransactionLoaders, }, - accounts_db::{ErrorCounters, SnapshotStorages}, + accounts_db::{AccountShrinkThreshold, ErrorCounters, SnapshotStorages}, accounts_index::{AccountSecondaryIndexes, IndexKey}, ancestors::{Ancestors, AncestorsForSerialization}, blockhash_queue::BlockhashQueue, @@ -1010,6 +1010,7 @@ impl Bank { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), false, ) } @@ -1023,6 +1024,7 @@ impl Bank { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), false, ); @@ -1035,6 +1037,7 @@ impl Bank { genesis_config: &GenesisConfig, account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { Self::new_with_paths( &genesis_config, @@ -1044,6 +1047,7 @@ impl Bank { None, account_indexes, accounts_db_caching_enabled, + shrink_ratio, false, ) } @@ -1056,6 +1060,7 @@ impl Bank { additional_builtins: Option<&Builtins>, account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, debug_do_not_add_builtins: bool, ) -> Self { let mut bank = Self::default(); @@ -1068,6 +1073,7 @@ impl Bank { &genesis_config.cluster_type, account_indexes, accounts_db_caching_enabled, + shrink_ratio, )); bank.process_genesis_config(genesis_config); bank.finish_init( @@ -5316,7 +5322,7 @@ pub(crate) mod tests { use super::*; use crate::{ accounts_background_service::{AbsRequestHandler, SendDroppedBankCallback}, - accounts_db::SHRINK_RATIO, + accounts_db::DEFAULT_ACCOUNTS_SHRINK_RATIO, accounts_index::{AccountIndex, AccountMap, AccountSecondaryIndexes, ITER_BATCH_SIZE}, ancestors::Ancestors, genesis_utils::{ @@ -9219,6 +9225,7 @@ pub(crate) mod tests { &genesis_config, account_indexes, false, + AccountShrinkThreshold::default(), )); let address = Pubkey::new_unique(); @@ -10668,6 +10675,7 @@ pub(crate) mod tests { &genesis_config, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), )); bank0.restore_old_behavior_for_fragile_tests(); goto_end_of_slot(Arc::::get_mut(&mut bank0).unwrap()); @@ -10679,11 +10687,13 @@ pub(crate) mod tests { .accounts .scan_slot(0, |stored_account| Some(stored_account.stored_size())); - // Create an account such that it takes SHRINK_RATIO of the total account space for + // Create an account such that it takes DEFAULT_ACCOUNTS_SHRINK_RATIO of the total account space for // the slot, so when it gets pruned, the storage entry will become a shrink candidate. let bank0_total_size: usize = sizes.into_iter().sum(); - let pubkey0_size = (bank0_total_size as f64 / (1.0 - SHRINK_RATIO)).ceil(); - assert!(pubkey0_size / (pubkey0_size + bank0_total_size as f64) > SHRINK_RATIO); + let pubkey0_size = (bank0_total_size as f64 / (1.0 - DEFAULT_ACCOUNTS_SHRINK_RATIO)).ceil(); + assert!( + pubkey0_size / (pubkey0_size + bank0_total_size as f64) > DEFAULT_ACCOUNTS_SHRINK_RATIO + ); pubkey0_size as usize } @@ -10701,6 +10711,7 @@ pub(crate) mod tests { &genesis_config, AccountSecondaryIndexes::default(), true, + AccountShrinkThreshold::default(), )); bank0.restore_old_behavior_for_fragile_tests(); @@ -11920,6 +11931,7 @@ pub(crate) mod tests { &genesis_config, AccountSecondaryIndexes::default(), accounts_db_caching_enabled, + AccountShrinkThreshold::default(), )); bank0.set_callback(drop_callback); diff --git a/runtime/src/serde_snapshot.rs b/runtime/src/serde_snapshot.rs index b46c83445e8e0a..756730efbada58 100644 --- a/runtime/src/serde_snapshot.rs +++ b/runtime/src/serde_snapshot.rs @@ -1,7 +1,9 @@ use { crate::{ accounts::Accounts, - accounts_db::{AccountStorageEntry, AccountsDb, AppendVecId, BankHashInfo}, + accounts_db::{ + AccountShrinkThreshold, AccountStorageEntry, AccountsDb, AppendVecId, BankHashInfo, + }, accounts_index::AccountSecondaryIndexes, ancestors::Ancestors, append_vec::{AppendVec, StoredMetaWriteVersion}, @@ -137,6 +139,7 @@ pub(crate) fn bank_from_stream( account_indexes: AccountSecondaryIndexes, caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, ) -> std::result::Result where R: Read, @@ -157,6 +160,7 @@ where account_indexes, caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; Ok(bank) }}; @@ -247,6 +251,7 @@ fn reconstruct_bank_from_fields( account_indexes: AccountSecondaryIndexes, caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, ) -> Result where E: SerializableStorage + std::marker::Sync, @@ -259,6 +264,7 @@ where account_indexes, caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; accounts_db.freeze_accounts( &Ancestors::from(&bank_fields.ancestors), @@ -289,6 +295,7 @@ fn reconstruct_accountsdb_from_fields( account_indexes: AccountSecondaryIndexes, caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, ) -> Result where E: SerializableStorage + std::marker::Sync, @@ -298,6 +305,7 @@ where cluster_type, account_indexes, caching_enabled, + shrink_ratio, ); let AccountsDbFields(storage, version, slot, bank_hash_info) = accounts_db_fields; diff --git a/runtime/src/serde_snapshot/tests.rs b/runtime/src/serde_snapshot/tests.rs index 68d79d54cf2576..6d8436f313e80c 100644 --- a/runtime/src/serde_snapshot/tests.rs +++ b/runtime/src/serde_snapshot/tests.rs @@ -3,7 +3,7 @@ use { super::*, crate::{ accounts::{create_test_accounts, Accounts}, - accounts_db::get_temp_accounts_paths, + accounts_db::{get_temp_accounts_paths, AccountShrinkThreshold}, bank::{Bank, StatusCacheRc}, hardened_unpack::UnpackedAppendVecMap, }, @@ -74,6 +74,7 @@ where AccountSecondaryIndexes::default(), false, None, + AccountShrinkThreshold::default(), ) } @@ -129,6 +130,7 @@ fn test_accounts_serialize_style(serde_style: SerdeStyle) { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut pubkeys: Vec = vec![]; @@ -228,6 +230,7 @@ fn test_bank_serialize_style(serde_style: SerdeStyle) { AccountSecondaryIndexes::default(), false, None, + AccountShrinkThreshold::default(), ) .unwrap(); dbank.src = ref_sc; diff --git a/runtime/src/snapshot_utils.rs b/runtime/src/snapshot_utils.rs index 8cbcdb25c0da09..0f47147df01ee8 100644 --- a/runtime/src/snapshot_utils.rs +++ b/runtime/src/snapshot_utils.rs @@ -1,6 +1,6 @@ use { crate::{ - accounts_db::AccountsDb, + accounts_db::{AccountShrinkThreshold, AccountsDb}, accounts_index::AccountSecondaryIndexes, bank::{Bank, BankSlotDelta, Builtins}, bank_forks::ArchiveFormat, @@ -612,6 +612,7 @@ pub fn bank_from_archive>( account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, test_hash_calculation: bool, ) -> Result<(Bank, BankFromArchiveTimings)> { let unpack_dir = tempfile::Builder::new() @@ -646,6 +647,7 @@ pub fn bank_from_archive>( account_indexes, accounts_db_caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; measure.stop(); @@ -814,6 +816,7 @@ fn rebuild_bank_from_snapshots( account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, ) -> Result { info!("snapshot version: {}", snapshot_version); @@ -850,6 +853,7 @@ fn rebuild_bank_from_snapshots( account_indexes, accounts_db_caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, ), }?) })?; diff --git a/validator/src/main.rs b/validator/src/main.rs index 070271ec3defed..9a97a54714ca55 100644 --- a/validator/src/main.rs +++ b/validator/src/main.rs @@ -39,6 +39,10 @@ use { solana_poh::poh_service, solana_rpc::{rpc::JsonRpcConfig, rpc_pubsub_service::PubSubConfig}, solana_runtime::{ + accounts_db::{ + AccountShrinkThreshold, DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE, + DEFAULT_ACCOUNTS_SHRINK_RATIO, + }, accounts_index::{ AccountIndex, AccountSecondaryIndexes, AccountSecondaryIndexesIncludeExclude, }, @@ -1011,6 +1015,9 @@ pub fn main() { let default_max_snapshot_to_retain = &DEFAULT_MAX_SNAPSHOTS_TO_RETAIN.to_string(); let default_min_snapshot_download_speed = &DEFAULT_MIN_SNAPSHOT_DOWNLOAD_SPEED.to_string(); let default_max_snapshot_download_abort = &MAX_SNAPSHOT_DOWNLOAD_ABORT.to_string(); + let default_accounts_shrink_optimize_total_space = + &DEFAULT_ACCOUNTS_SHRINK_OPTIMIZE_TOTAL_SPACE.to_string(); + let default_accounts_shrink_ratio = &DEFAULT_ACCOUNTS_SHRINK_RATIO.to_string(); let matches = App::new(crate_name!()).about(crate_description!()) .version(solana_version::version!()) @@ -1789,6 +1796,29 @@ pub fn main() { .conflicts_with("no_accounts_db_caching") .hidden(true) ) + .arg( + Arg::with_name("accounts_shrink_optimize_total_space") + .long("accounts-shrink-optimize-total-space") + .takes_value(true) + .value_name("BOOLEAN") + .default_value(default_accounts_shrink_optimize_total_space) + .help("When this is set to true, the system will shrink the most \ + sparse accounts and when the overall shrink ratio is above \ + the specified accounts-shrink-ratio, the shrink will stop and \ + it will skip all other less sparse accounts."), + ) + .arg( + Arg::with_name("accounts_shrink_ratio") + .long("accounts-shrink-ratio") + .takes_value(true) + .value_name("RATIO") + .default_value(default_accounts_shrink_ratio) + .help("Specifies the shrink ratio for the accounts to be shrunk. \ + The shrink ratio is defined as the ratio of the bytes alive over the \ + total bytes used. If the account's shrink ratio is less than this ratio \ + it becomes a candidate for shrinking. The value must between 0. and 1.0 \ + inclusive."), + ) .arg( Arg::with_name("no_duplicate_instance_check") .long("no-duplicate-instance-check") @@ -2087,6 +2117,23 @@ pub fn main() { let account_indexes = process_account_indexes(&matches); let restricted_repair_only_mode = matches.is_present("restricted_repair_only_mode"); + let accounts_shrink_optimize_total_space = + value_t_or_exit!(matches, "accounts_shrink_optimize_total_space", bool); + let shrink_ratio = value_t_or_exit!(matches, "accounts_shrink_ratio", f64); + if !(0.0..=1.0).contains(&shrink_ratio) { + eprintln!( + "The specified account-shrink-ratio is invalid, it must be between 0. and 1.0 inclusive: {}", + shrink_ratio + ); + exit(1); + } + + let accounts_shrink_ratio = if accounts_shrink_optimize_total_space { + AccountShrinkThreshold::TotalSpace { shrink_ratio } + } else { + AccountShrinkThreshold::IndividalStore { shrink_ratio } + }; + let mut validator_config = ValidatorConfig { require_tower: matches.is_present("require_tower"), tower_path: value_t!(matches, "tower", PathBuf).ok(), @@ -2189,6 +2236,7 @@ pub fn main() { accounts_db_use_index_hash_calculation: matches.is_present("accounts_db_index_hashing"), tpu_coalesce_ms, no_wait_for_vote_to_start_leader: matches.is_present("no_wait_for_vote_to_start_leader"), + accounts_shrink_ratio, ..ValidatorConfig::default() };