-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Support for a cache operator supporting asynchronous refresh #3573
Comments
Wouldn't it be a lot easier for you to have a single Or A Mono that change is no Mono |
@MikkelHJuul Can you please provide more info on how to use Sinks.many? or better way to handle following situation? |
So, if I had an app designed around reactor anyway I would use a But depending on your use it may be nice to have the token requested on subscribe since errors during its use could warrant refreshing the token. But if you can promise a time based token that is sure to not become rejected I would use the The cache(1) operator is essentially the same operator as the Sinks.many.replay.latest btw. The sinks thing just gives you the inverted control of being able to "send" messages on demand |
@MikkelHJuul - the semantics are different. The problem is that background processes are expensive, as is storing a value which isn't actually being accessed. If you make more calls to the backing service to get the data than the number of times the data is accessed, that's a bad thing. If you do either As a thought experiment, It might help to imagine creating 10000 of these, and the signals being 100 MBs of data extracted and transformed from different databases. obv. Take a look at caffeine to get a fuller picture. If anyone seems interested I could just open a PR with the implementation I made. |
@keddie is correct. A common misunderstanding with Caffeine's refresh is when users think of it as a periodic reload of the content at the fixed delay. That's a (unbounded) periodic replica of the source data, whereas a cache is more often a bounded subset of that data. In those cases, users can simply use a |
@keddie / @ben-manes - now I'm not sure if you agree or disagree. It sounds like yes except for the fact that you want the source to be cancelled if no one listens, so as to not cache data when it is unneeded. We actually do that as well where all subscribers use the same cached data ( Now it may be nicer to do something more specialized, sure. I think my initial thought however is that a data source that changes like this is logically, not, a But I guess that is only slightly related to whether to add such a construct or not :) |
I played with the idea a little bit and I think it would be interesting functionality. Although I think I do have some reservations, similar to the one from @MikkelHJuul. With current In this proposal I think it seems that the lifecycle can span between a batch of Subscribers that triggered the first resolution and another batch that can potentially see a refreshed value after the first batch retrieved a result. Let's say that once the first retrieval completes, current Subscribers are served and a refresh is triggered. What Anyways, here's my experiment on this to understand better, let me know if I caught the idea. Below the code is the output: Refreshing Cache implementation
Output
|
@keddie @ben-manes I'm really interested in your input. I'm not sure the above idea is generic enough to incorporate an an operator, but I think we could add a FAQ section to our reference documentation for users to tailor it to their use cases. Do you think this is near something you'd recommend? WDYT? |
Caffeine supports a concept called "refresh" where after a value is written into the cache, it will be refreshed if it's referenced after a certain duration. While the refresh is in progress, the original value is returned. This allows low-latency, while still getting a relatively up-to-date value. I propose adding an operator
Mono.refreshCache(Duration refreshAfterWrite, Duration expireAfterWrite)
with this functionality.Motivation
For example suppose you have a method which returns after 60 minutes
takes60Minutes()
... callers toMono.fromSupplier(() -> takes60Minutes()).cache(Duration.ofHours(2))
will wait for an hour every 2 hours when the value expires. On the other hand an operator supporting refresh
Mono.fromSupplier(() -> takes60Minutes()).refreshCache(refresh: Duration.ofMinutes(30), expire: Duration.ofHours(2))
Will always immediately have a value as long as there is at least one read per half hour.
Desired solution
Add an implementation of the operator above.
Considered alternatives
One approach people might take is to just grab the latest value from a Flux periodically polling the slow emitter. One key advantage of the refreshCache() structure is that the call is only made when/if there are further subscriptions, so expensive operations are not re-run excessively. Note that the refresh does rate limit the upstream.
I feel like there ought to be some combination of other operators that could build up this behavior (i.e. two stacked cache() calls... but I don't see it.
Another option would be to just provide said operator as a transformation used like:
sourceMono.transform(Extensions.refreshCache(ofHours(1), ofHours(3)))
In some suitable extensions library.
A clear downside of adding any new operator is that the proliferation of methods on
Mono
is already fairly baffling, and comes with a support cost :) This would argue for just providing a transformation.Additional context
I made an standalone implementation
Mono.refreshCache()
. I'm happy to contribute it. But since I made it with a few things specific to a project for my current employer(who are supportive)... before I go to the trouble of making a PR. I thought I'd check on your interest in such an operator. I did not yet implement a corresponding operator for Flux, but it'd be straightforward to do, and I think I should do that just for consistency.The text was updated successfully, but these errors were encountered: