cassandra/.claude/skills/tla-plus/references/patterns/distributed-systems.md

5.7 KiB

Distributed Systems Patterns

Message Passing with Queues

The most common pattern for distributed systems. Model each node as a process, communication as queue operations.

FIFO Queue (Mutable)

variables
  queue = <<>>;   \* sequence of messages

\* Send
queue := Append(queue, msg);

\* Receive (destructive)
await queue # <<>>;
msg := Head(queue);
queue := Tail(queue);

Per-Process Queues

variables
  queues = [p \in Processes |-> <<>>];   \* each process has inbox

\* Send to one process
queues[dest] := Append(queues[dest], msg);

\* Broadcast to all
queues := [p \in Processes |-> Append(queues[p], msg)];

\* Send to subset (models unreliable delivery)
with recipients \in SUBSET Processes do
  queues := [p \in recipients |-> Append(queues[p], msg)] @@ queues;
end with;

\* Receive from own queue
await queues[self] # <<>>;
msg := Head(queues[self]);
queues[self] := Tail(queues[self]);

Message Types

\* Typed messages with discriminator
RequestMsg  == [type: {"request"},  from: Nodes, data: DataType, id: 1..MaxId]
ResponseMsg == [type: {"response"}, from: Nodes, data: DataType, id: 1..MaxId]
MessageType == RequestMsg \union ResponseMsg

Network Failures

Message Loss (At-Most-Once Delivery)

\* Instead of deterministic send, nondeterministically drop
either
  queues[dest] := Append(queues[dest], msg);   \* delivered
or
  skip;                                         \* lost
end either;

Message Reordering

\* Use a SET instead of a sequence for the channel
variables network = [p \in Processes |-> {}];

\* Send
network[dest] := network[dest] \union {msg};

\* Receive any message (unordered)
with m \in network[self] do
  msg := m;
  network[self] := network[self] \ {m};
end with;

Node Crashes

\* Model with nondeterministic crash action
process node \in Nodes
variables alive = TRUE;
begin
  Run:
    while alive do
      either
        \* normal operation
      or
        alive := FALSE;     \* crash
      end either;
    end while;
end process;

Consensus Patterns

Two-Phase Commit

---- MODULE TwoPhaseCommit ----
EXTENDS Integers, Sequences, FiniteSets, TLC

CONSTANTS Coordinators, Participants, NULL
ASSUME Cardinality(Coordinators) = 1

(*--algorithm two_phase_commit
variables
  coord_state = [c \in Coordinators |-> "init"];
  part_state  = [p \in Participants |-> "working"];
  prepared    = [c \in Coordinators |-> {}];
  msgs        = {};

define
  TypeInvariant ==
    /\ coord_state \in [Coordinators -> {"init", "waiting", "committed", "aborted"}]
    /\ part_state  \in [Participants -> {"working", "prepared", "committed", "aborted"}]

  \* Safety: no participant commits while another aborts
  Consistency ==
    \A p1, p2 \in Participants:
      ~(part_state[p1] = "committed" /\ part_state[p2] = "aborted")
end define;

process coordinator \in Coordinators
begin
  C_Prepare:
    coord_state[self] := "waiting";
    msgs := msgs \union {[type |-> "prepare", from |-> self]};

  C_Decide:
    await prepared[self] = Participants \/ \E p \in Participants: part_state[p] = "aborted";
    if prepared[self] = Participants then
      coord_state[self] := "committed";
      msgs := msgs \union {[type |-> "commit", from |-> self]};
    else
      coord_state[self] := "aborted";
      msgs := msgs \union {[type |-> "abort", from |-> self]};
    end if;
end process;

process participant \in Participants
begin
  P_Prepare:
    await \E m \in msgs: m.type = "prepare";
    either
      part_state[self] := "prepared";
      with c \in Coordinators do
        prepared[c] := prepared[c] \union {self};
      end with;
    or
      part_state[self] := "aborted";
    end either;

  P_Decide:
    either
      await \E m \in msgs: m.type = "commit";
      part_state[self] := "committed";
    or
      await \E m \in msgs: m.type = "abort";
      part_state[self] := "aborted";
    end either;
end process;
end algorithm; *)
====

Leader Election (Bully Algorithm Sketch)

variables
  leader = NULL;
  election_in_progress = FALSE;
  candidates = {};

define
  \* At most one leader at any time
  AtMostOneLeader ==
    \A n1, n2 \in Nodes:
      (leader = n1 /\ leader = n2) => n1 = n2

  \* Eventually a leader is elected (liveness)
  EventuallyLeader == <>(leader # NULL)
end define;

State Machine Pattern

Model systems as explicit state machines with transition functions.

VARIABLE state

Trans(from, to) ==
  /\ state = from
  /\ state' = to

Init == state = "idle"

Next ==
  \/ Trans("idle", "connecting")
  \/ Trans("connecting", "connected")
  \/ Trans("connecting", "failed")
  \/ Trans("connected", "disconnecting")
  \/ Trans("disconnecting", "idle")
  \/ Trans("failed", "idle")

Spec == Init /\ [][Next]_state

Clocks and Logical Time

Lamport Clocks

variables
  clock = [p \in Processes |-> 0];

\* Local event
clock[self] := clock[self] + 1;

\* Send: increment, attach clock
clock[self] := clock[self] + 1;
msg := [data |-> payload, ts |-> clock[self]];

\* Receive: merge clocks
clock[self] := IF msg.ts > clock[self]
               THEN msg.ts + 1
               ELSE clock[self] + 1;

Quorum Systems

CONSTANT Nodes

Quorum == {Q \in SUBSET Nodes : Cardinality(Q) * 2 > Cardinality(Nodes)}

\* Any two quorums overlap
QuorumOverlap == \A Q1, Q2 \in Quorum: Q1 \intersect Q2 # {}

Replication

variables
  replicas = [n \in Nodes |-> [key \in Keys |-> NULL]];

define
  \* Eventually consistent: if no more writes, all replicas converge
  EventualConsistency ==
    <>[](\A n1, n2 \in Nodes, k \in Keys:
           replicas[n1][k] = replicas[n2][k])

  \* Strong consistency: all replicas always agree
  StrongConsistency ==
    \A n1, n2 \in Nodes, k \in Keys:
      replicas[n1][k] = replicas[n2][k]
end define;