Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Pub/Sub in AF ?


@AristosQueue(NI) wrote:

How often? Never within an application. I consider pub/sub to be useful (even critical) across networks, but at this point I consider it to be an anti-pattern within an application. 


AQ! This is like seeing a ghost 😄

 

Could you elaborate about why you think pub/sub within an application is an anti-pattern?

0 Kudos
Message 11 of 27
(1,546 Views)

@OneOfTheDans wrote:

@AristosQueue(NI) wrote:

How often? Never within an application. I consider pub/sub to be useful (even critical) across networks, but at this point I consider it to be an anti-pattern within an application. 


AQ! This is like seeing a ghost 😄

 

Could you elaborate about why you think pub/sub within an application is an anti-pattern?


I’ve written about it before somewhere around here… can’t find it at the moment. The short version is that it tends to make it hard to protect against cyclic graphs of listeners, it’s hard to debug because replay-ability is inhibited, and it makes static links hard to identify. The entire reason for the AF design is to push back against arbitrary graphs. Over the 12 years since I introduced AF, I’ve come to the conclusion that the dynamism of pub/sub should be saved for things that are truly developed independently for better performance, better learn-ability of code, and better playback for debugging. 

Message 12 of 27
(1,536 Views)

@AristosQueue(NI) wrote:

@drjdpowell  non-broker-based publication and subscription designs.

I don’t think I’ve ever heard the term outside of such systems. As far as I’m concerned, pub/sub is message brokering, by definition. A service publishes its information and clients subscribe for updates. What else is there?


Reflecting back on things, I am perhaps being unhelpful.  You and Dmitry are mostly using the term "Pub/Sub", which an outside reader could probably identify as industry jargon that labels a specific thing, rather than a more general english-language meaning of the words "publication" and "subscription".  

 

Note, though, that your eloquent reasons of why you think Pub/Sub is an anti-pattern don't actually depend on publication or subscription.  Any system where any component can send a message to any other component has all the problems you describe.  Imagine, for example, an architecture based entirely on commands sent to Named Queues, with nothing looking like a "subscription" in sight.  To me, this is the anti-pattern of "Global Addressing"^^.

 

In "Messenger Library" I have what I call "Registration" for "Notifications".  I could easily have chosen "Subscription" and "Publications" and I'm glad now that I did not, but those english-language terms would fit. I subscribe via registration messages for published notifications.  But I have no Global Addressing.  My "actors" can only subscribe to publishers that they know the address of, and they only know the address of actors they created, or whose address they received in a message sent by an actor that does know that address**.  This means I don't have the "cyclic" or "arbitrary" graph issues of Global Addressing.

 

So I guess I have to admit I am not using "Pub/Sub", the industry jargon, but I still criticize what I see as poor industry jargon, especially when that jargon undercuts other uses of common english words.

 

^^A violation of what is called "locality" in the Actor Model, but "locality" is a physics metaphor, so I'm not sure it is useful to someone without a physics background.

 

**I have the exception I have to make (as you point out, I think) with networking between different computers or EXEs, where there must be an initial establishment of a TCP connection between existing actors, and thus Global Addressing of some kind.

0 Kudos
Message 13 of 27
(1,509 Views)

@drjdpowell wrote:
But I have no Global Addressing.  My "actors" can only subscribe to publishers that they know the address of, and they only know the address of actors they created, or whose address they received in a message sent by an actor that does know that address**.  This means I don't have the "cyclic" or "arbitrary" graph issues of Global Addressing.

 

You are correct that I never thought of your system as a pub/sub system, but you're right, it is, however specialized. You're also right that you avoid the pitfalls of pub/sub systems by your limited scope of message access. I will amend my statement to say, "Pub/sub is an anti-pattern except in the highly specialized case where subscriptions are limited below the threshold where most industry conversations would think of them as publish/subscribe." Can we call yours a "restaurant menu" pattern instead of a "pub/sub" pattern? I go to the restaurant nearest my home and I can order off its menu, but I can't order from the restaurant across town. Of course, if there's a directory of restaurants that let me eat anywhere in the city, then we're back to pub/sub. 🙂 But I concede Messenger Library avoids the trap. 

 


@drjdpowell wrote:

Note, though, that your eloquent reasons of why you think Pub/Sub is an anti-pattern don't actually depend on publication or subscription.  Any system where any component can send a message to any other component has all the problems you describe. 


Not any system. A token passing ring can be built to be stable... any static for-purpose ring can be built, with static analysis tools to ensure that it is correct and analyzable (example: a sequence of N modules that pass control of specific hardware tools in a circle around the N stations of a manufacturing process -- asynchronous, autonomous, and still well-defined).

 

But, yes, any such system is at risk of the problems I describe, and must take pains to prevent the problems. Pub/sub is a specific horror show beyond most of them because it explicitly -- as part of the specification of pub/sub -- allows them to be built dynamically at runtime with either zero safety bars or huge performance overhead for cycle detection. The libraries tend to be built without detection because "most messages are safe", so it is left to every individual message to do the checking (see absurdity of guards in "height" and "width" properties of any non-trivial UI layout that uses pub/sub). 

 


@drjdpowell wrote:
Imagine, for example, an architecture based entirely on commands sent to Named Queues, with nothing looking like a "subscription" in sight.  To me, this is the anti-pattern of "Global Addressing"^^.

Named queues are absolutely pub/sub systems. Don't use them. One module publishes out on a given token name, another module anonymously subscribes to those tokens. In fact, most pub/sub libraries I've seen in LabVIEW are built entirely out of named queues (or notifiers, which are mostly just 1-element, lossy queues), often named with GUIDs. Any queue with a pre-defined name is just a static connection, but it is still pub/sub, with all the attendant problems of figuring out where all the endpoints are.  

 

Global Addressing is a more generic form of pub/sub, and it has its own problems and more varied solutions. Mutex locking can make some forms of global addressing safe, but mutexes don't protect the cycle of information that causes Heisenburg state machines -- it takes pub/sub to do that. 

 


@drjdpowell wrote:

**I have the exception I have to make (as you point out, I think) with networking between different computers or EXEs, where there must be an initial establishment of a TCP connection between existing actors, and thus Global Addressing of some kind.


Exactly. 🙂 

 

0 Kudos
Message 14 of 27
(1,484 Views)

@AristosQueue wrote:

@drjdpowell wrote:
Imagine, for example, an architecture based entirely on commands sent to Named Queues, with nothing looking like a "subscription" in sight.  To me, this is the anti-pattern of "Global Addressing"^^.

Named queues are absolutely pub/sub systems. Don't use them. One module publishes out on a given token name, another module anonymously subscribes to those tokens. In fact, most pub/sub libraries I've seen in LabVIEW are built entirely out of named queues (or notifiers, which are mostly just 1-element, lossy queues), often named with GUIDs. Any queue with a pre-defined name is just a static connection, but it is still pub/sub, with all the attendant problems of figuring out where all the endpoints are.  

 


I was thinking of a system where each module has a dedicated named receive queue ("DAQ Module" listens on the queue named "Commands_to_DAQ_Module"), then one or more other modules use that named queue to send Requests to that module (like "Start DAQ").  In this system the Senders are anonymous and hard to track down, while who the Receivers are is very clear and obvious.  This is a global ability to connect and send messages, but the words "subscribe" and "publish" suggest the ability to connect and receive published messages.  The "sign me up" direction is reversed: senders choose who to send to, rather than a "subscription" where receivers choose who to receive from.  Again, you may be right about what the "Pub/Sub" jargon actually means, but it is poor terminology as it confuses the use of the real-world analogy, that of a magazine publisher with many customer subscribers.

 

The DQMH is a good example of the difference, as each Module has both "Requests" (that can be sent to it) and "Broadcasts" (which can be subscribed for).

0 Kudos
Message 15 of 27
(1,446 Views)

while who the Receivers are is

> very clear and obvious.

 

Prove to me that there’s no other receiver. Prove that (true story) no junior developer has used the name of the queue to Preview Queue Element to drive the UI directly such that when you try to refactor the queue data type, the UI just stops working for unknown reasons (because of course the error from Preview Queue Element isn’t propagated to any log because how could it ever be broken?).

 

Once the name is published, it can be subscribed to from anywhere, and the named queues just make it available.  

0 Kudos
Message 16 of 27
(1,430 Views)

The actual named Queues might be a poor example, but one could easily make a system where one can look up a module's "enqueuer" by name (an object that only allows one to send to an unnamed queue).

0 Kudos
Message 17 of 27
(1,420 Views)

@drjdpowell wrote:

The actual named Queues might be a poor example, but one could easily make a system where one can look up a module's "enqueuer" by name (an object that only allows one to send to an unnamed queue).


True. I don't think my basic critique changes though. If the connections can be anonymously and arbitrarily constructed, you lose replayability, and I believe that's a needless loss within an application. Consider a simple logger that receives messages from all over the system -- events in the log file, even when timestamped and even when senders remember to include sender context information (such as call chain) are easier to understand when the messages reached the log through a chain of trust than when they all just pile into a log file. And someone is still in charge of stopping that logger... someone who has to know all the possible writers to the log so that the log doesn't get stopped before everyone is done writing, a situation that is avoided when the messages pass through a known path among modules. 

 


@drjdpowell wrote:

that of a magazine publisher with many customer subscribers.


Pub/sub is an industry of publishers with many customer subscribers, and each publisher's set of subscribers overlaps the subscribers of other publishers. Every step you take away from that general model (single publisher, single subscriber, constrained menu, etc) is as step toward safety. Take enough steps and you aren't a pub/sub system any more. 

 

Pub/sub creates arbitrary, anonymous connections. Once you have that first one, it's harder and harder as the application scales up to prove that you haven't created a deadlock, an infinite echo chamber, a lifetime mismatch, or an undefined state transition. With a networked set of applications that come and go on their own time tables, that's a necessary risk, and we spend a lot of code and processor cycles ameliorating that risk. Within an application, it's needless overhead (as far as I can tell) compared to other models. 

0 Kudos
Message 18 of 27
(1,400 Views)

@AristosQueue wrote:

Consider a simple logger that receives messages from all over the system -- events in the log file, even when timestamped and even when senders remember to include sender context information (such as call chain) are easier to understand when the messages reached the log through a chain of trust than when they all just pile into a log file. And someone is still in charge of stopping that logger... someone who has to know all the possible writers to the log so that the log doesn't get stopped before everyone is done writing, a situation that is avoided when the messages pass through a known path among modules. 

 



Could you go a little more in-depth on the "right" way to do something like this? I haven't written this one personally, but my kneejerk would be to share the logger's enqueuer with anything that needed to log, and let anything register with the logger to let it know it's sending messages, allowing the logger to keep up with its own list of publishers. That would potentially save a lot of repeated code when compared to sending all log messages through the actor tree. Granted, you do lose the ability to recontextualize messages (e.g., an error in a DAQ module gets translated from cryptic DAQ error codes into simpler "Position sensor error" with call chain/further details/etc.) That doesn't seem like a showstopper though, but it sounds like I'd just be implementing a pub/sub model that you are cautioning against.

 

What's the best way to write a general purpose logger that doesn't force every actor that uses it to include message forwarding code? Or, perhaps am I asking the wrong question- is it actually good practice to force the message through the actor tree?

0 Kudos
Message 19 of 27
(1,395 Views)

@BertMcMahan wrote:

What's the best way to write a general purpose logger that doesn't force every actor that uses it to include message forwarding code?


There isn't a best or worst way. There's just tradeoffs. Loggers are an area where I'm as likely as anyone to create a direct-send model, particularly since the receiver isn't acting on the contents of the log. But I keep in mind the limitations of that system and the fact that it creates a particular kind of log file, one full of low-level information that doesn't reflect well the system as a whole. And it creates some thread friction that gets applied to all of your modules. 

 

Passing the messages through the layers allows layers to add context and to aggregate noisy parts. It keeps the whole system from bogging down when one part wants to write to the log frequently.

 

Neither model is wrong. It's just a choice. 

 

My point in using it in this discussion was just to highlight that even the simplest use case starts to show signs of the problems that general pub/sub contains. I didn't mean to waive anyone off of writing a logger! 🙂 

 


@BertMcMahan wrote:
Or, perhaps am I asking the wrong question- is it actually good practice to force the message through the actor tree?

For anything that is actually being acted upon, I strongly side with this. Log messages that aren't acted upon have the option to take a shortcut. 

0 Kudos
Message 20 of 27
(1,381 Views)