Events Store — Persistent Pub/Sub
Publish and subscribe to persistent events with replay, retention, and durable consumer groups.
Think of Events Store as a DVR for your messages. Just like a DVR records live TV so you can watch it later, Events Store records every event published to a channel. Subscribers can rewind to the beginning, fast-forward to a specific point, or jump in live — all from the same persistent stream.
KubeMQ Events Store implements persistent publish/subscribe with at-least-once delivery. Publishers send messages to a named channel; KubeMQ writes each event to disk with a monotonically increasing sequence number, and subscribers choose a start position to receive historical events, new events, or both. Because events are durable, a subscriber that was offline at publish time can still replay everything it missed.
The concept it implements
Events Store is KubeMQ's implementation of two fundamental ideas. The interaction style is pub/sub (fan-out) — one publisher, many subscribers — extended with durable storage so subscribers can replay from any position. The delivery guarantee is at-least-once — events are persisted and durable subscriptions track their position, so a message is redelivered until the subscriber has processed it. New to these terms? Start with the Fundamentals track.
Key Properties
| Property | This pattern | Learn the concept |
|---|---|---|
| Interaction style | pub/sub (fan-out) with replay | Interaction styles |
| Delivery guarantee | at-least-once | Delivery guarantees |
| Persistence | Disk-backed — survives restarts | Ordering & replay |
| Ordering | Sequenced per channel (sequence number + timestamp) | Ordering & replay |
| Scaling | Fan-out, or load-balance with durable consumer groups | Scaling & flow |
| Addressing | Named channels | Channels & routing |
Key Features
- Persistent storage — events are written to disk and survive server restarts
- Replay from any point — subscribe from the first message, last message, a specific sequence number, an absolute timestamp, or a relative time delta
- Durable subscriptions — subscribers resume from their last position after reconnecting
- Consumer groups — distribute event processing across multiple consumers with automatic position tracking
- Sequenced messages — every stored event receives a monotonically increasing sequence number and a server timestamp
- Stream publishing — high-throughput bidirectional streaming with per-event acknowledgment
How It Works
A publisher persists events to a durable channel; a live subscriber streams new events while a late subscriber replays missed history from a chosen offset.
- A publisher sends an event to a named channel with persistence enabled
- KubeMQ writes the event to the store on disk
- Each event receives a sequence number and timestamp
- Subscribers connect and specify a start position — they receive historical and/or new events based on that position
- Durable subscriptions track the subscriber's position so reconnections resume automatically
For fire-and-forget pub/sub without persistence, use Events instead.
Quick Example
ctx := context.Background()
client, err := kubemq.NewClient(ctx,
kubemq.WithAddress("localhost", 50000),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
result, err := client.SendEventStore(ctx, kubemq.NewEvent().
SetChannel("orders.events").
SetBody([]byte(`{"action":"order.created","orderId":"ORD-1001"}`)),
)
if err != nil {
log.Fatal(err)
}
log.Printf("Event stored: ID=%s, Sent=%v", result.EventID, result.Sent)from kubemq import PubSubClient, EventStoreMessage
with PubSubClient(address="localhost:50000") as client:
result = client.publish_event_store(
EventStoreMessage(
channel="orders.events",
body=b'{"action":"order.created","orderId":"ORD-1001"}',
)
)
print(f"Event stored: ID={result.id}, Sent={result.sent}")import { KubeMQClient, createEventStoreMessage } from 'kubemq-js';
const client = await KubeMQClient.create({ address: 'localhost:50000' });
const result = await client.sendEventStore(
createEventStoreMessage({
channel: 'orders.events',
body: '{"action":"order.created","orderId":"ORD-1001"}',
})
);
console.log(`Event stored: ID=${result.id}, Sent=${result.sent}`);PubSubClient client = PubSubClient.builder()
.address("localhost:50000")
.clientId("order-publisher")
.build();
EventSendResult result = client.sendEventsStoreMessage(
EventStoreMessage.builder()
.channel("orders.events")
.body("{\"action\":\"order.created\",\"orderId\":\"ORD-1001\"}".getBytes())
.build());
System.out.println("Event stored: " + result.getId());
client.close();await using var client = new KubeMQClient(new KubeMQClientOptions());
await client.ConnectAsync();
var result = await client.SendEventStoreAsync(new EventStoreMessage
{
Channel = "orders.events",
Body = Encoding.UTF8.GetBytes(
"{\"action\":\"order.created\",\"orderId\":\"ORD-1001\"}")
});
Console.WriteLine($"Event stored: ID={result.Id}, Sent={result.Sent}");val client = KubeMQClient.pubSub {
address = "localhost:50000"
clientId = "order-publisher"
}
client.use {
val result = client.sendEventStore(eventStoreMessage {
channel = "orders.events"
body = """{"action":"order.created","orderId":"ORD-1001"}""".toByteArray()
})
println("Event stored: ID=${result.id}, Sent=${result.sent}")
}kubemq::ClientOptions options;
options.set_address("localhost", 50000);
options.set_client_id("order-publisher");
auto client = kubemq::Client::Create(options).value();
kubemq::EventStoreMessage msg;
msg.set_channel("orders.events");
msg.set_body(R"({"action":"order.created","orderId":"ORD-1001"})");
auto result = client->SendEventStore(msg);
if (result.ok()) {
std::cout << "Event stored: " << result->id() << std::endl;
}use kubemq::prelude::*;
use kubemq::EventStoreBuilder;
#[tokio::main]
async fn main() -> kubemq::Result<()> {
let client = KubemqClient::builder()
.host("localhost")
.port(50000)
.build()
.await?;
let event = EventStoreBuilder::new()
.channel("orders.events")
.body(br#"{"action":"order.created","orderId":"ORD-1001"}"#.to_vec())
.build();
let result = client.send_event_store(event).await?;
println!("Event stored: id={}, sent={}", result.id, result.sent);
client.close().await?;
Ok(())
}require 'kubemq'
client = KubeMQ::PubSubClient.new(address: 'localhost:50000', client_id: 'order-publisher')
msg = KubeMQ::PubSub::EventStoreMessage.new(
channel: 'orders.events',
body: '{"action":"order.created","orderId":"ORD-1001"}'
)
result = client.send_event_store(msg)
puts "Event stored: id=#{result.id}, sent=#{result.sent}"
client.close{:ok, client} = KubeMQ.Client.start_link(address: "localhost:50000", client_id: "order-publisher")
event =
KubeMQ.EventStore.new(
channel: "orders.events",
body: ~s({"action":"order.created","orderId":"ORD-1001"})
)
{:ok, result} = KubeMQ.Client.send_event_store(client, event)
IO.puts("Event stored: sent=#{result.sent}")
KubeMQ.Client.close(client)Subscription Start Positions
| Position | Description | Use Case |
|---|---|---|
StartNewOnly | Only new events published after subscribing | Live monitoring, real-time alerting |
StartFromFirst | Replay all events from the beginning | State rebuild, full audit replay |
StartFromLast | Start from the last stored event, then new | Resume from most recent |
StartAtSequence | Start from a specific sequence number | Checkpoint-based recovery |
StartAtTime | Start from a specific timestamp | Point-in-time recovery |
StartAtTimeDelta | Start from N seconds ago | Recent history replay |
When to Use Events Store
| Scenario | Events | Events Store |
|---|---|---|
| Audit trails | ❌ Messages can be lost | ✅ Best choice |
| Event sourcing | ❌ No persistence | ✅ Best choice |
| Late or offline subscribers | ❌ Miss everything published while away | ✅ Replay missed history |
| Real-time notifications | ✅ Best choice | Overkill |
| Live dashboards (no history) | ✅ Best choice | Use if historical data needed |
| Wildcard subscriptions | ✅ Supported | ❌ Not supported |
When not to use Events Store: if you never need replay, persistence, or guaranteed delivery — fire-and-forget Events have lower latency and support wildcard subscriptions. If a message must be processed by exactly one of several competing workers (work distribution, not fan-out), reach for Queues instead.
Events vs Events Store at a glance
| Feature | Events | Events Store |
|---|---|---|
| Persistence | No | Yes (disk-backed) |
| Replay | No | Yes (6 start positions) |
| Delivery guarantee | at-most-once | at-least-once |
| Wildcard subscriptions | Yes | No |
| Consumer groups | Yes (ephemeral) | Yes (durable) |
| Sequence numbers | No | Yes |
| Timestamps | No | Yes (server-assigned) |
| Latency | Lowest | Slightly higher |
Events Store is also available via the CloudEvents protocol — use any language with a CloudEvents SDK, no KubeMQ client library needed.
Learn More
Getting Started
Store and replay your first persistent event in minutes.
Persistent Pub/Sub
Publish persistent events and subscribe with replay.
Replay Events
Replay events from a specific offset, time, or sequence.
Consumer Groups
Distribute processing with durable consumer groups.
Stream Publishing
High-throughput publishing with bidirectional streaming.
Event Sourcing
Implement event sourcing patterns with KubeMQ.
Configure Retention
Set time-based, size-based, or count-based retention.
Events Store Reference
Message structure, subscription modes, and configuration.
Was this page helpful?