2022-05-10 14:41:04 -07:00
//! helpers for squashing append vecs into ancient append vecs
//! an ancient append vec is:
//! 1. a slot that is older than an epoch old
//! 2. multiple 'slots' squashed into a single older (ie. ancient) slot for convenience and performance
//! Otherwise, an ancient append vec is the same as any other append vec
use {
2023-02-03 10:53:49 -08:00
crate ::{
2023-02-22 16:10:34 -08:00
account_storage ::{ meta ::StoredAccountMeta , ShrinkInProgress } ,
2023-02-08 09:40:17 -08:00
accounts_db ::{
2023-02-09 11:06:17 -08:00
AccountStorageEntry , AccountsDb , AliveAccounts , GetUniqueAccountsResult , ShrinkCollect ,
ShrinkCollectAliveSeparatedByRefs , ShrinkStatsSub , StoreReclaims ,
2023-02-09 14:47:22 -08:00
INCLUDE_SLOT_IN_HASH_IRRELEVANT_APPEND_VEC_OPERATION ,
2023-02-08 09:40:17 -08:00
} ,
2023-02-15 17:24:04 -08:00
accounts_file ::AccountsFile ,
2023-04-16 12:24:25 -07:00
accounts_index ::{ AccountsIndexScanResult , ZeroLamport } ,
2023-02-14 06:53:31 -08:00
active_stats ::ActiveStatItem ,
2023-02-22 16:10:34 -08:00
append_vec ::aligned_stored_size ,
2023-02-09 14:47:22 -08:00
storable_accounts ::{ StorableAccounts , StorableAccountsBySlot } ,
2023-02-03 10:53:49 -08:00
} ,
rand ::{ thread_rng , Rng } ,
2023-04-16 12:24:25 -07:00
rayon ::prelude ::{ IntoParallelIterator , IntoParallelRefIterator , ParallelIterator } ,
2023-02-15 12:43:47 -08:00
solana_measure ::measure_us ,
2023-04-17 13:52:26 -07:00
solana_sdk ::{
account ::ReadableAccount , clock ::Slot , hash ::Hash , pubkey ::Pubkey , saturating_add_assign ,
} ,
2023-02-14 06:53:31 -08:00
std ::{
collections ::HashMap ,
num ::NonZeroU64 ,
2023-04-14 13:57:46 -07:00
sync ::{ atomic ::Ordering , Arc , Mutex } ,
2023-02-14 06:53:31 -08:00
} ,
2022-05-10 14:41:04 -07:00
} ;
2023-02-07 10:35:40 -08:00
/// ancient packing algorithm tuning per pass
#[ derive(Debug) ]
struct PackedAncientStorageTuning {
/// shrink enough of these ancient append vecs to realize this% of the total dead data that needs to be shrunk
/// Doing too much burns too much time and disk i/o.
/// Doing too little could cause us to never catch up and have old data accumulate.
percent_of_alive_shrunk_data : u64 ,
/// number of ancient slots we should aim to have. If we have more than this, combine further.
max_ancient_slots : usize ,
/// # of bytes in an ideal ancient storage size
2023-02-07 13:34:27 -08:00
ideal_storage_size : NonZeroU64 ,
2023-02-08 07:08:20 -08:00
/// true if storages can be randomly shrunk even if they aren't eligible
can_randomly_shrink : bool ,
2023-02-07 10:35:40 -08:00
}
2023-02-03 10:53:49 -08:00
/// info about a storage eligible to be combined into an ancient append vec.
/// Useful to help sort vecs of storages.
#[ derive(Debug) ]
struct SlotInfo {
storage : Arc < AccountStorageEntry > ,
/// slot of storage
slot : Slot ,
/// total capacity of storage
capacity : u64 ,
/// # alive bytes in storage
alive_bytes : u64 ,
/// true if this should be shrunk due to ratio
should_shrink : bool ,
}
/// info for all storages in ancient slots
/// 'all_infos' contains all slots and storages that are ancient
#[ derive(Default, Debug) ]
struct AncientSlotInfos {
/// info on all ancient storages
all_infos : Vec < SlotInfo > ,
/// indexes to 'all_info' for storages that should be shrunk because alive ratio is too low.
/// subset of all_infos
shrink_indexes : Vec < usize > ,
/// total alive bytes across contents of 'shrink_indexes'
total_alive_bytes_shrink : u64 ,
/// total alive bytes across all slots
total_alive_bytes : u64 ,
}
impl AncientSlotInfos {
/// add info for 'storage'
2023-04-24 07:40:45 -07:00
/// return true if item was randomly shrunk
fn add (
& mut self ,
slot : Slot ,
storage : Arc < AccountStorageEntry > ,
can_randomly_shrink : bool ,
) -> bool {
let mut was_randomly_shrunk = false ;
2023-02-03 10:53:49 -08:00
let alive_bytes = storage . alive_bytes ( ) as u64 ;
if alive_bytes > 0 {
let capacity = storage . accounts . capacity ( ) ;
let should_shrink = if capacity > 0 {
let alive_ratio = alive_bytes * 100 / capacity ;
2023-04-24 07:40:45 -07:00
alive_ratio < 90
| | if can_randomly_shrink & & thread_rng ( ) . gen_range ( 0 , 10000 ) = = 0 {
was_randomly_shrunk = true ;
true
} else {
false
}
2023-02-03 10:53:49 -08:00
} else {
false
} ;
// two criteria we're shrinking by later:
// 1. alive ratio so that we don't consume too much disk space with dead accounts
// 2. # of active ancient roots, so that we don't consume too many open file handles
if should_shrink {
// alive ratio is too low, so prioritize combining this slot with others
// to reduce disk space used
saturating_add_assign! ( self . total_alive_bytes_shrink , alive_bytes ) ;
self . shrink_indexes . push ( self . all_infos . len ( ) ) ;
}
self . all_infos . push ( SlotInfo {
slot ,
capacity ,
storage ,
alive_bytes ,
should_shrink ,
} ) ;
2023-02-14 08:03:16 -08:00
saturating_add_assign! ( self . total_alive_bytes , alive_bytes ) ;
2023-02-03 10:53:49 -08:00
}
2023-04-24 07:40:45 -07:00
was_randomly_shrunk
2023-02-03 10:53:49 -08:00
}
2023-02-05 12:14:14 -08:00
2023-02-07 10:35:40 -08:00
/// modify 'self' to contain only the slot infos for the slots that should be combined
/// (and in this process effectively shrunk)
fn filter_ancient_slots ( & mut self , tuning : & PackedAncientStorageTuning ) {
// figure out which slots to combine
// 1. should_shrink: largest bytes saved above some cutoff of ratio
self . choose_storages_to_shrink ( tuning . percent_of_alive_shrunk_data ) ;
// 2. smallest files so we get the largest number of files to remove
self . filter_by_smallest_capacity ( tuning . max_ancient_slots , tuning . ideal_storage_size ) ;
}
2023-02-05 12:14:14 -08:00
// sort 'shrink_indexes' by most bytes saved, highest to lowest
fn sort_shrink_indexes_by_bytes_saved ( & mut self ) {
self . shrink_indexes . sort_unstable_by ( | l , r | {
let amount_shrunk = | index : & usize | {
let item = & self . all_infos [ * index ] ;
item . capacity - item . alive_bytes
} ;
amount_shrunk ( r ) . cmp ( & amount_shrunk ( l ) )
} ) ;
}
2023-02-06 11:04:53 -08:00
2023-02-07 10:35:40 -08:00
/// clear 'should_shrink' for storages after a cutoff to limit how many storages we shrink
2023-02-06 14:29:54 -08:00
fn clear_should_shrink_after_cutoff ( & mut self , percent_of_alive_shrunk_data : u64 ) {
let mut bytes_to_shrink_due_to_ratio = 0 ;
// shrink enough slots to write 'percent_of_alive_shrunk_data'% of the total alive data
// from slots that exceeded the shrink threshold.
// The goal is to limit overall i/o in this pass while making progress.
2023-02-14 08:03:16 -08:00
let threshold_bytes = self . total_alive_bytes_shrink * percent_of_alive_shrunk_data / 100 ;
2023-02-06 14:29:54 -08:00
for info_index in & self . shrink_indexes {
let info = & mut self . all_infos [ * info_index ] ;
2023-02-14 08:03:16 -08:00
if bytes_to_shrink_due_to_ratio > = threshold_bytes {
2023-02-07 10:35:40 -08:00
// we exceeded the amount to shrink due to alive ratio, so don't shrink this one just due to 'should_shrink'
2023-02-06 14:29:54 -08:00
// It MAY be shrunk based on total capacity still.
2023-02-07 10:35:40 -08:00
// Mark it as false for 'should_shrink' so it gets evaluated solely based on # of files.
2023-02-06 14:29:54 -08:00
info . should_shrink = false ;
} else {
saturating_add_assign! ( bytes_to_shrink_due_to_ratio , info . alive_bytes ) ;
}
}
}
/// after this function, only slots that were chosen to shrink are marked with
/// 'should_shrink'
/// There are likely more candidates to shrink than will be chosen.
fn choose_storages_to_shrink ( & mut self , percent_of_alive_shrunk_data : u64 ) {
// sort the shrink_ratio_slots by most bytes saved to fewest
// most bytes saved is more valuable to shrink
self . sort_shrink_indexes_by_bytes_saved ( ) ;
self . clear_should_shrink_after_cutoff ( percent_of_alive_shrunk_data ) ;
}
2023-02-06 11:04:53 -08:00
/// truncate 'all_infos' such that when the remaining entries in
/// 'all_infos' are combined, the total number of storages <= 'max_storages'
/// The idea is that 'all_infos' is sorted from smallest capacity to largest,
/// but that isn't required for this function to be 'correct'.
2023-02-07 13:34:27 -08:00
fn truncate_to_max_storages ( & mut self , max_storages : usize , ideal_storage_size : NonZeroU64 ) {
2023-02-06 11:04:53 -08:00
// these indexes into 'all_infos' are useless once we truncate 'all_infos', so make sure they're cleared out to avoid any issues
self . shrink_indexes . clear ( ) ;
let total_storages = self . all_infos . len ( ) ;
let mut cumulative_bytes = 0 u64 ;
for ( i , info ) in self . all_infos . iter ( ) . enumerate ( ) {
saturating_add_assign! ( cumulative_bytes , info . alive_bytes ) ;
2023-02-07 13:34:27 -08:00
let ancient_storages_required = ( cumulative_bytes / ideal_storage_size + 1 ) as usize ;
2023-02-06 11:04:53 -08:00
let storages_remaining = total_storages - i - 1 ;
// if the remaining uncombined storages and the # of resulting
// combined ancient storages is less than the threshold, then
// we've gone too far, so get rid of this entry and all after it.
// Every storage after this one is larger.
if storages_remaining + ancient_storages_required < max_storages {
self . all_infos . truncate ( i ) ;
break ;
}
}
}
/// remove entries from 'all_infos' such that combining
/// the remaining entries into storages of 'ideal_storage_size'
/// will get us below 'max_storages'
/// The entires that are removed will be reconsidered the next time around.
/// Combining too many storages costs i/o and cpu so the goal is to find the sweet spot so
/// that we make progress in cleaning/shrinking/combining but that we don't cause unnecessary
/// churn.
2023-02-07 13:34:27 -08:00
fn filter_by_smallest_capacity ( & mut self , max_storages : usize , ideal_storage_size : NonZeroU64 ) {
2023-02-06 11:04:53 -08:00
let total_storages = self . all_infos . len ( ) ;
if total_storages < = max_storages {
// currently fewer storages than max, so nothing to shrink
self . shrink_indexes . clear ( ) ;
self . all_infos . clear ( ) ;
return ;
}
// sort by 'should_shrink' then smallest capacity to largest
self . all_infos . sort_unstable_by ( | l , r | {
r . should_shrink
. cmp ( & l . should_shrink )
. then_with ( | | l . capacity . cmp ( & r . capacity ) )
} ) ;
// remove any storages we don't need to combine this pass to achieve
// # resulting storages <= 'max_storages'
self . truncate_to_max_storages ( max_storages , ideal_storage_size ) ;
}
2023-02-03 10:53:49 -08:00
}
2023-02-08 09:40:17 -08:00
/// Used to hold the result of writing a single ancient storage
/// and results of writing multiple ancient storages
#[ derive(Debug, Default) ]
struct WriteAncientAccounts < ' a > {
/// 'ShrinkInProgress' instances created by starting a shrink operation
shrinks_in_progress : HashMap < Slot , ShrinkInProgress < ' a > > ,
metrics : ShrinkStatsSub ,
}
2023-02-03 10:53:49 -08:00
impl AccountsDb {
2023-02-14 06:53:31 -08:00
/// Combine account data from storages in 'sorted_slots' into packed storages.
/// This keeps us from accumulating storages for each slot older than an epoch.
/// Ater this function the number of alive roots is <= # alive roots when it was called.
/// In practice, the # of alive roots after will be significantly less than # alive roots when called.
/// Trying to reduce # roots and storages (one per root) required to store all the data in ancient slots
pub ( crate ) fn combine_ancient_slots_packed (
& self ,
sorted_slots : Vec < Slot > ,
can_randomly_shrink : bool ,
) {
let tuning = PackedAncientStorageTuning {
// only allow 10k slots old enough to be ancient
max_ancient_slots : 10_000 ,
// re-combine/shrink 55% of the data savings this pass
percent_of_alive_shrunk_data : 55 ,
ideal_storage_size : NonZeroU64 ::new ( get_ancient_append_vec_capacity ( ) ) . unwrap ( ) ,
can_randomly_shrink ,
} ;
let _guard = self . active_stats . activate ( ActiveStatItem ::SquashAncient ) ;
let mut stats_sub = ShrinkStatsSub ::default ( ) ;
let ( _ , total_us ) = measure_us! ( self . combine_ancient_slots_packed_internal (
sorted_slots ,
tuning ,
& mut stats_sub
) ) ;
Self ::update_shrink_stats ( & self . shrink_ancient_stats . shrink_stats , stats_sub ) ;
self . shrink_ancient_stats
. total_us
. fetch_add ( total_us , Ordering ::Relaxed ) ;
// only log when we've spent 1s total
// results will continue to accumulate otherwise
if self . shrink_ancient_stats . total_us . load ( Ordering ::Relaxed ) > 1_000_000 {
self . shrink_ancient_stats . report ( ) ;
}
}
fn combine_ancient_slots_packed_internal (
& self ,
sorted_slots : Vec < Slot > ,
tuning : PackedAncientStorageTuning ,
metrics : & mut ShrinkStatsSub ,
) {
2023-07-11 11:51:16 -07:00
self . shrink_ancient_stats
. slots_considered
. fetch_add ( sorted_slots . len ( ) as u64 , Ordering ::Relaxed ) ;
2023-02-14 06:53:31 -08:00
let ancient_slot_infos = self . collect_sort_filter_ancient_slots ( sorted_slots , & tuning ) ;
if ancient_slot_infos . all_infos . is_empty ( ) {
return ; // nothing to do
}
let accounts_per_storage = self
. get_unique_accounts_from_storage_for_combining_ancient_slots (
& ancient_slot_infos . all_infos [ .. ] ,
) ;
let accounts_to_combine = self . calc_accounts_to_combine ( & accounts_per_storage ) ;
// pack the accounts with 1 ref
let pack = PackedAncientStorage ::pack (
accounts_to_combine
. accounts_to_combine
. iter ( )
. map ( | shrink_collect | & shrink_collect . alive_accounts . one_ref ) ,
tuning . ideal_storage_size ,
) ;
if pack . len ( ) > accounts_to_combine . target_slots_sorted . len ( ) {
2023-04-16 12:24:25 -07:00
// Not enough slots to contain the accounts we are trying to pack.
// `shrink_collect` previously unref'd some accounts. We need to addref them
// to restore the correct state since we failed to combine anything.
self . addref_accounts_failed_to_shrink_ancient ( accounts_to_combine ) ;
return ;
2023-02-14 06:53:31 -08:00
}
let write_ancient_accounts = self . write_packed_storages ( & accounts_to_combine , pack ) ;
self . finish_combine_ancient_slots_packed_internal (
accounts_to_combine ,
write_ancient_accounts ,
metrics ,
) ;
}
2023-04-16 12:24:25 -07:00
/// for each account in `unrefed_pubkeys`, in each `accounts_to_combine`, addref
fn addref_accounts_failed_to_shrink_ancient ( & self , accounts_to_combine : AccountsToCombine ) {
self . thread_pool_clean . install ( | | {
accounts_to_combine
. accounts_to_combine
. into_par_iter ( )
. for_each ( | combine | {
self . accounts_index . scan (
combine . unrefed_pubkeys . into_iter ( ) ,
| _pubkey , _slots_refs , entry | {
if let Some ( entry ) = entry {
entry . addref ( ) ;
}
2023-05-05 10:39:32 -07:00
AccountsIndexScanResult ::OnlyKeepInMemoryIfDirty
2023-04-16 12:24:25 -07:00
} ,
None ,
true ,
) ;
} ) ;
} ) ;
}
2023-02-08 07:08:20 -08:00
/// calculate all storage info for the storages in slots
2023-02-08 08:00:14 -08:00
/// Then, apply 'tuning' to filter out slots we do NOT want to combine.
fn collect_sort_filter_ancient_slots (
2023-02-08 07:08:20 -08:00
& self ,
slots : Vec < Slot > ,
2023-02-10 09:40:13 -08:00
tuning : & PackedAncientStorageTuning ,
2023-02-08 07:08:20 -08:00
) -> AncientSlotInfos {
let mut ancient_slot_infos = self . calc_ancient_slot_info ( slots , tuning . can_randomly_shrink ) ;
2023-02-10 09:40:13 -08:00
ancient_slot_infos . filter_ancient_slots ( tuning ) ;
2023-02-08 07:08:20 -08:00
ancient_slot_infos
}
2023-02-08 09:40:17 -08:00
/// create append vec of size 'bytes'
/// write 'accounts_to_write' into it
/// return shrink_in_progress and some metrics
fn write_ancient_accounts < ' a , ' b : ' a , T : ReadableAccount + Sync + ZeroLamport + ' a > (
& ' b self ,
bytes : u64 ,
accounts_to_write : impl StorableAccounts < ' a , T > ,
2023-02-11 11:41:33 -08:00
write_ancient_accounts : & mut WriteAncientAccounts < ' b > ,
) {
2023-02-08 09:40:17 -08:00
let target_slot = accounts_to_write . target_slot ( ) ;
let ( shrink_in_progress , create_and_insert_store_elapsed_us ) =
measure_us! ( self . get_store_for_shrink ( target_slot , bytes ) ) ;
let ( store_accounts_timing , rewrite_elapsed_us ) = measure_us! ( self . store_accounts_frozen (
accounts_to_write ,
None ::< Vec < Hash > > ,
shrink_in_progress . new_storage ( ) ,
None ,
StoreReclaims ::Ignore ,
) ) ;
2023-02-11 11:41:33 -08:00
write_ancient_accounts . metrics . accumulate ( & ShrinkStatsSub {
store_accounts_timing ,
rewrite_elapsed_us ,
create_and_insert_store_elapsed_us ,
} ) ;
write_ancient_accounts
. shrinks_in_progress
. insert ( target_slot , shrink_in_progress ) ;
2023-02-08 09:40:17 -08:00
}
2023-02-03 10:53:49 -08:00
/// go through all slots and populate 'SlotInfo', per slot
/// This provides the list of possible ancient slots to sort, filter, and then combine.
fn calc_ancient_slot_info (
& self ,
slots : Vec < Slot > ,
can_randomly_shrink : bool ,
) -> AncientSlotInfos {
let len = slots . len ( ) ;
let mut infos = AncientSlotInfos {
shrink_indexes : Vec ::with_capacity ( len ) ,
all_infos : Vec ::with_capacity ( len ) ,
.. AncientSlotInfos ::default ( )
} ;
2023-04-24 07:40:45 -07:00
let mut randoms = 0 ;
2023-02-03 10:53:49 -08:00
for slot in & slots {
if let Some ( storage ) = self . storage . get_slot_storage_entry ( * slot ) {
2023-04-24 07:40:45 -07:00
if infos . add ( * slot , storage , can_randomly_shrink ) {
randoms + = 1 ;
}
2023-02-03 10:53:49 -08:00
}
}
2023-04-24 07:40:45 -07:00
if randoms > 0 {
self . shrink_ancient_stats
. random_shrink
. fetch_add ( randoms , Ordering ::Relaxed ) ;
}
2023-02-03 10:53:49 -08:00
infos
}
2023-02-07 18:22:54 -08:00
2023-02-13 07:25:47 -08:00
/// write packed storages as described in 'accounts_to_combine'
/// and 'packed_contents'
fn write_packed_storages < ' a , ' b > (
& ' a self ,
accounts_to_combine : & ' b AccountsToCombine < ' b > ,
packed_contents : Vec < PackedAncientStorage < ' b > > ,
) -> WriteAncientAccounts < ' a > {
2023-04-14 13:57:46 -07:00
let write_ancient_accounts = Mutex ::new ( WriteAncientAccounts ::default ( ) ) ;
2023-02-13 07:25:47 -08:00
// ok if we have more slots, but NOT ok if we have fewer slots than we have contents
assert! ( accounts_to_combine . target_slots_sorted . len ( ) > = packed_contents . len ( ) ) ;
// write packed storages containing contents from many original slots
// iterate slots in highest to lowest
2023-04-14 13:57:46 -07:00
let packer = accounts_to_combine
2023-02-13 07:25:47 -08:00
. target_slots_sorted
. iter ( )
. rev ( )
. zip ( packed_contents )
2023-04-14 13:57:46 -07:00
. collect ::< Vec < _ > > ( ) ;
2023-04-18 13:33:02 -07:00
// keep track of how many slots were shrunk away
self . shrink_ancient_stats
. ancient_append_vecs_shrunk
. fetch_add (
accounts_to_combine
. target_slots_sorted
. len ( )
. saturating_sub ( packer . len ( ) ) as u64 ,
Ordering ::Relaxed ,
) ;
2023-04-14 13:57:46 -07:00
self . thread_pool_clean . install ( | | {
packer . par_iter ( ) . for_each ( | ( target_slot , pack ) | {
let mut write_ancient_accounts_local = WriteAncientAccounts ::default ( ) ;
self . write_one_packed_storage (
pack ,
* * target_slot ,
& mut write_ancient_accounts_local ,
) ;
let mut write = write_ancient_accounts . lock ( ) . unwrap ( ) ;
write
. shrinks_in_progress
. extend ( write_ancient_accounts_local . shrinks_in_progress ) ;
write
. metrics
. accumulate ( & write_ancient_accounts_local . metrics ) ;
2023-02-13 07:25:47 -08:00
} ) ;
2023-04-14 13:57:46 -07:00
} ) ;
let mut write_ancient_accounts = write_ancient_accounts . into_inner ( ) . unwrap ( ) ;
2023-02-13 07:25:47 -08:00
// write new storages where contents were unable to move because ref_count > 1
self . write_ancient_accounts_to_same_slot_multiple_refs (
accounts_to_combine . accounts_keep_slots . values ( ) ,
& mut write_ancient_accounts ,
) ;
write_ancient_accounts
}
2023-02-07 18:22:54 -08:00
/// for each slot in 'ancient_slots', collect all accounts in that slot
/// return the collection of accounts by slot
fn get_unique_accounts_from_storage_for_combining_ancient_slots < ' a > (
& self ,
ancient_slots : & ' a [ SlotInfo ] ,
) -> Vec < ( & ' a SlotInfo , GetUniqueAccountsResult < ' a > ) > {
let mut accounts_to_combine = Vec ::with_capacity ( ancient_slots . len ( ) ) ;
for info in ancient_slots {
let unique_accounts = self . get_unique_accounts_from_storage_for_shrink (
& info . storage ,
& self . shrink_ancient_stats . shrink_stats ,
) ;
accounts_to_combine . push ( ( info , unique_accounts ) ) ;
}
accounts_to_combine
}
2023-02-09 11:06:17 -08:00
2023-02-13 09:10:13 -08:00
/// finish shrink operation on slots where a new storage was created
/// drop root and storage for all original slots whose contents were combined into other storages
2023-02-16 08:12:51 -08:00
fn finish_combine_ancient_slots_packed_internal (
2023-02-13 09:10:13 -08:00
& self ,
2023-02-16 08:12:51 -08:00
accounts_to_combine : AccountsToCombine < '_ > ,
2023-02-13 09:10:13 -08:00
mut write_ancient_accounts : WriteAncientAccounts ,
metrics : & mut ShrinkStatsSub ,
) {
let mut dropped_roots = Vec ::with_capacity ( accounts_to_combine . accounts_to_combine . len ( ) ) ;
for shrink_collect in accounts_to_combine . accounts_to_combine {
let slot = shrink_collect . slot ;
let shrink_in_progress = write_ancient_accounts . shrinks_in_progress . remove ( & slot ) ;
if shrink_in_progress . is_none ( ) {
dropped_roots . push ( slot ) ;
}
self . remove_old_stores_shrink (
& shrink_collect ,
& self . shrink_ancient_stats . shrink_stats ,
shrink_in_progress ,
true ,
) ;
2023-06-09 09:28:49 -07:00
// If the slot is dead, remove the need to shrink the storage as the storage entries will be purged.
self . shrink_candidate_slots . lock ( ) . unwrap ( ) . remove ( & slot ) ;
2023-02-13 09:10:13 -08:00
}
self . handle_dropped_roots_for_ancient ( dropped_roots . into_iter ( ) ) ;
metrics . accumulate ( & write_ancient_accounts . metrics ) ;
}
2023-02-09 11:06:17 -08:00
/// given all accounts per ancient slot, in slots that we want to combine together:
/// 1. Look up each pubkey in the index
/// 2. separate, by slot, into:
/// 2a. pubkeys with refcount = 1. This means this pubkey exists NOWHERE else in accounts db.
/// 2b. pubkeys with refcount > 1
2023-02-11 12:13:03 -08:00
/// Note that the return value can contain fewer items than 'accounts_per_storage' if we find storages which won't be affected.
/// 'accounts_per_storage' should be sorted by slot
2023-02-09 11:06:17 -08:00
fn calc_accounts_to_combine < ' a > (
& self ,
2023-02-11 12:13:03 -08:00
accounts_per_storage : & ' a Vec < ( & ' a SlotInfo , GetUniqueAccountsResult < ' a > ) > ,
2023-02-09 11:06:17 -08:00
) -> AccountsToCombine < ' a > {
let mut accounts_keep_slots = HashMap ::default ( ) ;
2023-02-11 12:13:03 -08:00
let len = accounts_per_storage . len ( ) ;
2023-02-10 12:08:13 -08:00
let mut target_slots_sorted = Vec ::with_capacity ( len ) ;
2023-02-09 11:06:17 -08:00
2023-04-16 12:23:11 -07:00
// `shrink_collect` all accounts in the append vecs we want to combine.
// This also unrefs all dead accounts in those append vecs.
let mut accounts_to_combine = self . thread_pool_clean . install ( | | {
accounts_per_storage
. par_iter ( )
. map ( | ( info , unique_accounts ) | {
self . shrink_collect ::< ShrinkCollectAliveSeparatedByRefs < '_ > > (
& info . storage ,
unique_accounts ,
& self . shrink_ancient_stats . shrink_stats ,
)
} )
. collect ::< Vec < _ > > ( )
} ) ;
2023-04-24 07:40:05 -07:00
let mut remove = Vec ::default ( ) ;
for ( i , ( shrink_collect , ( info , _unique_accounts ) ) ) in accounts_to_combine
2023-04-16 12:23:11 -07:00
. iter_mut ( )
. zip ( accounts_per_storage . iter ( ) )
2023-04-24 07:40:05 -07:00
. enumerate ( )
2023-04-16 12:23:11 -07:00
{
2023-04-17 13:52:26 -07:00
self . revisit_accounts_with_many_refs ( shrink_collect ) ;
2023-02-09 11:06:17 -08:00
let many_refs = & mut shrink_collect . alive_accounts . many_refs ;
if ! many_refs . accounts . is_empty ( ) {
// there are accounts with ref_count > 1. This means this account must remain IN this slot.
// The same account could exist in a newer or older slot. Moving this account across slots could result
// in this alive version of the account now being in a slot OLDER than the non-alive instances.
2023-04-24 07:40:05 -07:00
if shrink_collect . unrefed_pubkeys . is_empty ( )
& & shrink_collect . alive_accounts . one_ref . accounts . is_empty ( )
{
// all accounts in this append vec are alive and have > 1 ref, so nothing to be done for this append vec
remove . push ( i ) ;
continue ;
}
2023-02-10 13:53:03 -08:00
accounts_keep_slots . insert ( info . slot , std ::mem ::take ( many_refs ) ) ;
2023-02-09 11:06:17 -08:00
} else {
// No alive accounts in this slot have a ref_count > 1. So, ALL alive accounts in this slot can be written to any other slot
// we find convenient. There is NO other instance of any account to conflict with.
2023-02-10 12:08:13 -08:00
target_slots_sorted . push ( info . slot ) ;
2023-02-09 11:06:17 -08:00
}
}
2023-04-24 07:40:05 -07:00
remove . into_iter ( ) . rev ( ) . for_each ( | i | {
accounts_to_combine . remove ( i ) ;
} ) ;
2023-02-09 11:06:17 -08:00
AccountsToCombine {
accounts_to_combine ,
accounts_keep_slots ,
2023-02-10 12:08:13 -08:00
target_slots_sorted ,
2023-02-09 11:06:17 -08:00
}
}
2023-02-09 14:47:22 -08:00
2023-04-17 13:52:26 -07:00
/// return pubkeys from `many_refs` accounts
fn get_many_refs_pubkeys < ' a > (
shrink_collect : & ShrinkCollect < ' a , ShrinkCollectAliveSeparatedByRefs < ' a > > ,
) -> Vec < Pubkey > {
shrink_collect
. alive_accounts
. many_refs
. accounts
. iter ( )
. map ( | account | * account . pubkey ( ) )
. collect ::< Vec < _ > > ( )
}
/// After calling `shrink_collect()` on many slots, any dead accounts in those slots would be unref'd.
/// Alive accounts which had ref_count > 1 are stored in `shrink_collect.alive_accounts.many_refs`.
/// Since many slots were being visited, it is possible that at a point in time, an account was found to be alive and have ref_count > 1.
/// Concurrently, another slot was visited which also had the account, but the account was dead and unref'd in that `shrink_collect()` call.
/// So, now that all unrefs have occurred, go back through the small number of `many_refs` accounts and for all that now only have 1 ref_count,
/// move the account from `many_refs` to `one_ref`.
fn revisit_accounts_with_many_refs < ' a > (
& self ,
shrink_collect : & mut ShrinkCollect < ' a , ShrinkCollectAliveSeparatedByRefs < ' a > > ,
) {
// collect pk values here to avoid borrow checker
let pks = Self ::get_many_refs_pubkeys ( shrink_collect ) ;
let mut index = 0 ;
2023-04-18 13:33:02 -07:00
let mut saved = 0 ;
2023-04-17 13:52:26 -07:00
self . accounts_index . scan (
pks . iter ( ) ,
| _pubkey , slots_refs , _entry | {
index + = 1 ;
if let Some ( ( _slot_list , ref_count ) ) = slots_refs {
if ref_count = = 1 {
// This entry has been unref'd during shrink ancient, so it can now move out of `many_refs` and into `one_ref`.
// This could happen if the same pubkey is in 2 append vecs that are BOTH being shrunk right now.
// Note that `shrink_collect()`, which was previously called to create `shrink_collect`, unrefs any dead accounts.
2023-04-18 13:33:02 -07:00
let many_refs = & mut shrink_collect . alive_accounts . many_refs ;
let account = many_refs . accounts . remove ( index - 1 ) ;
if many_refs . accounts . is_empty ( ) {
// all accounts in `many_refs` now have only 1 ref, so this slot can now be combined into another.
saved + = 1 ;
}
2023-04-17 13:52:26 -07:00
let bytes = account . stored_size ( ) ;
shrink_collect . alive_accounts . one_ref . accounts . push ( account ) ;
saturating_add_assign! ( shrink_collect . alive_accounts . one_ref . bytes , bytes ) ;
2023-04-18 13:33:02 -07:00
many_refs . bytes - = bytes ;
2023-04-17 13:52:26 -07:00
// since we removed an entry from many_refs.accounts, we need to index one less
index - = 1 ;
}
}
2023-05-05 10:39:32 -07:00
AccountsIndexScanResult ::OnlyKeepInMemoryIfDirty
2023-04-17 13:52:26 -07:00
} ,
None ,
false ,
) ;
2023-04-18 13:33:02 -07:00
self . shrink_ancient_stats
. second_pass_one_ref
. fetch_add ( saved , Ordering ::Relaxed ) ;
2023-04-17 13:52:26 -07:00
}
2023-02-09 14:47:22 -08:00
/// create packed storage and write contents of 'packed' to it.
/// accumulate results in 'write_ancient_accounts'
fn write_one_packed_storage < ' a , ' b : ' a > (
& ' b self ,
packed : & ' a PackedAncientStorage < ' a > ,
target_slot : Slot ,
write_ancient_accounts : & mut WriteAncientAccounts < ' b > ,
) {
let PackedAncientStorage {
bytes : bytes_total ,
accounts : accounts_to_write ,
} = packed ;
let accounts_to_write = StorableAccountsBySlot ::new (
target_slot ,
2023-02-12 13:42:03 -08:00
accounts_to_write ,
2023-02-09 14:47:22 -08:00
INCLUDE_SLOT_IN_HASH_IRRELEVANT_APPEND_VEC_OPERATION ,
) ;
2023-02-11 11:41:33 -08:00
self . write_ancient_accounts ( * bytes_total , accounts_to_write , write_ancient_accounts )
2023-02-09 14:47:22 -08:00
}
2023-02-10 09:39:30 -08:00
/// For each slot and alive accounts in 'accounts_to_combine'
/// create a PackedAncientStorage that only contains the given alive accounts.
/// This will represent only the accounts with ref_count > 1 from the original storage.
/// These accounts need to be rewritten in their same slot, Ideally with no other accounts in the slot.
/// Other accounts would have ref_count = 1.
/// ref_count = 1 accounts will be combined together with other slots into larger append vecs elsewhere.
fn write_ancient_accounts_to_same_slot_multiple_refs < ' a , ' b : ' a > (
& ' b self ,
accounts_to_combine : impl Iterator < Item = & ' a AliveAccounts < ' a > > ,
write_ancient_accounts : & mut WriteAncientAccounts < ' b > ,
) {
for alive_accounts in accounts_to_combine {
let packed = PackedAncientStorage {
bytes : alive_accounts . bytes as u64 ,
accounts : vec ! [ ( alive_accounts . slot , & alive_accounts . accounts [ .. ] ) ] ,
} ;
self . write_one_packed_storage ( & packed , alive_accounts . slot , write_ancient_accounts ) ;
}
}
2023-02-09 11:06:17 -08:00
}
/// hold all alive accounts to be shrunk and/or combined
2023-02-09 15:02:10 -08:00
#[ derive(Debug, Default) ]
2023-02-09 11:06:17 -08:00
struct AccountsToCombine < ' a > {
/// slots and alive accounts that must remain in the slot they are currently in
/// because the account exists in more than 1 slot in accounts db
/// This hashmap contains an entry for each slot that contains at least one account with ref_count > 1.
/// The value of the entry is all alive accounts in that slot whose ref_count > 1.
/// Any OTHER accounts in that slot whose ref_count = 1 are in 'accounts_to_combine' because they can be moved
/// to any slot.
/// We want to keep the ref_count > 1 accounts by themselves, expecting the multiple ref_counts will be resolved
/// soon and we can clean the duplicates up (which maybe THIS one).
2023-02-10 13:53:03 -08:00
accounts_keep_slots : HashMap < Slot , AliveAccounts < ' a > > ,
2023-02-09 11:06:17 -08:00
/// all the rest of alive accounts that can move slots and should be combined
2023-02-11 12:13:03 -08:00
/// This includes all accounts with ref_count = 1 from the slots in 'accounts_keep_slots'.
/// There is one entry here for each storage we are processing. Even if all accounts are in 'accounts_keep_slots'.
2023-02-09 11:06:17 -08:00
accounts_to_combine : Vec < ShrinkCollect < ' a , ShrinkCollectAliveSeparatedByRefs < ' a > > > ,
/// slots that contain alive accounts that can move into ANY other ancient slot
/// these slots will NOT be in 'accounts_keep_slots'
/// Some of these slots will have ancient append vecs created at them to contain everything in 'accounts_to_combine'
/// The rest will become dead slots with no accounts in them.
2023-02-10 12:08:13 -08:00
target_slots_sorted : Vec < Slot > ,
2023-02-03 10:53:49 -08:00
}
2023-02-09 14:47:22 -08:00
#[ derive(Default) ]
/// intended contents of a packed ancient storage
struct PackedAncientStorage < ' a > {
/// accounts to move into this storage, along with the slot the accounts are currently stored in
accounts : Vec < ( Slot , & ' a [ & ' a StoredAccountMeta < ' a > ] ) > ,
/// total bytes required to hold 'accounts'
bytes : u64 ,
}
2023-02-10 12:06:36 -08:00
impl < ' a > PackedAncientStorage < ' a > {
/// return a minimal set of 'PackedAncientStorage's to contain all 'accounts_to_combine' with
/// the new storages having a size guided by 'ideal_size'
fn pack (
mut accounts_to_combine : impl Iterator < Item = & ' a AliveAccounts < ' a > > ,
ideal_size : NonZeroU64 ,
) -> Vec < PackedAncientStorage < ' a > > {
let mut result = Vec ::default ( ) ;
let ideal_size : u64 = ideal_size . into ( ) ;
let ideal_size = ideal_size as usize ;
let mut current_alive_accounts = accounts_to_combine . next ( ) ;
// starting at first entry in current_alive_accounts
let mut partial_inner_index = 0 ;
// 0 bytes written so far from the current set of accounts
let mut partial_bytes_written = 0 ;
// pack a new storage each iteration of this outer loop
loop {
let mut bytes_total = 0 usize ;
let mut accounts_to_write = Vec ::default ( ) ;
// walk through each set of alive accounts to pack the current new storage up to ideal_size
let mut full = false ;
while ! full & & current_alive_accounts . is_some ( ) {
let alive_accounts = current_alive_accounts . unwrap ( ) ;
if partial_inner_index > = alive_accounts . accounts . len ( ) {
// current_alive_accounts have all been written, so advance to next set from accounts_to_combine
current_alive_accounts = accounts_to_combine . next ( ) ;
// reset partial progress since we're starting over with a new set of alive accounts
partial_inner_index = 0 ;
partial_bytes_written = 0 ;
continue ;
}
let bytes_remaining_this_slot =
alive_accounts . bytes . saturating_sub ( partial_bytes_written ) ;
let bytes_total_with_this_slot =
bytes_total . saturating_add ( bytes_remaining_this_slot ) ;
let mut partial_inner_index_max_exclusive ;
if bytes_total_with_this_slot < = ideal_size {
partial_inner_index_max_exclusive = alive_accounts . accounts . len ( ) ;
bytes_total = bytes_total_with_this_slot ;
} else {
partial_inner_index_max_exclusive = partial_inner_index ;
// adding all the alive accounts in this storage would exceed the ideal size, so we have to break these accounts up
// look at each account and stop when we exceed the ideal size
while partial_inner_index_max_exclusive < alive_accounts . accounts . len ( ) {
let account = alive_accounts . accounts [ partial_inner_index_max_exclusive ] ;
let account_size = aligned_stored_size ( account . data ( ) . len ( ) ) ;
let new_size = bytes_total . saturating_add ( account_size ) ;
if new_size > ideal_size & & bytes_total > 0 {
full = true ;
// partial_inner_index_max_exclusive is the index of the first account that puts us over the ideal size
// so, save it for next time
break ;
}
// this account fits
saturating_add_assign! ( partial_bytes_written , account_size ) ;
bytes_total = new_size ;
partial_inner_index_max_exclusive + = 1 ;
}
}
if partial_inner_index < partial_inner_index_max_exclusive {
// these accounts belong in the current packed storage we're working on
accounts_to_write . push ( (
alive_accounts . slot ,
// maybe all alive accounts from the current or could be partial
& alive_accounts . accounts
[ partial_inner_index .. partial_inner_index_max_exclusive ] ,
) ) ;
}
// start next storage with the account we ended with
// this could be the end of the current alive accounts or could be anywhere within that vec
partial_inner_index = partial_inner_index_max_exclusive ;
}
if accounts_to_write . is_empty ( ) {
// if we returned without any accounts to write, then we have exhausted source data and have packaged all the storages we need
break ;
}
// we know the full contents of this packed storage now
result . push ( PackedAncientStorage {
bytes : bytes_total as u64 ,
accounts : accounts_to_write ,
} ) ;
}
result
}
}
2022-05-10 14:41:04 -07:00
/// a set of accounts need to be stored.
/// If there are too many to fit in 'Primary', the rest are put in 'Overflow'
2022-05-22 18:00:42 -07:00
#[ derive(Copy, Clone, Debug, PartialEq, Eq) ]
2022-05-10 14:41:04 -07:00
pub enum StorageSelector {
Primary ,
Overflow ,
}
/// reference a set of accounts to store
/// The accounts may have to be split between 2 storages (primary and overflow) if there is not enough room in the primary storage.
/// The 'store' functions need data stored in a slice of specific type.
/// We need 1-2 of these slices constructed based on available bytes and individual account sizes.
2023-02-14 08:03:16 -08:00
/// The slice arithmetic across both hashes and account data gets messy. So, this struct abstracts that.
2022-05-10 14:41:04 -07:00
pub struct AccountsToStore < ' a > {
2023-01-10 13:57:34 -08:00
accounts : & ' a [ & ' a StoredAccountMeta < ' a > ] ,
2022-05-10 14:41:04 -07:00
/// if 'accounts' contains more items than can be contained in the primary storage, then we have to split these accounts.
/// 'index_first_item_overflow' specifies the index of the first item in 'accounts' that will go into the overflow storage
index_first_item_overflow : usize ,
2022-11-18 10:15:41 -08:00
pub slot : Slot ,
2022-05-10 14:41:04 -07:00
}
impl < ' a > AccountsToStore < ' a > {
/// break 'stored_accounts' into primary and overflow
/// available_bytes: how many bytes remain in the primary storage. Excess accounts will be directed to an overflow storage
pub fn new (
mut available_bytes : u64 ,
2023-01-10 13:57:34 -08:00
accounts : & ' a [ & ' a StoredAccountMeta < ' a > ] ,
2022-11-30 15:46:52 -08:00
alive_total_bytes : usize ,
2022-05-10 14:41:04 -07:00
slot : Slot ,
) -> Self {
2022-11-30 10:09:23 -08:00
let num_accounts = accounts . len ( ) ;
2022-05-10 14:41:04 -07:00
// index of the first account that doesn't fit in the current append vec
let mut index_first_item_overflow = num_accounts ; // assume all fit
2022-11-30 15:46:52 -08:00
if alive_total_bytes > available_bytes as usize {
// not all the alive bytes fit, so we have to find how many accounts fit within available_bytes
for ( i , account ) in accounts . iter ( ) . enumerate ( ) {
2023-02-27 10:03:22 -08:00
let account_size = account . stored_size ( ) as u64 ;
2022-11-30 15:46:52 -08:00
if available_bytes > = account_size {
available_bytes = available_bytes . saturating_sub ( account_size ) ;
} else if index_first_item_overflow = = num_accounts {
// the # of accounts we have so far seen is the most that will fit in the current ancient append vec
index_first_item_overflow = i ;
break ;
}
2022-05-10 14:41:04 -07:00
}
2022-11-30 10:09:23 -08:00
}
2022-05-10 14:41:04 -07:00
Self {
accounts ,
index_first_item_overflow ,
2022-11-18 10:15:41 -08:00
slot ,
2022-05-10 14:41:04 -07:00
}
}
2022-05-12 08:14:27 -07:00
/// true if a request to 'get' 'Overflow' would return accounts & hashes
pub fn has_overflow ( & self ) -> bool {
self . index_first_item_overflow < self . accounts . len ( )
}
2022-11-22 15:36:57 -08:00
/// get the accounts to store in the given 'storage'
2023-01-10 13:57:34 -08:00
pub fn get ( & self , storage : StorageSelector ) -> & [ & ' a StoredAccountMeta < ' a > ] {
2022-05-10 14:41:04 -07:00
let range = match storage {
StorageSelector ::Primary = > 0 .. self . index_first_item_overflow ,
StorageSelector ::Overflow = > self . index_first_item_overflow .. self . accounts . len ( ) ,
} ;
2022-11-22 15:36:57 -08:00
& self . accounts [ range ]
2022-05-10 14:41:04 -07:00
}
}
2022-05-11 06:02:05 -07:00
/// capacity of an ancient append vec
pub fn get_ancient_append_vec_capacity ( ) -> u64 {
use crate ::append_vec ::MAXIMUM_APPEND_VEC_FILE_SIZE ;
// smaller than max by a bit just in case
// some functions add slop on allocation
2023-02-01 07:22:34 -08:00
// The bigger an append vec is, the more unwieldy it becomes to shrink, create, write.
// 1/10 of max is a reasonable size in practice.
2022-07-19 06:51:36 -07:00
MAXIMUM_APPEND_VEC_FILE_SIZE / 10 - 2048
2022-05-11 06:02:05 -07:00
}
/// is this a max-size append vec designed to be used as an ancient append vec?
2023-02-15 17:24:04 -08:00
pub fn is_ancient ( storage : & AccountsFile ) -> bool {
match storage {
AccountsFile ::AppendVec ( storage ) = > storage . capacity ( ) > = get_ancient_append_vec_capacity ( ) ,
}
2022-05-11 06:02:05 -07:00
}
2022-05-10 14:41:04 -07:00
#[ cfg(test) ]
pub mod tests {
use {
super ::* ,
crate ::{
2023-04-17 13:52:26 -07:00
account_info ::AccountInfo ,
2023-02-22 16:10:34 -08:00
account_storage ::meta ::{ AccountMeta , StoredAccountMeta , StoredMeta } ,
2023-02-03 10:53:49 -08:00
accounts_db ::{
get_temp_accounts_paths ,
tests ::{
2023-02-09 11:06:17 -08:00
append_single_account_with_default_hash , compare_all_accounts ,
create_db_with_storages_and_index , create_storages_and_update_index ,
2023-02-14 06:53:31 -08:00
get_all_accounts , remove_account_for_tests , CAN_RANDOMLY_SHRINK_FALSE ,
2023-02-03 10:53:49 -08:00
} ,
2023-06-09 10:38:34 -07:00
INCLUDE_SLOT_IN_HASH_TESTS , MAX_RECYCLE_STORES ,
2023-02-03 10:53:49 -08:00
} ,
2023-04-17 13:52:26 -07:00
accounts_index ::UpsertReclaim ,
2023-03-09 15:52:51 -08:00
append_vec ::{ aligned_stored_size , AppendVec , AppendVecStoredAccountMeta } ,
2023-02-08 09:40:17 -08:00
storable_accounts ::StorableAccountsBySlot ,
2022-05-10 14:41:04 -07:00
} ,
2022-11-01 07:45:52 -07:00
solana_sdk ::{
2023-02-10 09:39:30 -08:00
account ::{ AccountSharedData , ReadableAccount , WritableAccount } ,
2022-11-22 15:36:57 -08:00
hash ::Hash ,
2022-11-01 07:45:52 -07:00
pubkey ::Pubkey ,
} ,
2023-02-07 18:22:54 -08:00
std ::ops ::Range ,
2023-02-07 10:35:40 -08:00
strum ::IntoEnumIterator ,
strum_macros ::EnumIter ,
2022-05-10 14:41:04 -07:00
} ;
2023-02-07 18:22:54 -08:00
fn get_sample_storages (
slots : usize ,
2023-02-09 11:41:36 -08:00
account_data_size : Option < u64 > ,
2023-02-07 18:22:54 -08:00
) -> (
AccountsDb ,
Vec < Arc < AccountStorageEntry > > ,
Range < u64 > ,
Vec < SlotInfo > ,
) {
let alive = true ;
2023-02-09 11:41:36 -08:00
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , slots , account_data_size ) ;
2023-02-07 18:22:54 -08:00
let original_stores = ( 0 .. slots )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( ( slot as Slot ) + slot1 ) )
. collect ::< Vec < _ > > ( ) ;
let slot_infos = original_stores
. iter ( )
. map ( | storage | SlotInfo {
storage : Arc ::clone ( storage ) ,
slot : storage . slot ( ) ,
capacity : 0 ,
alive_bytes : 0 ,
should_shrink : false ,
} )
. collect ( ) ;
(
db ,
original_stores ,
slot1 .. ( slot1 + slots as Slot ) ,
slot_infos ,
)
}
fn unique_to_accounts < ' a > (
one : impl Iterator < Item = & ' a GetUniqueAccountsResult < ' a > > ,
) -> Vec < ( Pubkey , AccountSharedData ) > {
one . flat_map ( | result | {
result
. stored_accounts
. iter ( )
. map ( | result | ( * result . pubkey ( ) , result . to_account_shared_data ( ) ) )
} )
. collect ( )
}
pub ( crate ) fn compare_all_vec_accounts < ' a > (
one : impl Iterator < Item = & ' a GetUniqueAccountsResult < ' a > > ,
two : impl Iterator < Item = & ' a GetUniqueAccountsResult < ' a > > ,
) {
compare_all_accounts ( & unique_to_accounts ( one ) , & unique_to_accounts ( two ) ) ;
}
2023-02-13 07:25:47 -08:00
#[ test ]
fn test_write_packed_storages_empty ( ) {
let ( db , _storages , _slots , _infos ) = get_sample_storages ( 0 , None ) ;
let write_ancient_accounts =
db . write_packed_storages ( & AccountsToCombine ::default ( ) , Vec ::default ( ) ) ;
assert! ( write_ancient_accounts . shrinks_in_progress . is_empty ( ) ) ;
}
#[ test ]
#[ should_panic(
expected = " assertion failed: accounts_to_combine.target_slots_sorted.len() >= packed_contents.len() "
) ]
fn test_write_packed_storages_too_few_slots ( ) {
let ( db , storages , slots , _infos ) = get_sample_storages ( 1 , None ) ;
let accounts_to_combine = AccountsToCombine ::default ( ) ;
let accounts = [ storages
. first ( )
. unwrap ( )
. accounts
. accounts ( 0 )
. pop ( )
. unwrap ( ) ] ;
let accounts = accounts . iter ( ) . collect ::< Vec < _ > > ( ) ;
let packed_contents = vec! [ PackedAncientStorage {
bytes : 0 ,
accounts : vec ! [ ( slots . start , & accounts [ .. ] ) ] ,
} ] ;
db . write_packed_storages ( & accounts_to_combine , packed_contents ) ;
}
2023-02-10 09:39:30 -08:00
#[ test ]
fn test_write_ancient_accounts_to_same_slot_multiple_refs_empty ( ) {
let ( db , _storages , _slots , _infos ) = get_sample_storages ( 0 , None ) ;
let mut write_ancient_accounts = WriteAncientAccounts ::default ( ) ;
db . write_ancient_accounts_to_same_slot_multiple_refs (
2023-02-10 13:53:03 -08:00
AccountsToCombine ::default ( ) . accounts_keep_slots . values ( ) ,
2023-02-10 09:39:30 -08:00
& mut write_ancient_accounts ,
) ;
assert! ( write_ancient_accounts . shrinks_in_progress . is_empty ( ) ) ;
}
2023-02-10 12:06:36 -08:00
#[ test ]
fn test_pack_ancient_storages_one_account_per_storage ( ) {
for num_slots in 0 .. 4 {
for ( ideal_size , expected_storages ) in [
( 1 , num_slots ) ,
( get_ancient_append_vec_capacity ( ) , 1. min ( num_slots ) ) ,
] {
let ( db , storages , slots , _infos ) = get_sample_storages ( num_slots , None ) ;
let original_results = storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let slots_vec = slots . collect ::< Vec < _ > > ( ) ;
let accounts_to_combine = original_results
. iter ( )
. zip ( slots_vec . iter ( ) . cloned ( ) )
. map ( | ( accounts , slot ) | AliveAccounts {
accounts : accounts . stored_accounts . iter ( ) . collect ::< Vec < _ > > ( ) ,
bytes : accounts
. stored_accounts
. iter ( )
. map ( | account | aligned_stored_size ( account . data ( ) . len ( ) ) )
. sum ( ) ,
slot ,
} )
. collect ::< Vec < _ > > ( ) ;
let result = PackedAncientStorage ::pack (
accounts_to_combine . iter ( ) ,
NonZeroU64 ::new ( ideal_size ) . unwrap ( ) ,
) ;
let storages_needed = result . len ( ) ;
assert_eq! ( storages_needed , expected_storages ) ;
}
}
}
#[ test ]
fn test_pack_ancient_storages_one_partial ( ) {
// n slots
// m accounts per slot
// divide into different ideal sizes so that we combine multiple slots sometimes and combine partial slots
solana_logger ::setup ( ) ;
let total_accounts_per_storage = 10 ;
let account_size = 184 ;
for num_slots in 0 .. 4 {
for ( ideal_size , expected_storages ) in [
( 1 , num_slots * total_accounts_per_storage ) ,
( account_size - 1 , num_slots * total_accounts_per_storage ) ,
( account_size , num_slots * total_accounts_per_storage ) ,
( account_size + 1 , num_slots * total_accounts_per_storage ) ,
( account_size * 2 - 1 , num_slots * total_accounts_per_storage ) ,
( account_size * 2 , num_slots * total_accounts_per_storage / 2 ) ,
( get_ancient_append_vec_capacity ( ) , 1. min ( num_slots ) ) ,
] {
let ( db , storages , slots , _infos ) = get_sample_storages ( num_slots , None ) ;
let account_template = storages
. first ( )
. map ( | storage | {
storage
. accounts
. account_iter ( )
. next ( )
. unwrap ( )
. to_account_shared_data ( )
} )
. unwrap_or_default ( ) ;
// add some accounts to each storage so we can make partial progress
let mut lamports = 1000 ;
let _pubkeys_and_accounts = storages
. iter ( )
. map ( | storage | {
( 0 .. ( total_accounts_per_storage - 1 ) )
. map ( | _ | {
let pk = solana_sdk ::pubkey ::new_rand ( ) ;
let mut account = account_template . clone ( ) ;
account . set_lamports ( lamports ) ;
lamports + = 1 ;
append_single_account_with_default_hash (
storage ,
& pk ,
& account ,
0 ,
true ,
Some ( & db . accounts_index ) ,
) ;
( pk , account )
} )
. collect ::< Vec < _ > > ( )
} )
. collect ::< Vec < _ > > ( ) ;
let original_results = storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let slots_vec = slots . collect ::< Vec < _ > > ( ) ;
let accounts_to_combine = original_results
. iter ( )
. zip ( slots_vec . iter ( ) . cloned ( ) )
. map ( | ( accounts , slot ) | AliveAccounts {
accounts : accounts . stored_accounts . iter ( ) . collect ::< Vec < _ > > ( ) ,
bytes : accounts
. stored_accounts
. iter ( )
. map ( | account | aligned_stored_size ( account . data ( ) . len ( ) ) )
. sum ( ) ,
slot ,
} )
. collect ::< Vec < _ > > ( ) ;
let result = PackedAncientStorage ::pack (
accounts_to_combine . iter ( ) ,
NonZeroU64 ::new ( ideal_size ) . unwrap ( ) ,
) ;
let storages_needed = result . len ( ) ;
assert_eq! ( storages_needed , expected_storages , " num_slots: {num_slots}, expected_storages: {expected_storages}, storages_needed: {storages_needed}, ideal_size: {ideal_size} " ) ;
compare_all_accounts (
& packed_to_compare ( & result ) [ .. ] ,
& unique_to_compare ( & original_results ) [ .. ] ,
) ;
}
}
}
fn packed_to_compare ( packed : & [ PackedAncientStorage ] ) -> Vec < ( Pubkey , AccountSharedData ) > {
packed
. iter ( )
. flat_map ( | packed | {
packed . accounts . iter ( ) . flat_map ( | ( _slot , stored_metas ) | {
stored_metas . iter ( ) . map ( | stored_meta | {
( * stored_meta . pubkey ( ) , stored_meta . to_account_shared_data ( ) )
} )
} )
} )
. collect ::< Vec < _ > > ( )
}
#[ test ]
fn test_pack_ancient_storages_varying ( ) {
// n slots
// different number of accounts in each slot
// each account has different size
// divide into different ideal sizes so that we combine multiple slots sometimes and combine partial slots
// compare at end that all accounts are in result exactly once
solana_logger ::setup ( ) ;
let total_accounts_per_storage = 10 ;
let account_size = 184 ;
for num_slots in 0 .. 4 {
for ideal_size in [
1 ,
account_size - 1 ,
account_size ,
account_size + 1 ,
account_size * 2 - 1 ,
account_size * 2 ,
get_ancient_append_vec_capacity ( ) ,
] {
let ( db , storages , slots , _infos ) = get_sample_storages ( num_slots , None ) ;
let account_template = storages
. first ( )
. map ( | storage | {
storage
. accounts
. account_iter ( )
. next ( )
. unwrap ( )
. to_account_shared_data ( )
} )
. unwrap_or_default ( ) ;
// add some accounts to each storage so we can make partial progress
let mut data_size = 450 ;
// random # of extra accounts here
let total_accounts_per_storage =
thread_rng ( ) . gen_range ( 0 , total_accounts_per_storage ) ;
let _pubkeys_and_accounts = storages
. iter ( )
. map ( | storage | {
( 0 .. ( total_accounts_per_storage - 1 ) )
. map ( | _ | {
let pk = solana_sdk ::pubkey ::new_rand ( ) ;
let mut account = account_template . clone ( ) ;
account . set_data ( ( 0 .. data_size ) . map ( | x | ( x % 256 ) as u8 ) . collect ( ) ) ;
data_size + = 1 ;
append_single_account_with_default_hash (
storage ,
& pk ,
& account ,
0 ,
true ,
Some ( & db . accounts_index ) ,
) ;
( pk , account )
} )
. collect ::< Vec < _ > > ( )
} )
. collect ::< Vec < _ > > ( ) ;
let original_results = storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let slots_vec = slots . collect ::< Vec < _ > > ( ) ;
let accounts_to_combine = original_results
. iter ( )
. zip ( slots_vec . iter ( ) . cloned ( ) )
. map ( | ( accounts , slot ) | AliveAccounts {
accounts : accounts . stored_accounts . iter ( ) . collect ::< Vec < _ > > ( ) ,
bytes : accounts
. stored_accounts
. iter ( )
. map ( | account | aligned_stored_size ( account . data ( ) . len ( ) ) )
. sum ( ) ,
slot ,
} )
. collect ::< Vec < _ > > ( ) ;
let result = PackedAncientStorage ::pack (
accounts_to_combine . iter ( ) ,
NonZeroU64 ::new ( ideal_size ) . unwrap ( ) ,
) ;
let largest_account_size = aligned_stored_size ( data_size ) as u64 ;
// all packed storages should be close to ideal size
result . iter ( ) . enumerate ( ) . for_each ( | ( i , packed ) | {
if i + 1 < result . len ( ) & & ideal_size > largest_account_size {
// cannot assert this on the last packed storage - it may be small
// cannot assert this when the ideal size is too small to hold the largest account size
assert! (
packed . bytes > = ideal_size - largest_account_size ,
" packed size too small: bytes: {}, ideal: {}, largest: {} " ,
packed . bytes ,
ideal_size ,
largest_account_size
) ;
}
assert! (
packed . bytes > 0 ,
" packed size of zero "
) ;
assert! (
packed . bytes < = ideal_size | | packed . accounts . iter ( ) . map ( | ( _slot , accounts ) | accounts . len ( ) ) . sum ::< usize > ( ) = = 1 ,
" packed size too large: bytes: {}, ideal_size: {}, data_size: {}, num_slots: {}, # accounts: {} " ,
packed . bytes ,
ideal_size ,
data_size ,
num_slots ,
packed . accounts . len ( )
) ;
} ) ;
result . iter ( ) . for_each ( | packed | {
assert_eq! (
packed . bytes ,
packed
. accounts
. iter ( )
. map ( | ( _slot , accounts ) | accounts
. iter ( )
. map ( | account | aligned_stored_size ( account . data ( ) . len ( ) ) as u64 )
. sum ::< u64 > ( ) )
. sum ::< u64 > ( )
) ;
} ) ;
compare_all_accounts (
& packed_to_compare ( & result ) [ .. ] ,
& unique_to_compare ( & original_results ) [ .. ] ,
) ;
}
}
}
fn unique_to_compare ( unique : & [ GetUniqueAccountsResult ] ) -> Vec < ( Pubkey , AccountSharedData ) > {
unique
. iter ( )
. flat_map ( | unique | {
unique . stored_accounts . iter ( ) . map ( | stored_meta | {
( * stored_meta . pubkey ( ) , stored_meta . to_account_shared_data ( ) )
} )
} )
. collect ::< Vec < _ > > ( )
}
2023-02-13 07:25:47 -08:00
#[ derive(EnumIter, Debug, PartialEq, Eq) ]
enum TestWriteMultipleRefs {
MultipleRefs ,
PackedStorages ,
}
2023-02-13 09:10:13 -08:00
#[ test ]
2023-02-14 06:53:31 -08:00
fn test_finish_combine_ancient_slots_packed_internal ( ) {
2023-02-13 09:10:13 -08:00
// n storages
// 1 account each
// all accounts have 1 ref
// nothing shrunk, so all storages and roots should be removed
// or all slots shrunk so no roots or storages should be removed
2023-06-09 09:28:49 -07:00
for in_shrink_candidate_slots in [ false , true ] {
for all_slots_shrunk in [ false , true ] {
for num_slots in 0 .. 3 {
let ( db , storages , slots , infos ) = get_sample_storages ( num_slots , None ) ;
let accounts_per_storage = infos
. iter ( )
. zip (
storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) ) ,
)
. collect ::< Vec < _ > > ( ) ;
2023-02-13 09:10:13 -08:00
2023-06-09 09:28:49 -07:00
let accounts_to_combine = db . calc_accounts_to_combine ( & accounts_per_storage ) ;
let mut stats = ShrinkStatsSub ::default ( ) ;
let mut write_ancient_accounts = WriteAncientAccounts ::default ( ) ;
2023-02-13 09:10:13 -08:00
slots . clone ( ) . for_each ( | slot | {
2023-06-09 09:28:49 -07:00
db . add_root ( slot ) ;
let storage = db . storage . get_slot_storage_entry ( slot ) ;
assert! ( storage . is_some ( ) ) ;
if in_shrink_candidate_slots {
db . shrink_candidate_slots
. lock ( )
. unwrap ( )
. insert ( slot , storage . unwrap ( ) ) ;
}
2023-02-13 09:10:13 -08:00
} ) ;
2023-06-09 09:28:49 -07:00
let roots = db
. accounts_index
. roots_tracker
. read ( )
. unwrap ( )
. alive_roots
. get_all ( ) ;
assert_eq! ( roots , slots . clone ( ) . collect ::< Vec < _ > > ( ) ) ;
2023-02-13 09:10:13 -08:00
if all_slots_shrunk {
2023-06-09 09:28:49 -07:00
// make it look like each of the slots was shrunk
slots . clone ( ) . for_each ( | slot | {
write_ancient_accounts
. shrinks_in_progress
. insert ( slot , db . get_store_for_shrink ( slot , 1 ) ) ;
} ) ;
2023-02-13 09:10:13 -08:00
}
2023-06-09 09:28:49 -07:00
db . finish_combine_ancient_slots_packed_internal (
accounts_to_combine ,
write_ancient_accounts ,
& mut stats ,
) ;
slots . clone ( ) . for_each ( | slot | {
assert! ( ! db
. shrink_candidate_slots
. lock ( )
. unwrap ( )
. contains_key ( & slot ) ) ;
} ) ;
let roots_after = db
. accounts_index
. roots_tracker
. read ( )
. unwrap ( )
. alive_roots
. get_all ( ) ;
assert_eq! (
roots_after ,
if all_slots_shrunk {
slots . clone ( ) . collect ::< Vec < _ > > ( )
} else {
vec! [ ]
} ,
" all_slots_shrunk: {all_slots_shrunk} "
) ;
slots . for_each ( | slot | {
let storage = db . storage . get_slot_storage_entry ( slot ) ;
if all_slots_shrunk {
assert! ( storage . is_some ( ) ) ;
} else {
assert! ( storage . is_none ( ) ) ;
}
} ) ;
}
2023-02-13 09:10:13 -08:00
}
}
}
2023-02-09 11:06:17 -08:00
#[ test ]
fn test_calc_accounts_to_combine_simple ( ) {
// n storages
// 1 account each
// all accounts have 1 ref or all accounts have 2 refs
2023-04-24 07:40:05 -07:00
for add_dead_account in [ true , false ] {
for method in TestWriteMultipleRefs ::iter ( ) {
for num_slots in 0 .. 3 {
for two_refs in [ false , true ] {
let ( db , storages , slots , infos ) = get_sample_storages ( num_slots , None ) ;
let original_results = storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
2023-02-13 07:25:47 -08:00
. collect ::< Vec < _ > > ( ) ;
2023-04-24 07:40:05 -07:00
if two_refs {
original_results . iter ( ) . for_each ( | results | {
results . stored_accounts . iter ( ) . for_each ( | account | {
let entry = db
. accounts_index
. get_account_read_entry ( account . pubkey ( ) )
. unwrap ( ) ;
entry . addref ( ) ;
} )
} ) ;
2023-02-13 07:25:47 -08:00
}
2023-04-24 07:40:05 -07:00
if add_dead_account {
storages . iter ( ) . for_each ( | storage | {
let pk = solana_sdk ::pubkey ::new_rand ( ) ;
let alive = false ;
let write_version = 0 ;
append_single_account_with_default_hash (
storage ,
& pk ,
& AccountSharedData ::default ( ) ,
write_version ,
alive ,
Some ( & db . accounts_index ) ,
) ;
assert! ( db . accounts_index . purge_exact (
& pk ,
& [ storage . slot ( ) ]
. into_iter ( )
. collect ::< std ::collections ::HashSet < Slot > > ( ) ,
& mut Vec ::default ( )
) ) ;
} ) ;
2023-02-13 07:25:47 -08:00
}
2023-04-24 07:40:05 -07:00
let original_results = storages
2023-02-10 09:39:30 -08:00
. iter ( )
2023-04-24 07:40:05 -07:00
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
2023-02-13 07:25:47 -08:00
. collect ::< Vec < _ > > ( ) ;
2023-04-24 07:40:05 -07:00
let accounts_per_storage = infos
. iter ( )
. zip ( original_results . into_iter ( ) )
. collect ::< Vec < _ > > ( ) ;
let accounts_to_combine =
db . calc_accounts_to_combine ( & accounts_per_storage ) ;
let slots_vec = slots . collect ::< Vec < _ > > ( ) ;
if ! add_dead_account & & two_refs {
assert! ( accounts_to_combine . accounts_to_combine . is_empty ( ) ) ;
continue ;
} else {
assert_eq! (
accounts_to_combine . accounts_to_combine . len ( ) ,
num_slots ,
" method: {method:?}, num_slots: {num_slots}, two_refs: {two_refs} "
) ;
}
if two_refs {
// all accounts should be in many_refs
let mut accounts_keep = accounts_to_combine
. accounts_keep_slots
. keys ( )
. cloned ( )
. collect ::< Vec < _ > > ( ) ;
assert! ( ! accounts_to_combine
. accounts_to_combine
2023-02-13 07:25:47 -08:00
. iter ( )
2023-04-24 07:40:05 -07:00
. any ( | a | a . unrefed_pubkeys . is_empty ( ) ) ) ;
accounts_keep . sort_unstable ( ) ;
assert_eq! ( accounts_keep , slots_vec ) ;
assert! ( accounts_to_combine . target_slots_sorted . is_empty ( ) ) ;
assert_eq! ( accounts_to_combine . accounts_keep_slots . len ( ) , num_slots ) ;
assert! ( accounts_to_combine . accounts_to_combine . iter ( ) . all (
| shrink_collect | shrink_collect
. alive_accounts
. one_ref
. accounts
. is_empty ( )
) ) ;
assert! ( accounts_to_combine . accounts_to_combine . iter ( ) . all (
| shrink_collect | shrink_collect
. alive_accounts
. many_refs
. accounts
. is_empty ( )
) ) ;
} else {
if add_dead_account {
assert! ( ! accounts_to_combine
. accounts_to_combine
. iter ( )
. any ( | a | a . unrefed_pubkeys . is_empty ( ) ) ) ;
}
// all accounts should be in one_ref and all slots are available as target slots
assert_eq! ( accounts_to_combine . target_slots_sorted , slots_vec ) ;
assert! ( accounts_to_combine . accounts_keep_slots . is_empty ( ) ) ;
assert! ( accounts_to_combine . accounts_to_combine . iter ( ) . all (
| shrink_collect | ! shrink_collect
. alive_accounts
. one_ref
. accounts
. is_empty ( )
) ) ;
assert! ( accounts_to_combine . accounts_to_combine . iter ( ) . all (
| shrink_collect | shrink_collect
. alive_accounts
. many_refs
. accounts
. is_empty ( )
) ) ;
}
// test write_ancient_accounts_to_same_slot_multiple_refs since we built interesting 'AccountsToCombine'
let write_ancient_accounts = match method {
TestWriteMultipleRefs ::MultipleRefs = > {
let mut write_ancient_accounts = WriteAncientAccounts ::default ( ) ;
db . write_ancient_accounts_to_same_slot_multiple_refs (
accounts_to_combine . accounts_keep_slots . values ( ) ,
& mut write_ancient_accounts ,
) ;
write_ancient_accounts
}
TestWriteMultipleRefs ::PackedStorages = > {
let packed_contents = Vec ::default ( ) ;
db . write_packed_storages ( & accounts_to_combine , packed_contents )
}
} ;
if two_refs {
assert_eq! ( write_ancient_accounts . shrinks_in_progress . len ( ) , num_slots ) ;
let mut shrinks_in_progress = write_ancient_accounts
. shrinks_in_progress
2023-02-13 07:25:47 -08:00
. iter ( )
2023-04-24 07:40:05 -07:00
. collect ::< Vec < _ > > ( ) ;
shrinks_in_progress . sort_unstable_by ( | a , b | a . 0. cmp ( b . 0 ) ) ;
assert_eq! (
shrinks_in_progress
. iter ( )
. map ( | ( slot , _ ) | * * slot )
. collect ::< Vec < _ > > ( ) ,
slots_vec
) ;
assert_eq! (
shrinks_in_progress
. iter ( )
. map ( | ( _ , shrink_in_progress ) | shrink_in_progress
. old_storage ( )
. append_vec_id ( ) )
. collect ::< Vec < _ > > ( ) ,
storages
. iter ( )
. map ( | storage | storage . append_vec_id ( ) )
. collect ::< Vec < _ > > ( )
) ;
} else {
assert! ( write_ancient_accounts . shrinks_in_progress . is_empty ( ) ) ;
}
2023-02-13 07:25:47 -08:00
}
2023-02-10 09:39:30 -08:00
}
2023-02-09 11:06:17 -08:00
}
}
}
#[ test ]
fn test_calc_accounts_to_combine_opposite ( ) {
// 1 storage
// 2 accounts
// 1 with 1 ref
// 1 with 2 refs
2023-02-13 07:25:47 -08:00
for method in TestWriteMultipleRefs ::iter ( ) {
let num_slots = 1 ;
let ( db , storages , slots , infos ) = get_sample_storages ( num_slots , None ) ;
let original_results = storages
2023-02-09 11:06:17 -08:00
. iter ( )
2023-02-13 07:25:47 -08:00
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let storage = storages . first ( ) . unwrap ( ) . clone ( ) ;
let pk_with_1_ref = solana_sdk ::pubkey ::new_rand ( ) ;
let slot1 = slots . start ;
let account_with_2_refs = original_results
. first ( )
. unwrap ( )
. stored_accounts
. first ( )
. unwrap ( ) ;
let pk_with_2_refs = account_with_2_refs . pubkey ( ) ;
let mut account_with_1_ref = account_with_2_refs . to_account_shared_data ( ) ;
_ = account_with_1_ref . checked_add_lamports ( 1 ) ;
append_single_account_with_default_hash (
& storage ,
& pk_with_1_ref ,
& account_with_1_ref ,
0 ,
true ,
Some ( & db . accounts_index ) ,
) ;
original_results . iter ( ) . for_each ( | results | {
results . stored_accounts . iter ( ) . for_each ( | account | {
let entry = db
. accounts_index
. get_account_read_entry ( account . pubkey ( ) )
. unwrap ( ) ;
entry . addref ( ) ;
} )
} ) ;
2023-02-10 09:39:30 -08:00
2023-02-13 07:25:47 -08:00
// update to get both accounts in the storage
let original_results = storages
2023-02-10 09:39:30 -08:00
. iter ( )
2023-02-13 07:25:47 -08:00
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
assert_eq! ( original_results . first ( ) . unwrap ( ) . stored_accounts . len ( ) , 2 ) ;
let accounts_per_storage = infos
2023-02-10 09:39:30 -08:00
. iter ( )
2023-02-13 07:25:47 -08:00
. zip ( original_results . into_iter ( ) )
. collect ::< Vec < _ > > ( ) ;
let accounts_to_combine = db . calc_accounts_to_combine ( & accounts_per_storage ) ;
let slots_vec = slots . collect ::< Vec < _ > > ( ) ;
assert_eq! ( accounts_to_combine . accounts_to_combine . len ( ) , num_slots ) ;
// all accounts should be in many_refs
let mut accounts_keep = accounts_to_combine
. accounts_keep_slots
. keys ( )
. cloned ( )
. collect ::< Vec < _ > > ( ) ;
accounts_keep . sort_unstable ( ) ;
assert_eq! ( accounts_keep , slots_vec ) ;
assert! ( accounts_to_combine . target_slots_sorted . is_empty ( ) ) ;
assert_eq! ( accounts_to_combine . accounts_keep_slots . len ( ) , num_slots ) ;
assert_eq! (
accounts_to_combine
. accounts_keep_slots
. get ( & slot1 )
. unwrap ( )
. accounts
. iter ( )
. map ( | meta | meta . pubkey ( ) )
. collect ::< Vec < _ > > ( ) ,
vec! [ pk_with_2_refs ]
) ;
assert_eq! ( accounts_to_combine . accounts_to_combine . len ( ) , 1 ) ;
let one_ref_accounts = & accounts_to_combine
. accounts_to_combine
2023-02-10 09:39:30 -08:00
. first ( )
. unwrap ( )
2023-02-13 07:25:47 -08:00
. alive_accounts
. one_ref
. accounts ;
assert_eq! (
one_ref_accounts
. iter ( )
. map ( | meta | meta . pubkey ( ) )
. collect ::< Vec < _ > > ( ) ,
vec! [ & pk_with_1_ref ]
) ;
assert_eq! (
one_ref_accounts
. iter ( )
. map ( | meta | meta . to_account_shared_data ( ) )
. collect ::< Vec < _ > > ( ) ,
vec! [ account_with_1_ref ]
) ;
assert! ( accounts_to_combine
. accounts_to_combine
. iter ( )
. all ( | shrink_collect | shrink_collect . alive_accounts . many_refs . accounts . is_empty ( ) ) ) ;
// test write_ancient_accounts_to_same_slot_multiple_refs since we built interesting 'AccountsToCombine'
let write_ancient_accounts = match method {
TestWriteMultipleRefs ::MultipleRefs = > {
let mut write_ancient_accounts = WriteAncientAccounts ::default ( ) ;
db . write_ancient_accounts_to_same_slot_multiple_refs (
accounts_to_combine . accounts_keep_slots . values ( ) ,
& mut write_ancient_accounts ,
) ;
write_ancient_accounts
}
TestWriteMultipleRefs ::PackedStorages = > {
let packed_contents = Vec ::default ( ) ;
db . write_packed_storages ( & accounts_to_combine , packed_contents )
}
} ;
assert_eq! ( write_ancient_accounts . shrinks_in_progress . len ( ) , num_slots ) ;
let mut shrinks_in_progress = write_ancient_accounts
. shrinks_in_progress
. iter ( )
. collect ::< Vec < _ > > ( ) ;
shrinks_in_progress . sort_unstable_by ( | a , b | a . 0. cmp ( b . 0 ) ) ;
assert_eq! (
shrinks_in_progress
. iter ( )
. map ( | ( slot , _ ) | * * slot )
. collect ::< Vec < _ > > ( ) ,
slots_vec
) ;
assert_eq! (
shrinks_in_progress
. iter ( )
. map ( | ( _ , shrink_in_progress ) | shrink_in_progress . old_storage ( ) . append_vec_id ( ) )
. collect ::< Vec < _ > > ( ) ,
storages
. iter ( )
. map ( | storage | storage . append_vec_id ( ) )
. collect ::< Vec < _ > > ( )
) ;
// assert that we wrote the 2_ref account to the newly shrunk append vec
let shrink_in_progress = shrinks_in_progress . first ( ) . unwrap ( ) . 1 ;
let accounts_shrunk_same_slot = shrink_in_progress . new_storage ( ) . accounts . accounts ( 0 ) ;
assert_eq! ( accounts_shrunk_same_slot . len ( ) , 1 ) ;
assert_eq! (
accounts_shrunk_same_slot . first ( ) . unwrap ( ) . pubkey ( ) ,
pk_with_2_refs
) ;
assert_eq! (
accounts_shrunk_same_slot
. first ( )
. unwrap ( )
. to_account_shared_data ( ) ,
account_with_2_refs . to_account_shared_data ( )
) ;
}
2023-02-09 11:06:17 -08:00
}
2023-02-07 18:22:54 -08:00
#[ test ]
fn test_get_unique_accounts_from_storage_for_combining_ancient_slots ( ) {
for num_slots in 0 .. 3 {
for reverse in [ false , true ] {
2023-02-09 11:41:36 -08:00
let ( db , storages , slots , mut infos ) = get_sample_storages ( num_slots , None ) ;
2023-02-07 18:22:54 -08:00
let original_results = storages
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
if reverse {
// reverse the contents for further testing
infos = infos . into_iter ( ) . rev ( ) . collect ( ) ;
}
let results =
db . get_unique_accounts_from_storage_for_combining_ancient_slots ( & infos ) ;
let all_accounts = get_all_accounts ( & db , slots ) ;
assert_eq! ( all_accounts . len ( ) , num_slots ) ;
compare_all_vec_accounts (
original_results . iter ( ) ,
results . iter ( ) . map ( | ( _ , accounts ) | accounts ) ,
) ;
compare_all_accounts (
& all_accounts ,
& unique_to_accounts ( results . iter ( ) . map ( | ( _ , accounts ) | accounts ) ) ,
) ;
let map = | info : & SlotInfo | {
(
info . storage . append_vec_id ( ) ,
info . slot ,
info . capacity ,
info . alive_bytes ,
info . should_shrink ,
)
} ;
assert_eq! (
infos . iter ( ) . map ( map ) . collect ::< Vec < _ > > ( ) ,
results
. into_iter ( )
. map ( | ( info , _ ) | map ( info ) )
. collect ::< Vec < _ > > ( )
) ;
}
}
}
2022-05-10 14:41:04 -07:00
#[ test ]
fn test_accounts_to_store_simple ( ) {
2022-05-16 13:54:40 -07:00
let map = vec! [ ] ;
2022-05-10 14:41:04 -07:00
let slot = 1 ;
2022-11-30 15:46:52 -08:00
let accounts_to_store = AccountsToStore ::new ( 0 , & map , 0 , slot ) ;
2022-05-10 14:41:04 -07:00
for selector in [ StorageSelector ::Primary , StorageSelector ::Overflow ] {
2022-11-22 15:36:57 -08:00
let accounts = accounts_to_store . get ( selector ) ;
2022-05-10 14:41:04 -07:00
assert! ( accounts . is_empty ( ) ) ;
}
2022-05-12 08:14:27 -07:00
assert! ( ! accounts_to_store . has_overflow ( ) ) ;
2022-05-10 14:41:04 -07:00
}
#[ test ]
fn test_accounts_to_store_more ( ) {
2023-01-21 10:06:27 -08:00
let pubkey = Pubkey ::from ( [ 1 ; 32 ] ) ;
2022-05-10 14:41:04 -07:00
let account_size = 3 ;
let account = AccountSharedData ::default ( ) ;
let account_meta = AccountMeta {
lamports : 1 ,
2023-01-21 10:06:27 -08:00
owner : Pubkey ::from ( [ 2 ; 32 ] ) ,
2022-05-10 14:41:04 -07:00
executable : false ,
rent_epoch : 0 ,
} ;
let offset = 3 ;
let hash = Hash ::new ( & [ 2 ; 32 ] ) ;
let stored_meta = StoredMeta {
/// global write version
2022-12-14 07:43:40 -08:00
write_version_obsolete : 0 ,
2022-05-10 14:41:04 -07:00
/// key for the account
pubkey ,
data_len : 43 ,
} ;
2023-03-09 15:52:51 -08:00
let account = StoredAccountMeta ::AppendVec ( AppendVecStoredAccountMeta {
2022-05-10 14:41:04 -07:00
meta : & stored_meta ,
/// account data
account_meta : & account_meta ,
data : account . data ( ) ,
offset ,
2022-07-07 16:37:14 -07:00
stored_size : account_size ,
2022-05-10 14:41:04 -07:00
hash : & hash ,
2023-03-09 15:52:51 -08:00
} ) ;
2023-01-10 13:57:34 -08:00
let map = vec! [ & account ] ;
2022-05-10 14:41:04 -07:00
for ( selector , available_bytes ) in [
( StorageSelector ::Primary , account_size ) ,
( StorageSelector ::Overflow , account_size - 1 ) ,
] {
let slot = 1 ;
2022-11-30 15:46:52 -08:00
let alive_total_bytes = account_size ;
let accounts_to_store =
AccountsToStore ::new ( available_bytes as u64 , & map , alive_total_bytes , slot ) ;
2022-11-22 15:36:57 -08:00
let accounts = accounts_to_store . get ( selector ) ;
2022-05-10 14:41:04 -07:00
assert_eq! (
2023-01-10 13:57:34 -08:00
accounts . iter ( ) . collect ::< Vec < _ > > ( ) ,
map . iter ( ) . collect ::< Vec < _ > > ( ) ,
2022-05-10 14:41:04 -07:00
" mismatch "
) ;
2022-11-22 15:36:57 -08:00
let accounts = accounts_to_store . get ( get_opposite ( & selector ) ) ;
2022-05-12 08:14:27 -07:00
assert_eq! (
selector = = StorageSelector ::Overflow ,
accounts_to_store . has_overflow ( )
) ;
2022-05-10 14:41:04 -07:00
assert! ( accounts . is_empty ( ) ) ;
}
}
fn get_opposite ( selector : & StorageSelector ) -> StorageSelector {
match selector {
StorageSelector ::Overflow = > StorageSelector ::Primary ,
StorageSelector ::Primary = > StorageSelector ::Overflow ,
}
}
2022-05-11 06:02:05 -07:00
#[ test ]
fn test_get_ancient_append_vec_capacity ( ) {
assert_eq! (
get_ancient_append_vec_capacity ( ) ,
2022-07-19 06:51:36 -07:00
crate ::append_vec ::MAXIMUM_APPEND_VEC_FILE_SIZE / 10 - 2048
2022-05-11 06:02:05 -07:00
) ;
}
#[ test ]
fn test_is_ancient ( ) {
for ( size , expected_ancient ) in [
( get_ancient_append_vec_capacity ( ) + 1 , true ) ,
( get_ancient_append_vec_capacity ( ) , true ) ,
( get_ancient_append_vec_capacity ( ) - 1 , false ) ,
] {
let tf = crate ::append_vec ::test_utils ::get_append_vec_path ( " test_is_ancient " ) ;
let ( _temp_dirs , _paths ) = get_temp_accounts_paths ( 1 ) . unwrap ( ) ;
2023-02-15 17:24:04 -08:00
let av = AccountsFile ::AppendVec ( AppendVec ::new ( & tf . path , true , size as usize ) ) ;
2022-05-11 06:02:05 -07:00
assert_eq! ( expected_ancient , is_ancient ( & av ) ) ;
}
}
2023-02-03 10:53:49 -08:00
2023-02-14 06:53:31 -08:00
fn get_one_packed_ancient_append_vec_and_others (
alive : bool ,
num_normal_slots : usize ,
) -> ( AccountsDb , Slot ) {
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , num_normal_slots + 1 , None ) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
let created_accounts = db . get_unique_accounts_from_storage ( & storage ) ;
db . combine_ancient_slots_packed ( vec! [ slot1 ] , CAN_RANDOMLY_SHRINK_FALSE ) ;
assert! ( db . storage . get_slot_storage_entry ( slot1 ) . is_some ( ) ) ;
let after_store = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
let GetUniqueAccountsResult {
stored_accounts : after_stored_accounts ,
capacity : after_capacity ,
} = db . get_unique_accounts_from_storage ( & after_store ) ;
assert_eq! ( created_accounts . capacity , after_capacity ) ;
assert_eq! ( created_accounts . stored_accounts . len ( ) , 1 ) ;
// always 1 account: either we leave the append vec alone if it is all dead
// or we create a new one and copy into it if account is alive
assert_eq! ( after_stored_accounts . len ( ) , 1 ) ;
( db , slot1 )
}
2023-02-05 12:13:39 -08:00
fn assert_storage_info ( info : & SlotInfo , storage : & AccountStorageEntry , should_shrink : bool ) {
2023-02-03 10:53:49 -08:00
assert_eq! ( storage . append_vec_id ( ) , info . storage . append_vec_id ( ) ) ;
assert_eq! ( storage . slot ( ) , info . slot ) ;
assert_eq! ( storage . capacity ( ) , info . capacity ) ;
assert_eq! ( storage . alive_bytes ( ) , info . alive_bytes as usize ) ;
assert_eq! ( should_shrink , info . should_shrink ) ;
}
2023-02-08 07:08:20 -08:00
#[ derive(EnumIter, Debug, PartialEq, Eq) ]
enum TestCollectInfo {
CollectSortFilterInfo ,
CalcAncientSlotInfo ,
Add ,
}
2023-02-03 10:53:49 -08:00
#[ test ]
2023-02-08 07:08:20 -08:00
fn test_calc_ancient_slot_info_one_alive_only ( ) {
2023-02-03 10:53:49 -08:00
let can_randomly_shrink = false ;
let alive = true ;
let slots = 1 ;
2023-02-08 07:08:20 -08:00
for method in TestCollectInfo ::iter ( ) {
2023-02-03 10:53:49 -08:00
// 1_040_000 is big enough relative to page size to cause shrink ratio to be triggered
for data_size in [ None , Some ( 1_040_000 ) ] {
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , slots , data_size ) ;
let mut infos = AncientSlotInfos ::default ( ) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
let alive_bytes_expected = storage . alive_bytes ( ) ;
2023-02-08 07:08:20 -08:00
match method {
TestCollectInfo ::Add = > {
// test lower level 'add'
infos . add ( slot1 , Arc ::clone ( & storage ) , can_randomly_shrink ) ;
}
TestCollectInfo ::CalcAncientSlotInfo = > {
infos = db . calc_ancient_slot_info ( vec! [ slot1 ] , can_randomly_shrink ) ;
}
TestCollectInfo ::CollectSortFilterInfo = > {
let tuning = PackedAncientStorageTuning {
percent_of_alive_shrunk_data : 100 ,
max_ancient_slots : 0 ,
// irrelevant
ideal_storage_size : NonZeroU64 ::new ( 1 ) . unwrap ( ) ,
can_randomly_shrink ,
} ;
2023-02-10 09:40:13 -08:00
infos = db . collect_sort_filter_ancient_slots ( vec! [ slot1 ] , & tuning ) ;
2023-02-08 07:08:20 -08:00
}
2023-02-03 10:53:49 -08:00
}
assert_eq! ( infos . all_infos . len ( ) , 1 ) ;
let should_shrink = data_size . is_none ( ) ;
assert_storage_info ( infos . all_infos . first ( ) . unwrap ( ) , & storage , should_shrink ) ;
if should_shrink {
// data size is so small compared to min aligned file size that the storage is marked as should_shrink
2023-02-08 07:08:20 -08:00
assert_eq! (
infos . shrink_indexes ,
if ! matches! ( method , TestCollectInfo ::CollectSortFilterInfo ) {
vec! [ 0 ]
} else {
Vec ::default ( )
}
) ;
2023-02-03 10:53:49 -08:00
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected as u64 ) ;
assert_eq! ( infos . total_alive_bytes_shrink , alive_bytes_expected as u64 ) ;
} else {
assert! ( infos . shrink_indexes . is_empty ( ) ) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected as u64 ) ;
assert_eq! ( infos . total_alive_bytes_shrink , 0 ) ;
}
}
}
}
#[ test ]
fn test_calc_ancient_slot_info_one_dead ( ) {
let can_randomly_shrink = false ;
let alive = false ;
let slots = 1 ;
for call_add in [ false , true ] {
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , slots , None ) ;
let mut infos = AncientSlotInfos ::default ( ) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
if call_add {
infos . add ( slot1 , Arc ::clone ( & storage ) , can_randomly_shrink ) ;
} else {
infos = db . calc_ancient_slot_info ( vec! [ slot1 ] , can_randomly_shrink ) ;
}
assert! ( infos . all_infos . is_empty ( ) ) ;
assert! ( infos . shrink_indexes . is_empty ( ) ) ;
assert_eq! ( infos . total_alive_bytes , 0 ) ;
assert_eq! ( infos . total_alive_bytes_shrink , 0 ) ;
}
}
#[ test ]
fn test_calc_ancient_slot_info_several ( ) {
let can_randomly_shrink = false ;
for alive in [ true , false ] {
2023-02-10 06:25:37 -08:00
for slots in 0 .. 4 {
2023-02-03 10:53:49 -08:00
// 1_040_000 is big enough relative to page size to cause shrink ratio to be triggered
for data_size in [ None , Some ( 1_040_000 ) ] {
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , slots , data_size ) ;
let slot_vec = ( slot1 .. ( slot1 + slots as Slot ) ) . collect ::< Vec < _ > > ( ) ;
let storages = slot_vec
. iter ( )
. map ( | slot | db . storage . get_slot_storage_entry ( * slot ) . unwrap ( ) )
. collect ::< Vec < _ > > ( ) ;
let alive_bytes_expected = storages
. iter ( )
. map ( | storage | storage . alive_bytes ( ) as u64 )
. sum ::< u64 > ( ) ;
let infos = db . calc_ancient_slot_info ( slot_vec . clone ( ) , can_randomly_shrink ) ;
if ! alive {
assert! ( infos . all_infos . is_empty ( ) ) ;
assert! ( infos . shrink_indexes . is_empty ( ) ) ;
assert_eq! ( infos . total_alive_bytes , 0 ) ;
assert_eq! ( infos . total_alive_bytes_shrink , 0 ) ;
} else {
assert_eq! ( infos . all_infos . len ( ) , slots ) ;
let should_shrink = data_size . is_none ( ) ;
storages
. iter ( )
. zip ( infos . all_infos . iter ( ) )
. for_each ( | ( storage , info ) | {
assert_storage_info ( info , storage , should_shrink ) ;
} ) ;
if should_shrink {
// data size is so small compared to min aligned file size that the storage is marked as should_shrink
assert_eq! (
infos . shrink_indexes ,
slot_vec
. iter ( )
. enumerate ( )
. map ( | ( i , _ ) | i )
. collect ::< Vec < _ > > ( )
) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected ) ;
assert_eq! ( infos . total_alive_bytes_shrink , alive_bytes_expected ) ;
} else {
assert! ( infos . shrink_indexes . is_empty ( ) ) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected ) ;
assert_eq! ( infos . total_alive_bytes_shrink , 0 ) ;
}
}
}
}
}
}
#[ test ]
fn test_calc_ancient_slot_info_one_alive_one_dead ( ) {
let can_randomly_shrink = false ;
2023-02-08 07:08:20 -08:00
for method in TestCollectInfo ::iter ( ) {
for slot1_is_alive in [ false , true ] {
let alives = vec! [ false /* dummy */ , slot1_is_alive , ! slot1_is_alive ] ;
let slots = 2 ;
// 1_040_000 is big enough relative to page size to cause shrink ratio to be triggered
for data_size in [ None , Some ( 1_040_000 ) ] {
let ( db , slot1 ) =
create_db_with_storages_and_index ( true /* alive */ , slots , data_size ) ;
assert_eq! ( slot1 , 1 ) ; // make sure index into alives will be correct
assert_eq! ( alives [ slot1 as usize ] , slot1_is_alive ) ;
let slot_vec = ( slot1 .. ( slot1 + slots as Slot ) ) . collect ::< Vec < _ > > ( ) ;
let storages = slot_vec
. iter ( )
. map ( | slot | db . storage . get_slot_storage_entry ( * slot ) . unwrap ( ) )
. collect ::< Vec < _ > > ( ) ;
storages . iter ( ) . for_each ( | storage | {
let slot = storage . slot ( ) ;
let alive = alives [ slot as usize ] ;
if ! alive {
// make this storage not alive
remove_account_for_tests (
storage ,
storage . written_bytes ( ) as usize ,
false ,
) ;
}
2023-02-03 10:53:49 -08:00
} ) ;
2023-02-08 07:08:20 -08:00
let alive_storages = storages
. iter ( )
. filter_map ( | storage | alives [ storage . slot ( ) as usize ] . then_some ( storage ) )
. collect ::< Vec < _ > > ( ) ;
let alive_bytes_expected = alive_storages
. iter ( )
. map ( | storage | storage . alive_bytes ( ) as u64 )
. sum ::< u64 > ( ) ;
let infos = match method {
TestCollectInfo ::CalcAncientSlotInfo = > {
db . calc_ancient_slot_info ( slot_vec . clone ( ) , can_randomly_shrink )
}
TestCollectInfo ::Add = > {
continue ; // unsupportable
}
TestCollectInfo ::CollectSortFilterInfo = > {
let tuning = PackedAncientStorageTuning {
percent_of_alive_shrunk_data : 100 ,
max_ancient_slots : 0 ,
// irrelevant
ideal_storage_size : NonZeroU64 ::new ( 1 ) . unwrap ( ) ,
can_randomly_shrink ,
} ;
2023-02-10 09:40:13 -08:00
db . collect_sort_filter_ancient_slots ( slot_vec . clone ( ) , & tuning )
2023-02-08 07:08:20 -08:00
}
} ;
assert_eq! ( infos . all_infos . len ( ) , 1 , " method: {method:?} " ) ;
let should_shrink = data_size . is_none ( ) ;
alive_storages . iter ( ) . zip ( infos . all_infos . iter ( ) ) . for_each (
| ( storage , info ) | {
assert_storage_info ( info , storage , should_shrink ) ;
} ,
) ;
if should_shrink {
// data size is so small compared to min aligned file size that the storage is marked as should_shrink
assert_eq! (
infos . shrink_indexes ,
if ! matches! ( method , TestCollectInfo ::CollectSortFilterInfo ) {
vec! [ 0 ]
} else {
Vec ::default ( )
}
) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected ) ;
assert_eq! ( infos . total_alive_bytes_shrink , alive_bytes_expected ) ;
} else {
assert! ( infos . shrink_indexes . is_empty ( ) ) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected ) ;
assert_eq! ( infos . total_alive_bytes_shrink , 0 ) ;
}
2023-02-03 10:53:49 -08:00
}
}
}
}
2023-02-06 11:04:53 -08:00
fn create_test_infos ( count : usize ) -> AncientSlotInfos {
let ( db , slot1 ) = create_db_with_storages_and_index ( true /* alive */ , 1 , None ) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
AncientSlotInfos {
all_infos : ( 0 .. count )
. map ( | index | SlotInfo {
storage : Arc ::clone ( & storage ) ,
slot : index as Slot ,
capacity : 1 ,
alive_bytes : 1 ,
should_shrink : false ,
} )
. collect ( ) ,
shrink_indexes : ( 0 .. count ) . collect ( ) ,
.. AncientSlotInfos ::default ( )
}
}
2023-02-07 10:35:40 -08:00
#[ derive(EnumIter, Debug, PartialEq, Eq) ]
enum TestSmallestCapacity {
FilterAncientSlots ,
FilterBySmallestCapacity ,
}
2023-02-06 11:04:53 -08:00
#[ test ]
fn test_filter_by_smallest_capacity_empty ( ) {
2023-02-07 10:35:40 -08:00
for method in TestSmallestCapacity ::iter ( ) {
for max_storages in 1 .. 3 {
// requesting N max storage, has 1 storage, N >= 1 so nothing to do
let ideal_storage_size_large = get_ancient_append_vec_capacity ( ) ;
let mut infos = create_test_infos ( 1 ) ;
match method {
TestSmallestCapacity ::FilterAncientSlots = > {
let tuning = PackedAncientStorageTuning {
max_ancient_slots : max_storages ,
2023-02-07 13:34:27 -08:00
ideal_storage_size : NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
2023-02-07 10:35:40 -08:00
// irrelevant since we clear 'shrink_indexes'
percent_of_alive_shrunk_data : 0 ,
2023-02-08 07:08:20 -08:00
can_randomly_shrink : false ,
2023-02-07 10:35:40 -08:00
} ;
infos . shrink_indexes . clear ( ) ;
infos . filter_ancient_slots ( & tuning ) ;
}
TestSmallestCapacity ::FilterBySmallestCapacity = > {
2023-02-07 13:34:27 -08:00
infos . filter_by_smallest_capacity (
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-07 10:35:40 -08:00
}
}
assert! ( infos . all_infos . is_empty ( ) ) ;
}
2023-02-06 11:04:53 -08:00
}
}
#[ test ]
fn test_filter_by_smaller_capacity_sort ( ) {
// max is 3
// 4 storages
// storage[3] is big enough to cause us to need another storage
// so, storage[0] and [1] can be combined into 1, resulting in 3 remaining storages, which is
// the goal, so we only have to combine the first 2 to hit the goal
2023-02-07 10:35:40 -08:00
for method in TestSmallestCapacity ::iter ( ) {
let ideal_storage_size_large = get_ancient_append_vec_capacity ( ) ;
for reorder in [ false , true ] {
let mut infos = create_test_infos ( 4 ) ;
2023-02-06 11:04:53 -08:00
infos
. all_infos
2023-02-07 10:35:40 -08:00
. iter_mut ( )
. enumerate ( )
. for_each ( | ( i , info ) | info . capacity = 1 + i as u64 ) ;
if reorder {
infos . all_infos [ 3 ] . capacity = 0 ; // sort to beginning
}
infos . all_infos [ 3 ] . alive_bytes = ideal_storage_size_large ;
let max_storages = 3 ;
match method {
TestSmallestCapacity ::FilterBySmallestCapacity = > {
2023-02-07 13:34:27 -08:00
infos . filter_by_smallest_capacity (
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-07 10:35:40 -08:00
}
TestSmallestCapacity ::FilterAncientSlots = > {
let tuning = PackedAncientStorageTuning {
max_ancient_slots : max_storages ,
2023-02-07 13:34:27 -08:00
ideal_storage_size : NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
2023-02-07 10:35:40 -08:00
// irrelevant since we clear 'shrink_indexes'
percent_of_alive_shrunk_data : 0 ,
2023-02-08 07:08:20 -08:00
can_randomly_shrink : false ,
2023-02-07 10:35:40 -08:00
} ;
infos . shrink_indexes . clear ( ) ;
infos . filter_ancient_slots ( & tuning ) ;
}
}
assert_eq! (
infos
. all_infos
. iter ( )
. map ( | info | info . slot )
. collect ::< Vec < _ > > ( ) ,
if reorder { vec! [ 3 , 0 , 1 ] } else { vec! [ 0 , 1 ] } ,
" reorder: {reorder} "
) ;
}
2023-02-06 11:04:53 -08:00
}
}
#[ test ]
fn test_truncate_to_max_storages ( ) {
for filter in [ false , true ] {
let test = | infos : & mut AncientSlotInfos , max_storages , ideal_storage_size | {
if filter {
infos . filter_by_smallest_capacity ( max_storages , ideal_storage_size ) ;
} else {
infos . truncate_to_max_storages ( max_storages , ideal_storage_size ) ;
}
} ;
let ideal_storage_size_large = get_ancient_append_vec_capacity ( ) ;
let mut infos = create_test_infos ( 1 ) ;
let max_storages = 1 ;
// 1 storage, 1 max, but 1 storage does not fill the entire new combined storage, so truncate nothing
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert_eq! ( infos . all_infos . len ( ) , usize ::from ( ! filter ) ) ;
let mut infos = create_test_infos ( 1 ) ;
let max_storages = 1 ;
infos . all_infos [ 0 ] . alive_bytes = ideal_storage_size_large + 1 ; // too big for 1 ideal storage
// 1 storage, 1 max, but 1 overflows the entire new combined storage, so truncate nothing
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert_eq! ( infos . all_infos . len ( ) , usize ::from ( ! filter ) ) ;
let mut infos = create_test_infos ( 1 ) ;
let max_storages = 2 ;
// all truncated because these infos will fit into the # storages
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert! ( infos . all_infos . is_empty ( ) ) ;
let mut infos = create_test_infos ( 1 ) ;
infos . all_infos [ 0 ] . alive_bytes = ideal_storage_size_large + 1 ;
let max_storages = 2 ;
// none truncated because the one storage calculates to be larger than 1 ideal storage, so we need to
// combine
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert_eq! (
infos
. all_infos
. iter ( )
. map ( | info | info . slot )
. collect ::< Vec < _ > > ( ) ,
if filter { Vec ::default ( ) } else { vec! [ 0 ] }
) ;
// both need to be combined to reach '1'
let max_storages = 1 ;
for ideal_storage_size in [ 1 , 2 ] {
let mut infos = create_test_infos ( 2 ) ;
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert_eq! ( infos . all_infos . len ( ) , 2 ) ;
}
// max is 3
// 4 storages
// storage[3] is big enough to cause us to need another storage
// so, storage[0] and [1] can be combined into 1, resulting in 3 remaining storages, which is
// the goal, so we only have to combine the first 2 to hit the goal
let mut infos = create_test_infos ( 4 ) ;
infos . all_infos [ 3 ] . alive_bytes = ideal_storage_size_large ;
let max_storages = 3 ;
2023-02-07 13:34:27 -08:00
test (
& mut infos ,
max_storages ,
NonZeroU64 ::new ( ideal_storage_size_large ) . unwrap ( ) ,
) ;
2023-02-06 11:04:53 -08:00
assert_eq! (
infos
. all_infos
. iter ( )
. map ( | info | info . slot )
. collect ::< Vec < _ > > ( ) ,
vec! [ 0 , 1 ]
) ;
}
}
2023-02-03 10:53:49 -08:00
#[ test ]
fn test_calc_ancient_slot_info_one_shrink_one_not ( ) {
let can_randomly_shrink = false ;
2023-02-08 07:08:20 -08:00
for method in TestCollectInfo ::iter ( ) {
for slot1_shrink in [ false , true ] {
let shrinks = vec! [ false /* dummy */ , slot1_shrink , ! slot1_shrink ] ;
let slots = 2 ;
// 1_040_000 is big enough relative to page size to cause shrink ratio to be triggered
let data_sizes = shrinks
. iter ( )
. map ( | shrink | ( ! shrink ) . then_some ( 1_040_000 ) )
. collect ::< Vec < _ > > ( ) ;
let ( db , slot1 ) =
create_db_with_storages_and_index ( true /* alive */ , 1 , data_sizes [ 1 ] ) ;
let dead_bytes = 184 ; // constant based on None data size
create_storages_and_update_index (
& db ,
None ,
slot1 + 1 ,
1 ,
true ,
data_sizes [ ( slot1 + 1 ) as usize ] ,
) ;
2023-02-03 10:53:49 -08:00
2023-02-08 07:08:20 -08:00
assert_eq! ( slot1 , 1 ) ; // make sure index into shrinks will be correct
assert_eq! ( shrinks [ slot1 as usize ] , slot1_shrink ) ;
let slot_vec = ( slot1 .. ( slot1 + slots as Slot ) ) . collect ::< Vec < _ > > ( ) ;
let storages = slot_vec
2023-02-03 10:53:49 -08:00
. iter ( )
2023-02-08 07:08:20 -08:00
. map ( | slot | {
let storage = db . storage . get_slot_storage_entry ( * slot ) . unwrap ( ) ;
assert_eq! ( * slot , storage . slot ( ) ) ;
storage
} )
. collect ::< Vec < _ > > ( ) ;
let alive_bytes_expected = storages
. iter ( )
. map ( | storage | storage . alive_bytes ( ) as u64 )
. sum ::< u64 > ( ) ;
let infos = match method {
TestCollectInfo ::CalcAncientSlotInfo = > {
db . calc_ancient_slot_info ( slot_vec . clone ( ) , can_randomly_shrink )
}
TestCollectInfo ::Add = > {
continue ; // unsupportable
}
TestCollectInfo ::CollectSortFilterInfo = > {
let tuning = PackedAncientStorageTuning {
percent_of_alive_shrunk_data : 100 ,
max_ancient_slots : 0 ,
// irrelevant
ideal_storage_size : NonZeroU64 ::new ( 1 ) . unwrap ( ) ,
can_randomly_shrink ,
} ;
// note this can sort infos.all_infos
2023-02-10 09:40:13 -08:00
db . collect_sort_filter_ancient_slots ( slot_vec . clone ( ) , & tuning )
2023-02-08 07:08:20 -08:00
}
} ;
assert_eq! ( infos . all_infos . len ( ) , 2 ) ;
storages . iter ( ) . for_each ( | storage | {
assert! ( infos
. all_infos
. iter ( )
. any ( | info | info . slot = = storage . slot ( ) ) ) ;
} ) ;
// data size is so small compared to min aligned file size that the storage is marked as should_shrink
assert_eq! (
infos . shrink_indexes ,
if matches! ( method , TestCollectInfo ::CollectSortFilterInfo ) {
Vec ::default ( )
} else {
shrinks
. iter ( )
. skip ( 1 )
. enumerate ( )
. filter_map ( | ( i , shrink ) | shrink . then_some ( i ) )
. collect ::< Vec < _ > > ( )
}
) ;
assert_eq! ( infos . total_alive_bytes , alive_bytes_expected ) ;
assert_eq! ( infos . total_alive_bytes_shrink , dead_bytes ) ;
}
2023-02-03 10:53:49 -08:00
}
}
2023-02-05 12:14:14 -08:00
2023-02-06 14:29:54 -08:00
#[ test ]
fn test_clear_should_shrink_after_cutoff_empty ( ) {
let mut infos = create_test_infos ( 2 ) ;
for count in 0 .. 2 {
for i in 0 .. count {
infos . all_infos [ i ] . should_shrink = true ;
}
}
infos . clear_should_shrink_after_cutoff ( 100 ) ;
assert_eq! (
0 ,
infos
. all_infos
. iter ( )
. filter_map ( | info | info . should_shrink . then_some ( ( ) ) )
. count ( )
) ;
}
2023-02-09 14:47:22 -08:00
#[ derive(EnumIter, Debug, PartialEq, Eq) ]
enum TestWriteAncient {
OnePackedStorage ,
AncientAccounts ,
2023-02-13 07:25:47 -08:00
PackedStorages ,
2023-02-09 14:47:22 -08:00
}
2023-02-08 09:40:17 -08:00
#[ test ]
fn test_write_ancient_accounts ( ) {
2023-02-09 11:41:36 -08:00
for data_size in [ None , Some ( 10_000_000 ) ] {
2023-02-09 14:47:22 -08:00
for method in TestWriteAncient ::iter ( ) {
for num_slots in 0 .. 4 {
for combine_into in 0 ..= num_slots {
if combine_into = = num_slots & & num_slots > 0 {
// invalid combination when num_slots > 0, but required to hit num_slots=0, combine_into=0
continue ;
}
let ( db , storages , slots , _infos ) =
get_sample_storages ( num_slots , data_size ) ;
2023-02-08 09:40:17 -08:00
2023-02-09 14:47:22 -08:00
let initial_accounts = get_all_accounts ( & db , slots . clone ( ) ) ;
2023-02-08 09:40:17 -08:00
2023-02-09 14:47:22 -08:00
let accounts_vecs = storages
. iter ( )
. map ( | storage | ( storage . slot ( ) , storage . accounts . accounts ( 0 ) ) )
. collect ::< Vec < _ > > ( ) ;
// reshape the data
let accounts_vecs2 = accounts_vecs
. iter ( )
. map ( | ( slot , accounts ) | ( * slot , accounts . iter ( ) . collect ::< Vec < _ > > ( ) ) )
. collect ::< Vec < _ > > ( ) ;
let accounts = accounts_vecs2
. iter ( )
. map ( | ( slot , accounts ) | ( * slot , & accounts [ .. ] ) )
. collect ::< Vec < _ > > ( ) ;
let target_slot = slots . clone ( ) . nth ( combine_into ) . unwrap_or ( slots . start ) ;
let accounts_to_write = StorableAccountsBySlot ::new (
target_slot ,
2023-02-12 13:42:03 -08:00
& accounts ,
2023-02-09 14:47:22 -08:00
INCLUDE_SLOT_IN_HASH_TESTS ,
) ;
2023-02-08 09:40:17 -08:00
2023-02-09 14:47:22 -08:00
let bytes = storages
. iter ( )
. map ( | storage | storage . written_bytes ( ) )
. sum ::< u64 > ( ) ;
assert_eq! (
2023-02-13 07:25:47 -08:00
bytes ,
initial_accounts
. iter ( )
. map ( | ( _ , account ) | aligned_stored_size ( account . data ( ) . len ( ) ) as u64 )
. sum ::< u64 > ( )
) ;
2023-02-08 09:40:17 -08:00
2023-02-09 14:47:22 -08:00
if num_slots > 0 {
2023-02-11 11:41:33 -08:00
let mut write_ancient_accounts = WriteAncientAccounts ::default ( ) ;
2023-02-09 14:47:22 -08:00
2023-02-11 11:41:33 -08:00
match method {
TestWriteAncient ::AncientAccounts = > db . write_ancient_accounts (
bytes ,
accounts_to_write ,
& mut write_ancient_accounts ,
) ,
TestWriteAncient ::OnePackedStorage = > {
2023-02-09 14:47:22 -08:00
let packed = PackedAncientStorage { accounts , bytes } ;
db . write_one_packed_storage (
& packed ,
target_slot ,
& mut write_ancient_accounts ,
) ;
}
2023-02-13 07:25:47 -08:00
TestWriteAncient ::PackedStorages = > {
let packed = PackedAncientStorage { accounts , bytes } ;
let accounts_to_combine = AccountsToCombine {
// target slots are supposed to be read in reverse order, so test that
target_slots_sorted : vec ! [
Slot ::MAX , // this asserts if it gets used
Slot ::MAX ,
target_slot ,
] ,
.. AccountsToCombine ::default ( )
} ;
write_ancient_accounts = db
. write_packed_storages ( & accounts_to_combine , vec! [ packed ] ) ;
}
2023-02-09 14:47:22 -08:00
} ;
2023-02-11 11:41:33 -08:00
let mut result = write_ancient_accounts . shrinks_in_progress ;
2023-02-09 14:47:22 -08:00
let one = result . drain ( ) . collect ::< Vec < _ > > ( ) ;
assert_eq! ( 1 , one . len ( ) ) ;
assert_eq! ( target_slot , one . first ( ) . unwrap ( ) . 0 ) ;
assert_eq! (
one . first ( ) . unwrap ( ) . 1. old_storage ( ) . append_vec_id ( ) ,
storages [ combine_into ] . append_vec_id ( )
) ;
// make sure the single new append vec contains all the same accounts
let accounts_in_new_storage =
one . first ( ) . unwrap ( ) . 1. new_storage ( ) . accounts . accounts ( 0 ) ;
compare_all_accounts (
& initial_accounts ,
& accounts_in_new_storage
. into_iter ( )
. map ( | meta | ( * meta . pubkey ( ) , meta . to_account_shared_data ( ) ) )
. collect ::< Vec < _ > > ( ) [ .. ] ,
) ;
}
let all_accounts = get_all_accounts ( & db , target_slot .. ( target_slot + 1 ) ) ;
2023-02-09 11:41:36 -08:00
2023-02-09 14:47:22 -08:00
compare_all_accounts ( & initial_accounts , & all_accounts ) ;
}
2023-02-09 11:41:36 -08:00
}
2023-02-08 09:40:17 -08:00
}
}
}
2023-02-07 10:35:40 -08:00
#[ derive(EnumIter, Debug, PartialEq, Eq) ]
enum TestShouldShrink {
FilterAncientSlots ,
ClearShouldShrink ,
ChooseStoragesToShrink ,
}
2023-02-06 14:29:54 -08:00
#[ test ]
fn test_clear_should_shrink_after_cutoff_simple ( ) {
for swap in [ false , true ] {
2023-02-07 10:35:40 -08:00
for method in TestShouldShrink ::iter ( ) {
for ( percent_of_alive_shrunk_data , mut expected_infos ) in
[ ( 0 , 0 ) , ( 9 , 1 ) , ( 10 , 1 ) , ( 89 , 2 ) , ( 90 , 2 ) , ( 91 , 2 ) , ( 100 , 2 ) ]
{
2023-02-06 14:29:54 -08:00
let mut infos = create_test_infos ( 2 ) ;
infos
. all_infos
. iter_mut ( )
. enumerate ( )
. for_each ( | ( i , info ) | {
info . should_shrink = true ;
info . capacity = ( ( i + 1 ) * 1000 ) as u64 ;
} ) ;
infos . all_infos [ 0 ] . alive_bytes = 100 ;
infos . all_infos [ 1 ] . alive_bytes = 900 ;
if swap {
infos . all_infos = infos . all_infos . into_iter ( ) . rev ( ) . collect ( ) ;
}
infos . total_alive_bytes_shrink =
infos . all_infos . iter ( ) . map ( | info | info . alive_bytes ) . sum ( ) ;
2023-02-07 10:35:40 -08:00
match method {
TestShouldShrink ::FilterAncientSlots = > {
let tuning = PackedAncientStorageTuning {
percent_of_alive_shrunk_data ,
// 0 so that we combine everything with regard to the overall # of slots limit
max_ancient_slots : 0 ,
// this is irrelevant since the limit is artificially 0
2023-02-07 13:34:27 -08:00
ideal_storage_size : NonZeroU64 ::new ( 1 ) . unwrap ( ) ,
2023-02-08 07:08:20 -08:00
can_randomly_shrink : false ,
2023-02-07 10:35:40 -08:00
} ;
infos . filter_ancient_slots ( & tuning ) ;
}
TestShouldShrink ::ClearShouldShrink = > {
infos . clear_should_shrink_after_cutoff ( percent_of_alive_shrunk_data ) ;
}
TestShouldShrink ::ChooseStoragesToShrink = > {
infos . choose_storages_to_shrink ( percent_of_alive_shrunk_data ) ;
}
2023-02-06 14:29:54 -08:00
}
2023-02-07 10:35:40 -08:00
if expected_infos = = 2 {
let modify = if method = = TestShouldShrink ::FilterAncientSlots {
// filter_ancient_slots modifies in several ways and doesn't retain the values to compare
percent_of_alive_shrunk_data = = 89 | | percent_of_alive_shrunk_data = = 90
} else {
infos . all_infos [ infos . shrink_indexes [ 0 ] ] . alive_bytes
> = infos . total_alive_bytes_shrink * percent_of_alive_shrunk_data
/ 100
} ;
if modify {
// if the sorting ends up putting the bigger alive_bytes storage first, then only 1 will be shrunk due to 'should_shrink'
expected_infos = 1 ;
}
2023-02-06 14:29:54 -08:00
}
let count = infos
. all_infos
. iter ( )
. filter_map ( | info | info . should_shrink . then_some ( ( ) ) )
. count ( ) ;
assert_eq! (
expected_infos ,
count ,
2023-02-07 10:35:40 -08:00
" percent_of_alive_shrunk_data: {percent_of_alive_shrunk_data}, infos: {expected_infos}, method: {method:?}, swap: {swap}, data: {:?} " ,
2023-02-06 14:29:54 -08:00
infos . all_infos . iter ( ) . map ( | info | ( info . slot , info . capacity , info . alive_bytes ) ) . collect ::< Vec < _ > > ( )
) ;
}
}
}
}
2023-02-05 12:14:14 -08:00
#[ test ]
fn test_sort_shrink_indexes_by_bytes_saved ( ) {
let ( db , slot1 ) = create_db_with_storages_and_index ( true /* alive */ , 1 , None ) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) . unwrap ( ) ;
// ignored
let slot = 0 ;
// info1 is first, equal, last
for info1_capacity in [ 0 , 1 , 2 ] {
let info1 = SlotInfo {
storage : storage . clone ( ) ,
slot ,
capacity : info1_capacity ,
alive_bytes : 0 ,
should_shrink : false ,
} ;
let info2 = SlotInfo {
storage : storage . clone ( ) ,
slot ,
capacity : 2 ,
alive_bytes : 1 ,
should_shrink : false ,
} ;
let mut infos = AncientSlotInfos {
all_infos : vec ! [ info1 , info2 ] ,
shrink_indexes : vec ! [ 0 , 1 ] ,
.. AncientSlotInfos ::default ( )
} ;
infos . sort_shrink_indexes_by_bytes_saved ( ) ;
let first = & infos . all_infos [ infos . shrink_indexes [ 0 ] ] ;
let second = & infos . all_infos [ infos . shrink_indexes [ 1 ] ] ;
let first_capacity = first . capacity - first . alive_bytes ;
let second_capacity = second . capacity - second . alive_bytes ;
assert! ( first_capacity > = second_capacity ) ;
}
}
2023-02-14 06:53:31 -08:00
#[ test ]
fn test_combine_ancient_slots_packed_internal ( ) {
let can_randomly_shrink = false ;
let alive = true ;
for num_slots in 0 .. 4 {
for max_ancient_slots in 0 .. 4 {
let ( db , slot1 ) = create_db_with_storages_and_index ( alive , num_slots , None ) ;
let original_stores = ( 0 .. num_slots )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( ( slot as Slot ) + slot1 ) )
. collect ::< Vec < _ > > ( ) ;
let original_results = original_stores
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let tuning = PackedAncientStorageTuning {
percent_of_alive_shrunk_data : 0 ,
max_ancient_slots ,
can_randomly_shrink ,
ideal_storage_size : NonZeroU64 ::new ( get_ancient_append_vec_capacity ( ) ) . unwrap ( ) ,
} ;
db . combine_ancient_slots_packed_internal (
( 0 .. num_slots ) . map ( | slot | ( slot as Slot ) + slot1 ) . collect ( ) ,
tuning ,
& mut ShrinkStatsSub ::default ( ) ,
) ;
let storage = db . storage . get_slot_storage_entry ( slot1 ) ;
if num_slots = = 0 {
assert! ( storage . is_none ( ) ) ;
continue ;
}
// any of the several slots could have been chosen to be re-used
let active_slots = ( 0 .. num_slots )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( ( slot as Slot ) + slot1 ) )
. count ( ) ;
let mut expected_slots = max_ancient_slots . min ( num_slots ) ;
if max_ancient_slots = = 0 {
expected_slots = 1 ;
}
assert_eq! (
active_slots , expected_slots ,
" slots: {num_slots}, max_ancient_slots: {max_ancient_slots}, alive: {alive} "
) ;
assert_eq! (
expected_slots ,
db . storage . all_slots ( ) . len ( ) ,
" slots: {num_slots}, max_ancient_slots: {max_ancient_slots} "
) ;
let stores = ( 0 .. num_slots )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( ( slot as Slot ) + slot1 ) )
. collect ::< Vec < _ > > ( ) ;
let results = stores
. iter ( )
. map ( | store | db . get_unique_accounts_from_storage ( store ) )
. collect ::< Vec < _ > > ( ) ;
let all_accounts = get_all_accounts ( & db , slot1 .. ( slot1 + num_slots as Slot ) ) ;
compare_all_accounts ( & vec_unique_to_accounts ( & original_results ) , & all_accounts ) ;
compare_all_accounts (
& vec_unique_to_accounts ( & results ) ,
& get_all_accounts ( & db , slot1 .. ( slot1 + num_slots as Slot ) ) ,
) ;
}
}
}
fn vec_unique_to_accounts ( one : & [ GetUniqueAccountsResult ] ) -> Vec < ( Pubkey , AccountSharedData ) > {
one . iter ( )
. flat_map ( | result | {
result
. stored_accounts
. iter ( )
. map ( | result | ( * result . pubkey ( ) , result . to_account_shared_data ( ) ) )
} )
. collect ( )
}
#[ test ]
fn test_combine_packed_ancient_slots_simple ( ) {
for alive in [ false , true ] {
_ = get_one_packed_ancient_append_vec_and_others ( alive , 0 ) ;
}
}
2023-04-17 13:52:26 -07:00
#[ test ]
fn test_get_many_refs_pubkeys ( ) {
let rent_epoch = 0 ;
let lamports = 0 ;
let executable = false ;
let owner = Pubkey ::default ( ) ;
let data = Vec ::new ( ) ;
let pubkey = solana_sdk ::pubkey ::new_rand ( ) ;
let pubkey2 = solana_sdk ::pubkey ::new_rand ( ) ;
let meta = StoredMeta {
write_version_obsolete : 5 ,
pubkey ,
data_len : 7 ,
} ;
let meta2 = StoredMeta {
write_version_obsolete : 5 ,
pubkey : pubkey2 ,
data_len : 7 ,
} ;
let account_meta = AccountMeta {
lamports ,
owner ,
executable ,
rent_epoch ,
} ;
let offset = 99 ;
let stored_size = 101 ;
let hash = Hash ::new_unique ( ) ;
let stored_account = StoredAccountMeta ::AppendVec ( AppendVecStoredAccountMeta {
meta : & meta ,
account_meta : & account_meta ,
data : & data ,
offset ,
stored_size ,
hash : & hash ,
} ) ;
let stored_account2 = StoredAccountMeta ::AppendVec ( AppendVecStoredAccountMeta {
meta : & meta2 ,
account_meta : & account_meta ,
data : & data ,
offset ,
stored_size ,
hash : & hash ,
} ) ;
for ( many_refs_accounts , expected ) in [
( Vec ::default ( ) , Vec ::default ( ) ) ,
( vec! [ & stored_account ] , vec! [ pubkey ] ) ,
(
vec! [ & stored_account , & stored_account2 ] ,
vec! [ pubkey , pubkey2 ] ,
) ,
] {
let shrink_collect = ShrinkCollect ::< ShrinkCollectAliveSeparatedByRefs > {
slot : 0 ,
capacity : 0 ,
aligned_total_bytes : 0 ,
unrefed_pubkeys : Vec ::default ( ) ,
alive_accounts : ShrinkCollectAliveSeparatedByRefs {
one_ref : AliveAccounts {
slot : 0 ,
accounts : Vec ::default ( ) ,
bytes : 0 ,
} ,
many_refs : AliveAccounts {
slot : 0 ,
accounts : many_refs_accounts ,
bytes : 0 ,
} ,
} ,
alive_total_bytes : 0 ,
total_starting_accounts : 0 ,
all_are_zero_lamports : false ,
_index_entries_being_shrunk : Vec ::default ( ) ,
} ;
let pks = AccountsDb ::get_many_refs_pubkeys ( & shrink_collect ) ;
assert_eq! ( pks , expected ) ;
}
}
#[ test ]
fn test_revisit_accounts_with_many_refs ( ) {
let db = AccountsDb ::new_single_for_tests ( ) ;
let rent_epoch = 0 ;
let lamports = 0 ;
let executable = false ;
let owner = Pubkey ::default ( ) ;
let data = Vec ::new ( ) ;
let pubkey = solana_sdk ::pubkey ::new_rand ( ) ;
let pubkey2 = solana_sdk ::pubkey ::new_rand ( ) ;
let meta = StoredMeta {
write_version_obsolete : 5 ,
pubkey ,
data_len : 7 ,
} ;
let meta2 = StoredMeta {
write_version_obsolete : 5 ,
pubkey : pubkey2 ,
data_len : 7 ,
} ;
let account_meta = AccountMeta {
lamports ,
owner ,
executable ,
rent_epoch ,
} ;
let offset = 99 ;
let stored_size = 1 ; // size is 1 byte for each entry to test `bytes` later
let hash = Hash ::new_unique ( ) ;
let stored_account = StoredAccountMeta ::AppendVec ( AppendVecStoredAccountMeta {
meta : & meta ,
account_meta : & account_meta ,
data : & data ,
offset ,
stored_size ,
hash : & hash ,
} ) ;
let stored_account2 = StoredAccountMeta ::AppendVec ( AppendVecStoredAccountMeta {
meta : & meta2 ,
account_meta : & account_meta ,
data : & data ,
offset ,
stored_size ,
hash : & hash ,
} ) ;
let empty_account = AccountSharedData ::default ( ) ;
// sweep through different contents of `many_refs.accounts`
for many_refs_accounts in [
Vec ::default ( ) ,
vec! [ & stored_account ] ,
vec! [ & stored_account , & stored_account2 ] ,
] {
// how many of `many_ref_accounts` should be found in the index with ref_count=1
for mut accounts_with_ref_count_one in 0 .. many_refs_accounts . len ( ) {
// if `set_to_two_ref_count`, then add to index with ref_count=2, and expect same results as accounts_with_ref_count_one = 0
for set_to_two_ref_count in [ false , true ] {
many_refs_accounts
. iter ( )
. take ( accounts_with_ref_count_one )
. for_each ( | account | {
let k = account . pubkey ( ) ;
for slot in 1 .. if set_to_two_ref_count { 3 } else { 2 } {
// each upserting here (to a different slot) adds a refcount of 1 since entry is NOT cached
db . accounts_index . upsert (
slot ,
slot ,
k ,
& empty_account ,
& crate ::accounts_index ::AccountSecondaryIndexes ::default ( ) ,
AccountInfo ::default ( ) ,
& mut Vec ::default ( ) ,
UpsertReclaim ::IgnoreReclaims ,
) ;
}
} ) ;
if set_to_two_ref_count {
// expect same results as accounts_with_ref_count_one = 0 since we set refcounts to 2
accounts_with_ref_count_one = 0 ;
}
let mut shrink_collect = ShrinkCollect ::< ShrinkCollectAliveSeparatedByRefs > {
slot : 0 ,
capacity : 0 ,
aligned_total_bytes : 0 ,
unrefed_pubkeys : Vec ::default ( ) ,
alive_accounts : ShrinkCollectAliveSeparatedByRefs {
one_ref : AliveAccounts {
slot : 0 ,
accounts : Vec ::default ( ) ,
bytes : 0 ,
} ,
many_refs : AliveAccounts {
slot : 0 ,
accounts : many_refs_accounts . clone ( ) ,
bytes : many_refs_accounts . len ( ) ,
} ,
} ,
alive_total_bytes : 0 ,
total_starting_accounts : 0 ,
all_are_zero_lamports : false ,
_index_entries_being_shrunk : Vec ::default ( ) ,
} ;
db . revisit_accounts_with_many_refs ( & mut shrink_collect ) ;
// verify what got moved `many_refs` to `one_ref`
assert_eq! (
shrink_collect . alive_accounts . one_ref . accounts . len ( ) ,
accounts_with_ref_count_one
) ;
assert_eq! (
shrink_collect . alive_accounts . one_ref . bytes ,
accounts_with_ref_count_one
) ;
assert_eq! (
shrink_collect . alive_accounts . many_refs . accounts ,
many_refs_accounts [ accounts_with_ref_count_one .. ] . to_vec ( ) ,
) ;
assert_eq! (
shrink_collect . alive_accounts . many_refs . bytes ,
many_refs_accounts . len ( ) - accounts_with_ref_count_one
) ;
}
}
}
}
2023-06-08 07:50:18 -07:00
/// combines ALL possible slots in `sorted_slots`
fn combine_ancient_slots_packed_for_tests ( db : & AccountsDb , sorted_slots : Vec < Slot > ) {
// combine normal append vec(s) into packed ancient append vec
let tuning = PackedAncientStorageTuning {
max_ancient_slots : 0 ,
// re-combine/shrink 55% of the data savings this pass
percent_of_alive_shrunk_data : 55 ,
ideal_storage_size : NonZeroU64 ::new ( get_ancient_append_vec_capacity ( ) ) . unwrap ( ) ,
can_randomly_shrink : CAN_RANDOMLY_SHRINK_FALSE ,
} ;
let mut stats_sub = ShrinkStatsSub ::default ( ) ;
db . combine_ancient_slots_packed_internal ( sorted_slots , tuning , & mut stats_sub ) ;
}
2023-02-14 06:53:31 -08:00
#[ test ]
fn test_shrink_packed_ancient ( ) {
solana_logger ::setup ( ) ;
2023-06-09 10:38:34 -07:00
// When we pack ancient append vecs, the packed append vecs are recycled first if possible. This means they aren't dropped directly.
// This test tests that we are releasing Arc refcounts for storages when we pack them into ancient append vecs.
let db = AccountsDb ::new_single_for_tests ( ) ;
let initial_slot = 0 ;
// create append vecs that we'll fill the recycler with when we pack them into 1 packed append vec
create_storages_and_update_index ( & db , None , initial_slot , MAX_RECYCLE_STORES , true , None ) ;
let max_slot_inclusive = initial_slot + ( MAX_RECYCLE_STORES as Slot ) - 1 ;
let range = initial_slot .. ( max_slot_inclusive + 1 ) ;
// storages with Arc::strong_count > 1 cannot be pulled out of the recycling bin, so hold refcounts so these storages are never re-used by the actual test code
let _storages_hold_to_prevent_recycling = range
. filter_map ( | slot | db . storage . get_slot_storage_entry ( slot ) )
. collect ::< Vec < _ > > ( ) ;
// fill up the recycler with storages
combine_ancient_slots_packed_for_tests ( & db , ( initial_slot ..= max_slot_inclusive ) . collect ( ) ) ;
let mut starting_slot = max_slot_inclusive + 1 ;
2023-06-08 07:50:18 -07:00
for num_normal_slots in 1 .. 4 {
2023-06-09 10:38:34 -07:00
let mut storages = vec! [ ] ;
2023-06-08 07:50:18 -07:00
// build an ancient append vec at slot 'ancient_slot'
2023-06-09 10:38:34 -07:00
let ancient_slot = starting_slot ;
create_storages_and_update_index ( & db , None , ancient_slot , num_normal_slots , true , None ) ;
2023-06-08 07:50:18 -07:00
let max_slot_inclusive = ancient_slot + ( num_normal_slots as Slot ) ;
2023-06-09 10:38:34 -07:00
let range = ancient_slot .. ( max_slot_inclusive + 1 ) ;
storages . extend (
range
. clone ( )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( slot ) ) ,
) ;
let initial_accounts = get_all_accounts ( & db , range ) ;
2023-06-08 07:50:18 -07:00
compare_all_accounts (
& initial_accounts ,
& get_all_accounts ( & db , ancient_slot .. ( max_slot_inclusive + 1 ) ) ,
) ;
2023-02-14 06:53:31 -08:00
2023-06-08 07:50:18 -07:00
combine_ancient_slots_packed_for_tests (
& db ,
( ancient_slot ..= max_slot_inclusive ) . collect ( ) ,
) ;
2023-02-14 06:53:31 -08:00
2023-06-08 07:50:18 -07:00
compare_all_accounts (
& initial_accounts ,
& get_all_accounts ( & db , ancient_slot .. ( max_slot_inclusive + 1 ) ) ,
) ;
2023-06-09 10:38:34 -07:00
// verify only `storages` is holding a refcount to each storage we packed
storages
. iter ( )
. for_each ( | storage | assert_eq! ( Arc ::strong_count ( storage ) , 1 ) ) ;
2023-02-14 06:53:31 -08:00
2023-06-08 07:50:18 -07:00
// create a 2nd ancient append vec at 'next_slot'
let next_slot = max_slot_inclusive + 1 ;
create_storages_and_update_index ( & db , None , next_slot , num_normal_slots , true , None ) ;
let max_slot_inclusive = next_slot + ( num_normal_slots as Slot ) ;
2023-06-09 10:38:34 -07:00
let range_all = ancient_slot .. ( max_slot_inclusive + 1 ) ;
let range = next_slot .. ( max_slot_inclusive + 1 ) ;
storages = vec! [ ] ;
storages . extend (
range
. clone ( )
. filter_map ( | slot | db . storage . get_slot_storage_entry ( slot ) ) ,
) ;
let initial_accounts_all = get_all_accounts ( & db , range_all . clone ( ) ) ;
let initial_accounts = get_all_accounts ( & db , range . clone ( ) ) ;
2023-06-08 07:50:18 -07:00
compare_all_accounts (
2023-06-09 10:38:34 -07:00
& initial_accounts_all ,
& get_all_accounts ( & db , range_all . clone ( ) ) ,
2023-06-08 07:50:18 -07:00
) ;
2023-06-09 10:38:34 -07:00
compare_all_accounts ( & initial_accounts , & get_all_accounts ( & db , range . clone ( ) ) ) ;
2023-02-14 06:53:31 -08:00
2023-06-09 10:38:34 -07:00
combine_ancient_slots_packed_for_tests ( & db , range . clone ( ) . collect ( ) ) ;
2023-02-14 06:53:31 -08:00
2023-06-09 10:38:34 -07:00
compare_all_accounts ( & initial_accounts_all , & get_all_accounts ( & db , range_all ) ) ;
compare_all_accounts ( & initial_accounts , & get_all_accounts ( & db , range ) ) ;
// verify only `storages` is holding a refcount to each storage we packed
storages
. iter ( )
. for_each ( | storage | assert_eq! ( Arc ::strong_count ( storage ) , 1 ) ) ;
starting_slot = max_slot_inclusive + 1 ;
2023-06-08 07:50:18 -07:00
}
2023-02-14 06:53:31 -08:00
}
2023-04-17 20:05:59 -07:00
#[ test ]
fn test_addref_accounts_failed_to_shrink_ancient ( ) {
let db = AccountsDb ::new_single_for_tests ( ) ;
let empty_account = AccountSharedData ::default ( ) ;
for count in 0 .. 3 {
let unrefed_pubkeys = ( 0 .. count )
. map ( | _ | solana_sdk ::pubkey ::new_rand ( ) )
. collect ::< Vec < _ > > ( ) ;
// how many of `many_ref_accounts` should be found in the index with ref_count=1
let mut expected_ref_counts = HashMap ::< Pubkey , u64 > ::default ( ) ;
unrefed_pubkeys . iter ( ) . for_each ( | k | {
for slot in 0 .. 2 {
// each upsert here (to a different slot) adds a refcount of 1 since entry is NOT cached
db . accounts_index . upsert (
slot ,
slot ,
k ,
& empty_account ,
& crate ::accounts_index ::AccountSecondaryIndexes ::default ( ) ,
AccountInfo ::default ( ) ,
& mut Vec ::default ( ) ,
UpsertReclaim ::IgnoreReclaims ,
) ;
}
// set to 2 initially, made part of `unrefed_pubkeys`, expect it to be addref'd to 3
expected_ref_counts . insert ( * k , 3 ) ;
} ) ;
let shrink_collect = ShrinkCollect ::< ShrinkCollectAliveSeparatedByRefs > {
// the only interesting field
unrefed_pubkeys : unrefed_pubkeys . iter ( ) . collect ( ) ,
// irrelevant fields
slot : 0 ,
capacity : 0 ,
aligned_total_bytes : 0 ,
alive_accounts : ShrinkCollectAliveSeparatedByRefs {
one_ref : AliveAccounts ::default ( ) ,
many_refs : AliveAccounts ::default ( ) ,
} ,
alive_total_bytes : 0 ,
total_starting_accounts : 0 ,
all_are_zero_lamports : false ,
_index_entries_being_shrunk : Vec ::default ( ) ,
} ;
let accounts_to_combine = AccountsToCombine {
accounts_keep_slots : HashMap ::default ( ) ,
accounts_to_combine : vec ! [ shrink_collect ] ,
target_slots_sorted : Vec ::default ( ) ,
} ;
db . addref_accounts_failed_to_shrink_ancient ( accounts_to_combine ) ;
db . accounts_index . scan (
unrefed_pubkeys . iter ( ) ,
| k , slot_refs , _entry | {
assert_eq! ( expected_ref_counts . remove ( k ) . unwrap ( ) , slot_refs . unwrap ( ) . 1 ) ;
2023-05-05 10:39:32 -07:00
AccountsIndexScanResult ::OnlyKeepInMemoryIfDirty
2023-04-17 20:05:59 -07:00
} ,
None ,
false ,
) ;
// should have removed all of them
assert! ( expected_ref_counts . is_empty ( ) ) ;
}
}
2022-05-10 14:41:04 -07:00
}