Luigi F
Luigi F

Reputation: 79

Scala and playframework shared cache between nodes

I have a complex problem and I can't figure out which one is the best solution to solve it.

this is the scenario:

  1. I have N servers under a single load balancer and a Database.

  2. All the servers connect to the database

  3. All the servers run the same identical application

I want to implement a Cache in order to decrease the response time and reduce to the minimum the HTTP calls Server -> Database

I implemented it and works like a charm on a single server...but I need to find a mechanism to update all the other caches in the other servers when the data is not valid anymore.

example:

I have server A and server B, both have their own cache. At the first request from the outside, for example, get user information, replies server A.

his cache is empty so he needs to get the information from the database.

the second request goes to B, also here server B cache is empty, so he needs to get information from the database.

the third request, again on server A, now the data is in the cache, it replies immediately without database request.

the fourth request, on server B, is a write request (for example change user name), server B can make the changes on the database and update his own cache, invalidating the old user.

but server A still has the old invalid user.

So I need a mechanism for server B to communicate to server A (or N other servers) to invalidate/update the data in the cache.

whats is the best way to do this, in scala play framework?

Also, consider that in the future servers can be in geo-redundancy, so in different geographical locations, in a different network, served by a different ISP.

would be great also to update all the other caches when one user is loaded (one server request from database update all the servers caches), this way all the servers are ready for future request.

Hope I have been clear.

Thanks

Upvotes: 0

Views: 125

Answers (2)

Levi Ramsey
Levi Ramsey

Reputation: 20551

Since you're using Play, which under the hood, already uses Akka, I suggest using Akka Cluster Sharding. With this, the instances of your Play service would form a cluster (including failure detection, etc.) at startup, and organize between themselves which instance owns a particular user's information.

So proceeding through your requests, the first request to GET /userinfo/:uid hits server A. The request handler hashes uid (e.g. with murmur3: consistent hashing is important) and resolves it to, e.g., shard 27. Since the instances started, this is the first time we've had a request involving a user in shard 27, so shard 27 is created and let's say it gets owned by server A. We send a message (e.g. GetUserInfoFor(uid)) to a new UserInfoActor which loads the required data from the DB, stores it in its state, and replies. The Play API handler receives the reply and generates a response to the HTTP request.

For the second request, it's for the same uid, but hits server B. The handler resolves it to shard 27 and its cluster sharding knows that A owns that shard, so it sends a message to the UserInfoActor on A for that uid which has the data in memory. It replies with the info and the Play API handler generates a response to the HTTP request from the reply.

In this way, all subsequent requests (e.g. the third, the same GET hitting server A) for the user info will not touch the DB, no matter which server they hit.

For the fourth request, which let's say is POST /userinfo/:uid and hits server B, the request handler again hashes the uid to shard 27 but this time, we send, e.g., an UpdateUserInfoFor(uid, newInfo) message to that UserInfoActor on server A. The actor receives the message, updates the DB, updates its in-memory user info and replies (either something simple like Done or the new info). The request handler generates a response from that reply.

This works really well: I've personally seen systems using cluster sharding keep terabytes in memory and operate with consistent single-digit millisecond latency for streaming analytics with interactive queries. Servers crash, and the actors running on the servers get rebalanced to surviving instances.

It's important to note that anything matching your requirements is a distributed system and you're requiring strong consistency, i.e. you're requiring that it be unavailable under a network partition (if B is unable to communicate an update to A, it has no choice but to fail the request). Once you start talking about geo-redundancy and multiple ISPs, you're going to see partitions pretty regularly. The only way to get availability under a network partition is to relax the consistency demand and accept that sometimes the GET will not incorporate the latest PUT/POST/DELETE.

Upvotes: 2

Matthias Berndt
Matthias Berndt

Reputation: 4587

This is probably not something that you want to build yourself. But there are plenty of distributed caches out there that you can use, such as Ehcache or InfiniSpan. I suggest you look into one of those two.

Upvotes: 0

Related Questions