0

How could actors be used in complex back-end services, that are made of receiving an initial request, doing some processing, then sending some requests/things-to-do to other services, waiting for responses from some of them, and deciding how to continue based of the responses, and so on, until we calculate the final result?

Trying to use actors for implementing such services raises the question - What parts of this workflow should be implemented by actors, and what parts should not?

Actor instances do not release the threads they are using until the task they are trying to accomplish is completed (unless we delegate it to a Future), so it looks to me like writing the whole workflow as a hierarchy of smaller and smaller actors, with N layers of children, would be on one hand the ideal design based on the actor concept, but on the other hand it would keep up to N-1 threads (per initial request) - constantly locked doing nothing but waiting on one of the bottom actors in the hierarchy to complete. Specifically, the topmost actor in the hierarchy will be idle most of the time.

This hierarchical design also matches the error kernel pattern.

But this sounds like very bad for concurrency.

And even if you tried to keep this hierarchy of actors, but wrap the calls to each actor in a Future (by asking the child actors, rather than telling), so locking would be much shorter (although it would still exist) - still working with so many Futures makes it impossible to work with stateful actors, or actors who modify the state of the system in ways that are not synchronized by themselves (i.e. not simply modifying a database, which at least for simple requests is done automatically through a database transaction - but rather modifying some global variable in the application).

So should actors be used only in the lower levels of the workflow?

Or should a workflow which is mostly hierarchical (and most are) be rewritten in a more serial way, so it won't require so many levels child actors (but that would be quite hard and unnatural, and seems like giving the implementation framework too much influence over the design).

That would mean that you recognize that actors are quite limited, and that failure tolerance should be mainly handled by traditional exception handling, and in any case that the desire to have a failure-tolerant application should not have so much influence over the design of the application. In that case, why bothering with actors? Working with a framework that requires reading the implementation of every actor in order to understand the workflow of the complex spaghetti-like knot of messages, is much less easier than working with frameworks that are based hierarchical structure that can be revealed to any desired extent by looking at the method signatures, without necessarily peering into their implementation.

A lot of the benefits of actors, such as easy scaling out, seem to be less relevant or practical, if only a small part of the application is implemented by actors.

rapt
  • 11,810
  • 35
  • 103
  • 145

1 Answers1

0

I think your understanding of "locking" in actors is incorrect.

You write

but on the other hand it would keep up to N-1 threads (per initial request) - constantly locked doing nothing

It's unclear which scenario you describe here. The number of threads being used is not determined by the number of actors, or layers in your actor hierarchy, but by the dispatcher you run your actors on, see https://doc.akka.io/docs/akka/2.5/dispatchers.html?language=scala

An actor will usually not spawn a new thread, nor block anything when it's idle. It will process a (configurable) number of messages and then yield control back to the dispatcher. So in terms of multithreading, Akka is, as opposed to what you are suspecting, extremely efficient.

if you tried to keep this hierarchy of actors, but wrap the calls to each actor in a Future (by asking the child actors, rather than telling), so locking would be much shorter

There must be some misunderstanding there. An actor that sends a message to another will not block a thread. It's unclear what "locking" you refer to. Also, the assumption that ask makes locking shorter is flawed, obviously, as there is no locking.

Maybe what you're worried about is using blocking IO from actors? That would indeed block the executing thread. The "trick" here is to put the blocking calls on a separate dispatcher, see https://doc.akka.io/docs/akka/snapshot/dispatchers.html?language=scala

lutzh
  • 4,917
  • 1
  • 18
  • 21
  • An initial message is sent to a topmost actor A. To process it, a thread A is allocated to actor A. During the processing, actor A asks one of its child actors B. To process the question, a thread B is allocated to actor B. Actor A needs the answer from actor B in order to decide how to continue with processing the initial message. All this time, threads A and B are in use. Most of this time, thread A is idle. You can repeat this process with actor B's children and grandchildren and so on. Is anything wrong with my understanding? – rapt Nov 21 '17 at 11:22
  • Yes. For all actor A knows, after sending a message to B it has nothing left to do, so it will yield and thus free thread A to do other things. Actor A is done now. If B should, when itself is done, send a message to A, then A will be run again on another thread (may be A, B, or some other thread entirely). Note that this is independent of actor hierarchy, it makes no difference for this consideration if B is a child of A or not. Child actors do not imply child threads. – lutzh Nov 21 '17 at 15:10
  • I think you actually confirmed my suspicion that actors (or the Akka implementation) have some serious problems. No, actor A is NOT done now. I explicitly said "Actor A needs the answer from actor B in order to decide how to continue with processing the initial message." An actor can send more than one message during one message processing, and in my case, which is quite common, the additional messages require first the response from actor B, in order to know how to continue. Which is why I keep thread A waiting idle for future B to complete, before continuing. – rapt Nov 23 '17 at 01:37
  • You seem to suggest that I should break the original massage processing in actor A, to 2 steps, the second will start as processing the response message from actor B (could be done in actor A or some other actor). I have mentioned this option (rewriting a mostly hierarchical workflow, into a more serial workflow) in my question, as problematic. It breaks the original workflow to numerous separate pieces, which means promoting spaghetti code. Now I get code which is unreadable. And a much bigger problem - it totally breaks the atomicity of the original workflow. – rapt Nov 23 '17 at 01:42
  • P.S. it's clear to me that the hierarchy is not relevant for this problem. I simply start off from the workflow I want to implement, then this workflow dictates a certain hierarchy on concerns, which then should guide me when I design the hierarchy. But it's the conceptual hierarchy of the workflow and its timeline (not hierarchy of the actors) that may cause overuse of threads. – rapt Nov 23 '17 at 01:44
  • You also seem to suggest that a solution for an actor executing blocking operations such as IO (or rather just O), may be using a separate thread pool for these operations. This is not an actual solution. Printing to console does not return anything to the calling workflow. Yes, it is a good idea to `tell()` these output messages to a dedicated actor that would use a different thread pool, but this is not the usual case of blocking operations, that do need to return a result. – rapt Nov 23 '17 at 01:47