-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
Feed LatestPrice query cache #11326
Feed LatestPrice query cache #11326
Conversation
I see that you haven't updated any CHANGELOG files. Would it make sense to do so? |
I see that you haven't updated any README files. Would it make sense to do so? |
0cca18b
to
9f7adda
Compare
b5b3a91
to
5f84251
Compare
SonarQube Quality Gate Reliability Rating on New Code (is worse than A) See analysis details on SonarQube Fix issues before they fail your Quality Gate with SonarLint in your IDE. |
6507d18
to
b97d8f2
Compare
b97d8f2
to
c8a74c5
Compare
318cb58
to
81fda1b
Compare
510b488
to
41a617a
Compare
@@ -100,7 +101,7 @@ func NewRelayer(lggr logger.Logger, chain evm.Chain, opts RelayerOpts) (*Relayer | |||
chain: chain, | |||
lggr: lggr, | |||
ks: opts.CSAETHKeystore, | |||
mercuryPool: wsrpc.NewPool(lggr), | |||
mercuryPool: opts.MercuryPool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this a top level pool now? Is the intention to share it across chains? That means it will cross the relay API, and I'm not sure how that is meant to work 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it is shared across chains. I asked @cedric-cordenier and this is what he suggested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the relay API has to proxy the shared websocket connections? Why is that better than each plugin process having it's own local pool?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jmank88 The reason why I suggested adding this here was because I didn't really consider this part of the relayer LOOPP API. For me this isn't part of it -- see for example how we're passing in the database. In a LOOP world, we wouldn't do that: we'd either pass in the config values and create the pool in the relayer proper, or pass in a proxy service.
I'm trying to understand your objection: I guess you're saying that this would break the isolation LOOPPs are supposed to provide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm observing that this at least adds to the technical debt in the way of LOOPPifying mercury.
- If we intend to share these pools between host and plugins, then I guess that is just something to figure out how to do (but it does not immediately make sense to me).
- If we do not intend to share these pools between host and plugins, then there is no need to refactor things to be global like this in the first place since we'll just have to undo it in a few weeks. Instead, each relayer should simply own and manage a pool internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, good point.
One interesting thought that occurred to me is that you could look at this as a symptom of there not being a single "Mercury relayer", i.e. it's a symptom of there being a mercury provider in all relayers.
Instead, maybe a more natural way to look at this is that Mercury is a "chain", and we should give it its own relayer. If we do that, this problem goes away as calls to Mercury would go via the same relayer and therefore would benefit from the wsrpc pool which in that case would be a totally internal, but shared resource. This Mercury relayer would only implement the write APIs (i.e. the transmitter, the codec etc), and would leave the read side unimplemented or stubbed out.
The benefit of this is that it cleanly maps the relayer as a 1-1 abstraction over the underlying resource, in the same way that relayers do with the chains they target.
It does mean that we'd need to refine the relayer abstraction somewhat, to allow products to use a different relayer for reading and for writing, but that doesn't feel wrong to me, and might even be a better underlying fit (CCIP might benefit from this for example).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think that separate reading and writing relayers makes sense 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samsondav Do you think that is a good long-term direction for Mercury?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, separating the read/write relayers makes sense to me. However, note that in this case the "write" relayer would also have to support reading. Since we send reports to mercury but also query reports from it.
Treating mercury server as a "chain" is definitely the right way to go.
// NOTE: must drop down to RawClient here otherwise we enter an | ||
// infinite loop of calling a client that calls back to this same cache | ||
// and on and on | ||
val, err = m.client.RawClient().LatestReport(ctx, req) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@samsondav Did you consider inverting the dependency here? i.e. rather than having a cache which depends on a client, you have a client which depends on a cache. That would allow the cache to be totally generic and reusable and not have any notion of client, while your client is responsible for making requests and storing things in the cache as needed (even if this happens in a loop).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this was how I designed it? The client does depend on the cache and calls to the cache if its there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We discussed this offline: an aside cache won't work here since we're also trying to dedupe in flight requests. The main reason for this cache is to service ETH/LINK feeds, which are high volume, so this complexity is worth it.
// Context should be set carefully and timed to be the maximum time we are | ||
// willing to wait for a result, the background thread will keep re-querying | ||
// until it gets one even on networking errors etc. | ||
func (m *memCache) LatestReport(ctx context.Context, req *pb.LatestReportRequest) (resp *pb.LatestReportResponse, err error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this more complicated than it needs to be? I can see that it accounts for the cache wait case, but I'm wondering if you considered using a simpler strategy (cache aside, rather than read through). This might be a bit less efficient, but I think would significantly reduce all of the edge cases you need to handle, which feels like a win.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you outline how the simpler strategy would work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update: as above.
Problem: Core nodes are querying LatestReport a lot (~70rps). This is not only wasteful, it potentially increases latency if Mercury server falls behind and is also completely unnecessary. We do not need such up-to-the-millisecond accuracy for billing, Proposed solution: Introduce a price cache for mercury with a TTL e.g. of 1 minute or whatever tolerance we have for billing price and only fetch from the server when the cache becomes stale.
23d746a
to
e359f42
Compare
SonarQube Quality Gate |
* Price retrieval cache for Mercury Problem: Core nodes are querying LatestReport a lot (~70rps). This is not only wasteful, it potentially increases latency if Mercury server falls behind and is also completely unnecessary. We do not need such up-to-the-millisecond accuracy for billing, Proposed solution: Introduce a price cache for mercury with a TTL e.g. of 1 minute or whatever tolerance we have for billing price and only fetch from the server when the cache becomes stale. * fix * Some PR comments * Fix more PR comments * PR comments * lint
No description provided.