ZorgoZ
ZorgoZ

Reputation: 3559

Triggering eager refresh with FusionCache from consumer side

I have a service that can produce some data. And some others that are consuming that data. The consumers can request the data over masstransit. But as producing that data is relatively expensive and prone to third party service outages, I intend to use FusionCache with FailSafe and EagerRefresh. The consumers are on the same Redis backplane, hence, it is straightforward for them to try to get it from the cache first and only address the producer service if it is not found there. I use .TryGetAsync() for that.

If I got it correctly, to trigger the eager refresh - as only the producer can produce the data -, the producer needs to try to get the value from the cache in the grace period.

I don't know if I can make the TryGet believe that in the same grace period the value is expired - even if it is not - so that I can address the service and trigger the eager refresh. If I understood correctly, the getter is not that sophisticated.

The best workaround that came into my mind is to get with the entry or at least read back somehow the metadata stored by FusionCache alongside the data form the memory cache (supposing that data is in the memory as well, not only in redis), and decide based on that. But how?

Upvotes: 0

Views: 290

Answers (1)

Jody Donetti
Jody Donetti

Reputation: 558

FusionCache creator here.

I'm reporting here the answer I gave on GitHub for completeness.

I suppose the consumer can ask the producer to produce the data via MassTransit, but in a kind of fire-and-forget way: the production then will happen on the producer side, which in turn will update the cache.

More specifically:

  • a consumer can ask via MassTransit to a producer to produce it, but cannot wait for the result
  • a producer can be contacted via MassTransit, generate the data and update the cache

In this case you can use some of the methods normally used for Conditional Refresh like ctx.NotModified(), and maybe a touch of Adaptive Caching too, in this way:

// CONSUMER SIDE
var product = await cache.GetOrSetAsync<Product>(
    $"product:{id}",
    async (ctx, ct) =>
    {
        // CALL MASS TRANSIT HERE...
        
        if (ctx.HasStaleValue) {
            // TEMPORARILY RENEW THE OLD VALUE
            ctx.Options.Duration = some_duration_here;
            return ctx.NotModified();
        }
        
        // NO STALE VALUE -> PRODUCER DID NOT PRODUCED THIS YET
        ctx.Options.Duration = maybe_some_different_duration_here;
        return some_default_value;
    },
    opt => opt.SetDuration(duration).SetFailSafe(true)
);

// PRODUCER SIDE
// 
public async Task Consume(ConsumeContext<CreateData> context)
{
    // HEAVY COMPUTING HERE...
    await _cache.SetAsync("cache-key", myData);
}

To avoid multiple MassTransit messages to kickstart multiple production on the producer side, we can do something like this: when the producer receives the MassTransit request, it will expire the cache entry and then immediately do a GetOrSet.

The GetOrSet includes automatic Cache Stampede protection, so even if 10 MassTransit requests come in at (roughly) the same time, only the first one will be effectively executed.

Hope this helps.

Upvotes: 0

Related Questions