From 269d9958328ae55f328f296bb36be894bc5832da Mon Sep 17 00:00:00 2001 From: Lijun Wang <83639177+lijunwangs@users.noreply.github.com> Date: Wed, 9 Jun 2021 21:21:32 -0700 Subject: [PATCH] Make account shrink configurable #17544 (#17778) 1. Added both options for measuring space usage using total accounts usage and for individual store shrink ratio using an enum. Validator CLI options: --accounts-shrink-optimize-total-space and --accounts-shrink-ratio 2. Added code for selecting candidates based on total usage in a separate function select_candidates_by_total_usage 3. Added unit tests for the new functions added 4. The default implementations is kept at 0.8 shrink ratio with --accounts-shrink-optimize-total-space set to true Fixes #17544 --- accounts-bench/src/main.rs | 2 + core/src/tvu.rs | 2 + core/src/validator.rs | 5 + core/tests/snapshots.rs | 2 + ledger/src/bank_forks_utils.rs | 1 + ledger/src/blockstore_processor.rs | 4 + local-cluster/src/validator_configs.rs | 1 + runtime/benches/accounts.rs | 10 + runtime/src/accounts.rs | 24 ++- runtime/src/accounts_db.rs | 275 ++++++++++++++++++++++++- runtime/src/bank.rs | 22 +- runtime/src/serde_snapshot.rs | 10 +- runtime/src/serde_snapshot/tests.rs | 5 +- runtime/src/snapshot_utils.rs | 6 +- validator/src/main.rs | 48 +++++ 15 files changed, 402 insertions(+), 15 deletions(-) diff --git a/accounts-bench/src/main.rs b/accounts-bench/src/main.rs index 3aebc466aaf2e4..469b7ccc609c4c 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 f4fe600c1e71e2..391240b9c8123c 100644 --- a/core/src/tvu.rs +++ b/core/src/tvu.rs @@ -38,6 +38,7 @@ use solana_runtime::{ accounts_background_service::{ AbsRequestHandler, AbsRequestSender, AccountsBackgroundService, SnapshotRequestHandler, }, + accounts_db::AccountShrinkThreshold, bank_forks::{BankForks, SnapshotConfig}, commitment::BlockCommitmentCache, vote_sender_types::ReplayVoteSender, @@ -89,6 +90,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 fda0f0108fc203..14440526e365ba 100644 --- a/core/src/validator.rs +++ b/core/src/validator.rs @@ -53,6 +53,7 @@ use solana_rpc::{ transaction_status_service::TransactionStatusService, }; use solana_runtime::{ + accounts_db::AccountShrinkThreshold, accounts_index::AccountSecondaryIndexes, bank::Bank, bank_forks::{BankForks, SnapshotConfig}, @@ -139,6 +140,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 { @@ -195,6 +197,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(), } } } @@ -726,6 +729,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, &cost_model, @@ -1099,6 +1103,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 05b00b261887ba..4cfddfcb26ebda 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(), ); bank0.freeze(); let mut bank_forks = BankForks::new(bank0); @@ -165,6 +166,7 @@ mod tests { AccountSecondaryIndexes::default(), false, None, + accounts_db::AccountShrinkThreshold::default(), ) .unwrap(); diff --git a/ledger/src/bank_forks_utils.rs b/ledger/src/bank_forks_utils.rs index 4b34d4547d2ee8..20eb017ca88b03 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, ) .expect("Load from snapshot failed"); if let Some(shrink_paths) = shrink_paths { diff --git a/ledger/src/blockstore_processor.rs b/ledger/src/blockstore_processor.rs index f10761d84c53f3..f0329608391044 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, @@ -373,6 +374,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( @@ -400,6 +402,7 @@ pub fn process_blockstore( Some(&crate::builtins::get(opts.bpf_jit)), opts.account_indexes.clone(), opts.accounts_db_caching_enabled, + opts.shrink_ratio, ); let bank0 = Arc::new(bank0); info!("processing ledger for slot 0..."); @@ -3064,6 +3067,7 @@ pub mod tests { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); *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 373301dd715987..f4e3383777baa7 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(), ); bencher.iter(|| { let mut pubkeys: Vec = vec![]; @@ -78,6 +80,7 @@ fn test_accounts_squash(bencher: &mut Bencher) { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), )); let mut pubkeys: Vec = vec![]; deposit_many(&prev_bank, &mut pubkeys, 250_000).unwrap(); @@ -103,6 +106,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; @@ -121,6 +125,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); @@ -138,6 +143,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); @@ -154,6 +160,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()); @@ -187,6 +194,7 @@ fn store_accounts_with_possible_contention( &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), )); let num_keys = 1000; let slot = 0; @@ -316,6 +324,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()), } @@ -1118,6 +1126,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); for ka in ka.iter() { accounts.store_slow_uncached(0, &ka.0, &ka.1); @@ -1655,6 +1664,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); // Load accounts owned by various programs into AccountsDb @@ -1683,6 +1693,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let mut error_counters = ErrorCounters::default(); let ancestors = vec![(0, 0)].into_iter().collect(); @@ -1706,6 +1717,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); accounts.bank_hash_at(1); } @@ -1727,6 +1739,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); @@ -1853,6 +1866,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); @@ -2003,6 +2017,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); { accounts @@ -2055,6 +2070,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()); @@ -2102,6 +2118,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let instructions_key = solana_sdk::sysvar::instructions::id(); @@ -2387,6 +2404,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let collected_accounts = accounts.collect_accounts_to_store( txs.iter(), @@ -2506,6 +2524,7 @@ mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); let collected_accounts = accounts.collect_accounts_to_store( txs.iter(), @@ -2540,6 +2559,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 ecac182a2867a8..8b61a418508d61 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), @@ -850,6 +874,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)] @@ -1293,6 +1319,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(), } } } @@ -1304,6 +1331,7 @@ impl AccountsDb { cluster_type, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ) } @@ -1312,6 +1340,7 @@ impl AccountsDb { cluster_type: &ClusterType, account_indexes: AccountSecondaryIndexes, caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { let mut new = if !paths.is_empty() { Self { @@ -1320,6 +1349,7 @@ impl AccountsDb { cluster_type: Some(*cluster_type), account_indexes, caching_enabled, + shrink_ratio, ..Self::default() } } else { @@ -1332,6 +1362,7 @@ impl AccountsDb { cluster_type: Some(*cluster_type), account_indexes, caching_enabled, + shrink_ratio, ..Self::default() } }; @@ -2267,15 +2298,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 } @@ -4818,6 +4939,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, @@ -4853,9 +4986,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 @@ -7343,6 +7474,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(); @@ -9148,6 +9280,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(); @@ -9646,6 +9871,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); @@ -9693,6 +9919,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); @@ -9741,6 +9968,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let zero_lamport_account_key = Pubkey::new_unique(); @@ -9872,6 +10100,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), )); let account_key = Pubkey::new_unique(); let account_key2 = Pubkey::new_unique(); @@ -9976,6 +10205,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let slot: Slot = 0; let num_keys = 10; @@ -10030,6 +10260,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; @@ -10428,6 +10659,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let account_key1 = Pubkey::new_unique(); let account_key2 = Pubkey::new_unique(); @@ -10690,6 +10922,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); db.load_delay = RACY_SLEEP_MS; let db = Arc::new(db); @@ -10761,6 +10994,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); db.load_delay = RACY_SLEEP_MS; let db = Arc::new(db); @@ -10836,6 +11070,7 @@ pub mod tests { &ClusterType::Development, AccountSecondaryIndexes::default(), caching_enabled, + AccountShrinkThreshold::default(), ); let db = Arc::new(db); let num_cached_slots = 100; @@ -11108,4 +11343,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 e59d61926d3890..c144b0f6c52f51 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, @@ -1011,6 +1011,7 @@ impl Bank { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ) } @@ -1023,6 +1024,7 @@ impl Bank { None, AccountSecondaryIndexes::default(), false, + AccountShrinkThreshold::default(), ); bank.ns_per_slot = std::u128::MAX; @@ -1034,6 +1036,7 @@ impl Bank { genesis_config: &GenesisConfig, account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { Self::new_with_paths( &genesis_config, @@ -1043,6 +1046,7 @@ impl Bank { None, account_indexes, accounts_db_caching_enabled, + shrink_ratio, ) } @@ -1054,6 +1058,7 @@ impl Bank { additional_builtins: Option<&Builtins>, account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, + shrink_ratio: AccountShrinkThreshold, ) -> Self { let mut bank = Self::default(); bank.ancestors = Ancestors::from(vec![bank.slot()]); @@ -1065,6 +1070,7 @@ impl Bank { &genesis_config.cluster_type, account_indexes, accounts_db_caching_enabled, + shrink_ratio, )); bank.process_genesis_config(genesis_config); bank.finish_init(genesis_config, additional_builtins); @@ -5292,7 +5298,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::{ @@ -9195,6 +9201,7 @@ pub(crate) mod tests { &genesis_config, account_indexes, false, + AccountShrinkThreshold::default(), )); let address = Pubkey::new_unique(); @@ -10644,6 +10651,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()); @@ -10655,11 +10663,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 } @@ -10677,6 +10687,7 @@ pub(crate) mod tests { &genesis_config, AccountSecondaryIndexes::default(), true, + AccountShrinkThreshold::default(), )); bank0.restore_old_behavior_for_fragile_tests(); @@ -11896,6 +11907,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 33ca72178ec26b..5077692ed419fb 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}, @@ -136,6 +138,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, @@ -156,6 +159,7 @@ where account_indexes, caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; Ok(bank) }}; @@ -246,6 +250,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, @@ -258,6 +263,7 @@ where account_indexes, caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; accounts_db.freeze_accounts( &Ancestors::from(&bank_fields.ancestors), @@ -284,6 +290,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, @@ -293,6 +300,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 8369ad3b8689a3..40f727eadda27f 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 ee31cf1cbbaf60..274d259608a1d3 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, @@ -605,6 +605,7 @@ pub fn bank_from_archive>( account_indexes: AccountSecondaryIndexes, accounts_db_caching_enabled: bool, limit_load_slot_count_from_snapshot: Option, + shrink_ratio: AccountShrinkThreshold, ) -> Result { let unpack_dir = tempfile::Builder::new() .prefix(TMP_SNAPSHOT_PREFIX) @@ -636,6 +637,7 @@ pub fn bank_from_archive>( account_indexes, accounts_db_caching_enabled, limit_load_slot_count_from_snapshot, + shrink_ratio, )?; if !bank.verify_snapshot_bank() { @@ -796,6 +798,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); @@ -832,6 +835,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 cae9478ffd596f..c21eec8b2beaf0 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, }, @@ -1009,6 +1013,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!()) @@ -1777,6 +1784,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") @@ -2075,6 +2105,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(), @@ -2172,6 +2219,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() };