Single-node · In-memory · gRPC

Heads up: I ended up switching to NATS + PostgreSQL. If you need consumer groups, bare NATS won't help — you need JetStream, which requires persistence. tinybroker fills the gap when you want in-memory consumer groups with no persistence overhead. The code works and is free to use; just know why you're reaching for it.

Singleton (leader election)

DELIVERY_MODE_SINGLETON ensures that within a consumer_group only one subscriber is active. All others wait in standby. When the active member disconnects, the broker promotes the next waiting member — no external coordination, no lease timeouts, no ZooKeeper.

Singleton requires DELIVERY_MODE_SINGLETON and a non-empty consumer_group.

When to use

  • Leader election for a scheduled job that must run on exactly one replica.
  • Hot-standby for a stateful component (cache warmer, rate-limiter coordinator).
  • Consume from a firehose topic where ordering matters and parallelism would break it.
  • Ensure a singleton side-effect (external webhook call, file write) happens once per event.

Example

Three replicas start up. Replica A wins the active slot and receives messages. If Replica A crashes, Replica B is promoted immediately and resumes from the next message.

tinybroker        Replica A (active)   Replica B (standby)
    │                   │                    │
    │── Message ────────►                    │
    │── Message ────────►                    │
    │                   │ (disconnects)      │
    │                   ✗                    │
    │────────────────────────── Promote ────►│
    │── Message ─────────────────────────────►

Protocol

All replicas open with the same subscription_id and consumer_group:

OpenSubscription {
  subscription_id: "scheduler-leader"
  topic_patterns:  ["schedule.*"]
  mode:            DELIVERY_MODE_SINGLETON
  consumer_group:  "scheduler"
}

The broker sends subscription_id as the first frame to confirm which replica is active. Standby replicas receive no messages until they are promoted.

Call Ack after processing each message to receive the next:

Ack {
  subscription_id: "scheduler-leader"
  system_id:       "<system_id>"
}

Failover

Promotion is instant — the broker promotes the next waiting member the moment the active stream closes. There is no configurable timeout and no external dependency. The trade-off is that if the active replica crashes mid-message (before Ack), that message is redelivered to the new active member. Design handlers to be idempotent.