Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Canonical state cache #2308

Merged
merged 4 commits into from
Sep 27, 2016
Merged

Canonical state cache #2308

merged 4 commits into from
Sep 27, 2016

Conversation

arkpar
Copy link
Collaborator

@arkpar arkpar commented Sep 24, 2016

Still left to do:

  • Handle reorgs more efficiently. Right now the whole cache is cleared on reorg
  • Don't copy data from global cache to state and back (a major slowdown)
  • Bind cache size to memory used, not to the number of accounts

@rphmeier
Copy link
Contributor

(note that this is against beta -- review carefully)

@arkpar arkpar added the A0-pleasereview 🤓 Pull request needs code review. label Sep 24, 2016
}
io.update_registration(self.token).ok();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is a fix from the master branch. Prevents peer disconnects in some cases.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.03%) to 87.367% when pulling 009f491 on state-cache into decca7f on beta.

@@ -465,7 +466,10 @@ impl LockedBlock {

impl Drain for LockedBlock {
/// Drop this object and return the underlieing database.
fn drain(self) -> Box<JournalDB> { self.block.state.drop().1 }
fn drain(mut self) -> StateDB {
self.block.state.commit_cache();
Copy link
Contributor

Choose a reason for hiding this comment

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

if commit_cache is now necessary to be called as part of LockedBlock's general maintenance, what guarantees are there that the rest of the codebase is using it correctly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is now removed

Copy link
Contributor

@gavofyork gavofyork left a comment

Choose a reason for hiding this comment

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

this is a pretty substantial change to the internals; i'd like to see the new model of usage documented properly.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.03%) to 87.367% when pulling 1685032 on state-cache into decca7f on beta.

@gavofyork
Copy link
Contributor

so this introduces a secondary cache for account data in StateDB which sticks around as long as there is no reorg and thus avoids flushing the temporary State cache and heading to the database for lookups between blocks?

Copy link
Contributor

@gavofyork gavofyork left a comment

Choose a reason for hiding this comment

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

looks pretty good in general.

a bit of docs on usage, particularly for State::commit_cache and some argument as to why it will get called when needed (e.g. in drain) would be nice to have.

e.remove();
}
},
_ => ()
Copy link
Contributor

Choose a reason for hiding this comment

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

we tend to use {}, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed to {}

@@ -285,10 +305,17 @@ impl State {
Ok(())
}

pub fn commit_cache(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why the separate function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Made if private. Called automatically on drop.

@arkpar
Copy link
Collaborator Author

arkpar commented Sep 25, 2016

Added in 25d66f4:
commit_cache is called on drop
Clean account items are not copied from StateDB to State and back

&mut Some(ref mut acc) => not_default(acc),
slot @ &mut None => *slot = Some(default()),
&mut AccountEntry::Cached(ref mut acc) => not_default(acc),
slot @ _ => *slot = AccountEntry::Cached(default()),
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just slot =>...?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.02%) to 87.422% when pulling 25d66f4 on state-cache into c374164 on beta.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.02%) to 87.422% when pulling d04b2b6 on state-cache into c374164 on beta.

@arkpar arkpar added A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. and removed A0-pleasereview 🤓 Pull request needs code review. labels Sep 25, 2016
@gavofyork gavofyork added M4-core ⛓ Core client code / Rust. and removed A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. labels Sep 26, 2016
@arkpar arkpar added the A0-pleasereview 🤓 Pull request needs code review. label Sep 26, 2016
Copy link
Contributor

@gavofyork gavofyork left a comment

Choose a reason for hiding this comment

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

a few questions and some additional docs would be good.

match self.storage_overlay.borrow_mut().entry(key) {
Entry::Occupied(ref mut entry) if entry.get().1 != value => {
entry.insert((Filth::Dirty, value));
match self.storage_changes.entry(key) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this work properly if the storage was previously cached? What about if the change is later back to the original value?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

storage_changes will hold the same value as storage_cache but there's nothing wrong with that. Everything will be merged properly on commit_storage

Copy link
Contributor

Choose a reason for hiding this comment

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

what if the two hold different values? is there a proof that this can never happen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It may happen and is supported. storage_cache reflects the contents of the disk database, storage_changes keeps changes over this contents. I'd prefer caching and tracking changes to be split into different structs, so that only the state can have changes. This is left for a future PR.

// Overlay on trie-backed storage - tuple is (<clean>, <value>).
storage_overlay: RefCell<HashMap<H256, (Filth, H256)>>,
// Cache of trie-backed storage
storage_cache: RefCell<LruCache<H256, H256>>,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be made explicit in the struct docs about the meaning (and precedence) of these members. Particularly what happens when the same key appears in both.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added docs

our_cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here
}
*self = other;
self.storage_cache = RefCell::new(our_cache);
Copy link
Contributor

Choose a reason for hiding this comment

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

seems a little over-complex in terms of assignments; why do our_cache and updated_cache need pulling out only to be replaced here? why reassign self?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

refactored

}
}

fn clone_basic(&self) -> AccountEntry {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add some docs for this function - difficult to immediately see what it's trying to do

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added

@@ -56,6 +56,22 @@ impl AccountEntry {
AccountEntry::Missing => false,
}
}

fn clone_dirty(&self) -> Option<AccountEntry> {
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add some docs for this function - difficult to immediately see what it's trying to do

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added

// check local cache first
if let Some(value) = self.cache.borrow().get(address).and_then(|a|
match *a {
AccountEntry::Cached(ref account) => account.modified_storage_at(key),
Copy link
Contributor

Choose a reason for hiding this comment

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

why only modified_storage_at (don't we want to see the cached storage value?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Storage key search and update now works like this:

  1. If there's an entry for the account in the local cache check for the key and return it if found.
  2. If there's an entry for the account in the global cache check for the key or load it into that account.
  3. If account is missing in the global cache load it into the local cache and cache the key there.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

refactored to reflect algorithm mentioned above

@gavofyork gavofyork added A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. and removed A0-pleasereview 🤓 Pull request needs code review. labels Sep 26, 2016
@gavofyork gavofyork modified the milestone: 1.3.2 Sep 26, 2016
@NikVolf NikVolf mentioned this pull request Sep 26, 2016
@arkpar
Copy link
Collaborator Author

arkpar commented Sep 27, 2016

Added docs
Removed commit_cache and clear_cache from StateDB interface.

@arkpar arkpar added A0-pleasereview 🤓 Pull request needs code review. and removed A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. labels Sep 27, 2016
@coveralls
Copy link

Coverage Status

Coverage decreased (-0.02%) to 87.42% when pulling 49ab189 on state-cache into c374164 on beta.

@coveralls
Copy link

coveralls commented Sep 27, 2016

Coverage Status

Coverage decreased (-0.02%) to 87.423% when pulling ca997e5 on state-cache into c374164 on beta.

@NikVolf NikVolf mentioned this pull request Sep 27, 2016
Copy link
Contributor

@gavofyork gavofyork left a comment

Choose a reason for hiding this comment

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

Generally looks good. Quite a few questions though.


/// Get cached storage value if any. Returns `None` if the
/// key is not in the cache.
pub fn cached_storage_at(&self, key: &H256) -> Option<H256> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Just call this from storage_at instead of the copy'n'paste?

}
self.storage_cache.borrow_mut().insert(k, v);
Copy link
Contributor

Choose a reason for hiding this comment

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

i guess there's no issue here with DB commits being pending while storage_cache is already updated? (db gets written out when t is dropped, right?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is just the local cache. Changes to the shared canonical state cache are applied along with the database write.

balance: self.balance.clone(),
nonce: self.nonce.clone(),
storage_root: self.storage_root.clone(),
storage_cache: Self::empty_storage_cache(),
Copy link
Contributor

Choose a reason for hiding this comment

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

why not copy the cache? waste of memory?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Exactly. And waste of time copying it around all the time. Storage queries will be cached in the shared Account anyway

pub fn clone_dirty(&self) -> Account {
let mut account = self.clone_basic();
account.storage_changes = self.storage_changes.clone();
account.code_cache = self.code_cache.clone();
Copy link
Contributor

Choose a reason for hiding this comment

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

is code_cache a cache of the database's current code entries? or does it contain dirty changes? if changes, then should probably be renamed code_changes, if a simple cache like storage_cache then surely if should be assigned only in clone_all?

Copy link
Collaborator Author

@arkpar arkpar Sep 27, 2016

Choose a reason for hiding this comment

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

There's no distinction currently. I'll add it.

let mut cache = self.storage_cache.borrow_mut();
for (k, v) in other.storage_cache.into_inner().into_iter() {
cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here
}
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we be doing something with other's storage_changes? asserting it's empty, perhaps? if it isn't always empty, it's probably safest to require the call-site to explicitly drop that data first, since dropping potentially important changes implicitly doesn't seem particularly anti-fragile.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

there's already an assert for this at the beginning of the function

},
AccountEntry::Missing => {
self.db.cache_account(address, None);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

missing , (or superfluous one above and below - i good with either rule)

account.cache_code(&AccountDB::from_hash(self.db.as_hashdb(), addr_hash));
fn update_account_cache(require: RequireCache, account: &mut Account, address: &Address, db: &HashDB) {
match require {
RequireCache::None => (),
Copy link
Contributor

Choose a reason for hiding this comment

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

{}?

/// State database abstraction.
/// Manages shared global state cache.
/// A clone of `StateDB` may be created as canonical or not.
/// For canonical clones cache changes are accumulated and applied
Copy link
Contributor

Choose a reason for hiding this comment

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

is this additional caching write-through? does it leave the state and block dbs in inconsistent states when there's an unexpected exit during a run of canon blocks?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It is write-through, writes are not affected by this PR

let mut cache = self.account_cache.lock();
for (address, account) in self.cache_overlay.drain(..) {
if let Some(&mut Some(ref mut existing)) = cache.accounts.get_mut(&address) {
if let Some(new) = account {
Copy link
Contributor

Choose a reason for hiding this comment

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

what would account == None mean?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

account is known to be missing in the DB

}

/// Apply pending cache changes.
fn commit_cache(&mut self) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this doesn't write through to the DB - is that not an issue on unexpected shutdown?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this only updated in-memory cache, writes are unaffected

@gavofyork gavofyork added the A8-looksgood 🦄 Pull request is reviewed well. label Sep 27, 2016
@gavofyork
Copy link
Contributor

looks good - be good to get those minor doc points and DRY in prior to merge.

@gavofyork gavofyork removed the A0-pleasereview 🤓 Pull request needs code review. label Sep 27, 2016
@arkpar
Copy link
Collaborator Author

arkpar commented Sep 27, 2016

Pushed documenation and syle changes

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.03%) to 87.408% when pulling 7e34afa on state-cache into c374164 on beta.

@gavofyork gavofyork merged commit c9cfcd2 into beta Sep 27, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A8-looksgood 🦄 Pull request is reviewed well. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants