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: allow for instance specific cache on Provider #3454

Closed
wants to merge 17 commits into from

Conversation

maschad
Copy link
Member

@maschad maschad commented Dec 8, 2024

Release notes

In this release, we:

  • introduced the resourceCacheStrategy parameter on the Provider class to allow for instance-specific cache

Summary

If one is using multiple Providers and would like to have different TTLs for each of them, one may want to use an instance-specific cache strategy, which means that each Provider instance will have its own cache.

This introduces that parameter to allow for an alternative to a global cache shared by all provider instances.

Checklist

  • All changes are covered by tests (or not applicable)
  • All changes are documented (or not applicable)
  • I reviewed the entire PR myself (preferably, on GH UI)
  • I described all Breaking Changes (or there's none)

Copy link

vercel bot commented Dec 8, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
fuels-template ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 9, 2024 8:40pm
ts-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 9, 2024 8:40pm
ts-docs-api ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 9, 2024 8:40pm

Copy link

codspeed-hq bot commented Dec 8, 2024

CodSpeed Performance Report

Merging #3454 will improve performances by 85.74%

Comparing mc/fix/adjust-global-cache-instances (350dfd0) with master (560664d)

Summary

⚡ 1 improvements
✅ 17 untouched benchmarks

Benchmarks breakdown

Benchmark master mc/fix/adjust-global-cache-instances Change
Instantiate a new Unlocked wallet 8.8 ms 4.7 ms +85.74%

@maschad maschad changed the title feat: allow for instance specific cache on Providers feat: allow for instance specific cache on Provider Dec 8, 2024
@maschad maschad marked this pull request as ready for review December 9, 2024 15:51
@maschad maschad requested a review from digorithm as a code owner December 9, 2024 15:51
Copy link
Contributor

Choose a reason for hiding this comment

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

For me, the ResourceCache is difficult to understand and not entirely extensible. Could we not implement an interface for each caching strategy and have the ResourceCache just a facade over these?

interface CacheStrategy<T> {
  all(): Map<string, T>;
  ttl(): number;
  get(key: string): T;
  set(key: string, value: T): void;
  clear(): void;
}

class InstanceCache implements CacheStrategy {}
class GlobalCache implements CacheStrategy {}

Copy link
Member Author

Choose a reason for hiding this comment

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

What specific extensibility needs did you have in mind?

I can appreciate the suggestion for a cleaner abstraction but the core difference between global and instance strategies is really just about where we store the Map - all the implementation logic of the methods(TTL handling, resource management, etc.) remains the same, so I'm not sure it's necessary here.

Copy link
Contributor

Choose a reason for hiding this comment

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

It could be something we want to use for the node and chain information. The above approach would also mean we don't check the strategy every time we get the cache.

getActiveCache(): Map<string, CachedResource> {
  return this.strategy === 'global' ? this.globalCache.getCache() : this.instanceCache;
}

We could just do:

getActiveCache(): Map<string, CachedResource> {
  return this.strategy.all();
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the clarification, but I am not sure I see the immediate use case(s) for node / chain info since at the moment this cache is only used for storing transactions.

Also I don't think that the removing the simple boolean check on the strategy field justifies duplicating all the caching logic across two separate classes.

I definitely do think there is validity in storing recently queried node and chain info though :) so that's a good insight, perhaps we should flesh that out in a separate issue? I think it would be more fruitful in exploring if we need a refactor at that point.

Copy link
Contributor

@Torres-ssf Torres-ssf Dec 10, 2024

Choose a reason for hiding this comment

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

@maschad, I believe what @petertonysmith94 is suggesting is that by having a simple, non-opinionated cache interface to extend from, we can reuse it for any future caching needs.

This would include the Provider cache for chain and node info. Is this correct, @petertonysmith94?

Copy link
Contributor

Choose a reason for hiding this comment

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

@Torres-ssf, that is what I was trying to suggest.

Copy link
Member

Choose a reason for hiding this comment

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

I definitely do think there is validity in storing recently queried node and chain info though :) so that's a good insight, perhaps we should flesh that out in a separate issue?

We have this already:

Copy link
Contributor

github-actions bot commented Dec 9, 2024

Coverage Report:

Lines Branches Functions Statements
76.5%(+0.08%) 69.96%(+0.05%) 74.62%(+0.16%) 76.53%(+0.08%)
Changed Files:
Ok File (✨=New File) Lines Branches Functions Statements
✨ packages/account/src/providers/global-cache.ts 100%
(+100%)
100%
(+100%)
100%
(+100%)
100%
(+100%)
🔴 packages/account/src/providers/provider.ts 68.8%
(-0.22%)
56.7%
(-0.44%)
70.96%
(+0%)
68.52%
(-0.21%)
🔴 packages/account/src/providers/resource-cache.ts 97.72%
(+1.57%)
86.36%
(-6.49%)
100%
(+0%)
97.82%
(+1.4%)

Comment on lines +28 to +31
// Only set TTL if not already set or if new TTL is smaller
if (this.ttl === 0 || ttl < this.ttl) {
this.ttl = ttl;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is the TTL only updated if the provided value is lower than the previously set one?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a preventative measure by taking a just conservative approach for a shared cache service. By keeping the smallest TTL it respects stricter freshness requirements.

Here's an example scenario with a global caching strategy:

  • User instantiates Provider 1 and Provider 2 with TTL = 60
  • User updates Provider 1 to use TTL = 300

If we allow the larger TTL to override the smaller one, then Provider 2 might may have stale data (up to 300 seconds old) which would violate Provider 2's original requirement for 60 second freshness. In this case Provider 2 may reject a 299 second old UTXO for instance, when that wasn't necessarily desired when a user set a 60 second TTL.

I think by taking this approach we give the user less chance of erring.

Copy link
Member

Choose a reason for hiding this comment

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

@maschad This reasoning sounds counterintuitive and exposes the [negative] nuances of going down this opinionated caching rabbit hole. The interplay between instance and singleton cannot happen like this; it is confusing and would require an in-depth DX reevaluation.

@Torres-ssf @nedsalk I know you guys discussed this in the past, and although I can't remember exactly the context of where it came from, I must repeat my [YAGNI-inspired] statement from the last sync: I don't see a strong use case to justify such a feature right now.

Comment on lines +22 to +25
private readonly ttl: number;
private readonly strategy: CacheStrategy;
private instanceCache: Map<string, CachedResource> = new Map();
private globalCache: GlobalCache;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does ResourceCache store an instance of GlobalCache, even though it might not be used when the instance strategy is selected?

Comment on lines +118 to +121
reset() {
this.clear();
this.globalCache.reset();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Will the GlobalCache be used exclusively by the ResourceCache class? I’m asking to ensure that resetting it won’t inadvertently clear data we still depend on.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, this seems wrong.

If one instantiates two providers, one local and one global, and then I call local.reset(), it will call the global.reset() and inadvertently clear the global cache.

Additionally, if one instantiates the local provider from a place/function that goes out of scope before the global, the latter will also be reset. This will happen because you chose to use Symbol.dispose and call reset from inside it.

[Symbol.dispose]() {
this.reset();
}

This case is even worse since users will never manually call .reset() and will still have their cache wiped out.

Copy link
Member Author

@maschad maschad Dec 11, 2024

Choose a reason for hiding this comment

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

When I had designed this the original intention was for consumers to use exclusively a global cache strategy or an instance specific cache strategy, not a combination of both, but you're right in that this isn't programmatically enforced so the above scenario could happen.

Copy link
Contributor

@Torres-ssf Torres-ssf left a comment

Choose a reason for hiding this comment

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

Hey @maschad, good job! 👍

I've left some questions here to better understand your motivations.

@maschad maschad marked this pull request as draft December 10, 2024 22:34
@maschad
Copy link
Member Author

maschad commented Dec 11, 2024

Thanks for the detailed reviews @FuelLabs/sdk-ts 👍🏾

Looking at the discussions, whilst there may be some improvements here, overall I agree with @arboleya that allowing for this caching strategy may cause more confusion for consumers than benefits offered. I'm going to close this PR as there's no immediate demand for this feature. We can revisit this if it makes sense when there's a stronger use case.

@maschad maschad closed this Dec 11, 2024
@petertonysmith94 petertonysmith94 deleted the mc/fix/adjust-global-cache-instances branch December 12, 2024 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat Issue is a feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Evaluate Global Resources Cache Within Provider Instances
4 participants