How to create shared drawing board with Akka & Cassandra

Krzysztof Borowski
Nov 16, 2016 · 5 min read

Introduction

Once upon a time, there was complete chaos,
where you could draw and instantly share anything like a boss.
However, the days of Adobe Flash are long gone,
so a new idea was born:
make chaos great again!
And this is how from chaos arose AkkaPaint

The idea was pretty straightforward: create a drawing space which is:

  • multiuser,
  • able to propagate changes to all users in real time,
  • scalable.

You may ask, where is the challenge? The answer is: make the implementation really small and simple.

Drawing board representation — basics

A drawing board can be represented as a simple map between pixel coordinates and color representation of the pixels. It’s a really naive approach which doesn’t include any optimization, but it works surprisingly well. Furthermore, every change on the drawing board can be represented as an event which contains a pixel sequence and the new color applied to these pixels.

Drawing board as an actor

For me, Akka Toolkit seems to be a perfect fit for this problem. The painting board can be easily represented as an actor. The internal actor state can contain a map where pixels are keys and colors are values (akkaPaintBoard: Map[Pixel, Color]). As we want to preserve the actor state between application restarts, we will use Persistent Actor here. Every change to the board will be saved as an event. Sounds great!

Scalable drawing board

The size of the drawing board can be enormous, which implies a lot of pixels to be processed and a lot of changes to be applied. Sadly, that’s too much for one actor. So, let’s split the whole board into small squares 100×100 pixels each. Such a square can be represented by one actor, and will hold the colors only of 10,000 pixels. Ideally, we want to scale the problem horizontally, as sometimes the whole board can’t fit into the memory of one machine, or we want to use the computing power of more machines. And here comes Akka Cluster Sharding! The idea is simple: actors (here called entities) form shards (a shard is simply a group of actors), and each of the shards can be located on a different machine. Furthermore, there is the concept of coordinator, which knows the location of each shard and entity. The shard ID and entity ID are extracted from incoming data using the two simple functions presented below (extractShardId and extractEntityId) . The shardingPixels method is responsible for splitting an incoming stream of pixel color changes into smaller packages addressed to exactly one entity.

This solution is illustrated here:

Creating a cluster and obtaining the ShardRegion reference (ShardRegion is a local actor representing the entrance to the cluster) can look like this:

We can send all messages to the ShardRegion, which knows (thanks to the coordinator) how to route messages to the proper entity. Also, it is worth mentioning that if a new machine joins the cluster, some of the shards will be moved to that machine (look at least-shard-allocation-strategy configuration). This process is called resharding and is performed in a few steps:

1. The shard that will be moved to another machine is chosen by the coordinator.
2. The coordinator informs ShardRegion to start buffering all messages that are incoming to this shard.
3. All of the actors inside the chosen shard are killed.
4. Shard and actors are started on a new machine (the state of the actor will be restored from the events that were previously persisted in a database).
5. All buffered data is sent to the newly restored shard.

The perceptive reader will surely notice here a potential inconvenience: with a lot of incoming messages during the resharding process, the buffer can overflow. Sadly, all you can do is to resize the buffer by setting the akka.cluster.sharding.buffer-size configuration parameter.
The current state of the board persists even after changing the size of it in the code. Shards and entities will be dynamically created at runtime if the board is resized.

Multiple users

To keep all active users updated we can use WebSockets. In Play! framework, each WebSocket connection can be represented as an actor (yay, what a surprise :)). This ClientConnectionActor can be registered in each entity, and each entity can send the updates completely asynchronously to the browser via ClientConnectionActorreference.

And now all we need is “just” a few lines of JavaScript, HTML and CSS and everything is up and running.

Total scalability of the AkkaPaint

The application consists of 3 main parts:

1. Play! web application (serves static data, parses json messages incoming via WebSocket, converts to json and pushes messages to the client browser)
2. Akka Cluster Sharding (updates internal actor state (saves events and snapshots), sends changes to all registered clients)
3. Cassandra database (saves events and snapshots streams, serves events and snapshots during cluster restart and resharding process)

Each of these parts can be easily scaled horizontally.

Try it on your own!

If you don’t want to download anything, you can try it online here: http://demo.akkapaint.org/. Maybe drop your country’s flag there?
Furthermore, you can lend your computer resources. If you want to join the cluster, find the akkapaint-web.conf file and apply the “if you want to join me" comments actions. Restart your application and voilà: in 10 seconds some of the shards should be moved to your machine (you should see some logging on your console).

Summary

We have walked through the general idea, and practical examples of some extraordinary Akka features (like persistent actors, clustering, sharding) which allowed us to build a multiuser, scalable AkkaPaint with the possibility of getting the updates in the real time. The current working implementation has only 288 LOC! Akkareally shines here. There are a lot more things I’ve implemented, such as:

  • snapshotting
  • performance optimizations (e.g. messages serialized via Protocol Buffer)
  • buffering messages (the updates to the browser are sent with a 1s tick)
  • adjusting play configuration
  • adjusting akka sharding configuration
  • preparing some gatling tests
  • painting images on AkkaPaint board, with the great help of akka-stream and rapture.io json library.

I am not able to describe everything in this blog post, as the text is already too long.
All those features can be found here: akkapaint. There are a lot of great ideas to be implemented (e.g. creating private boards, some compression and further performance optimizations, loading images through the browser, UI improvements…). All contributions are really welcome!

Happy hAkking!

VirtusLab

Virtus Lab company blog

Krzysztof Borowski

Written by

VirtusLab

VirtusLab

Virtus Lab company blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade