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
This commit is contained in:
Lijun Wang 2021-06-09 21:21:32 -07:00 committed by GitHub
parent a1fab0c5ca
commit 269d995832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 404 additions and 17 deletions

View File

@ -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");

View File

@ -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<u64>,
pub rocksdb_max_compaction_jitter: Option<u64>,
pub wait_for_vote_to_start_leader: bool,
pub accounts_shrink_ratio: AccountShrinkThreshold,
}
impl Tvu {

View File

@ -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<RwLock<Exit>>,
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()
};

View File

@ -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();

View File

@ -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 {

View File

@ -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<usize>,
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()
}

View File

@ -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,
}
}

View File

@ -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<Pubkey> = vec![];
@ -78,6 +80,7 @@ fn test_accounts_squash(bencher: &mut Bencher) {
None,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
));
let mut pubkeys: Vec<Pubkey> = 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<Pubkey> = 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<Pubkey> = 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<Pubkey> = 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<F: 'static>(
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
));
let num_keys = 1000;
let slot = 0;
@ -316,6 +324,7 @@ fn setup_bench_dashmap_iter() -> (Arc<Accounts>, DashMap<Pubkey, (AccountSharedD
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
));
let dashmap = DashMap::new();
@ -370,6 +379,7 @@ fn bench_load_largest_accounts(b: &mut Bencher) {
&ClusterType::Development,
AccountSecondaryIndexes::default(),
false,
AccountShrinkThreshold::default(),
);
let mut rng = rand::thread_rng();
for _ in 0..10_000 {

View File

@ -1,6 +1,7 @@
use crate::{
accounts_db::{
AccountsDb, BankHashInfo, ErrorCounters, LoadHint, LoadedAccount, ScanStorageResult,
AccountShrinkThreshold, AccountsDb, BankHashInfo, ErrorCounters, LoadHint, LoadedAccount,
ScanStorageResult,
},
accounts_index::{AccountSecondaryIndexes, IndexKey},
ancestors::Ancestors,
@ -117,12 +118,17 @@ pub enum AccountAddressFilter {
}
impl Accounts {
pub fn new(paths: Vec<PathBuf>, cluster_type: &ClusterType) -> Self {
pub fn new(
paths: Vec<PathBuf>,
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();

View File

@ -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<AtomicBool> = 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<R, B> {
Cached(Vec<R>),
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()
}
pub fn shrink_candidate_slots(&self) -> usize {
let shrink_slots = std::mem::take(&mut *self.shrink_candidate_slots.lock().unwrap());
let num_candidates = shrink_slots.len();
/// 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<AccountStorageEntry>,
}
let mut measure = Measure::start("select_top_sparse_storage_entries-ms");
let mut store_usage: Vec<StoreUsageInfo> = 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_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<AccountStorageEntry>) -> 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<AccountInfo>,
@ -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));
}
}

View File

@ -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::<Bank>::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);

View File

@ -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<R>(
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
limit_load_slot_count_from_snapshot: Option<usize>,
shrink_ratio: AccountShrinkThreshold,
) -> std::result::Result<Bank, Error>
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<E>(
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
limit_load_slot_count_from_snapshot: Option<usize>,
shrink_ratio: AccountShrinkThreshold,
) -> Result<Bank, Error>
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<E>(
account_indexes: AccountSecondaryIndexes,
caching_enabled: bool,
limit_load_slot_count_from_snapshot: Option<usize>,
shrink_ratio: AccountShrinkThreshold,
) -> Result<AccountsDb, Error>
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;

View File

@ -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<Pubkey> = vec![];
@ -228,6 +230,7 @@ fn test_bank_serialize_style(serde_style: SerdeStyle) {
AccountSecondaryIndexes::default(),
false,
None,
AccountShrinkThreshold::default(),
)
.unwrap();
dbank.src = ref_sc;

View File

@ -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<P: AsRef<Path>>(
account_indexes: AccountSecondaryIndexes,
accounts_db_caching_enabled: bool,
limit_load_slot_count_from_snapshot: Option<usize>,
shrink_ratio: AccountShrinkThreshold,
) -> Result<Bank> {
let unpack_dir = tempfile::Builder::new()
.prefix(TMP_SNAPSHOT_PREFIX)
@ -636,6 +637,7 @@ pub fn bank_from_archive<P: AsRef<Path>>(
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<usize>,
shrink_ratio: AccountShrinkThreshold,
) -> Result<Bank> {
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,
),
}?)
})?;

View File

@ -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()
};