If you’ve used Lightbend’s Scala-Akka templates that involve persisting Akka actor states, you’ll notice that LevelDB is usually configured as the default storage medium for persistence journals (and snapshots). In many of these templates, a shared LevelDB journal is shared by multiple actor systems. As reminded by the template documentation as well as code-level comments, such setup isn’t suitable for production systems.
Thanks to the prospering Akka user community which maintains a good list of journal plugins you could pick from to suit your specific needs. Journal choices include Cassandra, HBase, Redis, PostgreSQL and others. In this blog post, I’m going to highlight how to set up Akka persistent journal using a plugin for Redis, which is one of the most popular open-source key-value stores.
Redis client for Scala
First thing first, you’ll need a Redis server running on a server node you want your actor systems to connect to. If you haven’t already had one, download the server from Redis website and install it on a designated server host. The installation should include a command-line interface tool, redis-cli, that comes in handy for ad-hoc data update/lookup.
Next, you need a Redis client for Scala, Rediscala, which is a non-blocking Redis driver that wraps Redis requests/responses in Futures. To include the Rediscala in the application, simply specify it as a library dependency in build.sbt.
Redis journal plugin
The Redis journal plugin is from Hootsuite. Similar to how Rediscala is set up in build.sbt, you can add the dependency for the Redis journal plugin. To tell sbt where to locate the Ivy repo for the journal plugin, you’ll also need to add a resolver as well. The build.sbt content should look something like the following:
.... resolvers += Resolver.jcenterRepo libraryDependencies ++= Seq( ... "com.github.etaty" %% "rediscala" % "1.6.0", "com.hootsuite" %% "akka-persistence-redis" % "0.4.0", ... )
Alternatively, rather than specifying them as dependencies you can clone the git repos for the Redis client and journal plugin, use sbt to generate a jar file for each of them, and include them in your application library (e.g. under /activator-project-root/lib/).
Application configurations
Now that the library dependency setup for Redis journal and Redis client is taken care of, next in line is to update the configuration information in application.conf to replace LevelDB with Redis.
Besides Akka related configuration, the Redis host and port information is specified in the configuration file. The Redis journal plugin has the RedisJournal class that extends trait DefaultRedisComponent, which in turn reads the Redis host/port information from the configuration file and overrides the default host/port (localhost/6379) in the RedisClient case class within Rediscala.
As for the Akka persistence configuration, simply remove all LevelDB related lines in the configuration file and add the Redis persistence journal (and snapshot) plugin information. The application.conf content now looks like the following:
# Redis configuration redis { host = "localhost" port = 6379 } # Akka configuration akka { ... persistence { journal.plugin = "akka-persistence-redis.journal" snapshot-store.plugin = "akka-persistence-redis.snapshot" } } # Config for the plugin akka-persistence-redis { journal { # class name of the plugin class = "com.hootsuite.akka.persistence.redis.journal.RedisJournal" # Dispatcher for fetching and replaying messages replay-dispatcher = "akka.persistence.dispatchers.default-replay-dispatcher" } snapshot { # Class name of the plugin class = "com.hootsuite.akka.persistence.redis.snapshot.RedisSnapshotStore" # Dispatcher for the plugin actor. plugin-dispatcher = "akka.persistence.dispatchers.default-plugin-dispatcher" snapshot-interval = 3600 s } }
Onto the application source code
That’s all the configuration changes needed for using Redis persistence journal. To retire LevelDB as the journal store from within the application, you can simply remove all code and imports that reference LevelDB for journal/snapshot setup. Any existing code logic you’ve developed to persist for LevelDB should now be applied to the Redis journal without changes.
In other words, this LevelDB to Redis journal migration is almost entirely a configurative effort. For general-purpose persistence of actor states, Akka’s persist method abstracts you from having to directly deal with Redis-specific interactions. Tracing the source code of Akka’s PersistentActor.scala, persist method is defined as follows:
... trait PersistentActor extends Eventsourced with PersistenceIdentity { ... def persist[A](event: A)(handler: A => Unit): Unit = { internalPersist(event)(handler) } ... } ...
For instance, a typical persist snippet might look like the following:
... case work: Work => ... persist(WorkAccepted(work)) { event => sender() ! Master.Ack(work.workId) workState = workState.updated(event) } ...
In essence, as long as actor states are persisted with the proper method signature, any journal store specific interactions will be taken care of by the corresponding journal plugin.