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

[multiverse RPC]: add better universe root node cache #1169

Merged
merged 12 commits into from
Nov 19, 2024
Merged

Conversation

guggero
Copy link
Member

@guggero guggero commented Nov 4, 2024

Fixes #880.

Opening as draft, mainly looking for concept ACK.

@Roasbeef the idea here is the following:

  • Since the universe sync is what needs to be fast, we add a completely new cache for those queries
  • The syncer client always queries with sort=ascending and WithAmountsById=false, which allows us to optimize the new cache and keep all root nodes in memory for those queries
  • The old/existing page based root node cache will only be used for custom user queries that want descending order or WithAmountsById=true, which allows us to keep a much smaller cache
  • All the other caches remain untouched in their functionality (just renamed and made configurable).

TODOs:

  • Test new cache
  • Update comments in RPC proto file that order of results is dependent on query parameters (depending on whether they hit the syncer cache or not).

@guggero guggero requested a review from Roasbeef November 4, 2024 14:57
@coveralls
Copy link

coveralls commented Nov 4, 2024

Pull Request Test Coverage Report for Build 11920129966

Details

  • 413 of 577 (71.58%) changed or added relevant lines in 9 files are covered.
  • 28 unchanged lines in 9 files lost coverage.
  • Overall coverage increased (+0.3%) to 41.304%

Changes Missing Coverage Covered Lines Changed/Added Lines %
chain_bridge.go 0 3 0.0%
tapcfg/config.go 0 3 0.0%
tapcfg/server.go 0 3 0.0%
rpcserver.go 0 4 0.0%
universe/syncer.go 0 7 0.0%
tapdb/multiverse.go 132 177 74.58%
tapdb/multiverse_cache.go 271 370 73.24%
Files with Coverage Reduction New Missed Lines %
tapdb/multiverse.go 1 69.08%
universe/syncer.go 1 0.0%
tappsbt/create.go 2 53.22%
asset/asset.go 2 81.13%
tapchannel/aux_leaf_signer.go 3 36.33%
tapgarden/caretaker.go 4 68.5%
commitment/tap.go 4 84.17%
universe/interface.go 5 50.65%
tapdb/assets_store.go 6 63.69%
Totals Coverage Status
Change from base Build 11914558450: 0.3%
Covered Lines: 25463
Relevant Lines: 61648

💛 - Coveralls

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

Approach ACK!

tapdb/multiverse_cache_test.go Show resolved Hide resolved
tapdb/multiverse_cache.go Outdated Show resolved Hide resolved
tapdb/multiverse_cache.go Outdated Show resolved Hide resolved
tapdb/multiverse_cache.go Show resolved Hide resolved
tapdb/multiverse_cache.go Outdated Show resolved Hide resolved
tapdb/multiverse_cache.go Outdated Show resolved Hide resolved
tapdb/multiverse_cache.go Show resolved Hide resolved
tapdb/multiverse_cache.go Outdated Show resolved Hide resolved
tapdb/multiverse_cache.go Show resolved Hide resolved
// multiverse cache.
func DefaultMultiverseCacheConfig() MultiverseCacheConfig {
return MultiverseCacheConfig{
ProofsPerUniverse: 5,
Copy link
Member

Choose a reason for hiding this comment

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

Temp for this commit?

We should inherit the defaults defined in the existing package here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think the original value for this was way too high. Because this is the number of proofs we cache per universe (and we don't have a limit on the number of universes we cache proofs for, it's a lnutils.SyncMap here and not an lru.Cache), we should be quite conservative here.

Perhaps we should turn this into an lru.Cache too, same as with the universeLeafPageCache?

Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, the original intent was for that to be a instance wide value (eg: only cache 50k proofs total).

I think before merge, we should deploy a version of this on testnet/mainnet to observe the cache hit/miss rate.

@GeorgeTsagk GeorgeTsagk self-requested a review November 5, 2024 17:49
@guggero guggero force-pushed the universe-cache branch 2 times, most recently from 8c63baa to b63f383 Compare November 8, 2024 17:07
@guggero guggero marked this pull request as ready for review November 8, 2024 17:09
@guggero guggero requested a review from Roasbeef November 8, 2024 17:09
@guggero
Copy link
Member Author

guggero commented Nov 8, 2024

Addressed all comments and added proper unit tests. Good for full review now.

Copy link
Member

@GeorgeTsagk GeorgeTsagk left a comment

Choose a reason for hiding this comment

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

looking good!

do we have a way of verifying/testing:

  • new resource usage overhead
  • performance difference of having a new syncer cache (an itest?)

otherwise ACK, plus some minor Qs

tapdb/multiverse_cache.go Show resolved Hide resolved
tapdb/multiverse_cache_test.go Show resolved Hide resolved
tapdb/multiverse_cache_test.go Show resolved Hide resolved
@guggero
Copy link
Member Author

guggero commented Nov 14, 2024

I added a test that calculates the approximate size of the entries in the cache:

=== RUN   TestSyncerCacheMemoryUsage
    multiverse_cache_test.go:214: Generated 500 roots in 26.583318ms
=== RUN   TestSyncerCacheMemoryUsage/500_roots
    multiverse_cache_test.go:224: Memory usage for 500 roots: 139496 bytes
    multiverse_cache_test.go:226: Memory usage per root: 278 bytes
    multiverse_cache_test.go:228: Benchmark took 8.836µs
--- PASS: TestSyncerCacheMemoryUsage/500_roots (0.01s)
    multiverse_cache_test.go:214: Generated 5000 roots in 257.823179ms
=== RUN   TestSyncerCacheMemoryUsage/5000_roots
    multiverse_cache_test.go:224: Memory usage for 5000 roots: 1073384 bytes
    multiverse_cache_test.go:226: Memory usage per root: 214 bytes
    multiverse_cache_test.go:228: Benchmark took 104.568µs
--- PASS: TestSyncerCacheMemoryUsage/5000_roots (0.01s)
    multiverse_cache_test.go:214: Generated 170000 roots in 8.847171795s
=== RUN   TestSyncerCacheMemoryUsage/170000_roots
    multiverse_cache_test.go:224: Memory usage for 170000 roots: 34259176 bytes
    multiverse_cache_test.go:226: Memory usage per root: 201 bytes
    multiverse_cache_test.go:228: Benchmark took 1.820929ms
--- PASS: TestSyncerCacheMemoryUsage/170000_roots (0.08s)
--- PASS: TestSyncerCacheMemoryUsage (9.23s)

With around 170k roots currently in existence on mainnet, that means the new cache would only use ~34 MBytes of memory.

@guggero guggero requested a review from GeorgeTsagk November 14, 2024 11:07
Copy link
Member

@GeorgeTsagk GeorgeTsagk 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!

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🦠

params := sqlc.UniverseRootsParams{
SortDirection: sqlInt16(universe.SortAscending),
NumOffset: 0,
NumLimit: universe.MaxPageSize,
Copy link
Member

Choose a reason for hiding this comment

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

In cases like this, I wonder if it's better to just read the entire set of roots in one go. So one single query, instead of a bunch of smaller ones. With the current amount of assets on mainnet, we'll end up doing 300 or so queries here to read them all out. Some benchmarking would likely help us find the sweet spot here re page size to read.

Non-blocking.

@@ -271,6 +475,7 @@ func (r *rootNodeCache) cacheRoots(q universe.RootNodesQuery,

rootIndex := r.rootIndex.Load()
for _, rootNode := range rootNodes {
rootNode := rootNode
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, with the latest version of Go, we should no longer need to do this reassignment trick.

// multiverse cache.
func DefaultMultiverseCacheConfig() MultiverseCacheConfig {
return MultiverseCacheConfig{
ProofsPerUniverse: 5,
Copy link
Member

Choose a reason for hiding this comment

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

Ah ok, the original intent was for that to be a instance wide value (eg: only cache 50k proofs total).

I think before merge, we should deploy a version of this on testnet/mainnet to observe the cache hit/miss rate.

This commit adds a completely new cache that is tailor made for the
queries the syncer makes (ascending, without any amounts by ID).
This additional cache has all roots (without details), so the existing
cache will only be needed for custom queries that aren't coming from the
syncer.
We should be able to down-size the existing cache quite a bit, as it
should only serve manual requests or requests coming from an UI.

We'll make the size configurable in an upcoming commit.
This commit adds a test that calculates the approximate memory
consumption of a full syncer cache.
The results look pretty good:

=== RUN   TestSyncerCacheMemoryUsage
    multiverse_cache_test.go:214: Generated 500 roots in 26.583318ms
=== RUN   TestSyncerCacheMemoryUsage/500_roots
    multiverse_cache_test.go:224: Memory usage for 500 roots: 139496 bytes
    multiverse_cache_test.go:226: Memory usage per root: 278 bytes
    multiverse_cache_test.go:228: Benchmark took 8.836µs
--- PASS: TestSyncerCacheMemoryUsage/500_roots (0.01s)
    multiverse_cache_test.go:214: Generated 5000 roots in 257.823179ms
=== RUN   TestSyncerCacheMemoryUsage/5000_roots
    multiverse_cache_test.go:224: Memory usage for 5000 roots: 1073384 bytes
    multiverse_cache_test.go:226: Memory usage per root: 214 bytes
    multiverse_cache_test.go:228: Benchmark took 104.568µs
--- PASS: TestSyncerCacheMemoryUsage/5000_roots (0.01s)
    multiverse_cache_test.go:214: Generated 170000 roots in 8.847171795s
=== RUN   TestSyncerCacheMemoryUsage/170000_roots
    multiverse_cache_test.go:224: Memory usage for 170000 roots: 34259176 bytes
    multiverse_cache_test.go:226: Memory usage per root: 201 bytes
    multiverse_cache_test.go:228: Benchmark took 1.820929ms
--- PASS: TestSyncerCacheMemoryUsage/170000_roots (0.08s)
--- PASS: TestSyncerCacheMemoryUsage (9.23s)
This commit allows for a much larger (32x) page size than before. This
should allow us to fetch the complete set of roots even faster, given
that we should have 100% cache hits for the syncer calls now.

We also reduce the overall count of requests by 1 in almost all cases by
detecting that the returned page isn't full anymore (instead of reading
until we get an empty page).
@Roasbeef Roasbeef merged commit 77e8b28 into main Nov 19, 2024
17 of 18 checks passed
@guggero guggero deleted the universe-cache branch November 19, 2024 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

[bug]: universe_roots cache-hit ratio, gross underperformance
5 participants