add filler accounts gradually with cache flush (#25678)

This commit is contained in:
Jeff Washington (jwash) 2022-06-01 15:47:21 -05:00 committed by GitHub
parent b80a82a9e5
commit dd0bf4ff4b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 145 additions and 21 deletions

View File

@ -134,6 +134,11 @@ const CACHE_VIRTUAL_WRITE_VERSION: StoredMetaWriteVersion = 0;
pub(crate) const CACHE_VIRTUAL_OFFSET: Offset = 0;
const CACHE_VIRTUAL_STORED_SIZE: StoredSize = 0;
// the current best way to add filler accounts is gradually.
// In other scenarios, such as monitoring catchup with large # of accounts, it may be useful to be able to
// add filler accounts at the beginning, so that code path remains but won't execute at the moment.
const ADD_FILLER_ACCOUNTS_GRADUALLY: bool = true;
pub const ACCOUNTS_DB_CONFIG_FOR_TESTING: AccountsDbConfig = AccountsDbConfig {
index: Some(ACCOUNTS_INDEX_CONFIG_FOR_TESTING),
accounts_hash_cache_path: None,
@ -1127,6 +1132,12 @@ pub struct AccountsDb {
active_stats: ActiveStats,
/// number of filler accounts to add for each slot
pub filler_accounts_per_slot: AtomicU64,
/// number of slots remaining where filler accounts should be added
pub filler_account_slots_remaining: AtomicU64,
// # of passes should be a function of the total # of accounts that are active.
// higher passes = slower total time, lower dynamic memory usage
// lower passes = faster total time, higher dynamic memory usage
@ -1877,6 +1888,8 @@ impl AccountsDb {
Self::bins_per_pass(num_hash_scan_passes);
AccountsDb {
filler_accounts_per_slot: AtomicU64::default(),
filler_account_slots_remaining: AtomicU64::default(),
active_stats: ActiveStats::default(),
accounts_hash_complete_one_epoch_old: RwLock::default(),
skip_rewrites: false,
@ -2036,6 +2049,20 @@ impl AccountsDb {
new
}
/// Gradual means filler accounts will be added over the course of an epoch, during cache flush.
/// This is in contrast to adding all the filler accounts immediately before the validator starts.
fn init_gradual_filler_accounts(&self, slots_per_epoch: Slot) {
let count = self.filler_accounts_config.count;
if count > 0 {
// filler accounts are a debug only feature. integer division is fine here
let accounts_per_slot = (count as u64) / slots_per_epoch;
self.filler_accounts_per_slot
.store(accounts_per_slot, Ordering::Release);
self.filler_account_slots_remaining
.store(slots_per_epoch, Ordering::Release);
}
}
pub fn set_shrink_paths(&self, paths: Vec<PathBuf>) {
assert!(!paths.is_empty());
let mut shrink_paths = self.shrink_paths.write().unwrap();
@ -3419,6 +3446,15 @@ impl AccountsDb {
.get_all_less_than(slot)
}
fn get_prior_root(&self, slot: Slot) -> Option<Slot> {
self.accounts_index
.roots_tracker
.read()
.unwrap()
.alive_roots
.get_prior(slot)
}
/// get a sorted list of slots older than an epoch
/// squash those slots into ancient append vecs
fn shrink_ancient_slots(&self) {
@ -5529,6 +5565,31 @@ impl AccountsDb {
}
}
}
let mut filler_accounts = 0;
if self.filler_accounts_enabled() {
let slots_remaining = self.filler_account_slots_remaining.load(Ordering::Acquire);
if slots_remaining > 0 {
// figure out
let pr = self.get_prior_root(slot);
if let Some(prior_root) = pr {
let filler_account_slots =
std::cmp::min(slot.saturating_sub(prior_root), slots_remaining);
self.filler_account_slots_remaining
.fetch_sub(filler_account_slots, Ordering::Release);
let filler_accounts_per_slot =
self.filler_accounts_per_slot.load(Ordering::Acquire);
filler_accounts = filler_account_slots * filler_accounts_per_slot;
// keep space for filler accounts
let addl_size = (filler_accounts as u64)
* ((self.filler_accounts_config.size + STORE_META_OVERHEAD) as u64);
total_size += addl_size;
}
}
}
let (accounts, hashes): (Vec<(&Pubkey, &AccountSharedData)>, Vec<Hash>) = iter_items
.iter()
.filter_map(|iter_item| {
@ -5572,6 +5633,25 @@ impl AccountsDb {
Some(&flushed_store),
None,
);
if filler_accounts > 0 {
// add extra filler accounts at the end of the append vec
let (account, hash) = self.get_filler_account(&Rent::default());
let mut accounts = Vec::with_capacity(filler_accounts as usize);
let mut hashes = Vec::with_capacity(filler_accounts as usize);
let pubkeys = self.get_filler_account_pubkeys(filler_accounts as usize);
pubkeys.iter().for_each(|key| {
accounts.push((key, &account));
hashes.push(hash);
});
self.store_accounts_frozen(
(slot, &accounts[..]),
Some(&hashes),
Some(&flushed_store),
None,
);
}
// If the above sizing function is correct, just one AppendVec is enough to hold
// all the data for the slot
assert_eq!(
@ -7799,6 +7879,40 @@ impl AccountsDb {
}
}
/// return 'AccountSharedData' and a hash for a filler account
fn get_filler_account(&self, rent: &Rent) -> (AccountSharedData, Hash) {
let string = "FiLLERACCoUNTooooooooooooooooooooooooooooooo";
let hash = Hash::from_str(string).unwrap();
let owner = Pubkey::from_str(string).unwrap();
let space = self.filler_accounts_config.size;
let rent_exempt_reserve = rent.minimum_balance(space);
let lamports = rent_exempt_reserve;
let mut account = AccountSharedData::new(lamports, space, &owner);
// just non-zero rent epoch. filler accounts are rent-exempt
let dummy_rent_epoch = 2;
account.set_rent_epoch(dummy_rent_epoch);
(account, hash)
}
fn get_filler_account_pubkeys(&self, count: usize) -> Vec<Pubkey> {
(0..count)
.map(|_| {
let subrange = solana_sdk::pubkey::new_rand();
self.get_filler_account_pubkey(&subrange)
})
.collect()
}
fn get_filler_account_pubkey(&self, subrange: &Pubkey) -> Pubkey {
// pubkey begins life as entire filler 'suffix' pubkey
let mut key = self.filler_account_suffix.unwrap();
let rent_prefix_bytes = Self::filler_rent_partition_prefix_bytes();
// first bytes are replaced with rent partition range: filler_rent_partition_prefix_bytes
key.as_mut()[0..rent_prefix_bytes]
.copy_from_slice(&subrange.as_ref()[0..rent_prefix_bytes]);
key
}
/// filler accounts are space-holding accounts which are ignored by hash calculations and rent.
/// They are designed to allow a validator to run against a network successfully while simulating having many more accounts present.
/// All filler accounts share a common pubkey suffix. The suffix is randomly generated per validator on startup.
@ -7809,12 +7923,19 @@ impl AccountsDb {
&self,
epoch_schedule: &EpochSchedule,
rent: &Rent,
rent_epoch: Epoch,
slot: Slot,
) {
if self.filler_accounts_config.count == 0 {
return;
}
if ADD_FILLER_ACCOUNTS_GRADUALLY {
self.init_gradual_filler_accounts(
epoch_schedule.get_slots_in_epoch(epoch_schedule.get_epoch(slot)),
);
return;
}
let max_root_inclusive = self.accounts_index.max_root_inclusive();
let epoch = epoch_schedule.get_epoch(max_root_inclusive);
@ -7832,16 +7953,9 @@ impl AccountsDb {
let root_count = roots.len();
let per_pass = std::cmp::max(1, root_count / passes);
let overall_index = AtomicUsize::new(0);
let string = "FiLLERACCoUNTooooooooooooooooooooooooooooooo";
let hash = Hash::from_str(string).unwrap();
let owner = Pubkey::from_str(string).unwrap();
let space = self.filler_accounts_config.size;
let rent_exempt_reserve = rent.minimum_balance(space);
let lamports = rent_exempt_reserve;
let mut account = AccountSharedData::new(lamports, space, &owner);
// needs to be non-zero
account.set_rent_epoch(rent_epoch);
let added = AtomicUsize::default();
let (account, hash) = self.get_filler_account(rent);
let added = AtomicU32::default();
let rent_prefix_bytes = Self::filler_rent_partition_prefix_bytes();
for pass in 0..=passes {
self.accounts_index
.set_startup(Startup::StartupWithExtraThreads);
@ -7871,19 +7985,12 @@ impl AccountsDb {
let accounts = (0..filler_entries)
.map(|_| {
let my_id = added.fetch_add(1, Ordering::Relaxed);
let my_id_bytes = u32::to_be_bytes(my_id as u32);
// pubkey begins life as entire filler 'suffix' pubkey
let mut key = self.filler_account_suffix.unwrap();
let rent_prefix_bytes = Self::filler_rent_partition_prefix_bytes();
// first bytes are replaced with rent partition range: filler_rent_partition_prefix_bytes
key.as_mut()[0..rent_prefix_bytes]
.copy_from_slice(&subrange.start().as_ref()[0..rent_prefix_bytes]);
let mut key = self.get_filler_account_pubkey(subrange.start());
// next bytes are replaced with my_id: filler_unique_id_bytes
let my_id_bytes = u32::to_be_bytes(my_id);
key.as_mut()[rent_prefix_bytes
..(rent_prefix_bytes + Self::filler_unique_id_bytes())]
.copy_from_slice(&my_id_bytes);
assert!(subrange.contains(&key));
key
})
.collect::<Vec<_>>();

View File

@ -259,6 +259,23 @@ impl RollingBitField {
all
}
/// return highest item < 'max_slot_exclusive'
pub fn get_prior(&self, max_slot_exclusive: Slot) -> Option<Slot> {
let mut slot = max_slot_exclusive.saturating_sub(1);
self.min().and_then(|min| {
loop {
if self.contains(&slot) {
return Some(slot);
}
slot = slot.saturating_sub(1);
if slot == 0 || slot < min {
break;
}
}
None
})
}
pub fn get_all(&self) -> Vec<u64> {
let mut all = Vec::with_capacity(self.count);
self.excess.iter().for_each(|slot| all.push(*slot));

View File

@ -645,7 +645,7 @@ where
accounts_db.maybe_add_filler_accounts(
&genesis_config.epoch_schedule,
&genesis_config.rent,
genesis_config.epoch_schedule.get_epoch(snapshot_slot),
snapshot_slot,
);
handle.join().unwrap();