diff --git a/runtime/src/accounts_index.rs b/runtime/src/accounts_index.rs index c453e4a2e3..5a92c538e2 100644 --- a/runtime/src/accounts_index.rs +++ b/runtime/src/accounts_index.rs @@ -905,6 +905,11 @@ impl AccountsIndex { AccountsIndexIterator::new(self, range, collect_all_unsorted) } + /// is the accounts index using disk as a backing store + pub fn is_disk_index_enabled(&self) -> bool { + self.storage.storage.is_disk_index_enabled() + } + fn do_checked_scan_accounts( &self, metric_name: &'static str, diff --git a/runtime/src/accounts_index_storage.rs b/runtime/src/accounts_index_storage.rs index f13b4327a9..4462c386c5 100644 --- a/runtime/src/accounts_index_storage.rs +++ b/runtime/src/accounts_index_storage.rs @@ -102,11 +102,18 @@ impl AccountsIndexStorage { } self.storage.set_startup(value); if !value { + // transitioning from startup to !startup (ie. steady state) // shutdown the bg threads *self.startup_worker_threads.lock().unwrap() = None; + // maybe shrink hashmaps + self.shrink_to_fit(); } } + fn shrink_to_fit(&self) { + self.in_mem.iter().for_each(|mem| mem.shrink_to_fit()) + } + fn num_threads() -> usize { std::cmp::max(2, num_cpus::get() / 4) } diff --git a/runtime/src/bucket_map_holder.rs b/runtime/src/bucket_map_holder.rs index f0b4d269e7..a02019dccf 100644 --- a/runtime/src/bucket_map_holder.rs +++ b/runtime/src/bucket_map_holder.rs @@ -57,6 +57,11 @@ impl Debug for BucketMapHolder { #[allow(clippy::mutex_atomic)] impl BucketMapHolder { + /// is the accounts index using disk as a backing store + pub fn is_disk_index_enabled(&self) -> bool { + self.disk.is_some() + } + pub fn increment_age(&self) { // since we are about to change age, there are now 0 buckets that have been flushed at this age // this should happen before the age.fetch_add @@ -352,6 +357,7 @@ pub mod tests { solana_logger::setup(); let bins = 100; let test = BucketMapHolder::::new(bins, &Some(AccountsIndexConfig::default()), 1); + assert!(!test.is_disk_index_enabled()); let bins = test.bins as u64; let interval_ms = test.age_interval_ms(); // 90% of time elapsed, all but 1 bins flushed, should not wait since we'll end up right on time @@ -376,6 +382,17 @@ pub mod tests { assert_eq!(result, None); } + #[test] + fn test_disk_index_enabled() { + let bins = 1; + let config = AccountsIndexConfig { + index_limit_mb: Some(0), + ..AccountsIndexConfig::default() + }; + let test = BucketMapHolder::::new(bins, &Some(config), 1); + assert!(test.is_disk_index_enabled()); + } + #[test] fn test_age_time() { solana_logger::setup(); diff --git a/runtime/src/in_mem_accounts_index.rs b/runtime/src/in_mem_accounts_index.rs index fde34132f1..2098e09caa 100644 --- a/runtime/src/in_mem_accounts_index.rs +++ b/runtime/src/in_mem_accounts_index.rs @@ -28,13 +28,15 @@ type K = Pubkey; type CacheRangesHeld = RwLock>>>; pub type SlotT = (Slot, T); +type InMemMap = HashMap>; + #[allow(dead_code)] // temporary during staging // one instance of this represents one bin of the accounts index. pub struct InMemAccountsIndex { last_age_flushed: AtomicU8, // backing store - map_internal: RwLock>>, + map_internal: RwLock>, storage: Arc>, bin: usize, @@ -104,6 +106,16 @@ impl InMemAccountsIndex { &self.map_internal } + /// Release entire in-mem hashmap to free all memory associated with it. + /// Idea is that during startup we needed a larger map than we need during runtime. + /// When using disk-buckets, in-mem index grows over time with dynamic use and then shrinks, in theory back to 0. + pub fn shrink_to_fit(&self) { + // shrink_to_fit could be quite expensive on large map sizes, which 'no disk buckets' could produce, so avoid shrinking in case we end up here + if self.storage.is_disk_index_enabled() { + self.map_internal.write().unwrap().shrink_to_fit(); + } + } + pub fn items(&self, range: &Option<&R>) -> Vec<(K, AccountMapEntry)> where R: RangeBounds + std::fmt::Debug, @@ -898,6 +910,10 @@ impl InMemAccountsIndex { occupied.remove(); } } + if map.is_empty() { + map.shrink_to_fit(); + } + drop(map); self.stats() .insert_or_delete_mem_count(false, self.bin, removed); Self::update_stat(&self.stats().flush_entries_removed_from_mem, removed as u64);