This post was written in collaboration with Lightbend, a Knoldus’ Partner.
Most microservices applications are stateless, which means they delegate persistence and consistency to a database or external storage. But sometimes there’s a benefit to keeping state inside the application.
If you’ve worked with data storage systems long enough, you are bound to bump into the term “sharding“. Sharding is another word for partitioning and is a technique used in databases to improve elasticity and resilience. When data is sharded, it is spread out over multiple backend stores, allowing more data to be stored than possible on a single-server node.
The Akka toolkit provides cluster sharding as a way to introduce sharding into your application. In this blog post, you’ll learn how key Akka modules—namely Akka Cluster Sharding—can be used to build stateful systems.
What are Stateful Systems?
Before getting into what are stateful systems, let’s start with what they are not. Stateful systems are the opposite of the classic stateless systems.
The term ‘stateless’ is a bit of a misnomer in this context. With most stateless systems, there is a state of things that the system manages. One way to look at this is that there is a cold state and a hot state. The cold state is data that resides in a persistence layer, typically a database. The hot state is data that resides in memory, where it can be manipulated by code. With stateless systems, when the state of a thing needs to be changed, the system first retrieves the thing’s state from the cold state layer, brings it into the hot state layer, executes the code that changes the state, and then pushes the new state from the hot state layer back into the cold state layer. With stateless systems, all state changes operate with a read-change-write cycle. The cold state layer manages locking and concurrency to handle parallel stateless processes attempting to change a thing’s state simultaneously.
In a stateful system, the idea is that when a given thing’s state is in use, it is kept in the hot state for as long as it is active. The stateless read-change-write cycle becomes a read once, then changes the state and writes it to the cold state layer as each state transformation occurs. For this stateful processing flow to work, there must be a single writer for a given thing in the entire system.
Consider the flow for creating an order. Each order is created as a series of steps to build up a completed order. It is possible for two or more simultaneous requests to change the same order in a stateless system, resulting in potentially inconsistent results. The cold state persistence layer must enforce state consistency using lock and other concurrency controls.
Stateful systems require that all requests for a given thing must be routed to a single stateful writer. Managing this stateful processing complexity is the primary reason that the stateless approach is more commonly used. However, the stateless simplicity comes at a cost. This cost is due to the read-change-write cycle’s overhead and the complexity of locking and concurrency controls. Fortunately, implementing stateful systems is a solvable problem.
How to use Stateful Actors
Akka Cluster Sharding handles what are called entity actors. Entity actors are stateful. Each entity actor has a unique identifier, such as a shopping cart ID or a physical IoT device ID. Entities are the ‘things’ discussed in the previous section. State changing requests are routed to specific entity actor instances using request entity IDs. Akka Cluster Sharding manages the distribution and entity actor instance creation, and it also handles the routing of request messages.
Akka Persistence handles the initial state recovery from the cold state persistence layer as new instances of entity actors are activated by Akka Cluster Sharding. Akka Persistence is also used to write state changes to the cold state persistence layer.
Understanding Entity and Shard IDs
Entities and shards are important components of Akka Cluster Sharding. An entity ID is used to uniquely identify each entity, while a shard ID is used to identify each shard present in the cluster nodes. Although shard IDs are unique, many entities may share the same shard ID. So if we have entities A, B, and C, those entities may all live on the same shard, and therefore will share the same shard ID.
Mapping your entities to a shard ID is how you control the distribution of your entities. It’s very important to pay attention to how we map those entities to shard IDs, because if we do it improperly, then we can end up with a very unbalanced distribution. An unbalanced distribution can create hotspots within your application that can cause performance problems later down the road.
An Akka Cluster Sharding Example
The code implementation of a stateful system using Akka involves sending messages to specific entities via the Akka actor system. For example, in the shopping cart example, say incoming requests are sent via HTTP. The developer focuses on the code that translates the HTTP requests into messages sent to an Akka Cluster Sharding actor. The cluster sharding actor routes these messages to specific entity actors. The developer also implements the code used for processing the request messages in entity actors. In addition to message routing, Akka Cluster Sharding also creates entity actors when needed to process incoming messages.
The above image is a cluster sharding representation, and each circle represents an actor. The small light blue circles represent entity actors. Next in from the entity actors are what are called shard actors. The three large circles represent cluster sharding actors, and they also represent single nodes in a cluster.
In this three-node cluster example, HTTP requests arrive on any one of the nodes. These incoming HTTP requests are forwarded as actor messages to the local cluster sharding actors as described above. The cluster sharding actors route these messages to specific entity actors via the shard actors. When a message is sent to an entity actor that is not active, the shard actor will start an actor for the specified entity ID.
No More Blocking
An important thing to do when we are working inside an Akka Actor, i.e., to avoid blocking. Blocking inside an actor can have dramatic effects on the performance of the application. Because blocking ties up thread(s) that cannot be used by other actors and it further creates unnecessary retention of resources.
In Akka Cluster Sharding, eventually, after a period of time, the sharded actors will drop out of memory. This happens because of a concept called Passivation in Akka Cluster Sharding.
In this post, we defined what makes up a stateful system. We then showed how to use two important Akka modules—Akka Cluster Sharding and Akka Persistence—to get started building one of these systems. If you’d like to dive deeper, please check out this webinar recording and accompanying slide deck.