Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update lru for read only cache if it has been long enough since last access #32560

Merged
merged 3 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 8 additions & 2 deletions accounts-db/src/accounts_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2438,7 +2438,10 @@ impl AccountsDb {
base_working_path: Option<PathBuf>,
) -> Self {
let num_threads = get_thread_count();
const MAX_READ_ONLY_CACHE_DATA_SIZE: usize = 400_000_000; // 400M bytes
// 400M bytes
const MAX_READ_ONLY_CACHE_DATA_SIZE: usize = 400_000_000;
// read only cache does not update lru on read of an entry unless it has been at least this many ms since the last lru update
const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE: u32 = 100;

let (base_working_path, accounts_hash_cache_path, temp_accounts_hash_cache_path) =
match base_working_path {
Expand Down Expand Up @@ -2483,7 +2486,10 @@ impl AccountsDb {
storage: AccountStorage::default(),
accounts_cache: AccountsCache::default(),
sender_bg_hasher: None,
read_only_accounts_cache: ReadOnlyAccountsCache::new(MAX_READ_ONLY_CACHE_DATA_SIZE),
read_only_accounts_cache: ReadOnlyAccountsCache::new(
MAX_READ_ONLY_CACHE_DATA_SIZE,
READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE,
),
recycle_stores: RwLock::new(RecycleStores::default()),
uncleaned_pubkeys: DashMap::new(),
next_id: AtomicAppendVecId::new(0),
Expand Down
48 changes: 41 additions & 7 deletions accounts-db/src/read_only_accounts_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use {
account::{AccountSharedData, ReadableAccount},
clock::Slot,
pubkey::Pubkey,
timing::timestamp,
},
std::sync::{
atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering},
Expand All @@ -23,7 +24,10 @@ type ReadOnlyCacheKey = (Pubkey, Slot);
#[derive(Debug)]
struct ReadOnlyAccountCacheEntry {
account: AccountSharedData,
/// Index of the entry in the eviction queue.
brooksprumo marked this conversation as resolved.
Show resolved Hide resolved
index: AtomicU32, // Index of the entry in the eviction queue.
/// lower bits of last timestamp when eviction queue was updated, in ms
last_update_time: AtomicU32,
}

#[derive(Default, Debug)]
Expand Down Expand Up @@ -53,7 +57,7 @@ impl ReadOnlyCacheStats {
}

#[derive(Debug)]
pub struct ReadOnlyAccountsCache {
pub(crate) struct ReadOnlyAccountsCache {
cache: DashMap<ReadOnlyCacheKey, ReadOnlyAccountCacheEntry>,
/// When an item is first entered into the cache, it is added to the end of
/// the queue. Also each time an entry is looked up from the cache it is
Expand All @@ -63,18 +67,21 @@ pub struct ReadOnlyAccountsCache {
queue: Mutex<IndexList<ReadOnlyCacheKey>>,
max_data_size: usize,
data_size: AtomicUsize,
// read only cache does not update lru on read of an entry unless it has been at least this many ms since the last lru update
ms_to_skip_lru_update: u32,

// Performance statistics
stats: ReadOnlyCacheStats,
}

impl ReadOnlyAccountsCache {
pub(crate) fn new(max_data_size: usize) -> Self {
pub(crate) fn new(max_data_size: usize, ms_to_skip_lru_update: u32) -> Self {
Self {
max_data_size,
cache: DashMap::default(),
queue: Mutex::<IndexList<ReadOnlyCacheKey>>::default(),
data_size: AtomicUsize::default(),
ms_to_skip_lru_update,
stats: ReadOnlyCacheStats::default(),
}
}
Expand Down Expand Up @@ -103,10 +110,15 @@ impl ReadOnlyAccountsCache {
// Move the entry to the end of the queue.
// self.queue is modified while holding a reference to the cache entry;
// so that another thread cannot write to the same key.
{
// If we updated the eviction queue within this much time, then leave it where it is. We're likely to hit it again.
let update_lru = entry.ms_since_last_update() >= self.ms_to_skip_lru_update;
if update_lru {
let mut queue = self.queue.lock().unwrap();
queue.remove(entry.index());
entry.set_index(queue.insert_last(key));
entry
.last_update_time
.store(ReadOnlyAccountCacheEntry::timestamp(), Ordering::Relaxed);
brooksprumo marked this conversation as resolved.
Show resolved Hide resolved
}
let account = entry.account.clone();
drop(entry);
Expand Down Expand Up @@ -185,7 +197,11 @@ impl ReadOnlyAccountCacheEntry {
fn new(account: AccountSharedData, index: Index) -> Self {
let index = unsafe { std::mem::transmute::<Index, u32>(index) };
let index = AtomicU32::new(index);
Self { account, index }
Self {
account,
index,
last_update_time: AtomicU32::new(Self::timestamp()),
}
}

#[inline]
Expand All @@ -199,6 +215,16 @@ impl ReadOnlyAccountCacheEntry {
let index = unsafe { std::mem::transmute::<Index, u32>(index) };
self.index.store(index, Ordering::Relaxed);
}

/// lower bits of current timestamp. We don't need higher bits and u32 packs with Index u32 in `ReadOnlyAccountCacheEntry`
fn timestamp() -> u32 {
timestamp() as u32
}

/// ms since `last_update_time` timestamp
fn ms_since_last_update(&self) -> u32 {
Self::timestamp().wrapping_sub(self.last_update_time.load(Ordering::Relaxed))
brooksprumo marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(test)]
Expand Down Expand Up @@ -226,7 +252,8 @@ mod tests {
let per_account_size = CACHE_ENTRY_SIZE;
let data_size = 100;
let max = data_size + per_account_size;
let cache = ReadOnlyAccountsCache::new(max);
let cache =
ReadOnlyAccountsCache::new(max, READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS);
let slot = 0;
assert!(cache.load(Pubkey::default(), slot).is_none());
assert_eq!(0, cache.cache_len());
Expand Down Expand Up @@ -261,7 +288,8 @@ mod tests {

// can store 2 items, 3rd item kicks oldest item out
let max = (data_size + per_account_size) * 2;
let cache = ReadOnlyAccountsCache::new(max);
let cache =
ReadOnlyAccountsCache::new(max, READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS);
cache.store(key1, slot, account1.clone());
assert_eq!(100 + per_account_size, cache.data_size());
assert!(accounts_equal(&cache.load(key1, slot).unwrap(), &account1));
Expand All @@ -284,13 +312,19 @@ mod tests {
assert_eq!(2, cache.cache_len());
}

/// tests like to deterministically update lru always
const READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS: u32 = 0;

#[test]
fn test_read_only_accounts_cache_random() {
const SEED: [u8; 32] = [0xdb; 32];
const DATA_SIZE: usize = 19;
const MAX_CACHE_SIZE: usize = 17 * (CACHE_ENTRY_SIZE + DATA_SIZE);
let mut rng = ChaChaRng::from_seed(SEED);
let cache = ReadOnlyAccountsCache::new(MAX_CACHE_SIZE);
let cache = ReadOnlyAccountsCache::new(
MAX_CACHE_SIZE,
READ_ONLY_CACHE_MS_TO_SKIP_LRU_UPDATE_FOR_TESTS,
);
let slots: Vec<Slot> = repeat_with(|| rng.gen_range(0..1000)).take(5).collect();
let pubkeys: Vec<Pubkey> = repeat_with(|| {
let mut arr = [0u8; 32];
Expand Down