Project 7: Mailbox Pressure Laboratory

Build a reproducible PubSub load laboratory that makes overload visible, classifies what may be compressed or refused, and proves that recovery preserves every durable responsibility.

Quick Reference

Attribute Value
Difficulty Level 3 — Advanced
Time Estimate 16–24 hours
Language Elixir (alternatives: Erlang, Gleam)
Primary Tools Phoenix.PubSub, Telemetry, Observer or recon, Benchee optional
Prerequisites BEAM mailboxes, process scheduling, percentiles, queueing rates, workload modeling
Key Topics Arrival/service rates, event age, fanout amplification, coalescing, sampling, admission, recovery
Observable Deliverable Repeatable baseline/overload/control/recovery report with per-message-class safety accounting

1. Learning Objectives

By completing this project, you will be able to:

  1. Explain why Phoenix.PubSub provides asynchronous fanout but no subscriber backpressure.
  2. Measure arrival rate, completion rate, queue length, event age, handler duration, reductions, and process memory without turning measurement into the bottleneck.
  3. Predict sustained queue growth when arrival rate exceeds service rate.
  4. Distinguish state-like messages that can be coalesced from commands/facts that require rejection or durable admission.
  5. Demonstrate fanout amplification across publisher work and subscriber mailboxes.
  6. Design degraded-mode activation and recovery thresholds with hysteresis.
  7. Prove that healthy subscribers remain isolated from a deliberately slow subscriber.
  8. Produce an overload report that states what was delivered, replaced, sampled, rejected, delayed, or durably queued.

2. Theoretical Foundation

2.1 PubSub Is Push, Not Demand

Phoenix.PubSub dispatches a publication to every matching local subscriber by sending ordinary BEAM messages; its distributed adapter forwards the notice to remote nodes and performs local dispatch there. Subscribers do not advertise capacity, and broadcast does not wait for handling acknowledgements. If a publisher emits 5,000 messages per second to a subscriber that completes 900 per second, the difference accumulates as mailbox work.

This behavior is valuable for low-latency ephemeral notifications, but it moves overload policy into the application. The absence of automatic backpressure is not a defect to hide. It is a semantic boundary to measure and design around.

2.2 Arrival Rate, Service Rate, and Queue Growth

Let λ be the arrival rate and μ be one subscriber’s sustainable completion rate.

if λ < μ: queue tends to drain after bursts
if λ = μ: small variance can create long tails
if λ > μ: expected growth ≈ (λ - μ) × duration

At 5,000 arrivals per second and 920 completions per second, a 30-second sustained interval can add roughly 122,400 messages before accounting for scheduling and measurement overhead. This simple estimate should be calculated before running the lab and compared with observed maximum queue length.

Little’s Law relates average items in a stable system (L), arrival rate (λ), and average time in system (W): L = λW. It is useful only when the measured interval is reasonably stable. An intentionally overloaded, ever-growing queue is not in equilibrium, so do not present one snapshot as a steady-state Little’s Law proof.

2.3 Event Age Is the Freshness Signal

Queue length counts terms, not their cost or relevance. A queue of twenty expensive events may be worse than a queue of two thousand cheap ones. Embed a monotonic creation timestamp or safe elapsed-time marker in the test envelope, then measure age when handling begins and ends.

event age at start = handler_start_monotonic - published_monotonic
processing time    = handler_stop_monotonic - handler_start_monotonic
end-to-end age     = handler_stop_monotonic - published_monotonic

Use monotonic time for elapsed durations. Wall-clock timestamps are useful for cross-system correlation but can jump because of clock adjustment.

2.4 Mailbox and Scheduler Costs

Every BEAM process has a message queue. Selective receive scans until it finds a match, so long queues plus narrow patterns can add scanning cost. Local message terms are generally copied between process heaps, except for shared ref-counted binaries and literals. Fanout therefore consumes publisher reductions, allocation/copying work, subscriber mailbox memory, and later garbage collection.

Process.info(pid, :message_queue_len) exposes queue length; process info can also expose memory and reductions. Sample these periodically. Inspecting every message or attaching a blocking Telemetry handler changes the system under test.

2.5 Fanout Amplification

                     one publication
                           │
             ┌─────────────┼─────────────┐
             v             v             v
         healthy A      healthy B      slow C
         1 message      1 message      1 message

50,000 publications × 100 subscribers = 5,000,000 mailbox sends

One slow subscriber does not normally block healthy subscribers from receiving sends, but the aggregate fanout work still affects the broadcasting process and schedulers. A giant payload multiplies copying/serialization costs. Subscriber isolation must therefore be measured, not assumed.

2.6 Message-Class Policy

Overload policy must follow meaning:

Message Class Example Safe Policy Unsafe Policy
Superseding state Latest device temperature Coalesce by device, keep newest Process every stale sample during emergency
Ephemeral hint Typing indicator Sample or drop Persist forever
Alert notification “Fraud score changed” Reload authoritative alert; coalesce IDs cautiously Drop with no repair path
Command “Charge card” Reject before acceptance or durably queue Accept into unbounded mailbox then crash
Audit fact “Payment settled” Durable log/broker with idempotency Transient PubSub only

Coalescing means an older state is semantically replaced by a newer state for the same key. It must not erase an independent command or fact.

2.7 Control and Recovery

Activation at one threshold and deactivation at the same threshold causes flapping. Use hysteresis and a minimum hold time:

enter controlled mode when:
  event_age_p99 > 1,000 ms for 3 windows OR queue > 10,000

leave controlled mode when:
  event_age_p99 < 300 ms AND queue < 1,000 for 5 windows

Recovery is part of correctness. The lab is not complete when overload stops; it is complete when age, queue, memory, and completion rate return to target and durable responsibilities are reconciled.

2.8 Observability Must Be Cheaper Than the Work

Telemetry handlers execute synchronously in the process emitting the event. Avoid per-event logging, high-cardinality metric labels, expensive serialization, or network I/O in handlers. Aggregate in a dedicated process from sampled/batched measurements, and quantify observer overhead by running the same profile with reporting enabled and disabled.

2.9 Common Misconceptions

  • “The BEAM mailbox protects me from overload.” It buffers asynchronously; it does not decide which work is safe.
  • “Queue length alone proves health.” Freshness and handler cost may fail first.
  • “Dropping messages is always wrong.” Dropping a typing hint may be correct; dropping a payment fact is not.
  • “One slow subscriber cannot affect others.” Mailboxes are isolated, but fanout, allocation, and scheduler resources are shared.
  • “Memory falling means all responsibility recovered.” Durable commands may still require reconciliation.

3. Project Specification

3.1 What You Will Build

Build a command-line load harness with:

  • Configurable publisher rate, duration, payload size, subscriber count, and fanout topic.
  • At least two healthy subscribers and one deterministic slow subscriber.
  • Five phases: warm-up, baseline, burst, sustained overload, controlled degradation, and recovery.
  • Three message classes: dashboard state, ephemeral hints, and durable-required commands.
  • Pluggable overload policies: pass-through, sample, coalesce by key, reject, and durable admission stub.
  • A low-overhead measurement aggregator and final machine-readable plus human-readable report.
  • Safety assertions that fail the run when durable-required messages disappear.

3.2 Functional Requirements

  1. Deterministic load plan: Fixed seed, phase durations, and subscriber service delays.
  2. Unique envelope: Event ID, class, key, sequence, publish time, payload size, and scenario ID.
  3. Per-subscriber accounting: Arrived, started, completed, failed, stale, replaced, sampled, and rejected counts.
  4. Age distributions: p50, p95, p99, and maximum start/end age.
  5. Resource samples: Queue length, process memory, reductions, and VM memory at bounded intervals.
  6. Healthy isolation: Healthy subscriber p99 age remains within its SLO while the slow subscriber overloads.
  7. Policy activation: Controlled mode activates from declared thresholds and uses hysteresis to recover.
  8. Class-specific policy: State may coalesce; hints may sample; durable-required commands must reject before acceptance or enter durable admission.
  9. Recovery proof: Queue, age, and memory return to target after arrivals fall.
  10. Observer-overhead comparison: Report measurement cost against a minimally instrumented control run.

3.3 Non-Functional Requirements

  • Reproducibility: Three runs with the same seed keep throughput and p99 age within a documented tolerance.
  • Measurement overhead: Under 5% throughput difference between bounded instrumentation and control.
  • Healthy SLO: Healthy subscriber p99 end-to-end age below 100 ms during the slow-subscriber overload scenario.
  • Controlled SLO: Slow subscriber p99 age below 500 ms after its safe degradation policy stabilizes.
  • Recovery: Return to queue under 100 and p99 age under 100 ms within 15 seconds after overload stops.
  • Safety: Zero silently dropped durable-required events.

3.4 Workload Profiles

Phase Duration Arrival Rate Slow Service Expected Observation
Warm-up 10 s 400/s 900/s capacity JIT/allocations settle
Baseline 20 s 800/s 900/s capacity Stable low queue
Burst 5 s 3,000/s 900/s capacity Queue rises then drains
Sustained overload 30 s 5,000/s 900/s capacity Queue and age grow continuously
Controlled 30 s 5,000/s coalescing/admission active Bounded edge and age
Recovery up to 30 s 200/s 900/s capacity Return to SLO and baseline memory

3.5 Real World Outcome

Run the sustained profile from a quiet machine. The baseline phase should show approximate equilibrium. During overload, the slow subscriber’s queue and event age rise while healthy subscriber age stays bounded. Controlled mode then coalesces dashboard states by device and admits commands through the durable-required path. Finally, the harness lowers arrival rate and measures return to SLO.

The exact reference output format is:

$ mix pubsub.pressure --profile sustained-60s
phase=baseline arrival=800/s completion=805/s queue_p99=8 age_p99_ms=18
phase=overload arrival=5000/s slow_completion=920/s queue=121440 age_p99_ms=22184
alert=SUBSCRIBER_FALLING_BEHIND
policy dashboard=coalesce_by_device command=durable_admission
phase=controlled queue_max=1950 age_p99_ms=438 coalesced=184220 rejected=0
healthy_subscriber_age_p99_ms=31
recovery_to_slo_ms=8240 memory_returns_to_baseline=true

The generated report must also contain a conservation table:

class=dashboard published=250000 completed=65780 replaced=184220 unaccounted=0
class=hint      published=50000  completed=10000 sampled=40000  unaccounted=0
class=command   published=5000   completed=5000  rejected=0      unaccounted=0

4. Solution Architecture

4.1 High-Level Design

┌──────────────┐ publications ┌──────────────────┐
│ Load planner │─────────────>│ Phoenix.PubSub   │
│ fixed phases │              │ transient fanout │
└──────┬───────┘              └───────┬──────────┘
       │                              │
       │                 ┌────────────┼────────────┐
       │                 v            v            v
       │             healthy A    healthy B     slow C
       │                                          │
       │                                 ┌────────┴────────┐
       │                                 │ policy boundary │
       │                                 │ sample/coalesce │
       │                                 │ reject/admit    │
       │                                 └────────┬────────┘
       │                                          │ sampled metrics
       v                                          v
┌──────────────────────────────────────────────────────────┐
│ Measurement aggregator: rates, age, queues, memory, loss │
└──────────────────────────┬───────────────────────────────┘
                           v
                    final safety report

4.2 Key Components

Component Responsibility Key Decision
Scenario planner Emit deterministic phase transitions Monotonic schedule and fixed seed
Envelope factory Classify and timestamp events Small fixed metadata
Healthy subscriber Establish unaffected SLO Minimal deterministic service
Slow subscriber Create known service bottleneck Controlled delay, no random I/O
Policy edge Sample/coalesce/reject/admit by class Never universal drop
Metrics sampler Periodically inspect processes Sampling, not per-event introspection
Conservation checker Reconcile all event outcomes unaccounted must be zero
Report renderer Produce exact text and structured artifact Separate measurement from formatting

4.3 Data Structures

LoadEnvelope
  scenario_id, event_id, class, key, sequence
  published_monotonic, payload_bytes

SubscriberCounters
  received, started, completed, failed
  coalesced, sampled, rejected, durable_admitted

WindowSample
  time, arrival_rate, completion_rate
  queue_len, memory_bytes, reductions_delta
  age_histogram, handler_histogram

4.4 Core Algorithms

Latest-value coalescing: Store one pending value per key. A newer sequence replaces the older pending value and increments the replacement counter. A worker drains keys only when capacity exists. The algorithm uses space proportional to active keys rather than publications.

Sampling: Keep a declared fraction or interval of ephemeral hints and count every omitted hint. Sampling must not be applied after a command was already accepted as durable work.

Hysteresis control: Evaluate sampled windows, enter degraded mode after repeated threshold breach, and exit only after a lower threshold holds for several windows.

5. Implementation Guide

5.1 Development Environment Setup

Use the same Elixir/OTP and Phoenix.PubSub versions as the parent guide. Establish an idle-machine control and record scheduler count, CPU model, and available memory.

$ elixir --version
Erlang/OTP 29 [...]
Elixir 1.20.x [...]

$ mix test --only pressure_smoke
1 test, 0 failures

$ mix pubsub.pressure --profile smoke
phase=baseline published=1000 completed=1000 unaccounted=0 PASS

The smoke profile must finish in seconds and run in CI; long performance profiles should be explicitly invoked.

5.2 Suggested Project Structure

mailbox_pressure_lab/
├── lib/mailbox_pressure_lab/
│   ├── scenario.ex             # deterministic phase plan
│   ├── envelope.ex             # event shape and class
│   ├── publisher.ex            # scheduled publication
│   ├── subscriber.ex           # configurable service behavior
│   ├── policy_edge.ex          # class-specific controls
│   ├── sampler.ex              # low-overhead process samples
│   └── report.ex               # conservation and SLO output
├── test/
│   ├── policy_edge_test.exs
│   ├── conservation_test.exs
│   └── pressure_smoke_test.exs
└── scenarios/
    ├── smoke.term
    └── sustained-60s.term

5.3 The Core Question You’re Answering

“When publications exceed capacity, which information may be compressed or refused, and how will the system prove it stayed safe?”

Answer this before coding for every message class. “The mailbox will hold it” is not an overload policy.

5.4 Concepts You Must Understand First

  1. BEAM signal and mailbox semantics
    • Which ordering guarantee exists?
    • Why does sending provide no processing acknowledgement?
    • Reference: Erlang Processes reference manual.
  2. Queueing rates
    • What happens when λ > μ?
    • When is Little’s Law valid?
    • Book: “Release It!, 2nd Edition,” stability antipatterns.
  3. Message semantics
    • Which state supersedes older state?
    • Which fact must remain replayable?
    • Book: “Enterprise Integration Patterns,” Message Filter and Aggregator.
  4. BEAM resource accounting
    • What do queue length, reductions, memory, and event age each reveal?
    • Reference: OTP efficiency guide.
  5. Telemetry execution
    • Why can a slow handler distort the benchmark?
    • Reference: Telemetry documentation.

5.5 Questions to Guide Your Design

  1. What exact load shape represents equilibrium, burst, and sustained overload?
  2. How will the slow subscriber’s service rate remain deterministic?
  3. Which fields must exist to reconcile every outcome?
  4. How often can process metrics be sampled before overhead matters?
  5. What threshold and hold time activate/deactivate controlled mode?
  6. What evidence proves memory returns and durable-required events remain safe?

5.6 Thinking Exercise

Classify these before implementation:

Scenario Choose One Explain Consequence of 60 s Offline
Typing indicator Drop/sample/coalesce/reject/durable What does the user lose?
Device gauge Drop/sample/coalesce/reject/durable Is the latest value sufficient?
Fraud alert changed Drop/sample/coalesce/reject/durable Is there a repair query?
Charge-card command Drop/sample/coalesce/reject/durable When was responsibility accepted?
Settlement fact Drop/sample/coalesce/reject/durable Where can it be replayed?

Any answer that chooses one universal policy fails the exercise.

5.7 The Interview Questions They’ll Ask

  1. “Does Phoenix.PubSub provide backpressure?”
  2. “Why is event age more informative than queue length?”
  3. “When is coalescing semantically safe?”
  4. “How can one slow subscriber affect publisher or VM health?”
  5. “How do you measure without perturbing the workload?”
  6. “What proves an overloaded system recovered safely?”

5.8 Hints in Layers

Hint 1 — Calibrate before overload: Determine each subscriber’s service rate in isolation.

Hint 2 — Predict the queue: Calculate (arrival - completion) × duration and compare with observation.

Hint 3 — Timestamp at creation: Measure age when handling starts; do not infer age from queue length.

Hint 4 — Count every policy outcome: Replacement and sampling are intentional loss and must remain visible.

Hint 5 — Reconcile durable work: A safety report needs conservation equations, not reassuring graphs.

5.9 Books That Will Help

Topic Book Focus
Stability and overload “Release It!, 2nd Edition” Stability antipatterns and patterns
Stream load “Designing Data-Intensive Applications, 2nd Edition” Stream processing and derived data
Message policy “Enterprise Integration Patterns” Filters, aggregators, routing, competing consumers
Measurement “Systems Performance, 2nd Edition” by Brendan Gregg Workload characterization and latency distributions

5.10 Implementation Phases

Phase 1 — Measurement calibration (4–6 hours)

  • Build the fixed envelope and counters.
  • Measure healthy and slow subscriber capacity separately.
  • Validate instrumentation overhead.

Checkpoint: Control and instrumented smoke runs differ by less than the overhead budget.

Phase 2 — Baseline, burst, and overload (4–6 hours)

  • Execute the declared profile.
  • Sample queue, age, reductions, and memory.
  • Compare predicted and observed queue growth.

Checkpoint: Sustained overload produces a monotonic age/queue trend and a falling-behind alert.

Phase 3 — Class-specific policy edge (5–7 hours)

  • Add state coalescing, hint sampling, and command durable admission/rejection.
  • Count each outcome.
  • Add hysteresis.

Checkpoint: Controlled mode bounds state/hint work and reports zero unaccounted commands.

Phase 4 — Isolation and recovery (4–6 hours)

  • Run healthy and slow subscribers together.
  • Stop overload and measure drain/recovery.
  • Verify process and VM memory trend.

Checkpoint: Healthy p99 remains within SLO and slow path returns to recovery targets.

Phase 5 — Report and reproducibility (5–9 hours)

  • Produce exact CLI and structured results.
  • Run three fixed-seed trials.
  • Document machine/runtime and tolerances.

Checkpoint: Results are repeatable and conservation checks all equal zero unaccounted events.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Time source Wall clock / monotonic Monotonic for age Immune to wall-clock adjustment
Observation Per event / sampled windows Sampled windows Limits perturbation
State overload Unbounded / coalesce by key Coalesce by key Space follows active keys
Command overload Mailbox / reject or durable admit Reject before acceptance or durable admit Makes responsibility explicit
Threshold One cutoff / hysteresis Hysteresis Avoids flapping
Benchmark assertion Exact throughput / tolerances and invariants Tolerances plus safety invariants Performance varies; correctness must not

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Validate policies and counters Newer gauge replaces older gauge
Property Conserve event outcomes Published equals all terminal outcomes
Smoke load Fast CI validation 1,000 events, zero unaccounted
Performance Measure phase behavior Sustained 5,000/s profile
Recovery Verify return to SLO Queue and age fall after load
Perturbation Bound observability cost Reporter on versus off

6.2 Critical Test Cases

  1. Two state events for one key leave only the newest pending value.
  2. State events for different keys remain independently pending.
  3. Sampling counts every omitted hint.
  4. Durable-required work is never routed to a lossy policy.
  5. Rejected commands are rejected before reporting acceptance.
  6. Arrival above service produces predicted queue direction.
  7. A short burst drains when later arrival falls below service.
  8. Sustained overload triggers controlled mode after the declared windows.
  9. Hysteresis prevents rapid activation/deactivation.
  10. Healthy subscriber p99 stays within isolation SLO.
  11. Recovery returns queue, age, and memory to target.
  12. Every run ends with unaccounted=0 for all classes.

6.3 Deterministic Test Data

Seed: 7007
Subscribers:
  healthy_a service=immediate
  healthy_b service=1ms
  slow_c service=1.087ms average, deterministic schedule

Event mix:
  dashboard_state 80% keys=device-0001..device-2000
  ephemeral_hint  15% keys=session-0001..session-0500
  command_required 5% keys=command UUID

Policy:
  dashboard_state -> latest per device
  ephemeral_hint  -> retain 1 in 5
  command_required -> durable admission

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Correction
Measure only queue length Users stale before alert Measure event age and processing duration
Per-event logging Benchmark slows when observed Sample and aggregate asynchronously
Random slow I/O Results cannot be reproduced Deterministic service schedule
Universal drop policy Commands or facts disappear Classify semantics first
No recovery phase “Controlled” queue never drains Lower load and measure return to SLO
Ignore payload size Unexpected copying/memory Report bytes and test multiple sizes

7.2 Debugging Strategies

  • Compare predicted queue growth with observed growth; a large difference suggests timing, fanout, or instrumentation errors.
  • Capture scheduler utilization and reductions when healthy subscribers also degrade.
  • Inspect one subscriber’s mailbox shape only in a small diagnostic run; full message inspection is invasive.
  • Disable reporters while retaining counters to isolate observability overhead.
  • Verify event conservation at every phase transition, not only at process shutdown.

7.3 Performance Traps

Large maps copied to thousands of subscribers distort a test intended to study rate. Conversely, tiny events hide serialization/copying costs. Run at least small and medium payload profiles. Avoid comparing throughput across machines without recording scheduler count and power state. Percentiles need enough samples; a p99 computed from a few dozen events is misleading.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a burst-only profile and graph queue drain.
  • Compare 100-byte and 10-kilobyte payloads.
  • Add a report warning when p99 sample count is insufficient.

8.2 Intermediate Extensions

  • Compare one topic with many subscribers against sharded topic groups.
  • Add per-tenant admission budgets and fairness metrics.
  • Compare on-heap and off-heap message queue data after measurement.

8.3 Advanced Extensions

  • Run the fanout across multiple BEAM nodes and account for serialization/network cost.
  • Feed durable-required events through a real broker and verify redelivery/idempotency.
  • Build an adaptive controller and prove it remains stable under oscillating arrival rates.

9. Real-World Connections

9.1 Industry Applications

  • IoT dashboards: Latest sensor state may coalesce; device commands require durable admission.
  • Financial systems: Price display updates may coalesce; orders and settlements may not.
  • Collaboration: Cursor/typing hints are lossy; document edits require durable history.
  • Security systems: UI notifications can reload state; audit evidence must remain durable.
  • Phoenix.PubSub: The push fanout path under test.
  • Telemetry: The instrumentation API whose synchronous handler cost must be bounded.
  • Observer/recon: Process and VM diagnostics for controlled experiments.
  • Benchee: Optional microbenchmark support, not a replacement for the end-to-end load scenario.

9.3 Interview Relevance

This project gives concrete numbers for overload rather than generic “the BEAM scales” claims. It demonstrates queueing intuition, message semantics, safety accounting, observability discipline, and recovery engineering.

10. Resources

10.1 Essential Reading

10.2 Tools & Documentation

  • Telemetry — synchronous handlers and spans.
  • Erlang process_info — queue, memory, reductions, and process diagnostics.
  • Observer or recon documentation for sampled process inspection.
  • Project 5: Supplies a real UI where render coalescing matters.
  • Project 6: Applies callback-pressure lessons to Tracker and Presence.
  • Project 8: Replaces uncontrolled work distribution with demand where the boundary allows it.

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain λ, μ, queue growth, and when Little’s Law applies.
  • I can distinguish queue length, event age, and handler duration.
  • I can classify state, hint, command, and fact messages.
  • I can explain why PubSub has no demand signal.
  • I can explain how Telemetry can perturb the system.

11.2 Implementation

  • Baseline, overload, controlled, and recovery phases are reproducible.
  • Healthy subscriber isolation is measured.
  • Class-specific counters conserve every event outcome.
  • Recovery returns queue, age, and memory to target.
  • Instrumentation overhead stays within budget.

11.3 Growth

  • I predicted queue growth before running the test.
  • I documented what may be compressed and why.
  • I can defend the activation and recovery thresholds.
  • I can present the report in a performance interview.

12. Submission / Completion Criteria

Minimum Viable Completion

  • A deterministic slow subscriber falls behind under sustained load.
  • Queue length and event age are both measured.
  • One state class coalesces safely and every replacement is counted.
  • The final report has zero unaccounted durable-required events.

Full Completion

  • All five workload phases and critical tests pass.
  • Healthy subscriber SLO and recovery SLO are verified.
  • Sampling, coalescing, rejection, and durable admission have explicit counters.
  • Three repeated runs remain within documented tolerances.

Excellence

  • Compare local and distributed fanout with payload-size sensitivity.
  • Produce a formal conservation report and queue-growth prediction error.
  • Demonstrate a real durable broker boundary for non-lossy responsibilities.

Return to the Pub/Sub in Elixir deep-dive guide or browse the expanded project index.