Skip to content
This repository has been archived by the owner on Jan 22, 2025. It is now read-only.

AcctIdx: respect disk idx mem size param #22050

Merged
merged 1 commit into from
Dec 22, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 72 additions & 17 deletions runtime/src/in_mem_accounts_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -806,31 +806,44 @@ 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 {
brooksprumo marked this conversation as resolved.
Show resolved Hide resolved
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,
current_age: Age,
entry: &AccountMapEntry<T>,
startup: bool,
update_stats: bool,
exceeds_budget: bool,
brooksprumo marked this conversation as resolved.
Show resolved Hide resolved
) -> 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
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -1063,13 +1111,15 @@ mod tests {
)),
startup,
false,
false,
));
// 1 element slot list
assert!(bucket.should_remove_from_mem(
current_age,
&one_element_slot_list_entry,
startup,
false,
false,
));
// 2 element slot list
assert!(!bucket.should_remove_from_mem(
Expand All @@ -1081,6 +1131,7 @@ mod tests {
)),
startup,
false,
false,
));

{
Expand All @@ -1095,6 +1146,7 @@ mod tests {
)),
startup,
false,
false,
));
}

Expand All @@ -1104,6 +1156,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));

// 1 element slot list, but not current age
Expand All @@ -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
Expand All @@ -1122,6 +1176,7 @@ mod tests {
&one_element_slot_list_entry,
startup,
false,
false,
));
}

Expand Down