KubeMQ
Learn

Events Store — Persistent Pub/Sub

Publish and subscribe to persistent events with replay, retention, and durable consumer groups.

PublisherEvents storeorder-historypublishreplaySubscriber ASubscriber BSubscriber Cnew — replays from start

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

PropertyThis patternLearn the concept
Interaction stylepub/sub (fan-out) with replayInteraction styles
Delivery guaranteeat-least-onceDelivery guarantees
PersistenceDisk-backed — survives restartsOrdering & replay
OrderingSequenced per channel (sequence number + timestamp)Ordering & replay
ScalingFan-out, or load-balance with durable consumer groupsScaling & flow
AddressingNamed channelsChannels & 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.

  1. A publisher sends an event to a named channel with persistence enabled
  2. KubeMQ writes the event to the store on disk
  3. Each event receives a sequence number and timestamp
  4. Subscribers connect and specify a start position — they receive historical and/or new events based on that position
  5. Durable subscriptions track the subscriber's position so reconnections resume automatically

For fire-and-forget pub/sub without persistence, use Events instead.

Quick Example

publish_store.go
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)
publish_store.py
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}")
publish_store.ts
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}`);
PublishStore.java
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();
PublishStore.cs
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}");
PublishStore.kt
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}")
}
publish_store.cc
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;
}
publish_store.rs
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(())
}
publish_store.rb
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
publish_store.exs
{: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

PositionDescriptionUse Case
StartNewOnlyOnly new events published after subscribingLive monitoring, real-time alerting
StartFromFirstReplay all events from the beginningState rebuild, full audit replay
StartFromLastStart from the last stored event, then newResume from most recent
StartAtSequenceStart from a specific sequence numberCheckpoint-based recovery
StartAtTimeStart from a specific timestampPoint-in-time recovery
StartAtTimeDeltaStart from N seconds agoRecent history replay

When to Use Events Store

ScenarioEventsEvents 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 choiceOverkill
Live dashboards (no history)✅ Best choiceUse 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

FeatureEventsEvents Store
PersistenceNoYes (disk-backed)
ReplayNoYes (6 start positions)
Delivery guaranteeat-most-onceat-least-once
Wildcard subscriptionsYesNo
Consumer groupsYes (ephemeral)Yes (durable)
Sequence numbersNoYes
TimestampsNoYes (server-assigned)
LatencyLowestSlightly higher

Events Store is also available via the CloudEvents protocol — use any language with a CloudEvents SDK, no KubeMQ client library needed.

Learn More

Was this page helpful?

On this page