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

feat: flat storage cache #8540

Merged
merged 19 commits into from
Feb 28, 2023
Merged

feat: flat storage cache #8540

merged 19 commits into from
Feb 28, 2023

Conversation

Longarithm
Copy link
Member

@Longarithm Longarithm commented Feb 8, 2023

Based on @jakmeier' estimations, we need to cache ValueRefs for flat storage head (see #8006). RocksDB internal impl and block cache doesn't help, and we need to make flat storage performance to be at least comparable to trie performance in MVP, in order not to make undercharging issue worse.

This cache lives inside FlatStorageState, can be accessed in get_ref before attempt to read value ref from flat storage head, and must be updated when we apply delta.

I think it makes sense to make cache capacity configurable, and this config fits into StoreConfig. I don't like that is propagated to FlatStorageState from ShardTries, it makes trie storage and flat storage mixed even more. Perhaps it needs to be fully moved inside FlatStateFactory, but I am not sure.

Testing

  • extend flat_storage_state_sanity to check both cached and non-cached versions;
  • flat_storage_state_cache_eviction to check that eviction strategy is applied correctly.

@Longarithm Longarithm marked this pull request as ready for review February 8, 2023 20:49
@Longarithm Longarithm requested a review from a team as a code owner February 8, 2023 20:49
@jakmeier
Copy link
Contributor

jakmeier commented Feb 9, 2023

@Longarithm I discussed with @akhi3030 that it seems a bit odd we need to build this cache on top of RocksDB, when RocksDB allows for efficient caching. I would like to test if enabling the RowCache (a cache that stores key-values pairs instead of blocks but it is currently disabled for all columns) in RocksDB couldn't achieve the same goal. I think it would make the code and its maintenance easier.

But it will take me at least a week to get this benchmarked based on current priorities. What do you think, should we wait with this PR until we know the results?

@Longarithm
Copy link
Member Author

It's funny that when I googled RowCache, this was the first link https://groups.google.com/g/rocksdb/c/YxdRryNVTyw/m/ZobfhrO8AAAJ

After reading code I found that rocksdb has RowCache, but it seems to be not widely spoken, not even in rocksdb wiki, wierd, Anyone got clue?

If it actually helps, that would be great, I'm interested in new results. If I remember correctly, RocksDB block cache didn't help with that.
I'll still use this custom cache in my runs though.

Comment on lines 1042 to 1050
if guard.value_ref_cache.len() == guard.value_ref_cache.cap() {
if let Some((key, _)) = guard.value_ref_cache.pop_lru() {
guard.metrics.value_ref_cache_total_key_size.sub(key.len() as i64);
}
}
if guard.value_ref_cache.len() < guard.value_ref_cache.cap() {
guard.metrics.value_ref_cache_total_key_size.add(key.len() as i64);
guard.value_ref_cache.put(key.clone(), value.clone());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

You may be able to simplify this code with the lru cache push method - it checks the capacity by itself and returns a popped item if any. Not sure how it would handle 0 capacity though.

https://docs.rs/lru/latest/lru/struct.LruCache.html#method.push

pub static FLAT_STORAGE_VALUE_REF_CACHE_TOTAL_KEY_SIZE: Lazy<IntGaugeVec> = Lazy::new(|| {
try_create_int_gauge_vec(
"flat_storage_value_ref_cache_total_key_size",
"Total size of all keys in flat storage cache for its head",
Copy link
Contributor

Choose a reason for hiding this comment

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

Out of curiosity why do you only measure the size of the keys and not keys and values?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because size of values is fixed - we store ValueRefs instead of values.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we measure both since we might implement inlining in the future?

@@ -991,6 +1011,9 @@ impl FlatStorageState {
};
}

if let Some(value_ref) = guard.get_cached_ref(key) {
return Ok(value_ref);
}
Ok(store_helper::get_ref(&guard.store, key)?)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should you also push the just read value back to the cache? Currently you only update values in the cache when writing new values there. I suppose either can be fine but cache hit rate will heavily depend on the usage pattern.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right!

@jakmeier
Copy link
Contributor

jakmeier commented Feb 9, 2023

To understand the row cache, I found this blog post (from January this year) helpful: https://betterprogramming.pub/navigating-the-minefield-of-rocksdb-configuration-options-246af1e1d3f9

Especially this part:

Row cache
This cache is used for storing actual data for a key. The cache’s documentation is almost non-existent, but it provides fast data access in case you do a lot of point lookups. RocksDB first looks in the row cache and then in the block cache for the data. Like block cache, you can use either LRUCache or ClockCache as the actual implementation for row cache.

@wacban
Copy link
Contributor

wacban commented Feb 10, 2023

lgtm but I'll let someone familiar with flat storage review it too
might be cool to add some cache hit rate metrics too if this is something you want to track


/// Get cached `ValueRef` for flat storage head.
#[cfg(feature = "protocol_feature_flat_state")]
fn get_cached_ref(&mut self, key: &[u8]) -> Option<Option<ValueRef>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Explain why the returned value is Option<Option<>> since it could be confusing.

Copy link
Contributor

@mzhangmzz mzhangmzz left a comment

Choose a reason for hiding this comment

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

LGTM. Previously I implemented an empty struct called FlatStateCache with the intention of adding cache implementation and it is part of FlatState. Could you remove that code? You can either do it in this PR or in a separate PR. Thank you!

pub static FLAT_STORAGE_VALUE_REF_CACHE_TOTAL_KEY_SIZE: Lazy<IntGaugeVec> = Lazy::new(|| {
try_create_int_gauge_vec(
"flat_storage_value_ref_cache_total_key_size",
"Total size of all keys in flat storage cache for its head",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we measure both since we might implement inlining in the future?

@Longarithm
Copy link
Member Author

We don't have time for researching RocksDB RowCache for now, added todo to the code: #8649

@near-bulldozer near-bulldozer bot merged commit 229e6ae into master Feb 28, 2023
@near-bulldozer near-bulldozer bot deleted the fs-cache branch February 28, 2023 13:41
near-bulldozer bot pushed a commit that referenced this pull request Mar 14, 2023
Part of #8577 and #8684, was introduced in #8540.

This PR removes custom `FlatState` cache from `FlatStorage` since experiments show very low hit rate which is not worth used memory.
nikurt pushed a commit to nikurt/nearcore that referenced this pull request Mar 15, 2023
Part of near#8577 and near#8684, was introduced in near#8540.

This PR removes custom `FlatState` cache from `FlatStorage` since experiments show very low hit rate which is not worth used memory.
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.

5 participants