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

Fix rooted accounts cleanup, simplify locking #12194

Merged
merged 20 commits into from
Sep 28, 2020

Conversation

carllin
Copy link
Contributor

@carllin carllin commented Sep 11, 2020

Problem

  1. There were some race conditions between accounts cleanup and accounts store
  2. Cleanup logic was divided into too many areas getting very complicated/hard to understand

Summary of Changes

  1. No more cleanup of old rooted storage entries in store(), store() now only purges storage entries from the same slot
  2. Cleaning up rooted storage entries only occurs in clean() and do_shrink_slots(), via a single handle_reclaims() function
  3. handle_reclaims no longer blocks the store() pipe;ine, so it will always force clean, removing the need for the dead slots counter

Fixes #

@carllin carllin requested review from ryoqun and sakridge September 11, 2020 23:10
@carllin carllin force-pushed the FixAccountsDb branch 2 times, most recently from 6c89460 to 91c208a Compare September 11, 2020 23:12
@codecov
Copy link

codecov bot commented Sep 12, 2020

Codecov Report

Merging #12194 into master will decrease coverage by 0.0%.
The diff coverage is 72.4%.

@@            Coverage Diff            @@
##           master   #12194     +/-   ##
=========================================
- Coverage    82.0%    81.9%   -0.1%     
=========================================
  Files         354      354             
  Lines       82643    82754    +111     
=========================================
+ Hits        67788    67857     +69     
- Misses      14855    14897     +42     

Comment on lines 812 to 828
if dead_slots.len() == 1 {
assert!(dead_slots.contains(&expected_slot));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits (I think this reads better):

if let Some(dead_slot) = dead_slots.first() {
    assert_eq!(dead_slot, expected_slot);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately I don't think Hashset has a first() method 😢

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.iter().next() tho?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or .values().first()? Anyway, this isn't important at all. :)

@@ -476,7 +474,6 @@ impl Default for AccountsDB {
min_num_stores: num_threads,
bank_hashes: RwLock::new(bank_hashes),
frozen_accounts: HashMap::new(),
dead_slots: RwLock::new(HashSet::new()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:byebye:

runtime/src/accounts_db.rs Outdated Show resolved Hide resolved
runtime/src/accounts_db.rs Outdated Show resolved Hide resolved
runtime/src/accounts_db.rs Outdated Show resolved Hide resolved
runtime/src/accounts_db.rs Outdated Show resolved Hide resolved
reclaims.extend(list.iter().filter(|(f, _)| *f == slot).cloned());
list.retain(|(f, _)| *f != slot);

lock.0.fetch_add(1, Ordering::Relaxed);
list.push((slot, account_info));
// now, do lazy clean
self.purge_older_root_entries(&mut list, reclaims);
Copy link
Contributor

@ryoqun ryoqun Sep 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is generally good direction. But, let's surface the consequences for quick consensus.

Is it ok to accept (1) slightly bigger index size, (2) more visible spikes at each clean_accounts(), and (3) amortized longer lookup time for every account index read for frequently-updated accounts (because of larger list)?

If so, why older_roots has been purged here to begin with? Just because of lack of independent older_root cleaning mechanism at the ancient times?

Also, the index size will be more easily controlled by malicious tx pattern.

I think these concerns are non-issue for now and only hypothetical worries.

As a good by-product, with this relaxed attitude to the index size, we pave a way for rather straightforward implmentation for #11161. Or, even snapshot commitments (now that capitalization check (#11927 ) is enabled, there is some need for this as well).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryoqun, yeah I think 1, 2, an 3 are acceptable tradeoffs.

"If so, why older_roots has been purged here to begin with? Just because of lack of independent older_root cleaning mechanism at the ancient times?"

Yeah, I think it was just a byproduct of the initial implementation 😸

ryoqun
ryoqun previously approved these changes Sep 14, 2020
Copy link
Contributor

@ryoqun ryoqun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with nits!

Welcome to AccountsDB land with such a great to improvement. :)

Really thanks for spotting and fixing the race condition...

@mergify mergify bot dismissed ryoqun’s stale review September 14, 2020 19:46

Pull request has been modified.

@ryoqun
Copy link
Contributor

ryoqun commented Sep 15, 2020

@carllin Did you notice any performance difference? Like any noticeable store throughput increase with slightly more memory usage? :)

ryoqun
ryoqun previously approved these changes Sep 15, 2020
Copy link
Contributor

@ryoqun ryoqun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM in code wise. Curious about the perf. impact as well.

@ryoqun
Copy link
Contributor

ryoqun commented Sep 15, 2020

@carllin Did you notice any performance difference? Like any noticeable store throughput increase with slightly more memory usage? :)

Also, does this somewhat alleviates the get_program_accounts bottleneck? Not much because accouns_index is still there?

@ryoqun
Copy link
Contributor

ryoqun commented Sep 15, 2020

Also, I'd rather want to ensure that this pr doesn't create bad (=unloadable) snapshosts after running a while against testnet/mainnet-beta.

@carllin carllin added the v1.3 label Sep 15, 2020
@carllin
Copy link
Contributor Author

carllin commented Sep 15, 2020

@carllin Did you notice any performance difference? Like any noticeable store throughput increase with slightly more memory usage? :)

@ryoqun When I was running against a small 5 node cluster, it didn't seem to affect things too much

Also, does this somewhat alleviates the get_program_accounts bottleneck? Not much because accouns_index is still there?

Yeah I suspect that bottleneck won't improve until I also fix the accounts index

Also, I'd rather want to ensure that this pr doesn't create bad (=unloadable) snapshosts after running a while against testnet/mainnet-beta.

Yeah should I upgrade one of our testnet/mainnet nodes with this fix first?

@ryoqun
Copy link
Contributor

ryoqun commented Sep 15, 2020

@carllin

Yeah should I upgrade one of our testnet/mainnet nodes with this fix first?

Or, you can just run a GCE instance running a validator ad-hoc on it, connecting to the testnet/mainnet.

In both of cases, you'll need to ssh to run while (cp $(ls -tr snapshot*tar.zst | tail -n 1) ./path/to/test/dir && solana-ledger-tool verify --ledger ./path/to/test/dir; do sleep 60; done or similar.

I think few hours is enough.

And cross your fingers. :)

sakridge
sakridge previously approved these changes Sep 15, 2020
Copy link
Contributor

@sakridge sakridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me as well

@carllin
Copy link
Contributor Author

carllin commented Sep 15, 2020

@ryoqun I've set up a validator on MB with these changes and the snapshot validation running at 34.83.141.238. Fingers very much crossed 🤞

@mergify mergify bot dismissed stale reviews from ryoqun and sakridge September 17, 2020 07:39

Pull request has been modified.

@ryoqun
Copy link
Contributor

ryoqun commented Oct 16, 2020

image

FYI: @carllin this is one of testnet validators which updated to v1.4. nothing odd; it just looks like set_root got a lot faster and unref got slower.

@carllin
Copy link
Contributor Author

carllin commented Oct 16, 2020

@ryoqun oh interesting! The unref is probably slower because there are a lot more dead pubkeys to clean in clean_accounts here because we aggregate the clean?

for (slot, pubkey) in slot_pubkeys {
                if let Some(ref mut purged_account_slots) = purged_account_slots {
                    purged_account_slots.entry(pubkey).or_default().insert(slot);
                }
                index.unref_from_storage(&pubkey);
            }

@ryoqun
Copy link
Contributor

ryoqun commented Oct 16, 2020

yeah, might be true. :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants