Expand description
Lock-free metrics for BEAM actor send/fanout observability.
§Why this exists
Before this module, the Router (and other actors) used let _ = addr.send(msg)
everywhere to dispatch messages. When an actor’s mailbox was full or closed,
Addr::send returned Err(()) and the codebase silently dropped it.
Silent drops are dangerous because:
- Invisible failures: a “successful”
Node::putmight never reach the storage adapter if its mailbox is full. The caller has no signal. - No debugging trail: when data doesn’t replicate, there’s no counter showing “1000 Puts were silently dropped” — the operator sees a working system that silently lost data.
- No backpressure visibility: a slow consumer looks identical to a fast one until messages start disappearing into the void.
This module fixes the observability gap without changing behavior.
Metrics is a tiny, lock-free counter struct that any actor can hold
(via ActorContext or directly) to record events of interest.
§Hot-path instrumentation
In addition to the original drop/ack counters, this module now tracks the relay hot path — the sequence every message traverses from WebSocket receive to WebSocket send. These counters let us identify the load-bearing component when throughput is bottlenecked:
messages_parsed— JSON parse entry (Message::try_from)messages_dropped_dup— dedup gate hit (Dup::checkreturned true)messages_relayed— successful relay fan-out (handle_put_relay)subscriber_fanout_total— total subscriber deliveries across all relaysserialization_calls— wire-format serialization (Message::to_string)ws_messages_received— inbound WebSocket framesws_messages_sent— outbound WebSocket frames
§Design principles
- Lock-free: all counters are
AtomicU64withRelaxedordering. They are advisory observation, not synchronization primitives. Snapshot reads may be slightly stale but are guaranteed monotonic. - Cumulative: counters only increase for the lifetime of the
Metricsinstance. There is no “reset” — if you need per-window counters, create a newMetrics. - Composition-Root IoC:
Metricsis passed into actors that need it, not accessed from a global registry. This makes the dependency graph explicit and testable. - No behavior change: recording a metric is a side effect that does not affect control flow. Existing fire-and-forget semantics are preserved.
- Negligible cost:
Relaxedatomic increments are a singleLOCKinstruction on x86 (~1-2 ns). At 100k TPS the total overhead is ~0.2 ms/s — well within the noise floor of any benchmark.
§Usage
use beam::metrics::Metrics;
use std::sync::Arc;
let metrics = Arc::new(Metrics::new());
// Record a drop
metrics.record_dropped_send();
// Snapshot for telemetry
let snap = metrics.snapshot();
println!("dropped_sends = {}", snap.dropped_sends);
println!("messages_parsed = {}", snap.messages_parsed);Structs§
- Metrics
- Lock-free counters for BEAM actor sends, drops, and hot-path throughput.
- Metrics
Snapshot - Plain-old-data snapshot of
Metricsfor safe export across threads.