Akka anti-patterns: overusing ActorSelection

In this part of Manuel Bernhardt’s series on Akka anti-patterns, we have a look at the things that you should keep in mind when using the ActorSelection mechanism.
Akka’s ActorSelection makes it possible to look up actors by logical path in the hierarchy:
val selection: ActorSelection = context.actorSelection("../processor/storage")
This selection can then be used like ActorRef
in order to send messages to it using the tell
or ask
patterns:
selection ! Storage.Store("important words") val allWords: Future[Seq[String]] = selection ? Storage.RetrieveAll
ActorSelection
is therefore quite useful and allows more flexibility when designing an actor system given that trees can be built dynamically and queried dynamically at runtime. That being said, there are a few things that you should be aware of when using this mechanism.
ActorSelection is unverified
When you hold an ActorRef
in your hands, you have something concrete and substantial and chances are high that the actor it points to exists (unless it has been stopped in the meanwhile). With ActorSelection
there is no such guarantee: make a typo in the path, and you will notice only when sending messages to the selection – if you happen to watch closely the dead letter logs. What’s more, if you overuse this feature and tend to write out (hardcode) actor paths all across your application, this might become a source of headache as the application grows and changes in case you rename actors or move them around in your hierarchy.
ActorSelection is less performant than ActorRef
The way that ActorSelection
works is that it will do the lookup at message delivery time, for each message sent. That might be alright if you are sending a few messages, but if you send many messages and have a deep hierarchy to traverse, you are paying unnecessary lookup costs every time.
Use Identify when appropriate
If your actor needs to dynamically resolve another actor and then send it plenty of messages, a solution is to use the built-in Identify
message that, when sent to an ActorSelection
, will give you back the ActorRef
– or nothing at all, if the ActorSelection
doesn’t match anything. The good news is that it always gives you back an answer (of type ActorIdentity
) so you know what is going on. Combine this technique with context.become
and you get a simple way to safely bootstrap actors that use this mechanism:
class ResolvingActor extends Actor with Stash with ActorLogging { val CorrelationId = 42 var storage: Option[ActorRef] = None context.actorSelection("../processor/storage") ! Identify(CorrelationId) def receive = initializing() def initializing(): Receive = { case ActorIdentity(CorrelationId, Some(ref)) => storage = Some(ref) unstashAll() context.become(ready()) case ActorIdentity(CorrelationId, None) => log.error("Storage not ready, unable to process requests") context.stop(self) case _ => stash() } def ready(): Receive = { case StoreSomething(message) => // do the usual processing storage.foreach(_ ! Storage.Store(message)) } }
In conclusion, overusing ActorSelection means one of two things:
- using it in too many places, with hardcoded paths that are tricky to maintain
- using it to send too many messages as it has a performance cost
This post was originally published on Manuel Bernhardt’s blog.
Read Manuel Bernhardt’s Akka anti-patterns collection:
Leave a Reply
Be the First to Comment!