68

I've been working with akka for 7-8 months now daily. When I started, I would be working on applications and notice that actors would be used basically anywhere once inside the actor system for communicating between most objects. So I did the same - spin up another actor for x/y/z.

It seems to me that this may be too indiscriminate, adding complexity where it isn't needed - but I can't find any discussions on where actors vs plain synchronous or even async logic via futures should be used. I started pondering my stance after my coworker mentioned something similar. I realized several cases more recently where I have pondered a task and then avoided creating another actor because I could achieve the same result safely in an immutable implementation - eg something like getting configuration values from a db or file somewhere where you access very infrequently and will wait for the result is the actual use case.

In particular, it seems to me that any case where you're playing with immutable state, actors create complexity and limit throughput - a pure function in an object, for example, can be called concurrently with no risk with any level of concurrency, yet an actor can only process one message at a time. The alternate consideration is you'll park the thread if you need to wait for the result unless you start using futures but cases where you don't need to worry about async messaging or scale it seems it may be overkill to employ an actor.

So my question is - is there a bad time to use actors? I'm curious how erlang looks and would really like other people's insight. Or if there are some principles around actor use.

5
  • What kind of job gives an opportunity to work with Akka daily for months?
    – Den
    Oct 16, 2014 at 13:27
  • 5
    Good ones. :) Or make the change yourself.
    – JasonG
    Oct 16, 2014 at 15:51
  • I'd want to know specifically what the trade-offs are between asking an actor and just using a plain Future.
    – Max Heiber
    Oct 22, 2015 at 3:13
  • 6
    I ended up writing a book about Akka a couple years later and I think you can sum it up like this: If you have some state - eg a counter - multiple threads reading and writing from/to that value will end up stomping on each other w/o synchronization and locking. If you put that state inside an actor, suddenly you can be sure that it's accessed safely and you aren't dropping writes or getting stale reads. Actors in erlang and akka also assume that state can go bad and the actor can throw errors so you have some self healing system characteristics. Futures are simpler if you don't need to mutate
    – JasonG
    Oct 22, 2015 at 14:25
  • 5
    many years later I'm an erlang/elixir programmer and I keep coming back to this with new insights :) Elixir is quite different as there are no objects as an alternative to processes so processes are always built when you need state. It just happens to be concurrent. This is the best heuristic still.
    – JasonG
    Feb 7, 2018 at 22:33

5 Answers 5

25

This is a question I am interested in and I have been doing some research on. For other viewpoints, see this blog post by Noel Walsh or this question on Stack Overflow. I have some opinions I would like to offer:

  • I think Akka, because it works with messages, encourages a "push mindset". Often, for concurrency, I would argue this is not what you want. Pull is much safer. For example one common pattern for distributed systems is to have a set of workers processing information in a queue. Obviously this is possible in Akka but it doesn't necessarily seem to be the first approach people try. Akka also offers durable mailboxes but again it depends how you use them - a single shared queue is a lot more flexible than per worker queues for balancing / re-assigning work.
  • It's easy to get into the mindset of replacing your classes with actors. In fact some people even seem to advocate it by saying actors should only do one thing. Taken to it's logical conclusion this increases code complexity as Jason describes, because if every class is an actor, that's a lot of extra messages and receive / send blocks. It also makes it harder to understand and test the code because you lose the formality of interfaces - and I am not convinced that comments are a solution to this. Also despite Akka's legendary efficiency, I suspect that actor proliferation is not a good idea performance wise - when we use Java threads we know they are precious and conserve them accordingly.
  • It's related to the previous point, but another annoyance is the loss of type information that Noel and Pino highlight, as for many of us that's why we are using Scala rather than other languages such as Python. There are some ways around this but they are either non-standard, not recommended or experimental.
  • Finally concurrency, even if you have a "let it crash" mindset, is hard. Alternative programming models can help but they don't make the problems go away - they change them - that's why it's good to think about them formally. It's also why Joe Average developer reaches for ready built tools like RabbitMQ, Storm, Hadoop, Spark, Kafka or NoSQL databases. Akka does have some prebuilt tools and components, which is cool, but it also feels quite low level, so more ready built common elements of distributed systems would help developers and ensure systems are built right.

Like Jason, I am keen to hear other people's insight here. How can I address some of the issues above and use Akka better?

7
  • 1
    I found this post about testing in Akka spot-on timgilbert.wordpress.com/2013/07/07/… Feb 12, 2014 at 18:35
  • "Composition over inheritance" is still useful for dependency injection. Eg, pass the dependency in as props (uses constructor params). The problem then would be supervision - the actor created would be supervised somewhere other than the actor. A router could be used to wrap a supervision strategy around actors created at top level but holy that's pretty complex now! I guess cake would be a better solution - have a trait with the actor creation for a db service actor for example. Then just use a test mock/stub db service trait in text context.
    – JasonG
    Mar 20, 2014 at 18:27
  • 1
    Yes, exactly - supervision doesn't work well with dependency injection. Also I have had instances where I it was necessary to inject a factory rather than the class, otherwise there were wierd closure problems - I am guessing due to Akka's treatment of threads. Mar 20, 2014 at 19:13
  • I think that's not a bad idea - thanks mark - I might actually try to write about this a bit and socialize it. You could inject something that gives the Props maybe? An actor factory of sorts where the consumer can instantiate the actor. eg def method(arg) = return Props(new ActorWithArgs(arg)) from that (psuedo?) factory so you can make the actor in the right context. This seems like a good idea and a good target for cake solutions for di too.
    – JasonG
    Mar 21, 2014 at 13:34
  • I just started following this course: coursera.org/course/posa Although it's primarily aimed at people programming Android, it's also a good overview in concurrency in Java. So one thing I wondering about is "Isn't Akka just a fancy form of event loops with some bells and whistles (because you can put event loops on different threads)?" May 29, 2014 at 21:27
23

It's worth considering what the actor model is used for: the actor model is

  1. a concurrency model
  2. that avoids concurrent access to mutable state
  3. using asynchronous communications mechanisms to provide concurrency.

This is valuable because using shared state from multiple threads gets really hard, especially when there are relationships among different components of the shared state that must be kept synchronized. However, if you have domain components in which:

  • You don't allow concurrency, OR
  • You don't allow mutable state (as in functional programming), OR
  • You must rely on some synchronous communications mechanism,

then the actor model will not provide much (if any) benefit.

Hope that helps.

4
  • Thanks - I guess you really have to evaluate for two things then - mutable state and concurrency ? No problem to solve if a class is stateless OR not concurrent. One more consideration - I think fault tolerance is another point? eg if you have an immutable redis client but it can fail while running - would that not be a good use case? that restarting that actor may be necessary? As - although the class may be purely immutable - it's very possible for there to be a corrupted state external to the immutable actor that can cause it to fail.
    – JasonG
    Sep 28, 2013 at 23:27
  • I guess the actor that is responsible for communicating with redis would get the exception and restart though.
    – JasonG
    Sep 28, 2013 at 23:32
  • @JasonG To my mind, "fault tolerance" is not an aspect of the actor model in general - it's something that comes with the actor model implementation in Erlang (and maybe Akka?). I can't speak to redis, though I have to admit, "immutable client" sounds strange to me... If a software component can fail, I don't see how it can be considered immutable. Sep 29, 2013 at 14:35
  • Fault tolerance and supervision exist in akka and it's very similar to erlang. I understand what you're saying about immutable client but immutable means that the code state doesn't change anywhere. If I initialize a connection on startup it's possible that the client code can be immutable with a restart strategy used on any sort of fault, simply reinitializing the actor upon encountering a problem.
    – JasonG
    Sep 29, 2013 at 18:08
14

Your intuition is correct, IMHO. Using actors everywhere is like having the proverbial hammer and seeing only nails.

The Erlang best practice is to use processes/actors for all activities that happen concurrently. That is, just like in real life. Sometimes it is difficult to find the right granularity, but most of the times you just know by looking at the modeled domain and using a bit of common sense. I'm afraid I don't have a better method than that, but I hope it helps.

1
  • Cool thanks. I'm really interested to see what happens in this discussion.
    – JasonG
    Sep 28, 2013 at 0:06
1

In order input/output messaging:

I recently met with an akka-based application where the actor model actually caused concurrency problems, a simpler model would have sufficed better under load.

The problem was that the incoming messages were moving in different 'lanes' (through different actor paths) but the code assumed the messages would arrive at their final destination in the same order they arrived. As long as data arrived with sufficiently large intervals this worked because there would be only a single conflicting message racing to the destination. When the intervals decreased they started arriving out of order and causing weird behavior.

The problem could have been solved correctly with a little less actors, but it's an easy mistake to make when overusing them.

2
-1

In my opinion there are two use cases for Actors. Shared resources such as ports and the like, and large state. The first has been covered well by the discussion so far, but large state is also a valid reason.

A large structure being passed with every procedure call can use a lot of stack. This state can be put into a separate process, the structure replaced by a process id, and that process queried on an as required basis.

Databases such as mnesia can be thought of as storing state externally to the querying process.

2
  • 1
    Can you clarify? Do you mean large state across the network? Within a local process passing a reference to an immutable structure has almost no cost and you can't pass mutable structures. Assuming you're talking about a large mutable structure that needs to be copied to send? So that's basically the same thing again - shared state. The size doesn't really change anything. Let me know if I'm misunderstanding.
    – JasonG
    Apr 6, 2016 at 15:24
  • So how do you pass by reference? Surely the way to do that is to put the structure in a process and pass the processid. Local calls will put the data on the stack, and every recursive call will do it again (with the exception of tail recursion). Recursive processing of the structure can be done using lists for transfering state from one call to the other, this state can then reference the structure in the other process. Jun 26, 2017 at 3:20

Not the answer you're looking for? Browse other questions tagged or ask your own question.