-
Notifications
You must be signed in to change notification settings - Fork 6
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
refactoring loading cache and metrics #127
base: master
Are you sure you want to change the base?
refactoring loading cache and metrics #127
Conversation
@aholstenson do you maintain this project anymore? |
Thanks for the PR! Can you give me more information on what your use case is here? The PR being a large commit makes this hard to review quickly, but I'll give my initial thoughts.
Looking at it I do have some thoughts about how the library is constructed, and that it might be time to think about a better design for a 3.x version. One of the things I regret in the current version is the builder, it's convenient but if you are targeting a browser and want smaller bundles you quickly end up having to construct caches yourself and the experience there isn't great. One of my thoughts here would be to target an API closer to this for creating unbounded vs bounded caches: // Unbounded cache
const cache = new Cache({
storage: new UnboundedStorage(),
});
// A cache using LFU eviction
const cache = new Cache({
storage: new LFUStorage({
maxSize: 100,
}),
}); As loading caches need that const cache = new LoadingCache({
storage: new LFUStorage({
maxSize: 100,
}),
loader: ...
});
// So it's possible to use get without a loader
await cache.get('key'); This would make expiration and metrics options instead, something similar to this for both new Cache({
storage: new LFUStorage({
maxSize: 100,
}),
expiration: expireAfterWrite(1000),
metrics: new StandardMetrics(),
}) I'm very interested to hear about your thoughts and use case here and if such a design would work for you. |
Yeah this ended up not being as fruitful as I had hoped. I still do think it is a valuable abstraction though - especially if you look to Caffeine cache for inspiration. Having access to the node could be useful for the following:
basically, it is particularly useful for mutations
Correction here: every cache has a As it stands now, I have a separate interface for LoadingCache and Cache. It just so happens that the implementation of both of them is the same, as the logic was so similar. The public API, though, forces the develop to think they are working with either LoadingCache or Cache.
I didn't actually increase complexity here, I moved lines of code around from within the same file, and also from the old cacheBuilder. So the complexity of this file is up slightly, but cacheBuilder has decreased significantly. Overall this means that the project as a whole is less complex.
Seems like a lot of delegation and complexity for not a lot of value. Difference in opinion, I think.
I think the builder should actually just be a nicely typed interface, so you can pass in a CacheConfig object or something rather than using a fluent API. Speaking of wishlist for 3.x, I have made some extensions I use for my company's code base that do the following, and may be useful (if you want me to contribute them to the project):
I think LoadingCache and Cache are pretty similar. In practice a LoadingCache is just a Cache with a default loader (which is incidentally how I implemented both of them with the same class). Not sure why the distinction really needs to be made, since the publicly exposed API interfaces will prevent the user from calling Personally, I don't use I think that there are probably some optimization tweaks you could make if you make it so |
I am introducing some refactorings which accomplish the following:
Loading Cache
get(key, loader)
method on them, withloader
being required.loader
to be optional, as they have a defaultloader
on the cache itself.null
orundefined
in order to not cache their result.Rationale
Firstly, this allows for caches to more effectively implement the interface of
get
, such in the case of a bounded cache, where it can avoid a lot of work by interfacing with theCacheNode
directly, rather than wastefully callinggetIfPresent
, which does a lot of extra work.Secondly, this makes sense from the perspective that there was nothing special about
builder.loading()
, since it didn't actually add any additional state or context onto the cache.This also has the benefit of flattening the delegation hierarchy, removing the
DefaultLoadingCache
class altogether.The change to
get
not cachingnull
results means that developers don't need to throw anError
in order to abort loading the cached value.Metrics
Rationale
Reducing complexity of the delegation hierarchy, which should make things easier from a coding perspective, but also from a performance one. The more delegate method hops, the slower the cache.