Part VII

Indirect Communication

Sockets and RMI make a sender talk to one named receiver that must be running right now. This Part puts an intermediary in the middle (a group, a broker, a queue or a shared space) so that senders and receivers need not know each other, or even exist at the same time. It covers all five paradigms the exam draws on, and how to choose between them for a scenario.

  • You willdefine indirect communication and explain space and time uncoupling with an example of every combination.
  • You willdraw FIFO, causal and total ordering on time-space diagrams and say how each is implemented.
  • You willexplain pub-sub subscription models and routing, message queues, DSM and tuple spaces.
  • You willchoose between tuple space, message queue and pub-sub for a scenario, and draw the design.
7.1

Direct vs Indirect Communication

Everything in the earlier Parts was direct communication. With sockets (Part III) a process sends bytes to one particular address and port; with request-reply, RPC and RMI (Part VI) a client invokes one particular server. In every case the sender aims at a specific receiver, and that receiver has to be present, running and reachable at the moment the message arrives.

That rigidity causes problems. A client that is bound to one server cannot easily switch to an equivalent server. If that server fails, the client sees the failure directly and has to handle it itself. And if the people you want to tell something are an unknown, changing crowd (everyone currently watching a share price, say), naming each receiver is impossible. Indirect communication solves this by putting something in the middle.

Definition · indirect communication exam

CDK §6.1: "communication between entities in a distributed system through an intermediary with no direct coupling between the sender and the receiver(s)."

The intermediary is the third party that both sides talk to instead of talking to each other. The definition says "receiver(s)" because many of the paradigms are one-to-many: a single message may reach several receivers.

DIRECT Sender Receiver S send(S, m) The sender names one specific receiver, and that receiver must be running now.
Direct. The sender knows exactly who it is talking to. Sockets, request-reply, RPC and RMI all look like this.
INDIRECT Sender Intermediary e.g. a queue Receiver 1 Receiver 2 Receiver 3 m Nobody names anybody; receivers can come and go.
Indirect. Blue is a sender, violet is the intermediary and green are receivers; these colours mean the same thing in every figure in this Part. The sender talks only to the intermediary.

The intermediary takes a different form in each paradigm, and the five forms are the five topics of this Part. A group delivers a message to all its members (§7.4–7.6). A channel or topic delivers an event to everyone who subscribed (publish-subscribe, §7.7–7.8). A queue holds a message until one consumer takes it (§7.9). Shared memory lets processes read and write common data by address (§7.10). A tuple space lets processes leave data for others to find by its content (§7.11).

Group one send reaches every member Topic / channel weather every subscriber gets a copy Queue each message goes to one consumer Shared memory 0x10 7 0x14 42 0x18 9 read and write by address Tuple space <"fred",1958> <"sid",1964> find tuples by matching content
Five intermediaries, five paradigms. The first three are communication-based (something is sent); the last two are state-based (something is shared). Notice the copy rule: a group or topic gives everyone a copy, a queue gives each message to one consumer.
Check yourselfWhich of these is direct communication?
  1. A Java RMI client invoking lookup("Shapes") and then calling a method on the stub
  2. A process posting a message on a JMS queue
  3. A process joining a JGroups channel and sending to it
  4. A process writing a tuple into a JavaSpace

A. Once the client has the remote reference, every invocation goes to that one server object, which must be running; the RMI registry only helps the client find it. The other three all go through an intermediary (a queue, a group, a tuple space), and the sender never names a receiver.

In short
  • Direct communication (sockets, RPC, RMI) targets a specific receiver that must be present.
  • Indirect communication goes through an intermediary with no direct coupling between sender and receiver(s).
  • The intermediary can be a group, a channel or topic, a queue, shared memory or a tuple space.
7.2

Space and Time Uncoupling exam

"No direct coupling" can mean two quite different things, and the exam expects you to separate them. A sender can be freed from knowing who receives (space), and it can be freed from needing the receiver to be alive at the same moment (time). An intermediary may give one, both or neither.

Space uncoupling

The sender does not know, or need to know, the identity of the receiver(s), and vice versa. Because nobody is named, participants can be replaced, updated, replicated or migrated without the others noticing.

Example: a share-price feed publishes to a topic; it has no idea which dealers are subscribed today.

Time uncoupling

The sender and receiver(s) have independent lifetimes, so they need not exist at the same time. This suits volatile environments, such as mobile users who drop off the network or services that get restarted.

Example: a web shop puts an order on a queue at 3 pm; the warehouse program that processes it is only started at midnight.

The four combinations

Because the two dimensions are independent, crossing them gives a 2×2 grid. Every paradigm in the subject sits in one cell, and a common exam question asks for an example of each.

Time-coupled the receiver must exist at that moment Time-uncoupled independent lifetimes Space-coupled directed at a given receiver Space-uncoupled sender does not know the receivers Sockets, RPC, RMI The sender addresses one known receiver, which must be running to accept the message. S R to R Email · queued RPC (Rover) Addressed to a named mailbox, but its owner reads it whenever they next connect. S R later the subtle cell IP multicast also non-persistent group communication and pub-sub: anyone not listening now misses it S group Most indirect paradigms message queues, tuple spaces, persistent DSM, durable pub-sub: stored until someone takes it S
The right-hand column always contains a store (the drum icon), and that is no accident: time uncoupling requires something that keeps the message after the sender has gone. The gold cell is the one students forget; the bottom-right cell is where most indirect paradigms live.

Three cells are easy. Sockets, RPC and RMI are coupled both ways. Message queues, tuple spaces, persistent DSM and durable pub-sub are uncoupled both ways. IP multicast (sending one IP packet to a multicast address that any host may join, from Part III) is space-uncoupled, since the sender only knows the group address, but time-coupled, since a host that has not joined when the packet passes simply never sees it. The same holds for group communication and pub-sub when they keep no history.

The space-coupled but time-uncoupled cell is the subtle one. CDK leaves it as an exercise (Ex 6.3/6.4), and the usual answers are email sent to a named mailbox and queued RPC, as in the Rover toolkit for mobile clients. In both, the sender names exactly one recipient, but the recipient does not have to be running: the mail server, or Rover's queue, keeps the message until it next connects.

Principle · time uncoupling requires persistency

For a message to outlive its sender and wait for a receiver that does not exist yet, the intermediary must store it until the receiver is ready. IP multicast stores nothing, which is why it is time-coupled. The slides put it this way: indirect communication normally provides space uncoupling, but it provides time uncoupling only when the intermediary retains state.

Same send, two intermediaries: why storage matters
IP MULTICAST · STORES NOTHING PERSISTENT MESSAGE QUEUE Sender Sender (exited) IP multicast routers forward, keep nothing Receiver (off) Receiver 1 · send m no member joined: m is gone nothing to receive Producer Producer (exited) Persistent queue keeps m until consumed Consumer (off) Consumer 1 · send m m written to disk 4 · deliver m
  1. Two systems, side by side. In both, the receiver is not running yet. The top one uses IP multicast; the bottom one uses a persistent message queue.
  2. The sender sends m in both systems. Neither sender names the receiver: one sends to a group address, the other to a queue name. So both are space-uncoupled.
  3. Now they differ. The multicast routers forward m to the hosts that have joined the group, and no one has, so m is simply dropped. The queue instead writes m to disk.
  4. Both senders exit. With direct communication this would be the end of the message's chances.
  5. The receivers start. The multicast receiver gets nothing: IP multicast is time-coupled. The queue delivers m, even though its producer is long gone: the queue is time-uncoupled, and only because it stored m.

Time uncoupling is not asynchronous communication exam

These two ideas sound alike and are easy to mix up, but they are different properties. Asynchronous communication, also called synchronization uncoupling, means that send does not block: the sender carries on immediately instead of waiting for the message to be received. Nothing in that says the receiver may be absent. Time uncoupling means the receiver need not exist at all when the message is sent.

A UDP datagram shows the difference. DatagramSocket.send returns at once, so UDP is asynchronous; but if the destination process has crashed, the datagram arrives at a port nobody owns and is lost. A producer that puts a message on a persistent queue and exits is different: a consumer started tomorrow will still get the message.

sender receiver crashes m (UDP) send returns at once The sender never blocked, yet m is lost.
Asynchronous but time-coupled. Not blocking the sender does nothing for a receiver that is not there.
producer queue consumer exits starts later m stored m The consumer did not exist when m was sent.
Time-uncoupled. The producer's and consumer's timelines never overlap at all; the queue's timeline bridges the gap.

The two properties are orthogonal: either can occur without the other. UDP is asynchronous but time-coupled. In the other direction, a blocking receive on a queue is synchronous communication over a time-uncoupled indirect service, and so is JGroups' MessageDispatcher (§7.6), where the caller blocks waiting for replies from the group.

Time-coupledTime-uncoupled
Asynchronous (send does not block)UDP datagram; IP multicastProducer puts a message on a persistent queue and carries on
Synchronous (caller blocks)RMI call; TCP request-replyConsumer does a blocking receive on a persistent queue
Common mistakes
  • "Asynchronous, so time-uncoupled." Wrong: asynchronous only means the sender does not wait. The receiver must still exist.
  • "Indirect, so time-uncoupled." Wrong: indirection gives space uncoupling; time uncoupling needs an intermediary that stores state. A non-persistent group or pub-sub system is time-coupled.
Check yourselfSelf-test Q1: define indirect communication, and explain space and time uncoupling with an example of each combination.

Indirect communication is communication through an intermediary, with no direct coupling between the sender and the receiver(s). Space uncoupling means the sender does not know who the receivers are. Time uncoupling means the sender and receivers have independent lifetimes, so they need not exist at the same time.

  • Coupled in space and time: RMI, sockets.
  • Space-uncoupled, time-coupled: IP multicast.
  • Space-coupled, time-uncoupled: email, queued RPC.
  • Uncoupled in both: message queues, tuple spaces.

In every case, time uncoupling needs persistence.

Check yourselfSelf-test Q2: is asynchronous communication time-uncoupled?

No. Asynchronous communication means only that the sender does not block; the receiver must still exist, so a UDP datagram sent to a dead process is lost. Time uncoupling means the receiver may not exist yet, which needs persistence. The two are independent: a blocking receive on a queue, for example, is synchronous yet time-uncoupled.

Check yourselfA colleague says: "Our chat app uses a pub-sub broker, so offline users will get their messages when they reconnect." What must be true of the broker for this to hold?

The broker must provide time uncoupling, which pub-sub gives only with retained events and durable subscriptions: it has to store events for a subscriber that is not connected. A plain broker gives space uncoupling only; an event published while a user is offline is delivered to nobody and lost.

In short
  • Space uncoupling: nobody needs to know who is on the other side. Time uncoupling: they need not be alive at the same time.
  • Examples of the four cells: RMI (both coupled), IP multicast (space only), email and queued RPC (time only), message queues and tuple spaces (both).
  • Time uncoupling needs persistency. It is not the same as asynchrony, which only means send does not block.
7.3

When Indirection Helps and Hurts

An extra party in every conversation is not free. Before choosing an indirect paradigm you should be able to say what it buys and what it costs, and a 5-mark "advantages and disadvantages" question asks exactly that.

When it helps

volatility

Change is expected

Mobile users connect and disconnect; servers are restarted or moved. Since nobody names anybody, participants can be swapped without breaking the others.
dissemination

Unknown receivers

Information has to reach receivers who are unknown and changing, such as everyone currently watching a financial feed.
fault tolerance

Replicas behind a name

Several replicas can sit behind one group; clients address the group, so one replica failing goes unnoticed.
integration

Heterogeneity and load

Applications that were never designed together can be joined through a common intermediary, and work can be spread over several consumers to balance load.

When it hurts

Indirection hurts in four ways. First, there is a performance overhead from the extra level: every message makes an extra hop and may be stored. Jim Gray's remark sums it up: any performance problem can be solved by removing a level of indirection. Second, the system is harder to manage: without direct coupling it is hard to tell who received what, and debugging is harder too. Third, it is hard to reason about end-to-end properties such as real-time behaviour, security and quality of service (QoS, guarantees about latency and throughput), because the intermediary sits between the two ends. Fourth, a centralised intermediary becomes a bottleneck and a single point of failure.

Sender 1 Sender 2 Sender 3 Sender 4 Receiver 1 Receiver 2 Receiver 3 Receiver 4 Central broker every message, every hop bottleneck: all traffic queues here single point of failure: if it crashes, nobody can talk to anybody Fix: distribute the intermediary (broker networks, §7.8; distributed queue managers, §7.9).
The price of one central intermediary. The same box that gives the uncoupling also concentrates all the load and all the risk. Many later designs in this Part (broker networks, WebSphere MQ's distributed queue managers, replicated tuple spaces) exist to remove this weakness.
Check yourselfSelf-test Q3: give the advantages and disadvantages of indirect communication.

Advantages. Indirection handles volatility, allows dissemination to unknown receivers, and lets participants be replaced or replicated. It also helps with fault tolerance, integration and load balancing.

Disadvantages. There is a performance overhead from the extra level, and systems are harder to manage. End-to-end properties such as real-time behaviour and security are hard to guarantee, and a centralised intermediary is a bottleneck and single point of failure.

Check yourselfA high-frequency trading system needs a guaranteed delivery time of under one millisecond between two known processes. Is indirect communication a good fit?

Probably not. The two processes are known and fixed, so space uncoupling buys little, while the intermediary adds a hop (performance overhead) and makes an end-to-end real-time guarantee hard to reason about. This is the situation Jim Gray's remark is about: removing the level of indirection is the performance fix.

In short

Indirection helps when participants are volatile or unknown, and for fault tolerance, integration and load balancing. It costs performance, manageability and end-to-end guarantees, and a centralised intermediary is a bottleneck and single point of failure.

7.4

Group Communication exam

Suppose three replicas of a bank server must all apply every update, or a stock feed must reach every dealer's screen. Sending to each one separately is clumsy and, as we will see, unsafe. Group communication lets a sender address the whole set with one name and one operation.

Definition · group communication

A message is sent to a group and delivered to all members of the group, and the sender does not know the receivers' identities. The service is an abstraction over multicast (one send that reaches many receivers), which may be IP multicast or an overlay (a virtual network of processes built on top of ordinary unicast). On top of that it adds membership management, failure detection, reliability and ordering.

Analogy

Group communication is to IP multicast what TCP is to IP. IP delivers packets on a best-effort basis and TCP adds reliability and order on top; IP multicast delivers to a group on a best-effort basis and group communication adds reliability, order and knowledge of who is in the group.

Groups are used in four broad ways: for reliable dissemination of information, such as financial data; for collaborative applications that need every user to share a common view, such as multiuser games; for fault tolerance, through the consistent update of replicas and highly available servers; and for monitoring and management, for example load balancing.

The programming model

Processes join and leave a group, and send to it with a single multicast operation, written aGroup.send(aMessage). Mind the terminology: a multicast goes to the members of a group, a broadcast goes to all processes in the system, and a unicast goes to exactly one process.

The slides list the group API as: create and delete a group; list and search groups; join and leave; list the members; and multicast.

Why one multicast beats N unicasts exam

A sender could reach a group of N members by looping over them and unicasting a copy to each. A single multicast operation is better for two separate reasons, and a full answer names both.

1. Efficiency. A multicast puts at most one copy on each link, because it travels over a distribution tree (the routers copy the packet only where paths split). It can also use hardware multicast, such as Ethernet multicast, and it gives lower total latency. CDK's example: a host in London sends to two hosts on an Ethernet in Palo Alto.

TWO SEPARATE UDP SENDS London ATLANTIC LINK router Palo Alto host A host B copy 1 for A copy 2 for B, later Two copies cross the Atlantic; the second waits. ONE IP MULTICAST London ATLANTIC LINK router Palo Alto host A host B one copy Ethernet multicast at the far end One copy per link; the far network fans it out.
Count the copies on the expensive link. With separate sends, the slow transatlantic link carries one copy per receiver and the last receiver waits longest. With a multicast it carries one copy, and the Palo Alto router hands it to Ethernet multicast (the teal bus), which reaches both hosts at once.

2. Group-wide guarantees. With N separate sends there is nothing to make the group behave as a unit. If the sender crashes midway through its loop, some members have the message and others never will. And when two senders send at once, the order in which their messages reach different members is undefined. A multicast operation is a single action that the middleware can make reliable and ordered.

p A B C p crashes m m A has m B has m C never gets m No all-or-nothing delivery.
Crash midway through the loop. The members now disagree about whether m happened at all, which is fatal for replicas.
p1 A B p2 m1 m2 A: m1, m2 B: m2, m1 Members see different orders.
Two senders, no agreed order. Each unicast takes its own time, so A and B process the same two messages in opposite orders.

Process groups vs object groups exam

Groups come in two kinds, which differ in what the members are and how much the middleware does for you. In short, process groups are at the level of sockets, and object groups are at the level of RMI.

Process PROCESS GROUP Process 1 receive(byte[]) Process 2 receive(byte[]) Process 3 receive(byte[]) sends byte[]
Process group (like sockets). Members are processes; messages are raw bytes, delivered to the process, which must decode them itself.
Client Local proxy m(args) OBJECT GROUP Replica 1 m(args) runs Replica 2 m(args) runs Replica 3 m(args) runs marshals and multicasts the call
Object group (like RMI). The client just calls a local proxy; the invocation is marshalled, multicast and dispatched to the same method on every replica, so replication is transparent.
Process groupsObject groups
MembersProcessesObjects, usually of one class
MessagesUnstructured byte arrays; no marshallingInvocations; arguments and results marshalled as in RMI
DispatchingNone: delivered to the processAutomatic dispatch to the right object and method
Client viewSends explicitly to the groupInvokes a local proxy that multicasts; replication transparent
Level / exampleLike sockets; JGroups (dominant in practice)Like RMI; Electra on CORBA

In an object group the member objects process the same invocation concurrently, and each one replies. So what does the caller get back? Electra, built on CORBA, offers two answers: its "transparent" mode returns the first reply, so the call looks like an ordinary single-object call, and its "non-transparent" mode returns all the replies, so the caller can, for example, compare them.

Other distinctions exam

Groups can be classified in three further ways. The first is who may send to the group.

CLOSED GROUP M1 M2 M3 Outsider may not send only members multicast
Closed group: only members may multicast to it. Suits cooperating servers, such as replicas talking among themselves.
OPEN GROUP M1 M2 M3 Outsider sends to group delivered to every member
Open group: outsiders may send to it. Suits delivering events to interested processes. An outsider can emulate openness on a closed group by unicasting to one member, which then multicasts to the group.

The second distinction is whether groups may overlap. In overlapping groups an entity can belong to several groups at once. That is the realistic case, but it complicates ordering, because a process in two groups receives two streams of messages that must somehow be interleaved. In non-overlapping groups each entity belongs to at most one group.

GROUP G1 GROUP G2 P1 P2 P3 P2 is in both groups, so it must order two streams of messages.
Overlap. P2 (gold) receives multicasts from G1 and from G2. Ordering guarantees within each group say nothing about how the two streams interleave at P2, which is why overlapping groups complicate ordering.

The third distinction is the system model. Group algorithms must handle both synchronous and asynchronous systems (Part II: in a synchronous system message delays and processing times have known bounds; in an asynchronous one they do not). The choice changes the algorithms significantly, for example in how failures can be detected.

Check yourselfSelf-test Q11: compare closed and open groups, and process and object groups.

In a closed group only members send, as with cooperating servers; in an open group outsiders may send, as with event delivery.

Process groups deliver byte arrays to processes, with no marshalling or dispatching, as in JGroups. In object groups, a proxy multicasts invocations, which are marshalled and dispatched to the replica objects, so replication is transparent, as in Electra.

Check yourselfA message sent to every process in the system is a…
  1. multicast
  2. broadcast
  3. unicast
  4. anycast

B, broadcast. A multicast goes to the members of a group, and a unicast goes to one process. (Anycast is not a term this subject uses.)

Check yourselfA replicated object group returns the first reply to the caller. Which mode is this in Electra, and why is it called that?

The transparent mode. Returning one reply makes the replicated call look exactly like a call on a single object, so replication is hidden (transparent) from the client. The non-transparent mode returns all the replies, which exposes the replication.

In short
  • A group message goes to all members; the sender does not know who they are. Group communication adds membership, failure detection, reliability and ordering to multicast.
  • One multicast beats N unicasts for efficiency (one copy per link) and for group-wide guarantees (all-or-nothing, ordered).
  • Process groups are socket-level (bytes, JGroups); object groups are RMI-level (proxy, marshalling, Electra). Groups may be closed or open, overlapping or not.
7.5

Reliability and Ordering in Groups exam

The previous section showed what goes wrong with N unicasts: some members miss a message, or members see messages in different orders. Group communication promises to prevent both, and this section says precisely what it promises. Reliability (does every member get the message?) and ordering (in what order?) are specified separately.

One word needs care first. A message arrives (is received) at a member's group-communication layer when the network hands it over; it is delivered when that layer passes it up to the application. Between the two, the layer may keep the message in a hold-back queue until it is safe to deliver. Every guarantee below is about delivery.

Reliability: integrity, validity, agreement

Reliable group communication is defined by three properties, which you should be able to state precisely.

Integrity

The message delivered is identical to the one sent, and it is delivered at most once.

Validity

Any message sent is eventually delivered.

Agreement

If a message is delivered to one member, it is delivered to all members.

Integrity broken must be unchanged, at most once S A m m again A delivers m twice Validity broken must eventually be delivered S A m m lost and never re-sent Agreement broken if one member gets it, all must S A B S crashes m A has m; B never will
One picture per broken promise. Reliable multicast must rule out all three: duplicates or corruption (integrity), loss (validity) and a crash that leaves members disagreeing (agreement). The third is the one N unicasts cannot fix on their own.
Check yourselfSelf-test Q4: why use one multicast operation rather than separate sends? Define the reliability properties of reliable multicast.

One multicast is more efficient: it sends one copy per link over a distribution tree, it can use hardware multicast, and it has lower latency. It also allows group-wide guarantees. Separate sends give no all-or-nothing delivery if the sender crashes partway through, and no consistent ordering.

Reliable multicast has three properties. Integrity means a message is delivered correctly and at most once. Validity means every message sent is eventually delivered. Agreement means that a message delivered to one member is delivered to all.

Ordering: FIFO, causal and total

Reliability says nothing about order: every member could get every message, but in different orders. So ordering is specified separately. There are three standard orderings, each with its own implementation technique.

OrderingGuaranteeImplementation (slides)
FIFO (source)If a process sends m1 before m2, all members deliver m1 before m2Per-sender sequence numbers; hold back out-of-sequence messages
CausalIf m1 happened-before m2, all members deliver m1 before m2Vector timestamps; hold back until causal predecessors are delivered
TotalIf any member delivers m1 before m2, all doA sequencer assigns global sequence numbers

FIFO ordering

FIFO ("first in, first out") ordering only cares about messages from the same sender: they must be delivered in the order that sender sent them. The implementation numbers each sender's messages 1, 2, 3… (a per-sender sequence number). Each member remembers, for every sender, which number it expects next, and a message that arrives early is held back until the gap is filled.

FIFO ordering with sequence numbers and a hold-back queue
A B C send m1 (#1) m1 m1 (slow) deliver m1 send m2 (#2) m2 m2 deliver m2 hold-back: m2 deliver m1, then m2
  1. A will multicast two messages to the group. B and C each expect A's message #1 next.
  2. A sends m1, stamped #1. It reaches B quickly, B was expecting #1, so B delivers m1 at once. The copy to C is slow (the long arrow).
  3. A sends m2, stamped #2. B is now expecting #2, so it delivers m2 straight away. The copy to C overtakes m1.
  4. m2 arrives at C first. C is still expecting #1, so #2 is out of sequence: C holds m2 back instead of delivering it.
  5. m1 finally arrives. C delivers m1, then finds #2 waiting in the hold-back queue and delivers m2. Both members delivered A's messages in the order A sent them.

Causal ordering

Causal ordering is about cause and effect between messages, even from different senders. Message m1 happened-before m2 if the same process sent m1 before m2, or if the sender of m2 had already delivered m1 when it sent m2 (so m2 might be a response to m1), or through a chain of such steps. Causal ordering says: if m1 happened-before m2, every member delivers m1 before m2. Two messages with no such link are concurrent, and causal ordering lets members deliver them in either order.

The implementation attaches a vector timestamp to each message: a list of counters, one per member, recording how many messages from each member the sender had delivered (or sent) at that point. A receiver holds a message back until it has delivered every message the timestamp says came causally before it. CDK's bulletin-board example (Ex 6.6) shows why this matters.

Causal ordering on a bulletin board (CDK Ex 6.6)
A B C post m1 "Question" · VT (1,0,0) m1 m1 (slow) B delivers m1 post m2 "Answer" · VT (1,1,0) m2 m2 A delivers m2 hold-back: m2 deliver m1, then m2
  1. A bulletin-board group {A, B, C}. Each post is multicast to the group; VT is the vector timestamp (A's count, B's count, C's count).
  2. A posts m1, "Question", stamped (1,0,0): it is A's first message. B receives it and delivers it. The copy to C is slow.
  3. Having read the question, B posts m2, "Answer", stamped (1,1,0): "I had seen 1 message from A and this is my 1st". So m1 happened-before m2. A delivers m2 without trouble.
  4. m2 reaches C before m1. Its timestamp says it depends on A's 1st message, which C has not delivered, so C holds m2 back. Showing the answer before the question would make no sense to C's user.
  5. m1 arrives. C delivers m1, which satisfies m2's dependency, and then delivers m2. C's screen shows the question before the answer, even though they arrived the other way round.

Total ordering

Total ordering asks for something different: every member delivers all messages in the same order, whatever that order is. The slides' implementation uses a sequencer, a process that gives each message a global sequence number; members then deliver in sequence-number order, holding back any message whose predecessor has not yet arrived. Here two senders, P and Q, send messages to the group, and we watch two members B and C.

Total ordering with a sequencer
P Q seq B C gold arrows: m3 teal arrows: m4 m3 m4 #1 = m4 #2 = m3 #2 before #1: hold m3 B delivers m4, m3 C delivers m4, m3
  1. Senders P and Q, a sequencer (seq), and two members B and C. P and Q are about to send at the same time, so their messages are concurrent.
  2. P sends m3 and Q sends m4, each to the sequencer. Q's message happens to arrive first.
  3. The sequencer assigns global numbers in the order it received them: m4 is #1, m3 is #2. This single decision is what everyone will follow.
  4. The sequencer multicasts each message with its number. The copy of m4 to B is slow; the others are quick.
  5. At B, m3 (#2) arrives before m4 (#1). B has not delivered #1 yet, so it holds m3 back.
  6. m4 reaches B, which delivers m4 and then the held-back m3. C received them in order and delivered them at once. Both deliver m4, m3: the same order everywhere.

How the orderings relate

There is a pitfall in how these relate. Causal ordering implies FIFO ordering: two messages from the same sender are causally related (the sender sent one before the other), so causal order already keeps them in order. But total ordering implies neither, since it promises only "the same order everywhere", and that same order could put a sender's second message first, or an answer before its question. That is why hybrids exist: FIFO-total and causal-total ordering.

Causal respects happened-before FIFO each sender's order implies Total same order everywhere implies neither Hybrids FIFO-total FIFO + total causal-total causal + total
Only one implication holds. Causal is stronger than FIFO. Total is a different kind of promise (agreement on one order) and says nothing about senders' order or cause and effect, so you combine it with one of the others when you need both.

Causal vs total: the distinguishing example

Continue the bulletin board. Now A posts m3 and C posts m4 at the same moment, neither having seen the other's message, so m3 and m4 are concurrent. Causal ordering has nothing to say about concurrent messages, so it allows different members to deliver them in different orders. Total ordering does not.

A B C m3 m4 A: m3, m4 B: m4, m3 C: m4, m3 Allowed by causal ordering.
Causal only. Each member delivers what arrives first, so A disagrees with B and C. Fine for a bulletin board: two unrelated posts.
A B C m3 m4 A: m3, m4 B: m3, m4 C: m3, m4 Total: B and C hold m4 back.
Total. Same arrivals, but everyone delivers m3 then m4. (The agreed order could just as well have been m4 then m3; what matters is that it is the same.)

Total ordering is what you need when replicas apply updates. If "deposit $100" and "add 10% interest" reach two replicas of an account in different orders, the replicas end up with different balances, and from then on they are no longer replicas of anything.

Replica 1 $1000 deposit $100 $1100 add 10% interest $1210 Replica 2 $1000 add 10% interest $1100 deposit $100 $1200 Same two updates, different order: the replicas now disagree.
Why replicas need total order. The two updates are concurrent (from different clients), so causal ordering would allow either order at each replica. Only total ordering forces both replicas to apply them in the same order, keeping them identical. (The dollar amounts are just an illustration.)
Check yourselfSelf-test Q5: explain FIFO, causal and total ordering with their implementations, and give an example that distinguishes causal from total.

FIFO ordering preserves each sender's order and is implemented with per-sender sequence numbers. Causal ordering respects happened-before, is implemented with vector timestamps, and implies FIFO. Total ordering gives the same order everywhere, is implemented with a sequencer, and does not imply causal ordering.

To distinguish causal from total: under causal ordering a reply must follow its question, but two concurrent messages m3 and m4 may arrive in different orders at different members. Total ordering does not allow that.

Check yourselfWhich statement is true?
  1. Total ordering implies causal ordering.
  2. Causal ordering implies FIFO ordering.
  3. FIFO ordering implies total ordering.
  4. Total ordering implies FIFO ordering.

B. Messages from one sender are causally related, so causal order preserves them. Total ordering implies neither FIFO nor causal (A and D are the classic traps), and FIFO says nothing about messages from different senders, so it cannot give one global order (C).

Check yourselfA multiplayer game multicasts each player's moves. A player's own moves must never appear out of order, but moves by different players may be shown in any order. What is the weakest ordering that suffices, and how is it implemented?

FIFO ordering: it only constrains messages from the same sender. Each player numbers its messages with a per-sender sequence number, and every receiver holds back a message until it has delivered that player's earlier numbers.

Check yourselfIn the bulletin board, B delivers m1 "Question" and then posts m2 "Answer". A member delivers m2 before m1. Which ordering guarantee is violated, and which is not necessarily violated?

Causal ordering is violated, because m1 happened-before m2 (B had delivered m1 when it sent m2). FIFO is not violated: m1 and m2 come from different senders, and FIFO only orders each sender's own messages. Total ordering is violated only if some other member delivered them in the other order.

In short
  • Reliability = integrity (unchanged, at most once) + validity (eventually delivered) + agreement (one gets it, all get it).
  • FIFO: per-sender order, by sequence numbers. Causal: happened-before order, by vector timestamps. Total: same order everywhere, by a sequencer. All use a hold-back queue.
  • Causal implies FIFO; total implies neither, hence FIFO-total and causal-total. Replicas need total order.
7.6

Group Membership and JGroups

A sender addresses "the group", but the network can only deliver to actual processes. Something has to know, at every moment, which processes are in the group, notice when one crashes, and turn the group's name into that list. That is the group membership service. This section describes it and then looks at JGroups, the Java toolkit that packages all of the above.

Group membership management exam

In the picture, a sender addresses the group only by its identifier g. The membership service tracks joins, leaves and failures, and address expansion turns the identifier into the current set of members.

Sender outside the group Address expansion group id → members GROUP g Member 1 Member 2 Member 3 Membership mgmt joins · leaves · failures New process send(g) leave fails join view change current view
Two violet services do the work. Black arrows carry the application's message; violet arrows carry membership information. Membership management learns of every join, leave and failure (red, dashed: nobody announces a crash, it must be detected), tells the members, and keeps address expansion up to date with the current view (the agreed member list).

A group membership service has four tasks.

  1. An interface for membership changes, so that groups can be created and destroyed, and processes added and withdrawn.
  2. Failure detection: it monitors members for crashes and unreachability, marks them Suspected or Unsuspected, and excludes suspected members. (In an asynchronous system a slow member and a crashed one look the same, which is why the service can only suspect.)
  3. Notifying members of joins and exclusions.
  4. Group address expansion: expanding the group identifier into the current members. It coordinates delivery with membership changes, so that each message's set of recipients is decided consistently.
IP multicast is a weak membership service

IP multicast does have join and leave operations (a host joins a multicast address) and address expansion (routers forward to whoever has joined). But members cannot find out who the other members are, and delivery is not coordinated with membership changes: a message sent while a host is joining may or may not reach it. That coordination needs view-synchronous communication, covered in CDK chapter 18. Because membership needs synchrony assumptions, groups work best in small, static systems.

Check yourselfSelf-test Q6: list the tasks of a group membership service. Why is IP multicast weak?

A membership service provides an interface for membership changes; detects failures, suspecting and excluding members; notifies members of changes; and performs group address expansion, coordinated with those changes.

IP multicast is weak because it offers only join and leave and address expansion. Members have no knowledge of the membership, and delivery is not coordinated with membership changes.

Case study: JGroups

JGroups is a Java toolkit for reliable group communication, descended from the research systems ISIS, Horus and Ensemble. It uses process groups: members are processes that exchange messages. Its design has three parts: channels, building blocks and a protocol stack.

Application MessageDispatcher RpcDispatcher NotificationBus Channel connect · send · receive · getView · getState CAUSALcausal ordering GMSgroup membership: views MERGEre-merges the group after a partition FRAGfragments messages (8,192 bytes) UDPIP multicast + UDP unicast (or TCP for WANs) NETWORK building blocks: higher-level helpers socket-like handle on one group protocol stack: composable layers, each with up(Event) and down(Event); the same stack in every member
Messages go down the stack in the sender and up it in each receiver. Each protocol layer adds one property, so you get causal ordering, membership, partition healing and fragmentation by stacking the layers you need. This is CDK's example stack, from top to bottom.

A channel is a socket-level handle on one group. Calling connect("name") joins the group, creating it if it is new; disconnect leaves it, and close closes the channel. Messages go out with send and come in with receive. getView returns the current members, and getState returns the group's history so that a newcomer can catch up; this gives an optional form of time uncoupling. Despite the name, a JGroups channel has nothing to do with "channel-based" pub-sub (§7.7).

There are three building blocks on top of channels. MessageDispatcher.castMessage sends a message and blocks for n replies, a majority, or all of them (which is why it counts as synchronous communication over an indirect service, §7.2). RpcDispatcher invokes a method on all members. NotificationBus is a distributed event bus, used for example to keep a replicated cache consistent.

The third part is the protocol stack, made of composable layers, each with up(Event) and down(Event) methods. All members share the same stack. CDK's example stack, from top to bottom, is CAUSAL (causal order), GMS (membership views), MERGE (re-merging after a partition), FRAG (fragmentation, 8,192 bytes) and UDP (IP multicast and UDP unicast, or TCP for WANs).

The fire alarm in JGroups (CDK Fig 6.5)

import org.jgroups.JChannel;
import org.jgroups.Message;

public class FireAlarmJG {                              // CDK Fig 6.5
    public void raise() {
        try {
            JChannel channel = new JChannel();          // default protocol stack
            channel.connect("AlarmChannel");            // join group
            channel.send(new Message(null, null, "Fire!")); // dest null = all members
        } catch (Exception e) { }
    }
}
// Receiver: same connect, then  Message m = (Message) channel.receive(0);  // block

Line by line: new JChannel() creates a channel with the default protocol stack. connect("AlarmChannel") joins the group of that name, so the alarm raiser becomes a member. new Message(null, null, "Fire!") builds a message whose first argument, the destination, is null, meaning all members; the second is the source (filled in by JGroups) and the third the payload. send multicasts it. A receiver does the same connect and then calls channel.receive(0), which blocks (0 means no timeout) until a message arrives. Newer versions of JGroups use a receive callback instead.

raise() receiver 1 receiver 2 connects now send "Fire!" Fire! receives the alarm misses the alarm entirely
Space-uncoupled, time-coupled. The raiser never names a receiver, but a receiver that is not connected when raise() runs misses the alarm. The JavaSpaces version in §7.11 fixes this.
Check yourselfIn CDK's JGroups stack, which layer re-merges a group that was split by a network partition, and which provides membership views?
  1. MERGE; GMS
  2. GMS; MERGE
  3. CAUSAL; FRAG
  4. FRAG; UDP

A. MERGE re-merges after a partition; GMS (group membership service) maintains views. CAUSAL gives causal order, FRAG fragments messages at 8,192 bytes, and UDP is the transport.

Check yourselfWhich JGroups feature gives an optional form of time uncoupling, and how?

getState. It returns the group's history, so a process that joins late can catch up on what it missed. Without it, JGroups is time-coupled, as the fire alarm shows.

In short
  • Membership service: change interface, failure detection (suspect and exclude), notification, address expansion coordinated with changes. IP multicast is weak: no member list, no coordination.
  • JGroups: process groups; a channel per group (connect, send, receive, getView, getState); building blocks (MessageDispatcher, RpcDispatcher, NotificationBus); a stack of layers (CAUSAL, GMS, MERGE, FRAG, UDP).
  • The JGroups fire alarm is space-uncoupled but time-coupled.
7.7

Publish-Subscribe: Model and Subscriptions exam

Groups suit a fixed, known set of members who all need everything. Many applications instead have a large, changing crowd of receivers, each interested in a different slice of a stream of happenings: this dealer wants IBM prices, that one wants anything over $100. Publish-subscribe lets each receiver describe what it wants and leaves the matching to the middleware.

Definition · publish-subscribe

Publishers publish structured events to an event service, and subscribers express their interest through subscriptions, which are patterns over events. The service matches events against subscriptions and delivers notifications. These systems are also called distributed event-based systems. They are one-to-many, and pub-sub is the most widely used indirect technique.

An event here is a record of something that happened, with named fields: for example a price update with fields stock="IBM" and price=142.5.

Applications include financial systems, live feeds such as news and RSS, cooperative working, ubiquitous computing (with events from sensors or about location), and monitoring. The standard illustration is the dealing room: an information provider process for each external source publishes every price update as an event, and dealer processes subscribe to the stocks they care about and display the notifications they receive.

External source 1 External source 2 Provider 1information provider Provider 2information provider Event service publish publish Dealer 1wants IBM Dealer 2wants IBM, ORCL Dealer 3wants price > 100 notify notify notify display
The dealing room. Providers (blue) publish every update without knowing who is interested; dealers (green) each receive only the events their subscription matches. Adding a fourth dealer changes nothing for the providers: that is space uncoupling.

The programming model exam

The interface is small:

  • publish(e) publishes event e.
  • subscribe(f) registers interest through a filter f (the pattern), and unsubscribe(f) cancels it.
  • notify(e) is how the system delivers e to the matching subscribers.
  • advertise(f) and unadvertise(f) are optional. They let publishers declare, or revoke, the kinds of events they will publish in future.
Publisher 1 Publisher 2 Event service stored subscriptions S1: f1 S2: f2 S3: f3 match(e, subscriptions) decides who gets e publish(e) advertise(f) optional Subscriber 1 Subscriber 2 Subscriber 3 subscribe(f1) notify(e) subscribe(f2) notify(e) unsubscribe(f3)
Publishers and subscribers only ever talk to the event service. Black arrows are calls the clients make; teal arrows are notifications the service sends. The service's job is the gold box: matching each published event against every stored subscription.

Subscription models exam

Subscription models differ in how a subscriber describes the events it wants, which is the same as asking what part of an event the filter may look at. The slides use a single event to illustrate them: topic weather/melbourne, type TemperatureReading, location = "Melbourne", temperature = 31. The figure shows how a subscriber would select that event under each model.

One event (its fields) topicweather/melbourne typeTemperatureReading location"Melbourne" temperature31 Channel-based channel "MelbourneWeather": receive everything sent on it Topic-based "weather/melbourne" anyanyany Content-based anyany = "Melbourne" > 30 AND Type-based any TemperatureReadingor any subtype anyany
Read each row as "what does this subscriber's filter look at?" Gold cells are what the subscription constrains; "any" means it ignores that field. Channel-based subscribers do not look inside events at all, they just tune in. Content-based is the only model that tests the values of ordinary attributes.
ModelExampleNotes
Channel-basedchannel "MelbourneWeather"Named physical channel; get everything; most primitive (CORBA Event Service)
Topic-based (subject-based)subscribe("weather/melbourne") → all Melbourne readingsTopic is an explicit event field; can be hierarchical
Content-basedsubscribe(location="Melbourne" AND temperature > 30)Predicates over attributes; most expressive; hardest to route
Type-basedsubscribe(TemperatureReading) → readings anywhere, incl. subtypesLanguage-integrated, type-checked

A few terms from the table. A hierarchical topic is written like a path, so weather/melbourne sits under weather alongside, say, weather/sydney. A predicate is a true/false test on an event's attributes. Type-based subscription works on the event's programming-language type, so a subscription to TemperatureReading also matches events of any subclass, and the compiler can check the subscription.

There are further models as well. With objects of interest, as in Jini events, a subscriber watches the state of one particular object; because this uses a direct connection to that object, it loses the uncoupling. Context-based subscriptions select on context, for example location. Concept-based subscriptions filter on the semantics (meaning) of events, which helps with heterogeneity when publishers use different words for the same thing. Finally, complex event processing looks for patterns across many events, which may be logical, temporal or spatial; an example is "river level up 20% at three sites and flood risk predicted".

Slides vs CDK

The slides merge the channel and topic models, whereas CDK distinguishes them. If asked, give CDK's four (channel, topic, content, type) and note that channel-based is the primitive "tune in to a named channel" form.

Characteristics

Four characteristics shape how pub-sub systems behave.

Heterogeneity. Components that were not designed to interoperate can still cooperate, because all they have to do is publish event types or handle notifications.

Asynchronicity. Publishers never synchronise with subscribers: a publish call does not wait for anyone to receive the event.

Delivery guarantees are chosen per application. With at-most-once delivery, a lost event is not redelivered; that is fine for a game's latest position, since the next update will arrive anyway. At-least-once delivery retries, so duplicates are possible. The dealing room needs reliable delivery for the sake of fairness, so that all the dealers of a stock see the same data. Guarantees can also be mixed: Mushroom is a hybrid.

Update User replica 1 User replica 2 User replica 3 Serverauthoritative state unreliable multicast reliable re-fetch state
Mushroom: reliable where it matters, cheap where it doesn't. The server (red) always gets every update reliably and holds the authoritative state. Users' replicas (green) get a cheap unreliable multicast, and a replica that misses an update re-fetches the state from the server.

Applications with real-time needs, such as a power station or a patient monitor, need bounded low latency, which is achievable in synchronous systems.

Coupling. The broker gives space uncoupling, but time uncoupling comes only with retained events and durable subscriptions (the broker keeps events for subscribers that are not currently connected).

Check yourselfSelf-test Q7: describe the pub-sub programming model and the subscription models.

The programming model consists of publish, subscribe, unsubscribe and notify, plus advertise and unadvertise. The subscription models are channel-based, topic-based (which can be hierarchical), content-based (predicates over attributes) and type-based (matching types and their subtypes). There are also concept-based and context-based models, and complex event processing.

Check yourselfA subscriber wants "every reading whose temperature is above 40, from any city". Which subscription model can express this directly?
  1. Channel-based
  2. Topic-based
  3. Content-based
  4. Type-based

C, content-based, with the predicate temperature > 40. Topic- and type-based filters cannot look at attribute values, and a channel delivers everything on it. With those you would have to receive every reading and filter in the application.

Check yourselfWhy do "objects of interest" subscriptions, as in Jini events, lose the uncoupling that pub-sub normally gives?

Because the subscriber watches one particular object through a direct connection to it. It must know that object's identity (no space uncoupling) and the object must be there (no time uncoupling), which is direct communication in disguise.

Check yourselfA multiplayer game publishes each player's position 30 times a second. Which delivery guarantee fits, and why is the dealing room different?

At-most-once fits the game: if one position update is lost, the next one arrives a moment later and supersedes it, so retrying would only waste bandwidth. The dealing room needs reliable delivery for fairness: every dealer of a stock must see the same prices.

In short
  • Publishers publish events; subscribers subscribe with filters; the event service matches and notifies. API: publish, subscribe, unsubscribe, notify, optional advertise and unadvertise.
  • Subscription models: channel (tune in), topic (a named field, maybe hierarchical), content (predicates, most expressive), type (language types and subtypes); plus objects of interest, context, concept, complex event processing.
  • Heterogeneous, asynchronous, per-application delivery guarantees. Space-uncoupled always; time-uncoupled only with retained events and durable subscriptions.
7.8

Implementing Publish-Subscribe exam

The event service is easy to describe and hard to build at scale. Every event must be matched against possibly millions of subscriptions and carried to the right subscribers without flooding the network. This section covers where the service runs and how events find their way.

Centralised, distributed or peer-to-peer

There are three broad ways to build the event service, and they trade simplicity against scale and resilience.

Centralised broker Broker simple bottleneck, single point of failure Distributed broker network B B B B survives failures, Internet scale routing state must be distributed Peer-to-peer every node publishes, subscribes and routes fully decentralised complex
From one box to no boxes. Blue dots are publishers, green dots subscribers, violet are brokers. The broker network is the usual answer for scale; peer-to-peer removes the distinction between roles altogether.
  • A centralised broker stores all the subscriptions and matches every event against them. It is simple, but it is a single point of failure and a bottleneck.
  • A distributed broker network uses cooperating brokers. It survives failures and works at Internet scale, but the subscription and routing state must be distributed among the brokers.
  • In a peer-to-peer design there is no distinction between publishers, subscribers and brokers, and all nodes take part in routing. It is fully decentralised, but complex.

The architecture in layers (CDK Fig 6.10)

CDK splits a distributed implementation into four layers. Each layer offers several interchangeable techniques.

Matching does event e satisfy subscription s? Event routing flooding filtering rendezvous informed gossip Overlay networks broker network group multicast DHT gossip Network protocols TCP/IP IP multicast
Pick one option per layer. For example, Scribe is a DHT overlay with rendezvous routing; Siena is a broker network with filtering. A DHT (distributed hash table) is an overlay that maps any key to the node responsible for it; gossip means nodes repeatedly exchange information with random neighbours.

Routing: why content-based is hard

Channel and topic schemes map simply onto groups: one group per topic, and publishing is a multicast to that group. Content-based subscription cannot be mapped this way, since every subscription is a different predicate (there could be one "group" per subscriber), so it needs content-based routing (CBR). There are five approaches:

  • Flooding sends every event to all nodes and does the matching at the subscribers, or alternatively floods subscriptions to the publishers. It is simple but wasteful.
  • Filtering forwards events only along paths that lead to matching subscribers. To do this, brokers propagate subscriptions towards the publishers, and each broker keeps a neighbours list, a subscription list (its local subscribers) and a routing table that maps each neighbour to the subscriptions reachable via it.
  • Advertisements are propagated towards subscribers, so that subscriptions need not be flooded everywhere: a subscription only travels towards brokers whose publishers advertised matching events.
  • Rendezvous routing partitions the event space among the nodes (see below). It is often built on a DHT.
  • Informed gossip is a probabilistic exchange with neighbours, guided by content. It works well under churn (nodes constantly joining and leaving).

Filtering-based routing exam

The algorithm each broker runs is short. "x" is whoever the message came from, and match(e, list) returns the entries whose subscriptions e satisfies.

upon receive publish(e) from x:   notify(e) to match(e, subscriptions)
                                  publish(e) to match(e, routing) - x
upon receive subscribe(s) from x: if x is a client then add x to subscriptions
                                  else add (x, s) to routing
                                  subscribe(s) to neighbours - x

Read it as two rules. When an event arrives, notify every local subscriber it matches, and forward it to every neighbour whose routing-table entry it matches, except the one it came from. When a subscription arrives, record it (as a local subscriber if it came from a client, or as a routing entry "neighbour x leads to s" if it came from another broker), then pass it on to all other neighbours. The stepper traces CDK's example.

Filtering-based routing on a broker network
P1 P2 P3 B1 B2 B3 B4 B5 S1 S2 S3 sub(IBM) subs: S1→IBM sub(IBM) route: B5→IBM route: B3→IBM route: B3→IBM route: B2→IBM pub(IBM, 142) S1 notified B1: never sees it B4: never sees it
  1. CDK's broker network. Publishers P1–P3 (blue) and subscribers S1–S3 (green) are clients; brokers B1–B5 (violet) are linked to each other. Nobody has subscribed yet, so every routing table is empty.
  2. S1 sends subscribe(stock = "IBM") to its broker B5. S1 is a client, so B5 adds S1 to its subscription list.
  3. B5 forwards the subscription to its only other neighbour, B3. B3 got it from a broker, so it adds the routing entry (B5, IBM): "IBM events should go towards B5".
  4. B3 forwards it to its other neighbours, B1 and B2, and each records (B3, IBM).
  5. B2 forwards it on to B4, which records (B2, IBM). B1's only other neighbour is a client, so propagation stops there. Every broker now knows which direction leads to an IBM subscriber.
  6. P2 publishes an IBM event at B2. B2 has no local subscribers for it, but its routing table says IBM goes towards B3, so it forwards the event there.
  7. B3's table points to B5; B5's subscription list matches S1, so B5 notifies S1. B1 and B4 never see the event, whereas with flooding they would. That saving is the point of filtering.

Rendezvous routing

Rendezvous routing takes a different approach: instead of spreading subscriptions along paths, it gives each part of the event space a meeting point. Two functions decide where things go. SN(s) names the node or nodes that store subscription s, and EN(e) names the node or nodes that match event e. For this to work, an event and every subscription it matches must meet somewhere, so the scheme requires EN(e) ∩ SN(s) ≠ ∅ whenever e matches s. This is the mapping intersection rule.

N1 N2 N3 N4 N5 N6 Subscriber Publisher subscribe(s) publish(e) forward e notify SN(s) = {N2} EN(e) = {N2, N4} N2 is in both: the rendezvous point
Subscriptions and events meet at a rendezvous node. The subscription is stored only at SN(s); the event is sent only to EN(e). Because the two sets intersect (at gold N2), the match is guaranteed to happen there. With a DHT, SN and EN are computed by hashing, so any node can work them out.

Step by step (CDK Fig 6.12): when node i receives subscribe(s), it computes SN(s); if i is in that set it stores s, otherwise it forwards s to those nodes. When node i receives publish(e), it computes EN(e); if i is in that set it matches e against its stored subscriptions and notifies any matches, and it forwards e to the rest of EN(e). Having SN and EN return several nodes adds reliability, since a match can still happen if one node fails.

Example systems

The example systems show different points in this design space.

SystemSubscription modelRouting / architecture
CORBA Event ServiceChannel-basedCentralised
TIB RendezvousTopic-basedFiltering
ScribeTopic-basedDHT rendezvous
SienaContent-basedFiltering

The slides add modern systems: Kafka, a durable log with replay and per-partition ordering; RabbitMQ and IBM MQ; MQTT, which is lightweight, topic-based, aimed at the Internet of Things, and offers QoS levels; and SNS + SQS, where a topic fans out into durable queues.

Check yourselfSelf-test Q8: compare centralised and distributed pub-sub, and explain filtering-based routing.

A centralised broker is simple, but it is a bottleneck and a single point of failure. A broker network or a peer-to-peer design is scalable and resilient, but complex.

In filtering-based routing, subscriptions propagate through the brokers, and routing tables record which neighbour leads to which subscriptions. Events are then forwarded only along matching paths, which means less traffic than flooding. Rendezvous routing over a DHT, and advertisements, are alternatives.

Check yourselfWhy can topic-based pub-sub be implemented with one group per topic, while content-based pub-sub cannot?

A topic is a fixed name shared by many subscribers, so "everyone subscribed to topic T" is a natural group and publishing is a multicast to it. In content-based pub-sub every subscription is its own predicate (price > 100, price > 120 AND stock = "IBM"…), so there is no small set of groups to map onto; the system needs content-based routing such as filtering or rendezvous.

Check yourselfIn rendezvous routing, what does the mapping intersection rule require, and what goes wrong if it is broken?

It requires EN(e) ∩ SN(s) ≠ ∅ whenever e matches s: at least one node that stores the subscription must also receive the event. If it is broken, the event and the matching subscription are sent to disjoint sets of nodes, no node ever compares them, and the subscriber silently misses the event.

In short
  • Centralised broker: simple, bottleneck and single point of failure. Broker network: resilient, scalable, distributed routing state. Peer-to-peer: no roles, complex.
  • Layers: matching, event routing, overlay, network. Topics map onto groups; content needs content-based routing: flooding, filtering, advertisements, rendezvous, informed gossip.
  • Filtering: subscriptions flow towards publishers and build routing tables; events follow them back. Rendezvous: SN(s) and EN(e) must intersect.
7.9

Message Queues exam

Pub-sub hands every event to every interested subscriber. Many business tasks need the opposite: an order must be processed by exactly one warehouse system, reliably, even if that system is down for the night. Message queues provide this point-to-point, store-and-forward service.

Definition · message queue

A message queue is a point-to-point indirect service. Producers send messages to a queue, which is an explicit third-party entity, and each message is removed by one consumer. The queue provides both space and time uncoupling.

This style is also called Message-Oriented Middleware (MOM), with examples such as WebSphere MQ, MSMQ and Oracle AQ. It is used for Enterprise Application Integration (EAI), meaning connecting a company's separate applications, for transaction processing, for distributing units of work, and for command and control; but not for streaming or bulk data.

The programming model exam

Producers have one operation, and consumers have three ways of getting messages, plus an acknowledgement:

  • send puts a message on a named queue, and may block if the queue is full.
  • A blocking receive waits until a message is available.
  • A non-blocking receive (poll) returns a message, or "none", immediately.
  • Notify issues an event when a message is available, so the consumer need not wait or keep asking.
  • acknowledge, which the slides add, confirms that a message has been processed so that it can be discarded.
Producer A Producer B Persistent queue m5 m4 m3 m2 m1 FIFO, usually with priorities send send Consumer 1blocking receive Consumer 2poll Consumer 3notify m1 m2 m3
Many producers, many consumers, one consumer per message. m1, m2 and m3 each go to a different consumer and are then gone from the queue. Consumers that take turns like this are called competing consumers, and they give load balancing for free.

As the figure shows, many producers and consumers can share a queue, but each message goes to only one consumer. These competing consumers give load balancing. The queue is FIFO, usually with priorities (a higher-priority message jumps ahead), and consumers may also select messages using predicates over their metadata.

A message consists of a destination, metadata such as priority and delivery mode, and an opaque body: the queue never looks inside it. The body is serialized and can be as large as about 100 MB. In Oracle AQ, queues are database tables and messages are rows, so they can be queried in SQL.

destinationwhich queue metadatapriority, delivery mode, … bodyopaque, serialized, up to about 100 MB read by the queue (routing, priority, selection) never inspected by the queue
The queue reads the shaded part only. Selection predicates work on the metadata; the body is just bytes to the middleware.

One message, start to finish

The stepper follows one message through a queue manager (the server process that hosts the queue). Watch for time uncoupling: the consumer is offline when the order is sent, and the producer exits before the consumer collects it, hours later.

An order travels through a persistent queue
Producer Queue manager Consumer offline 1 · send(order 42) 2 · write to disk 3 · accepted producer may exit now … hours later … 4 · blocking receive 5 · order 42 6 · acknowledge 7 · delete message
  1. Three participants. The consumer is offline: perhaps the warehouse system only runs at night.
  2. The producer sends order 42 to the named queue on the queue manager. It does not name the consumer.
  3. The queue manager writes the message to disk. This is the persistence that makes everything after it possible: the message now survives even a queue-manager restart.
  4. Only once it is safely stored does the queue manager reply accepted.
  5. The producer's job is done and it may exit. Producer and consumer have not overlapped in time at all.
  6. Hours later the consumer starts and does a blocking receive on the queue.
  7. The queue manager hands over order 42.
  8. The consumer processes the order and sends an acknowledge, confirming it is done.
  9. Only now does the queue manager delete the message. If the consumer had crashed before acknowledging, the message would still be there for another attempt.

Properties exam

Message queues have four main properties.

  • Persistence. Messages are stored until they are consumed, and are committed to disk. This gives time uncoupling, along with validity and integrity (each message is delivered once), but there is no timing guarantee: a message may sit in the queue for hours.
  • Transactions. Send and receive can be placed inside all-or-nothing transactions, for example "take order, update DB, send shipping request".
  • Message transformation. A message broker can convert between formats, for instance byte order, or between SOAP and IIOP (two different message formats used by web services and CORBA). This supports heterogeneity and EAI.
  • Security. WebSphere MQ, for example, provides SSL, authentication and access control.
transaction 1 · take orderreceive from orders queue 2 · update DBstock, invoices 3 · send shipping requestto shipping queue commit: all three happen abort: none happen, the order is back on the queue
All or nothing. Without a transaction, a crash after step 2 would leave the database updated but no shipment requested, or the order consumed but never processed. Inside one, the receive and the send succeed or fail together with the database update.

Compared with message passing exam

Ordinary message passing (sockets) also has queues, but they are implicit: buffers inside the sender's and receiver's operating systems. Message queues are explicit third-party entities with their own names and storage, and that is what gives them space and time uncoupling.

SENDER send buffer RECEIVER receive buffer implicit queues; both ends must be up
Message passing. The "queues" are buffers owned by the two endpoints and vanish with them.
Producer queue "orders" Consumer explicit, named, stored third party
Message queue. The queue is a separate entity with a name and a disk, so neither endpoint needs to know or outlive the other.

WebSphere MQ (IBM MQ)

A single central queue manager is simple but becomes a bottleneck, so WebSphere MQ distributes its queue managers. The design has four elements.

  • Queue managers host the queues, and applications talk to them through the MQI (Message Queue Interface: MQCONN, MQDISC, MQPUT, MQGET, i.e. connect, disconnect, put a message, get a message).
  • A client channel links an application to a queue manager. The client library acts as a proxy, and MQI calls go to the queue manager by RPC. The slides call this the "MQI channel".
  • A message channel is a unidirectional, asynchronous link between queue managers, run by message channel agents. Together with routing tables, message channels allow arbitrary topologies.
  • In a hub-and-spoke topology, clients connect over a LAN to a local spoke, and the spokes relay messages to the hub, which hosts the services. A client blocks only until its message reaches the spoke, and delivery across the WAN is then asynchronous but reliable. The hub is a bottleneck, which queue manager clusters address.
LAN · SITE 1 client app client app Spoke QMqueue manager MQI channel (RPC) LAN · SITE 2 client app client app Spoke QMqueue manager WAN message channel one-way, async, reliable MCA MCA HUB SITE Hub QMqueue manager service service service the hub is a bottleneck
Clients only wait for the local hop. A client's MQI call is an RPC to its nearby spoke, so it returns as soon as the message is stored there. The slow WAN hop to the hub runs afterwards over a one-way message channel driven by message channel agents (MCA). Queue manager clusters relieve the hub bottleneck.

Java Messaging Service (JMS)

JMS is a specification (an API standard, not a product), implemented by ActiveMQ, OpenMQ and JBoss, and also over WebSphere MQ. Its contribution is that it unifies pub-sub and message queues through two kinds of destination. A Topic is used through a TopicPublisher and a TopicSubscriber, and every subscriber gets a copy of each message. A Queue is used through a QueueSender and a QueueReceiver, and each message goes to one receiver.

Sender Queuem1 m2 m3 Receiver 1 Receiver 2 Receiver 3 m1 m2 m3 each message to ONE receiver
Queue: point-to-point. Three messages, three different receivers.
Publisher Topicm1 Subscriber 1 Subscriber 2 Subscriber 3 m1 m1 m1 every subscriber gets a COPY
Topic: one-to-many. One message, three copies.

A program reaches a destination through a chain of objects. It looks up a connection factory via JNDI (the Java Naming and Directory Interface, a name service) and uses it to create a connection, which is either for topics or for queues, not a mix of the two. From the connection it creates a session, which may be transactional, and the session creates the producers, consumers and messages.

InitialContextJNDI name service ConnectionFactorytopic or queue kind Connectionstart() begins delivery Sessionmay be transactional lookup create create ProducerTopicPublisher /QueueSender ConsumerTopicSubscriber /QueueReceiver MessageTextMessage, … creates Destination: Topic or Queuectx.lookup("Alarms") lookup producers and consumers are bound to a destination
Follow the arrows to send a message. Two things come from JNDI (gold and grey): the factory and the destination. Everything else is created from the object before it. The same chain exists in a Topic flavour and a Queue flavour, which is how JMS unifies the two paradigms.

A JMS message has three parts: a header (destination, priority, expiry, ID, timestamp), properties that carry user metadata, and an opaque body. Consumers may filter with a message selector, which is a subset of SQL evaluated over the header and properties but not the body. They receive messages either with a blocking receive() or through a MessageListener.onMessage() callback.

headerdestination, priority, expiry, ID, timestamp propertiesuser metadata, e.g. location = "B7" bodyopaque: "Fire!" a message selector (SQL subset) may test these selectors cannot look here
Put what you want to filter on into properties. That is why the fire-alarm publisher below sets a location property rather than writing the location into the text body.

The fire-alarm consumer below uses a selector so that it only waits for alarms at one location.

import javax.jms.*;
import javax.naming.*;

public class FireAlarmConsumerJMS {          // after CDK Fig 6.18, plus selector (Ex 6.14)
    public String await(String loc) {
        try {
            Context ctx = new InitialContext();                       // JNDI
            TopicConnectionFactory f =
                (TopicConnectionFactory) ctx.lookup("TopicConnectionFactory");
            Topic topic = (Topic) ctx.lookup("Alarms");
            TopicConnection conn = f.createTopicConnection();
            TopicSession s = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
            TopicSubscriber sub = s.createSubscriber(topic, "location = '" + loc + "'", false);
            conn.start();                                             // begin delivery
            return ((TextMessage) sub.receive()).getText();           // blocking receive
        } catch (Exception e) { return null; }
    }
}
// Publisher: s.createPublisher(topic); msg = s.createTextMessage("Fire!");
//            msg.setStringProperty("location", "B7"); pub.publish(msg);

Line by line, it walks the chain from the figure:

  1. new InitialContext() opens the JNDI name service.
  2. ctx.lookup("TopicConnectionFactory") fetches the factory, and ctx.lookup("Alarms") fetches the destination, a Topic named Alarms.
  3. f.createTopicConnection() creates a connection for topics only.
  4. createTopicSession(false, Session.AUTO_ACKNOWLEDGE) creates a session; false means not transactional, and AUTO_ACKNOWLEDGE means messages are acknowledged automatically when received.
  5. createSubscriber(topic, "location = '" + loc + "'", false) creates a subscriber with a message selector: for loc = "B7" the selector is location = 'B7', so only alarms whose location property is B7 are delivered. (The final false is JMS's noLocal flag: it does not suppress messages published on this same connection.)
  6. conn.start() begins delivery; nothing arrives before it.
  7. sub.receive() is a blocking receive; the result is cast to TextMessage and its text ("Fire!") returned.

The publisher, in the comment, creates a publisher on the same topic, makes a text message "Fire!", sets the property location to "B7" (so selectors can see it) and publishes it.

Check yourselfSelf-test Q9: describe the message-queue model and its reliability, and compare it with pub-sub.

A message queue offers send, blocking receive, poll and notify. Because the queue is persistent, it provides validity and integrity, though no timing guarantee; it also supports transactions and message transformation.

A queue is point-to-point, with one consumer per message. Pub-sub, by contrast, is one-to-many, filtered, and often non-persistent. JMS offers both, through its Queue and Topic destinations.

Check yourselfA consumer wants to check the queue and carry on with other work if nothing is there. Which operation does it use?
  1. Blocking receive
  2. Poll (non-blocking receive)
  3. Notify
  4. Acknowledge

B. Poll returns a message or "none" immediately. Blocking receive would wait; notify would have the queue raise an event later (also workable, but the question asks to check now); acknowledge confirms processing of a message already received.

Check yourselfA JMS subscriber uses the selector body LIKE '%Fire%' to catch fire alarms. Why will this not work, and what should the publisher do instead?

Message selectors are evaluated over the header and properties only, not the body, which is opaque to JMS. The publisher should put the information in a property, for example msg.setStringProperty("type", "fire"), and the subscriber should select on type = 'fire'.

Check yourselfThree image-resizing workers share the work of a photo site. Should uploads be sent to a JMS Topic or a JMS Queue?

A Queue. Each photo must be resized once, by whichever worker is free: that is competing consumers on a point-to-point queue, giving load balancing. A Topic would give every worker a copy, so each photo would be resized three times.

In short
  • Message queue: point-to-point, explicit third party, space- and time-uncoupled. send; blocking receive, poll, notify; acknowledge. Competing consumers balance load.
  • Properties: persistence (validity, integrity, no timing guarantee), transactions, transformation, security.
  • WebSphere MQ: queue managers, MQI client channels (RPC), one-way message channels, hub and spoke. JMS unifies Topic (copy to all) and Queue (one receiver); selectors see header and properties, not the body.
7.10

Distributed Shared Memory exam

The last two paradigms are state-based abstractions: instead of sending messages, processes share state. They differ in how that state is accessed. DSM shares bytes, which are accessed by address, while tuple spaces (§7.11) hold semi-structured data, which is accessed associatively, that is, by content.

Why would anyone want shared memory across machines? Because programmers already know how to write parallel programs where threads share variables (Part IV), and rewriting them with explicit messages is laborious. DSM keeps the shared-variable style even when the threads run on different computers.

Definition · distributed shared memory (DSM)

DSM is an abstraction for sharing data between computers that do not share physical memory. Processes read and update what looks like ordinary memory in their own address space, while a runtime transparently ensures that processes on different computers observe each other's updates.

The shared memory is a logical address space, not a central memory. The data is spread across the computers and replicated (cached) among them, and the runtime exchanges messages to locate data and keep the copies consistent. So underneath, DSM is still message passing; the programmer just does not see it.

Logical shared address space: what every process sees p0 p1 p2 p3 p4 p5 p6 p7 mapsmapsmaps COMPUTER 1 Process 1 local copies p2 p5 DSM RUNTIME COMPUTER 2 Process 2 local copies p2 p6 DSM RUNTIME COMPUTER 3 Process 3 local copies p0 p5 DSM RUNTIME runtimes exchange messages: locate pages, keep copies consistent
One logical memory, many physical copies. The shaded pages (p2, p5) are replicated on two computers, so a write to one copy must be propagated or the other invalidated. That is the runtime's job, done with messages the program never sees.

DSM is meant for parallel applications that share data directly. It is unsuitable for client-server systems, where resources are abstract data accessed by request, for reasons of modularity and protection: a server does not want clients poking at its memory. Persistent DSM outlives the processes that use it, which gives time uncoupling. Apollo Domain is an example: there, hosts map shared files into their address spaces.

DSM vs message passing exam

DSM is easiest to understand by contrast with message passing, and a "compare" question expects several of these rows.

AspectMessage passingDSM
DataMarshalled, sent, unmarshalledShared directly; no marshalling, even of pointers
ProtectionPrivate address spacesOne process can corrupt data and crash others
HeterogeneityMarshalling handles representationsHard (different integer formats?)
SynchronisationMessage primitives (lock server)Locks, semaphores
LifetimesMust overlapCan be non-overlapping (persistent)
Cost visibilityEvery remote access explicitAny access may silently cause communication

On efficiency, DSM can be comparable to message passing for some programs on about 10 nodes, but this depends on the sharing pattern.

Check yourselfSelf-test Q10: compare DSM with message passing.

DSM shares data directly with no marshalling, uses familiar locks and semaphores, and can be persistent, so lifetimes need not overlap. On the other hand, it gives no protection between processes, makes heterogeneity hard, and hides the cost of communication.

Message passing uses marshalling, which handles heterogeneity; it keeps address spaces private; and its costs are explicit. However, lifetimes must overlap. For some parallel programs on small clusters the two have comparable performance, but this depends on the sharing pattern.

Implementing DSM middleware

This material (CDK 4th ed. chapter 18, referenced from CDK §6.5.1) is the background for the 2016 exam question on DSM middleware, worked at the end of this section.

approach 1

Hardware

The memory system itself keeps copies consistent.
approach 2

Page-based

Uses the virtual-memory hardware; the unit of sharing is the page (a fixed-size block of memory, a few KB).
approach 3

Middleware / library

Shared objects or variables are managed by a language runtime.

Consistency model. A consistency model says which values a read may return when there are concurrent writes. The usual target is sequential consistency (all processes see all memory operations in one single order consistent with each process's own program order), because strict (atomic) consistency, where every read returns the most recent write in real time, is too expensive. Weaker models such as release consistency cut traffic by propagating changes only when a lock is released.

Write-invalidate is the multiple-readers/single-writer protocol: many processes may hold read-only copies of a page, or one process may hold a writable copy. The trick is the virtual-memory protection bits. A page held read-only is mapped read-only, so a write to it causes a page fault (a trap to the operating system), which the DSM runtime handles.

  • On a read fault, the process requests the page from its owner and maps it read-only.
  • On a write fault, it invalidates every other copy, collects the acknowledgements, takes ownership, and then maps the page read-write.
A · reader Owner / manager 1 · read fault 2 · PAGE_REQUEST(p, read) 3 · PAGE_REPLY(p, data) 4 · map read-only
A read fault is one request-reply. Other readers keep their copies: many read-only copies may coexist.
A write fault under write-invalidate
A · writer Owner / manager B · has read copy page 7: read-only page 7: read-write, owner page 7: read-only page 7: unmapped 1 · write fault (trap) 2 · PAGE_REQUEST(7, write) 3 · INVALIDATE(7) 4 · unmap page 7 5 · INV_ACK(7) 6 · PAGE_REPLY(7, data, owner) 7 · map RW, restart write
  1. Both A and B hold read-only copies of page 7, mapped read-only. The manager knows who owns the page and who holds copies (the copy set).
  2. A's program writes to page 7. The page is mapped read-only, so the hardware traps, and the OS passes the page fault to the DSM handler in A.
  3. The handler sends PAGE_REQUEST(7, write) to the owner or manager.
  4. Before A may write, every other copy must go. The manager sends INVALIDATE(7) to B (and to every other process in the copy set).
  5. B unmaps page 7. If B reads it again later, it will take a read fault and fetch a fresh copy.
  6. B confirms with INV_ACK(7). The manager waits for all acknowledgements.
  7. The manager sends PAGE_REPLY with the page's data and ownership to A.
  8. A maps the page read-write and restarts the faulting instruction, which now succeeds. A is the single writer; nobody else has a copy.

Write-update is the alternative: instead of invalidating, multicast each write to all copies. That needs totally ordered multicast (§7.5) so that all copies apply writes in the same order. With pages it is costly, because every single write must be trapped.

Messages and the protocol that carries them

MessageMeaning
PAGE_REQUEST(page, mode)Ask the owner for a page, for reading or writing
PAGE_REPLY(page, data)The page contents (and, for a write, ownership)
INVALIDATE(page)Discard your copy of this page
INV_ACK(page)Copy discarded
UPDATE(addr, value)Write-update only: apply this write to your copy

These should be carried by a lightweight request-reply protocol over UDP (Part VI), for three reasons. Faults are short, latency-critical, blocking exchanges, with no stream to set up, so TCP's connection setup would be pure overhead. Read fetches are idempotent (doing them twice has the same effect as once), so they can simply be retransmitted on timeout, although ownership transfers need duplicate filtering. And multicast speeds up invalidations. Owners are found through a central manager, a hash-based distributed manager, or "probable owner" forwarding (each node remembers who it thinks owns the page and forwards requests along that chain).

Granularity: false sharing and thrashing

Large pages amortise the cost of each message (one fetch brings a lot of data), but they cause false sharing and thrashing, where a page ping-pongs between writers.

Process Awrites x Process Bwrites y page 7 x y B writes y: whole page to B A writes x: whole page back repeat forever: thrashing
Sharing a page is not sharing data. A and B never touch each other's variable, but because x and y happen to live on the same page, every write by one invalidates the other's copy. Smaller units of sharing reduce this, at the cost of more messages.

What the operating system must provide

Page-based DSM leans heavily on the OS (Part V):

  • Paged virtual memory.
  • Delivery of page faults to a user-level handler, either as a signal handler or through an external pager as in the Mach microkernel.
  • Calls to map/unmap pages and to change their protection: read-only to detect writes, and no access to detect reads.
  • Threads, so that a faulting thread can block while a server thread answers remote requests.
  • Efficient networking.
Past exam 2016 Q5(b) · self-test Q14

Question. You are implementing DSM middleware. What protocol and messages would you use, and why? What OS support is required?

Model answer
  • Use page-based DSM with sequential consistency (or release consistency, for fewer messages), and a multiple-reader/single-writer write-invalidate protocol.
  • On a read fault, the node sends PAGE_REQUEST(p, read) to the owner or manager, receives PAGE_REPLY(p, data), and maps the page read-only.
  • On a write fault, it sends PAGE_REQUEST(p, write), and the owner sends the page and ownership. The manager sends INVALIDATE(p) to the copy set and collects the INV_ACKs, and the page is then mapped read-write. (Write-update would instead multicast UPDATE messages in total order.)
  • The protocol is a lightweight request-reply over UDP. The exchanges are small and latency-critical, and because faults block, synchronous request-reply fits them. Read fetches are idempotent, which allows simple retransmission (ownership transfer needs duplicate filtering), and multicast can be used for invalidations.
  • The owner is located through a central manager or by hashing the page number. Release consistency reduces traffic, and large pages bring a risk of false sharing and thrashing.
  • The OS must provide paged virtual memory; trapping of page faults to a user-level handler (through signals or an external pager, as in Mach); system calls to map and unmap pages and set their protection (read-only to detect writes); multithreading, so that a server thread can answer requests while the faulting thread blocks; and fast network I/O.
How this is marked

A 5-mark "protocol and messages, and why" question wants the named protocol (write-invalidate), the named messages in the right order for a write fault, a reason for the transport (request-reply over UDP: small, blocking, idempotent reads), and a separate list of OS facilities. Drawing the write-fault sequence, as in the stepper above, is a quick way to show the message order.

Check yourselfUnder write-invalidate, process A holds page 3 read-only and writes to it. Put the events in order: (i) A maps the page read-write; (ii) INVALIDATE to other copy holders; (iii) page fault traps to the DSM handler; (iv) INV_ACKs collected; (v) PAGE_REQUEST(3, write); (vi) PAGE_REPLY with ownership.

iii, v, ii, iv, vi, i. The write traps, the handler requests the page for writing, all other copies are invalidated and acknowledged, the page and ownership are sent, and only then is it mapped read-write and the instruction restarted.

Check yourselfWhy is a read PAGE_REQUEST safe to retransmit blindly, while an ownership transfer is not?

A read fetch is idempotent: fetching the same page twice leaves everything as it was, so a lost request or reply can just be repeated. An ownership transfer changes state (who owns the page, who holds copies), so executing a duplicate could transfer ownership twice or invalidate a fresh copy; it needs duplicate filtering (e.g. request ids).

Check yourselfA team proposes DSM so that web browsers can share a bank server's account table directly. What is wrong with this?

DSM is unsuitable for client-server systems. The server's resources should be abstract data accessed by request, so the server can enforce its rules; with DSM there is no protection (any client could corrupt the table and crash others) and no modularity. DSM is for cooperating parallel processes.

In short
  • DSM: a logical shared address space over computers without shared memory; data is replicated and kept consistent by messages. For parallel programs, not client-server. Persistent DSM is time-uncoupled.
  • Versus message passing: no marshalling, familiar locks, lifetimes may not overlap; but no protection, hard heterogeneity, hidden costs.
  • Page-based write-invalidate: PAGE_REQUEST, INVALIDATE, INV_ACK, PAGE_REPLY over request-reply UDP. Needs VM, user-level fault handling, map/protect calls, threads, fast networking. Big pages risk false sharing.
7.11

Tuple Spaces exam

DSM shares raw bytes at addresses, which suits number-crunching but forces every process to agree on memory layout. Tuple spaces share small typed records instead, and processes find them by what they contain rather than where they are. The result is a very loose way to coordinate: leave data in a common place, and whoever needs it picks it up.

Definition · tuple space (Linda)

Tuple spaces come from Gelernter's generative communication, known as the Linda model. Processes communicate by placing tuples, which are sequences of typed fields such as <"fred", 1958>, in a tuple space. Other processes read or remove them by pattern matching on content, because tuples have no address.

Three operations

write (out)

Adds a tuple to the space. Never blocks.

read (rd)

Returns a copy of a matching tuple, which stays in the space. Blocks until one exists. Many processes can read the same tuple.

take (in)

Returns a matching tuple and atomically removes it. Blocks. Only one process gets the tuple.

A process says which tuple it wants with a template, which gives the number of fields and, for each field, either a value (which must be equal) or a type (any value of that type matches). For example, take(<String, Integer>) matches <"fred",1958> or <"sid",1964>, whereas take(<String, 1958>) matches only the first. If several tuples match, one is chosen nondeterministically. Because read and take block, the operations also give synchronisation: a process can wait for a tuple that another process has not written yet.

write, read and take, one operation at a time
TUPLE SPACE <"fred",1958> <"sid",1964> <"ann",1970> <"bob",2001> P2 · reader P1 · writer P3 · taker P4 · taker write(<"ann",1970>) read(<String,1958>)gets a copy; fred stays take(<String,Integer>)got <"sid",1964>; removed take(<"bob",Integer>)blocked: nothing matches write(<"bob",2001>) unblocked: got <"bob",2001>
  1. The space holds two tuples, <"fred",1958> and <"sid",1964>. Four processes are about to use it; none of them knows the others exist.
  2. P1 does write(<"ann",1970>). The new tuple appears in the space and P1 carries on at once: write never blocks.
  3. P2 does read(<String, 1958>): two fields, the first any String, the second exactly 1958. Only fred matches. P2 gets a copy; the tuple stays for others to read.
  4. P3 does take(<String, Integer>). All three tuples match, so one is chosen nondeterministically, here sid. It is removed atomically: no other process can also get it.
  5. P4 does take(<"bob", Integer>). Nothing matches, so P4 blocks and waits.
  6. P1 later does write(<"bob",2001>). A matching tuple now exists.
  7. P4's take completes: it removes <"bob",2001> and unblocks. P1 and P4 coordinated without ever naming each other, and P4 started waiting before the data existed.

Matching in practice: the UK example (CDK Fig 6.20)

templates (italic = any value of that type) take String "Scotland" String take String "Scotland" Integer read "Population" String Integer tuples in the space "Capital" "Scotland" "Edinburgh" "Population" "Scotland" 5168000 removes removes copy; stays
Match field by field. Shaded template fields are actual values that must be equal; italic ones only fix the type. The last field decides the first two rows: a String matches "Edinburgh", an Integer matches 5168000. The read returns some population tuple and leaves it in the space.

Suppose the space contains <"Capital","Scotland","Edinburgh"> and <"Population","Scotland",5168000>. Then take(<String,"Scotland",String>) removes the capital tuple, and take(<String,"Scotland",Integer>) removes the population tuple. read(<"Population",String,Integer>) returns some population tuple, which stays in the space.

Tuples are immutable: updating a shared counter

Tuples are immutable, so to change one you take it and then write a replacement. While the tuple is out of the space, other processes trying to take it block, which gives mutual exclusion for free:

<s, count> := myTS.take(<"counter", Integer>);   // others now block: mutual exclusion
myTS.write(<"counter", count + 1>);
Two processes increment one counter
P Tuple space Q holds <"counter", 5> no counter tuple holds <"counter", 6> no counter tuple holds <"counter", 7> take(<"counter",Integer>) <"counter", 5> take(<"counter",Integer>) Q blocks write(<"counter", 6>) <"counter", 6>Q unblocks write(<"counter", 7>)
  1. The space holds <"counter", 5>. P and Q each want to add 1.
  2. P takes the counter tuple and receives 5. The tuple is now out of the space.
  3. Q tries the same take, but no counter tuple exists, so Q blocks. This is the mutual exclusion: Q cannot read a stale 5 while P is mid-update.
  4. P writes <"counter", 6> back.
  5. Q's waiting take now matches: Q unblocks with 6, and the tuple is out of the space again.
  6. Q writes <"counter", 7>. Both increments counted; no lost update.
Common mistake

Using read then write to update the counter. read leaves the old tuple in the space, so both processes can read 5 and both write 6 (a lost update), and the space now holds two counter tuples. The take is what provides the mutual exclusion.

Properties

Tuple spaces provide space uncoupling, which Linda calls "distributed naming": any sender can reach any one of many receivers, since a tuple goes to whoever matches it. They provide time uncoupling, because tuples persist until they are removed. Addressing is associative (by content). And the two retrieval operations have different patterns: read is one-to-many (many can read one tuple), while take is one-to-one (one tuple goes to one taker).

Programming patterns

  • A semaphore (Ex 6.19) is made by writing N <"sem"> tuples. P (wait) is take(<"sem">), which blocks when none are left, and V (signal) is write(<"sem">).
  • In master–worker, the master writes <"task", id, data> tuples, and idle workers take them and write back <"result", id, v>. Each task is done once, load balancing is automatic, and workers may come and go.
  • RPC over a tuple space (Ex 6.18) has the client write <"req", op, args, clientId> and then take(<"reply", clientId, Object>), while servers loop taking requests. This gains time uncoupling and replaceable servers, but costs extra hops and gives no direct indication of failure (a client cannot tell a dead server from a slow one).
Master TUPLE SPACE <"task",1,d1> <"task",2,d2> <"task",3,d3> <"result",1,v1> tasks wait here until a worker is free write(task) take(result) Worker 1 Worker 2 Worker 3 take(task) write(result)
Load balancing by take. Each task tuple can be taken by only one worker, so each task is done once; a fast worker simply takes more tasks. A new worker can join at any time without the master knowing: that is space and time uncoupling at work.

Variations. There can be multiple tuple spaces, which avoids a risk of a single global space, namely unintended aliasing, where a type-only template like <String, Integer> matches unrelated tuples written by some other application. Other variations are distributed implementations, Bauhaus Linda (in which everything is a set), and object spaces such as JavaSpaces.

Implementing a tuple space

There are four broad ways to implement a tuple space.

A centralised implementation is simple, but it is not fault tolerant and not scalable.

Replication can follow the state machine approach: the replicas start empty and deterministically apply the same operations in the same order, which requires totally ordered multicast (§7.5).

Alternatively, replication can follow Xu & Liskov, whose scheme exploits the semantics of each operation, working within the current view of the replicas:

  • A write is multicast to all replicas and repeated until all of them acknowledge; duplicates are detected and not re-applied. The writer returns immediately.
  • A read is multicast, and the first matching reply is the result, so the reader blocks only until one replica answers.
  • A take has two phases, shown in the stepper.
Xu & Liskov: a take on three replicas
Worker Replica 1 Replica 2 Replica 3 1 · take(tmpl) {t1, t2} {t1, t2} {t1} pick t1 from the intersection take returns t1 now 2 · remove(t1) ack
  1. A worker wants to take a tuple, and the space is replicated on three replicas (violet).
  2. Phase 1 begins: the worker multicasts the take request, with its template, to all replicas.
  3. Each replica locks the relevant tuple set (so no other take can grab those tuples meanwhile) and replies with its matching tuples. Replica 3's view differs slightly: it only has t1. (If only a minority of replicas accepted, they would release their locks and the operation would be retried.)
  4. The worker picks one tuple from the intersection of the replies: t1 is in every set. Phase 1 is over, so the take returns t1 to the program now.
  5. Phase 2: the worker multicasts "remove t1", repeating it until all replicas acknowledge.
  6. All replicas acknowledge, remove t1 and release their locks. Every replica now agrees that t1 is gone.

The scheme needs two extra constraints. A worker's operations must run at each replica in the order the worker issued them, and a write must wait until that worker's earlier takes have finished everywhere.

Partitioning is the approach of the York Linda Kernel. Tuples are spread over several Tuple Space Servers (TSSs), with one copy only of each tuple, and the server is chosen by hashing the tuple. A read or take hashes its template to a set of candidate servers and searches them linearly. A Local Tuple Space Manager on each node handles its tuples. Keeping single copies makes take simple (no replicas to agree); the goal is performance for parallel computation, not fault tolerance.

Process hash()tuple or template TSS 1 TSS 2 TSS 3 TSS 4 write(t): exactly one TSS take(tmpl): candidate set, searched one by one each node runs a Local Tuple Space Manager for its tuples
Partitioning instead of replication. Each tuple lives on exactly one server, chosen by hashing, so a take only has to remove one copy. The price is no fault tolerance: if TSS 2 crashes, its tuples are gone.

Finally, there are peer-to-peer implementations: PeerSpaces, LIME and TOTA.

Case study: JavaSpaces

JavaSpaces is a Sun specification built on Jini (Java's service-discovery framework), with implementations such as GigaSpaces and Blitz. It is an object space: the items in it are entries, which are objects of classes implementing net.jini.core.entry.Entry. It has four operations:

OperationWhat it does
Lease write(Entry e, Transaction txn, long lease)Places an entry in the space for the lease period, or for Lease.FOREVER. The lease actually granted may be shorter.
read(tmpl, txn, timeout)Returns a copy of a matching entry, blocking for up to the timeout. readIfExists(...) is the non-blocking version, which returns null if there is no match.
take(tmpl, txn, timeout)Removes a matching entry; blocking. takeIfExists(...) is its non-blocking version.
notify(tmpl, txn, listener, lease, handback)Sends a Jini remote event when a matching entry is written.

A lease is a time limit: when it expires the entry is removed automatically, so forgotten entries do not clog the space. An entry matches a template if it is of the same class as the template (or a subclass) and is equal to it on every non-null template field, so a null field acts as a wildcard. Any operation may take place inside a distributed transaction; passing null for txn means no transaction.

import net.jini.core.entry.Entry;
import net.jini.space.JavaSpace;

public class AlarmTupleJS implements Entry {        // CDK Fig 6.24: public fields, no-arg ctor
    public String alarmType;
    public AlarmTupleJS() { }
    public AlarmTupleJS(String t) { alarmType = t; }
}
// Producer: space.write(new AlarmTupleJS("Fire!"), null, 60 * 60 * 1000);   // lease 1 hour
// Consumer: AlarmTupleJS got = (AlarmTupleJS)
//               space.read(new AlarmTupleJS("Fire!"), null, Long.MAX_VALUE); // blocking read
// (space obtained with a helper such as GigaSpaces' SpaceAccessor.findSpace("AlarmSpace"))

Line by line: AlarmTupleJS implements Entry, and, as JavaSpaces requires, has public fields and a public no-argument constructor. Its one field, alarmType, is what templates match on. The producer writes an entry with alarmType = "Fire!", no transaction (null) and a lease of 60 * 60 * 1000 milliseconds, one hour. A consumer builds a template with the same field value and calls read, with no transaction and a timeout of Long.MAX_VALUE, i.e. wait essentially forever. A template new AlarmTupleJS() with a null field would match any alarm.

producer space consumer 1 consumer 2 entry "Fire!" in the space for its 1-hour lease starts later write, lease 1 h reads it still reads it lease expires
Compare the JGroups alarm in §7.6. Because consumers use read (not take), every consumer sees the alarm, and because the entry persists for its lease, even one that starts within the hour does. This version is space- and time-uncoupled.
Check yourselfSelf-test Q12: give the tuple-space operations and properties, and show how to increment a shared counter.

The operations are write (out), which adds a tuple; read (rd), which returns a copy of a matching tuple and blocks; and take (in), which removes a matching tuple atomically and also blocks. Tuples are found by template matching and are immutable. Tuple spaces are space- and time-uncoupled, and associative.

To increment a shared counter, take the tuple and write back a new one: <s,c> := take(<"counter", Integer>); write(<"counter", c+1>).

Check yourselfThe space holds <"x", 3>, <"y", 4> and <"x", "three">. Which tuples can take(<"x", Integer>) return?
  1. Only <"x", 3>
  2. <"x", 3> or <"x", "three">
  3. <"x", 3> or <"y", 4>
  4. Any of the three

A. The first field must equal "x" and the second must be of type Integer. <"x","three"> has a String second field, and <"y",4> fails the first field.

Check yourselfHow do you build a semaphore that lets at most 3 processes into a section at once, using only tuple-space operations?

Initialise by writing three <"sem"> tuples. To enter (P), take(<"sem">): the fourth process finds none left and blocks. To leave (V), write(<"sem">), which lets one blocked process through.

Check yourselfIn a replicated tuple space, why is take harder to implement than read or write?

A take is destructive and must be exclusive: all replicas must agree on which one tuple was removed, and no two takers may get the same tuple. A read can use the first reply from any replica, and a write is just multicast until acknowledged. That is why Xu & Liskov's take needs two phases with locking, and the state-machine approach needs totally ordered multicast. CDK names the destructive take as what limits tuple-space scalability.

In short
  • Linda: write (non-blocking), read (copy, blocking), take (remove atomically, blocking); templates match by value or type; nondeterministic choice; tuples are immutable, so update is take-then-write, which gives mutual exclusion.
  • Space- and time-uncoupled, associative; read is one-to-many, take one-to-one. Patterns: semaphore, master–worker, RPC over a tuple space.
  • Implementations: centralised; replicated (state machine with total order, or Xu & Liskov's two-phase take); partitioned (York Linda Kernel, hashing to one TSS); peer-to-peer. JavaSpaces: entries, leases, null fields as wildcards, transactions.
7.12

Comparing and Choosing a Technique exam

The exam rarely asks "define pub-sub" on its own. It describes a system and asks which paradigm fits and why. To answer, you need each paradigm's profile side by side, and a habit of matching the scenario's needs (who receives, how many, when, ordered or not) to those profiles.

The summary table (CDK Fig 6.27)

Read each column as one paradigm's profile, and compare across the first two rows to see where time uncoupling is guaranteed and where it is only possible.

GroupsPub-subMessage queuesDSMTuple spaces
Space-uncoupledYesYesYesYesYes
Time-uncoupledPossiblePossibleYesYesYes
StyleCommunicationCommunicationCommunicationStateState
Pattern1-to-many1-to-many1-to-11-to-many1-to-1 or 1-to-many
Main intentReliable distributed computingDissemination, EAI, mobileDissemination, EAI, transactionsParallel computationParallel computation, mobile
ScalabilityLimitedPossiblePossibleLimitedLimited
AssociativeNoContent-based onlyNoNoYes
ExamplesJGroupsSiena, Kafka, MQTTWebSphere MQ, JMS queuesApollo DomainLinda, JavaSpaces

Two observations explain the pattern in the table. First, time uncoupling depends on persistency: queues, DSM and tuple spaces store state by design, while groups and pub-sub only do so if they keep history (JGroups' getState, durable subscriptions). Second, communication-based approaches can scale given good routing, with the exception of groups, which pay for membership and ordering. State-based approaches are limited by the need for consistent shared state, and for tuple spaces the limiting factor is the destructive take.

Choosing: needs to technique

Each row of the next table starts from a need, names the technique that fits it, and gives a scenario to illustrate it.

NeedChooseScenario
Message must reach all current members of a managed group, with agreement/orderingGroup communicationThree replicas of a bank server: totally ordered reliable multicast keeps them identical
Many independent subscribers react to events selected by topic/content/typePub-subStock ticker: traders subscribe to their stocks (or price > X)
Each task processed by exactly one worker, reliably, possibly laterMessage queueWeb shop orders consumed by a warehouse system offline overnight; transactional
Tightly coupled parallel computation on shared dataDSMParallel matrix computation on a cluster
Coordinate by matching data/tasks, not naming recipientsTuple spaceRender farm with workers joining and leaving
Lightweight dissemination over flaky linksTopic pub-sub (e.g. MQTT with QoS)IoT sensors reporting to dashboards
Integrate applications with different data formatsMessage queue + transformationLegacy payroll feeding a new HR system (EAI)
Alert everyone, sender unaware of receiversPub-sub topic (durable if consumers may be offline)Building fire alarm

The same reasoning can be drawn as a rule-of-thumb decision tree. It is a study aid built from the two tables, not an official algorithm, but it asks the questions in the order that usually settles an exam scenario.

Should every interested receiver get its own copy? A known, managed group that needs agreement and ordering? Coordinate by sharing state rather than sending messages? yes no, one receiver each Groupcommunication Pub-subdurable if receivers offline yes no Message queuepersistent, transactional Shared by address, for tightly coupled parallel computation? no yes DSM Tuple spacematch by content yes no
Ask about copies first. "Everyone gets a copy" points to groups or pub-sub; "each item to one taker" points to queues or tuple spaces. Then refine: managed membership and ordering means a group; shared data by address means DSM; coordination by content means a tuple space.
Pitfalls
  • Asynchronous communication is not the same as time-uncoupled communication.
  • Indirect communication is not automatically time-uncoupled either.
  • Pub-sub gives every subscriber a copy of an event, whereas a queue gives each message to one consumer.
  • Total ordering is not the same as causal ordering.

A three-part scenario

Self-test Q15 asks for a choice per requirement in one hospital system. Notice that one system can sensibly use three paradigms.

(a) Alerts → pub-sub monitor topicward7 nurse 1 nurse 2 nurse 3 copy to every display; filter: severity; reliable, fast (b) Lab requests → queue ward analyser 1 analyser 2 analyser 3 each request done once, by a free analyser; waits if all are down (c) Records → group GROUP records 1 records 2 records 3 #1 #2 #3… reliable, totally ordered multicast; membership, failure detection
One system, three needs, three paradigms. (a) everyone must see each alert: one-to-many with filtering. (b) each request to exactly one worker, surviving outages: a persistent queue with competing consumers. (c) identical replicas: group communication with total order.
Check yourselfSelf-test Q15: a hospital needs (a) vital-sign alerts to all nurses' displays, (b) lab requests to whichever analyser is free, even if all are briefly down, (c) three replicated record servers kept identical. Choose for each.
  • (a) The alerts should use pub-sub, with a topic per ward and a content filter on severity, and with reliable low-latency delivery.
  • (b) The lab requests should go on a persistent message queue with competing consumers. Each request is then handled once, and time uncoupling covers the times when all the analysers are down.
  • (c) The record servers need group communication with reliable totally ordered multicast, plus membership and failure detection.
Check yourselfAccording to CDK's summary table, which paradigm is associative in all its forms?
  1. Pub-sub
  2. Message queues
  3. DSM
  4. Tuple spaces

D. Tuple spaces always find data by content. Pub-sub is associative only in its content-based form; queues and DSM are not associative (DSM is accessed by address).

Check yourselfFor which two paradigms does CDK say time uncoupling is only "possible" rather than guaranteed, and why?

Groups and pub-sub. They are time-uncoupled only if they keep state for absent receivers (group history via getState, retained events and durable subscriptions). Queues, DSM and tuple spaces store state by nature, so they are always time-uncoupled. Time uncoupling depends on persistency.

Check yourselfA legacy payroll system writes records in EBCDIC with big-endian integers; a new HR system expects JSON. Which paradigm, and which of its properties, helps?

A message queue with message transformation: a message broker converts between formats as messages pass through, which is the classic EAI use. Persistence also means neither system has to be up at the same time.

In short
  • All five are space-uncoupled; queues, DSM and tuple spaces are always time-uncoupled, groups and pub-sub only possibly. Groups, pub-sub, queues communicate; DSM and tuple spaces share state.
  • Copies to all: groups (managed, ordered) or pub-sub (filtered dissemination). One taker: queue (messages, transactions) or tuple space (coordination by content). Parallel shared data by address: DSM.
7.13

Exam Question: Choose and Draw exam

The 2016 final exam asked students to pick between three indirect paradigms for a concrete app and to draw the design. This section works that question in full and then shows what the two rejected designs would have looked like, so you can argue against them as well as for your choice.

Past exam 2016 Q5(a) · self-test Q13

Question. A music popularity app: devices report the song currently playing; a server ranks songs and returns the results to all users. Choose a tuple space, a message queue or publish-subscribe; explain your choice and draw the design.

How to answer a "choose and draw" question
  1. Name your choice in the first line, with its variant (here: topic-based pub-sub through a broker).
  2. Draw it: every participant, the intermediary, and every arrow labelled with the operation and the data (publish(NowPlaying), notify(Chart)…).
  3. Walk through the flow: setup (subscriptions), then what happens on each event.
  4. Justify with the vocabulary of this Part: one-to-many, space/time uncoupling, asynchrony, scale, delivery guarantee.
  5. Say why not the others, one concrete drawback each.

The model answer: publish-subscribe

Choose publish-subscribe, topic-based, through a broker.

Device 1 Device 2 Pub-sub broker (or broker network) topic nowPlaying topic chart last chart retained, so reconnecting phones get it Ranking servercounts, sliding window publish(NowPlaying) notify(Chart) publish(NowPlaying) notify(Chart) notify(NowPlaying) publish(Chart top 10) Setup: server subscribe("nowPlaying"); every device subscribe("chart").
Two topics, flowing in opposite directions. Teal is the stream of play reports (many devices → one server); gold is the chart (one server → every device). Solid arrows are publishes; dashed arrows are notifications from the broker. Nobody in the picture knows anybody else's address.
Model answer
  • There are two topics: nowPlaying, whose events carry the song, deviceId and time, and chart, which carries the ranked list.
  • At setup, the server calls subscribe("nowPlaying") and each device calls subscribe("chart"). A device could instead use a content-based subscription, for example genre = "rock".
  • When the song changes, a device calls publish(NowPlaying{song}). The broker notifies the server, which updates its counts, for example over a sliding window.
  • Every T seconds, the server calls publish(Chart{top10}), and the broker notifies all the devices.
  • Why pub-sub fits: disseminating results to a large, changing set of users is a one-to-many problem. Devices need not know the server (space uncoupling) or wait for it (asynchronous), and a broker network scales. If the last chart is retained or subscriptions are durable, phones that reconnect get the latest chart (time uncoupling). At-most-once delivery is acceptable for play reports.
  • Why not the others: with a message queue, devices send to a plays queue consumed by the server, and persistence means no reports are lost; but returning results to all users needs a queue per device or polling, which is awkward. With a tuple space, devices write(<"play", song>), and the server takes these and replaces a <"chart", version, list> tuple, which devices read. However, devices must poll for new versions, and a central space limits scale.

The rejected designs, drawn

Drawing the alternatives makes their weaknesses obvious. Both handle the reporting half well; both struggle with the results to every user half.

Device 1 Device 2 queue "plays" Ranking server send receive reports: fine, nothing lost queue "dev1" queue "dev2" one queue and one send per device, every time
Good for reports, awkward for results. A queue gives each message to one consumer, so broadcasting the chart means a queue per device (or devices polling the server).
Device 1 Device 2 CENTRAL TUPLE SPACE <"play", song> <"chart", v7, list> Ranking server write(play) take(play) take + write chart devices read again and again: has the version changed yet?
Elegant, but devices must poll. Nothing tells a device the chart changed, so it keeps reading the chart tuple; and one central space for every phone limits scale.
Check yourselfIn the pub-sub design, which property gives the app time uncoupling for a phone that was switched off when the latest chart was published?

Retained events or durable subscriptions on the chart topic. The broker keeps the last chart (or the events for a durable subscriber), so when the phone reconnects it is notified of the latest chart. Plain pub-sub would give space uncoupling only.

Check yourselfThe examiner changes the scenario: every play report must be counted exactly once even if the ranking server is down for an hour, and results go only to a dashboard. Does your choice change?

Yes, a message queue becomes the natural fit. The requirement is now one consumer per message, reliably, possibly later: a persistent plays queue gives validity and integrity (each report delivered once) and time uncoupling across the server's outage, and there is no longer a one-to-many results problem.

In short
  • Music app: topic-based pub-sub with topics nowPlaying (devices → server) and chart (server → all devices), through a scalable broker network; retain the last chart for reconnecting phones.
  • Queues fail the "results to everyone" half (a queue per device); tuple spaces force polling and a central space. Always draw the design and name one drawback of each rejected option.