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

Optimize max_flushed_root update in flushing roots #4320

Merged
merged 3 commits into from
Jan 10, 2025

Conversation

HaoranYi
Copy link

@HaoranYi HaoranYi commented Jan 7, 2025

Problem

When updating max flushed root in accounts index, we don't need to update it for ever flushed root in the loop. We can update it with the last flushed root.

split from #4304

Summary of Changes

update max flushed root in account index with the last flushed root.

Fixes #

@HaoranYi HaoranYi changed the title opt: fetch_max flush root Optimize max_flushed_root update in flushing roots Jan 7, 2025
@@ -6295,33 +6295,35 @@ impl AccountsDb {
});

// Always flush up to `requested_flush_root`, which is necessary for things like snapshotting.
let cached_roots: BTreeSet<Slot> = self.accounts_cache.clear_roots(requested_flush_root);
let flushed_roots: BTreeSet<Slot> = self.accounts_cache.clear_roots(requested_flush_root);
Copy link
Author

Choose a reason for hiding this comment

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

rename: cached_roots -> flushed_roots for clarity.

@HaoranYi
Copy link
Author

HaoranYi commented Jan 7, 2025

probably won't matter much in actual performance. But it is still nice to have.

Copy link

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

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

One thing I thought of, where dooes the accounts_cache's max_flush_root get used? Anywhere in the foreground/transaction processing? That's the only place I can think of where delaying when set_max_flush_root() may make a difference.

@HaoranYi
Copy link
Author

HaoranYi commented Jan 7, 2025

One thing I thought of, where dooes the accounts_cache's max_flush_root get used? Anywhere in the foreground/transaction processing? That's the only place I can think of where delaying when set_max_flush_root() may make a difference.

There are 3 usages of max_flushed_root.

  1. add_root()
  2. flush_accounts_cache()
  3. handle_snapshot_request()

2 and 3 are from the same account background thread. So this change should not
affect them.

1 is used from bank thread.

We can prove that any call of slot for add_root() will be greater than max_flushed_root.

Proof:

  1. root will be strictly increasing when we call add_root().
  2. max_flushed_root is computed from the split from w_maybe_flushed_roots
  3. 'w_maybe_flushed_roots' is populated by add_root().

Claim: Any slot 'S' that called on add_root() must be greater than the max slot in w_maybe_flushed_roots.
This is because 'w_maybe_flushed_roots' only contains previous slots, which are less than current slot 'S'.

Claim: max_flushed_root must be less than max slot in w_maybe_flushed_roots because of (2).

Therefore, S > max('w_maybe_flushed_roots') >= max_flushed_root.

Q.E.D

Therefore, 1 won't be affected by this change too.

@HaoranYi HaoranYi requested a review from brooksprumo January 8, 2025 15:18
brooksprumo
brooksprumo previously approved these changes Jan 8, 2025
Copy link

@brooksprumo brooksprumo left a comment

Choose a reason for hiding this comment

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

:shipit:

@HaoranYi
Copy link
Author

HaoranYi commented Jan 9, 2025

I think current code is good under the aggressive flush condition.

Imaging if slot X is flushed before it rooted. What the current code does
is to remove it from the dashmap and create a storage for slot X. Note that
We don't update 'max_flushed_root' or 'maybe_unflushed_roots'. So logically
from the point view of 'max_flushed_root', this slot X is not flushed.

When X is rooted, then we will added to maybe_unflushed_roots. By the time
flush see X again, it will find out that it doesn't exist in dashmap -
already flushed, and don't need to flush. But we still need to update
max_flushed_root in the 'logical' sense. So logically max_flushed_root is
correct.

In essence, 'max_flushed_root' is tracking the max flush root 'logically' from
the request flush slot, not the actual slots flushed physically, which could
be due to by aggressive flush.

Because max_flushed_root is tracking the flushed_root "logically", the flush
of unrooted storage by aggressive flush doesn't matter here. As what have
proved in the PR, the optimization is correct in logical flushed_root sense. Therefore, I
think the optimization good.

@jeffwashington

if root > max_flushed_root || (root == max_flushed_root && root == 0) {
self.maybe_unflushed_roots.write().unwrap().insert(root);
}
self.maybe_unflushed_roots.write().unwrap().insert(root);
Copy link
Author

Choose a reason for hiding this comment

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

Since we can prove that slot > max_flush_root, we don't need to check root > max_flush_root.
And since we remove the slot > max_flush_root check, there is no need to special case root == 0 for genesis block too.

Copy link

Choose a reason for hiding this comment

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

we can prove that slot > max_flush_root

Is this because roots are monotonically increasing?

Copy link
Author

Choose a reason for hiding this comment

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

yes, exactly!

Copy link

@jeffwashington jeffwashington left a comment

Choose a reason for hiding this comment

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

lgtm
I believe I understand what is going on here and this looks correct.

Copy link

@carllin carllin 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, been a while since I've been in this area :)

// Note that self.flush_slot_cache_with_clean() can return None if the
// slot is already been flushed. This can happen if the cache is
// overwhelmed and we flushed some yet to be rooted frozen slots.
// However, Independent of whether the last slot was actually flushed
Copy link

Choose a reason for hiding this comment

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

nit Independent -> independent

Copy link
Author

Choose a reason for hiding this comment

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

yeah. I will change it in another pr so that people don't need to re-approve this one again.

if root > max_flushed_root || (root == max_flushed_root && root == 0) {
self.maybe_unflushed_roots.write().unwrap().insert(root);
}
self.maybe_unflushed_roots.write().unwrap().insert(root);
Copy link

Choose a reason for hiding this comment

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

we can prove that slot > max_flush_root

Is this because roots are monotonically increasing?

@HaoranYi HaoranYi merged commit cb9451c into anza-xyz:master Jan 10, 2025
40 checks passed
@HaoranYi HaoranYi deleted the fetch_max branch January 10, 2025 16:07
@HaoranYi HaoranYi mentioned this pull request Jan 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants