Simple Caching For Akka Actors

May 22, 2015

Recently I needed to cache some lightweight data into actor’s local memory to
not make unnecessary calls to third party service over the internet. I think what I
came up with is a pretty simple implementation (hence I named the article
simple caching in akka). But the idea can be expended with small changes
if any to use it to cache actor messages. Let’s imagine the word of actors where
we have one actor per user and we send messages to user actors to get some info
back. Now when we say each user (actor) is responsible to cache it’s own data
it means each actor will use even more memory so at some point we’ll need to
scale out because we’ll run out of memory (not that we didn’t have that
problem without cache at first place). But it’s a good thing that we know we
need to scale out from the beginning because I didn’t mention about high
availability yet, so we at least need two boxes.

Pros of caching result in actor’s own memory is one
it’s gonna be fast and more reliable since it doesn’t have to go to a
centralize cache box over the network, and do you even wanna have to manage
boxes for cache and write all that boilerplate code to read and write to cache.
But of course not everything is as simple as it seems to be. With caching in
memory we’re still left with at least two historical problems, how to invalidate
cache when you have more than one instance running and other problem is
big cache means big memory and big memory is big heap that GC need
to manage, so at some point you’ll at very least have to spin up visual jvm or
turn on GC log or whatever you prefer to see what the heck is going on under
the hood, not even talking about trying to tune GC.

So how does akka help with those problems. What if we don’t have same
user actor run on more than one box like we usually do with traditional
architecture. We’re used to running same code on more than one box and
there we get both hight availability and that’s how we scale out. But
having user A run on a single box do we have high availability problem?
What if that box goes down. Since we designed it in a way where we have all
those small actors,
one per user in this case, we can spread those across many boxes and if one
goes down we’ll just start actors what were running on that box on the boxes
that or online, akka clustering so to speak. So this idea should take as pretty
far in theory. Let’s dive deep to see how it’s simple.

We well start by writing our user actor. As I mentioned we’re gonna create one
actor per user. Let’s make userId a constructor parameter so actor knows it’s
id. Also let’s say that is there is a data actor we can send
GetUserData(userId) to get user data and
GetUserCredits(userId) message to get user
credits. In real world this would be like a web service or if this data comes
form DB it would make more sense to let user actor manage it’s data also.
But this should be good for the demo.

User actor will take GetUserData and GetUserCredits case object as
messages. Let’s say credits is really important and we don’t want to cache it
ever but we do want to cache user data. The normal way to send that data back
would be to do this:

As you can see it can’t get simpler than this, it’s actually a shorter syntax.

Let’s prove that this is actually working. And what is a better way to do it
than write unit tests? There are few cases we want to test:

If cache is enabled first time we send GetUserData message to user actor
it will call data actor to get the data but next time it won’t.
We’ll have a configuration option to tell as for how long to
cache, when it’s set to zero we say cache is disabled for whole actor.

If cache is disabled first time we send GetUserData it will call data
actor and it will do it also when we call it second time, nothing was cached
so to speak.

We want to test that nothing would be cached if we use respond method
instead of cacheAndRespond

Pretty nice and simple, no crazy plumbing going, no boilerplate code and no
complexity. Let’s look at ActorCaching since it’s the base trait for our
actors with caching and it’s doing all the heavy lifting for us. First we create
a type alias for our cache type Cache = Map[Any, Any](mainly to hide this
ugly map of any to any thing.) If cache is enabled (we define it as enabled if
duration isn’t set to zero) we’re setting up a schedule to send ExpireCache
message to self every cacheDuration period of time (which comes from
Config trait) where we’re gonna set our cache to an empty map (initial state.)
Looking in receive partial function, when we get a message that we have cache
we’re gonna respond with cached version. When we receive (NoCache, msg) we’re
not gonna cache and will reply with the message. When getting (msg, response)
message we’re gonna store the response in cache under msg key
(yey case classes, getting so much for free.). When ExpireCache is received,
which is coming from the scheduler as I already mentioned we’re simply reseting
cache.

Now let’s look at respond and cacheAndRespond which we’re calling with
child class to send the respond back to caller. Those are getting a future,
mapping it to tuple of NoCache -> T or msg -> T and piping it to self where
receive function will take care of the rest.

One other small detail is in onMessageWithResponse where we don’t store the
message in cache if cache is disabled. This gives us an ability to turn of the
cache by setting cached duration to zero without changing the code.
cacheDuration is coming from Config trait and defined as following so child
actor can override it if it doesn’t want to live with the globally set cache
duration:

Another small detail that’s very important is in UserActor that I didn’t cover.
Notice that receive method is defined as
override def receive: Receive = super.receive orElse {}
This give base trait a chance to check the cache first, if it’s found in cache,
partial function after orElse won’t event hit.

Although we already know what it’s working by proving it with unit tests it
would be no fun if we don’t actually run it. Here in configuration I’m going to
put cache-duration as one minute.

Configuration

I created another actor called DriverActor, when it starts it’s gonna hit
our user actor asking for user data every 10 seconds. In Main class we’re
creating our actor system, creating data actor, creating one user actor for
user with id 10 and giving it to driver actor to hit ever 10 seconds.

As we can see from the log, the first time driver hits user actor, user actor logs
Getting user data message which is logged from receive partial function.
Consequent times driver hit’s it we don’t see that message but we see that
data is being received. Which means receive block isn’t being hit and
data is being returned from the cache. After about a minute we see our debug
line that we’re writing when we’re expiring cache. After cache was expired we
the log message from user-10 actor and everything repeats again.