Phoenix.PubSub.broadcast(Prismatic.PubSub, "updates", msg) is a one-liner. It is also how a careless change wakes every connected LiveView on the platform. PubSub is trivially cheap per-subscriber, which means it is catastrophically expensive at the wrong fan-out. Three rules keep /hub responsive.
#Rule 1: Topics are scoped, not global
A global topic like "updates" is a sign that someone stopped thinking. Real topics carry the smallest scope that correctly identifies the audience:
# β wakes everyone
Phoenix.PubSub.broadcast(Prismatic.PubSub, "updates", {:dd_case_updated, case})
# β
wakes only viewers of this one case
Phoenix.PubSub.broadcast(Prismatic.PubSub, "dd:case:#{case.id}", {:updated, case})The topic is the contract. A LiveView that subscribes to "dd:case:#{id}" is promising βI care about this case and nothing else.β Broadcasting to that topic from an unrelated code path is a bug β it means someone violated the contract.
#Rule 2: Topic names are a taxonomy
Prismatic uses a three-segment pattern: <domain>:<object>:<id>. Flat namespaces donβt scale mentally past ~20 topics. A taxonomy lets you grep production and answer βwho subscribes to this?β in seconds:
dd:case:<id> # per-case updates
dd:entity:<id> # per-entity changes
osint:run:<run_id> # per-OSINT-run execution stream
system:alert # global alerts (use sparingly)
system:error_feed # error stream (use sparingly)The last two are the exception to rule 1. Global topics are allowed when the audience is every operator β but every such topic needs a written justification.
#Rule 3: Broadcast the smallest payload
A PubSub message is copied into every subscriberβs mailbox. A 100 KB payload Γ 500 subscribers = 50 MB of BEAM heap allocated per broadcast. Donβt do that. Send the minimum β an id, an event type, a version β and let each subscriber decide whether to fetch more:
# β 100 KB Γ N copies
broadcast(topic, {:case_updated, case_with_all_entities})
# β
N copies of 40 bytes
broadcast(topic, {:case_updated, case.id, case.version})Subscribers that care can fetch/1 the full record. Subscribers that donβt (idle LiveViews, closed tabs that havenβt disconnected yet) throw the message away cheaply.
#Fan-out via Registry for low-cardinality cases
For low-cardinality, high-frequency broadcasts (say, 50 subscribers), Registry.dispatch/3 is cheaper than PubSub and gives you synchronous back-pressure. Use it when the set of subscribers is small and known. Use PubSub when the set is large or unknown.
#Observability on every broadcast
Every broadcast path emits telemetry:
:telemetry.execute([:pubsub, :broadcast], %{bytes: payload_size}, %{topic: topic})The dashboard groups by topic. A topic that suddenly starts broadcasting 10Γ more bytes is a regression worth a ticket. Observability for PubSub is the only way to catch these before users notice.
#Where to go next
- Academy: LiveView Dashboards β consuming PubSub in LiveView
- Glossary: PubSub, Phoenix, LiveView, Observability, Performance
Topics are a contract. Payloads are copies. Broadcast accordingly.