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

[RelayMiner] Implement relayminer query caching #1050

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

red-0ne
Copy link
Contributor

@red-0ne red-0ne commented Jan 31, 2025

Summary

Implements caching layer for query clients to reduce network calls and improve performance

Primary Changes:

  • Added generic KeyValueCache and ParamsCache interfaces with thread-safe implementations
  • Integrated caching across all query clients (Account, Application, Bank, Service, etc.)
  • Added cache clearing on new blocks via WithNewBlockCacheClearing option

Secondary Changes:

  • Replaced manual sync.Mutex implementation in accQuerier with new cache interface
  • Added cache configuration to integration tests
  • Updated relayer dependencies to include cache initialization

Issue

The RelayMiner RPC queries are not cached, which puts excessive load on the configured full node, degrading the performance of both off-chain and on-chain components.

Type of change

Select one or more from the following:

Sanity Checklist

  • I have updated the GitHub Issue assignees, reviewers, labels, project, iteration and milestone
  • For docs, I have run make docusaurus_start
  • For code, I have run make go_develop_and_test and make test_e2e
  • For code, I have added the devnet-test-e2e label to run E2E tests in CI
  • For configurations, I have update the documentation
  • I added TODOs where applicable

@red-0ne red-0ne added the relayminer Changes related to the Relayminer label Jan 31, 2025
@red-0ne red-0ne added this to the Beta TestNet Iteration milestone Jan 31, 2025
@red-0ne red-0ne requested review from Olshansk and adshmh January 31, 2025 04:38
@red-0ne red-0ne self-assigned this Jan 31, 2025
@red-0ne red-0ne added the push-image CI related - pushes images to ghcr.io label Jan 31, 2025
Copy link

The image is going to be pushed after the next commit.

You can use make trigger_ci to push an empty commit.

If you also want to run E2E tests, please add devnet-test-e2e label.

Copy link
Member

@Olshansk Olshansk left a comment

Choose a reason for hiding this comment

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

@red-0ne I did a first partial review but have a lot of comments & questions.

Here's a high-level summary but PTAL at the actual comments as well:

  1. Need to understand if/how this can build on top of [Off-chain] feat: in-memory query cache(s) #994 w/ @bryanchriswhite
  2. See a few comments (logs + comments) that need to be addressed in multiple places
  3. I’m a bit concerned (and don’t understand) how we’re not using “height” to retrieve things from the cache, especially when values are always changing
  4. When does the cache ever get cleared?
  5. Light on tests
  6. I’d be interested to see numbers of performance improvement

pkg/client/query/accquerier.go Show resolved Hide resolved
pkg/client/query/accquerier.go Show resolved Hide resolved
pkg/client/query/accquerier.go Show resolved Hide resolved
pkg/client/query/appquerier.go Show resolved Hide resolved
pkg/client/query/appquerier.go Outdated Show resolved Hide resolved
pkg/client/query/sessionquerier.go Outdated Show resolved Hide resolved
@@ -49,11 +53,19 @@ func NewSharedQuerier(deps depinject.Config) (client.SharedQueryClient, error) {
// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method
// to get the most recently (asynchronously) observed (and cached) value.
func (sq *sharedQuerier) GetParams(ctx context.Context) (*sharedtypes.Params, error) {
// Get the params from the cache if they exist.
if params, found := sq.paramsCache.Get(); found {
Copy link
Member

Choose a reason for hiding this comment

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

I'm concerned (and don't fully understand) the lack of a "height" param when retrieving things from the cache.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This cache implementation does not add any new functionality besides caching whatever has been queried.

It does not alter the RelayMiners current behavior

  • RelayMiner cold start
  • React to Params change

For those reasons, it does not leverage historical data that justifies the usage of height for cache querying.

Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that the cache implementations here are NOT historical; i.e. ONLY the most recently observed value is cached for each ParamsCache instance (or key, in the case of KeyValueCache).

While #994 does include historical caching as well (via the HistoricalQueryCache interface, that's an additional and distinct feature.

This shouldn't be necessary here because we're clearing the cache on every new block. The end result being, somewhat sub-optimal, but significant caching. This reformulates the number off-chain queries from being a function of API usage, to no more than one per block, per cache.

pkg/client/query/sharedquerier.go Outdated Show resolved Hide resolved
pkg/client/query/sharedquerier.go Show resolved Hide resolved
pkg/client/query/sharedquerier.go Show resolved Hide resolved
Copy link
Contributor

@bryanchriswhite bryanchriswhite left a comment

Choose a reason for hiding this comment

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

Nice one @red-0ne! 🙌

Thanks for doing this! ❤️


// WithNewBlockCacheClearing is a cache option that clears the cache every time
// a new block is observed.
func WithNewBlockCacheClearing[C Cache](ctx context.Context, deps depinject.Config, cache C) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

🔥 🔥 🔥

}

// KeyValueCache is an interface for a simple in-memory key-value cache implementation.
type KeyValueCache[V any] interface {
Copy link
Contributor

Choose a reason for hiding this comment

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

This interface is consistent with what I was calling QueryCache[T any] in #994. It should be quite straightforward to refactor #994 to use this instead. I see KeyValueCache[V any] as a subsequent iteration of QueryCache[T any] which includes generalizing the name.

pkg/client/query/interface.go Show resolved Hide resolved
pkg/client/query/cache/paramscache.go Outdated Show resolved Hide resolved
pkg/client/query/cache/paramscache.go Show resolved Hide resolved
clientConn grpc.ClientConn
sessionQuerier sessiontypes.QueryClient
sharedQueryClient client.SharedQueryClient
sessionsCache KeyValueCache[*sessiontypes.Session]
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -49,11 +53,19 @@ func NewSharedQuerier(deps depinject.Config) (client.SharedQueryClient, error) {
// Once `ModuleParamsClient` is implemented, use its replay observable's `#Last()` method
// to get the most recently (asynchronously) observed (and cached) value.
func (sq *sharedQuerier) GetParams(ctx context.Context) (*sharedtypes.Params, error) {
// Get the params from the cache if they exist.
if params, found := sq.paramsCache.Get(); found {
Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding is that the cache implementations here are NOT historical; i.e. ONLY the most recently observed value is cached for each ParamsCache instance (or key, in the case of KeyValueCache).

While #994 does include historical caching as well (via the HistoricalQueryCache interface, that's an additional and distinct feature.

This shouldn't be necessary here because we're clearing the cache on every new block. The end result being, somewhat sub-optimal, but significant caching. This reformulates the number off-chain queries from being a function of API usage, to no more than one per block, per cache.

pkg/deps/config/suppliers.go Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
push-image CI related - pushes images to ghcr.io relayminer Changes related to the Relayminer
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

3 participants