DEV Community

Cover image for Microsoft Orleans: Grain Caches
Russ Hammett
Russ Hammett

Posted on • Originally published at blog.kritner.com on

Microsoft Orleans: Grain Caches

Holy moly it’s been a while. Time for some more Orleans goodness, hopefully? Caches!

Overview

What are caches and what do they do? As usual, going to lean on Wikipedia to help me out:

From https://en.wikipedia.org/wiki/Cache_(computing):

In computing, a cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere.

So basically a means of getting data faster, sign me up!

Often, you’ll see multiple levels of caching on varying pieces of infrastructure, whether those pieces are all contained within a single piece of hardware (like a computer), or spread across multiple pieces of hardware (like a network). We have the need for speed, and caches gonna give it to ya… ideally!

Problems with caching

https://aws.amazon.com/builders-library/caching-challenges-and-strategies/ has a lot of good information, but just to name a few:

  • Stale data
  • Inconsistent data across caches
  • In the case of local caches - memory and/or additional CPU pressure on a system that is otherwise acting as a webserver
  • In the case of external caches - additional system complexity, additional hop to get to data (though seemingly still better than having to rerun some sort of querying/aggregating of data from potentially numerous data sources when not caching)

A (naive) Orleans Cache

So, it’s been a while since I’ve written anything, much less about Orleans. It’s Hacktoberfest though so I at least gotta try to get that shirt!

Like in past posts where I’m demonstrating something Orleansly, we’ll be working from my github repo starting here https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.62.0. Prior to us creating another IOrleansFunction, we actually need to write the thing we’re be demonstrating, the cache!

A cache abstraction can (and perhaps should?) have more methods available to it than what we’ll be doing in this post, but this isn’t meant to be a full fledged solution.

public interface IOrleansCache<TValue> : IGrainWithStringKey, IGrainInterfaceMarker
{
    Task AddOrUpdate(string key, TValue value);
    Task<TValue> Get(string key);
}
Enter fullscreen mode Exit fullscreen mode

The above should look pretty straight forward, but just to go over it anyway:

  • TValue is the type of value our cache will be storing
  • IGrainWithStringKey states that cache will have a string primary key
  • AddOrUpdate - for adding/updating values based on a string key (not to be confused with the grain primary key)
  • Get for retrieving a value from the cache based on a string key

In case it isn’t obvious, we have two “keys” in the above declaration. A grain “primary” key, and a “key value pair” key. What’s the difference? Well by having the primary key be a string, we can get different grain instances for each individual string key we provide. Why would we do that? You may have different caches with different value types you want to store, bring up a different cache per customer, or a bit of both.

As for the implementation of the grain, I’m leaning heavily on an LRU cache from the package https://github.com/bitfaster/BitFaster.Caching/.

public class OrleansLruCache<TValue> : Grain, IOrleansCache<TValue>
{
    private readonly ConcurrentLru<string, TValue> _cache;

    public OrleansLruCache()
    {
        _cache = new ConcurrentLru<string, TValue>(50);
    }

    public Task AddOrUpdate(string key, TValue value)
    {
        _cache.AddOrUpdate(key, value);
        return Task.CompletedTask;
    }

    public Task<TValue> Get(string key)
    {
        if (_cache.TryGet(key, out var result))
            return Task.FromResult(result);

        TValue nullResult = default;
        return Task.FromResult(nullResult);
    }
}
Enter fullscreen mode Exit fullscreen mode

Again this should all be pretty self explanatory, just a plain old consuming of a cache and not (currently) worrying about things like parameterizing configuration, providing cache eviction methods, persisting the cache to disk in any way, etc.

Wrap up

Keeping this one pretty short and sweet, I implemented within the final code (found here https://github.com/Kritner-Blogs/OrleansGettingStarted/releases/tag/v0.63.0) our IOrleansFunction so it can be played around with. I might have another post or two this month elaborating on this some more, just cuz I need to do some more PRs! (And get back into the habit of writing, but this is at least the 50th time I’ve said that)

References

Top comments (0)