AcctIdx: respect disk idx mem size param (#22050)

This commit is contained in:
Jeff Washington (jwash) 2021-12-22 09:54:05 -06:00 committed by GitHub
parent 4d62f03297
commit 61cc7b10a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 72 additions and 17 deletions

View File

@ -806,6 +806,13 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
thread_rng().gen_range(0, N) == 0
}
/// assumes 1 entry in the slot list. Ignores overhead of the HashMap and such
fn approx_size_of_one_entry() -> usize {
std::mem::size_of::<T>()
+ std::mem::size_of::<Pubkey>()
+ std::mem::size_of::<AccountMapEntry<T>>()
}
/// return true if 'entry' should be removed from the in-mem index
fn should_remove_from_mem(
&self,
@ -813,24 +820,30 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
entry: &AccountMapEntry<T>,
startup: bool,
update_stats: bool,
exceeds_budget: bool,
) -> bool {
// this could be tunable dynamically based on memory pressure
// we could look at more ages or we could throw out more items we are choosing to keep in the cache
if startup || (current_age == entry.age()) {
// only read the slot list if we are planning to throw the item out
let slot_list = entry.slot_list.read().unwrap();
if slot_list.len() != 1 {
if update_stats {
Self::update_stat(&self.stats().held_in_mem_slot_list_len, 1);
}
false // keep 0 and > 1 slot lists in mem. They will be cleaned or shrunk soon.
if exceeds_budget {
// if we are already holding too many items in-mem, then we need to be more aggressive at kicking things out
true
} else {
// keep items with slot lists that contained cached items
let remove = !slot_list.iter().any(|(_, info)| info.is_cached());
if !remove && update_stats {
Self::update_stat(&self.stats().held_in_mem_slot_list_cached, 1);
// only read the slot list if we are planning to throw the item out
let slot_list = entry.slot_list.read().unwrap();
if slot_list.len() != 1 {
if update_stats {
Self::update_stat(&self.stats().held_in_mem_slot_list_len, 1);
}
false // keep 0 and > 1 slot lists in mem. They will be cleaned or shrunk soon.
} else {
// keep items with slot lists that contained cached items
let remove = !slot_list.iter().any(|(_, info)| info.is_cached());
if !remove && update_stats {
Self::update_stat(&self.stats().held_in_mem_slot_list_cached, 1);
}
remove
}
remove
}
} else {
false
@ -848,6 +861,12 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
return;
}
let in_mem_count = self.stats().count_in_mem.load(Ordering::Relaxed);
let limit = self.storage.mem_budget_mb;
let exceeds_budget = limit
.map(|limit| in_mem_count * Self::approx_size_of_one_entry() >= limit * 1024 * 1024)
.unwrap_or_default();
// may have to loop if disk has to grow and we have to restart
loop {
let mut removes;
@ -863,7 +882,7 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
removes = Vec::with_capacity(map.len());
let m = Measure::start("flush_scan_and_update"); // we don't care about lock time in this metric - bg threads can wait
for (k, v) in map.iter() {
if self.should_remove_from_mem(current_age, v, startup, true) {
if self.should_remove_from_mem(current_age, v, startup, true, exceeds_budget) {
removes.push(*k);
} else if Self::random_chance_of_eviction() {
removes_random.push(*k);
@ -902,9 +921,19 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
let m = Measure::start("flush_remove_or_grow");
match disk_resize {
Ok(_) => {
if !self.flush_remove_from_cache(removes, current_age, startup, false)
|| !self.flush_remove_from_cache(removes_random, current_age, startup, true)
{
if !self.flush_remove_from_cache(
removes,
current_age,
startup,
false,
exceeds_budget,
) || !self.flush_remove_from_cache(
removes_random,
current_age,
startup,
true,
exceeds_budget,
) {
iterate_for_age = false; // did not make it all the way through this bucket, so didn't handle age completely
}
Self::update_time_stat(&self.stats().flush_remove_us, m);
@ -934,6 +963,7 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
current_age: Age,
startup: bool,
randomly_evicted: bool,
exceeds_budget: bool,
) -> bool {
let mut completed_scan = true;
if removes.is_empty() {
@ -956,7 +986,13 @@ impl<T: IndexValue> InMemAccountsIndex<T> {
if v.dirty()
|| (!randomly_evicted
&& !self.should_remove_from_mem(current_age, v, startup, false))
&& !self.should_remove_from_mem(
current_age,
v,
startup,
false,
exceeds_budget,
))
{
// marked dirty or bumped in age after we looked above
// these will be handled in later passes
@ -1053,6 +1089,18 @@ mod tests {
AccountMapEntryMeta::default(),
));
// exceeded budget
assert!(bucket.should_remove_from_mem(
current_age,
&Arc::new(AccountMapEntryInner::new(
vec![],
ref_count,
AccountMapEntryMeta::default()
)),
startup,
false,
true,
));
// empty slot list
assert!(!bucket.should_remove_from_mem(
current_age,
@ -1063,6 +1111,7 @@ mod tests {
)),
startup,
false,
false,
));
// 1 element slot list
assert!(bucket.should_remove_from_mem(
@ -1070,6 +1119,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));
// 2 element slot list
assert!(!bucket.should_remove_from_mem(
@ -1081,6 +1131,7 @@ mod tests {
)),
startup,
false,
false,
));
{
@ -1095,6 +1146,7 @@ mod tests {
)),
startup,
false,
false,
));
}
@ -1104,6 +1156,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));
// 1 element slot list, but not current age
@ -1113,6 +1166,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));
// 1 element slot list, but at startup and age not current
@ -1122,6 +1176,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));
}