SQLStreams

the messaging platform that is just Postgres

You last visited on 9999-99-99 Show what's new since then
Posted: 2026-09-12 · Report this thread
brandon Site Admin brandon profile Posts: 677

The mark of a healthy message architecture: producers don’t know consumers exist. A producer states what happened, tagged with a routing key; each consumer group declares which keys it cares about. In SQLStreams that declaration is a binding — a stored pattern matched against the message’s routing key when the group’s deliveries materialize.

Produce with a routing key

produced, err := instance.Produce(ctx, &OrderCreated{OrderId: order.Id},
	&sqlstreams.ProduceOptions{RoutingKey: "orders.eu.created"})

The key is optional. A message produced without one is received only by groups that declared no bindings — it carries nothing for a pattern to match.

Declare the group’s bindings

A group’s bindings are declared as a whole set, in ConsumerConfig.Bindings at Register — there is no separate bind verb to call one pattern at a time:

orders := client.Stream[OrderCreated](registered.Name)

// Receipts care about created orders.
receipts, err := orders.Consumer("email-receipts").Register(ctx,
	&sqlstreams.ConsumerConfig{Bindings: []string{"orders.*.created"}})

// EU compliance wants everything EU.
compliance, err := orders.Consumer("eu-compliance").Register(ctx,
	&sqlstreams.ConsumerConfig{Bindings: []string{"orders.eu.*"}})

// The analytics group passes nil: no bindings, the whole stream.
analytics, err := orders.Consumer("order-analytics").Register(ctx, nil)

Produce orders.eu.createdemail-receipts, eu-compliance, and order-analytics all receive it; a us-warehouse group bound to orders.us.* hears nothing. The producer changes zero lines when a new group comes online.

The pattern language: one wildcard, honestly scoped

* matches any run of characters — including dots. The pattern is anchored at both ends; everything else is literal:

Patternorders.eu.createdorders.us.central1.createdorders.eu.updated
orders.*.createdmatchesmatches — * crosses depthno
orders.eu.*matchesnomatches
orders.*matchesmatchesmatches

Note the second column: * is a true wildcard, so a pattern cannot pin an exact segment depth (“exactly one segment here”). If you need to keep hierarchies apart, make the literal parts distinctive. A NATS-style one-segment wildcard is a possible future refinement, not a shipped behavior.

How a set change lands

Bindings are per-group state that many deployed instances declare, so declaration has explicit outcomes rather than last-writer-wins:

  • installed — the declared set is now the group’s effective set (the first declarer, or nothing live disagrees).
  • joined — the declared set was already stored; nothing to change.
  • waiting — a live instance still declares a different set. The waiting instance retries (every BindingRetryInterval, default 10s) until the disagreeing instances go away — a deploy rolling out a new binding set converges as the old instances stop.

Inspect where every group stands:

sqlstreams system binding list

One group’s effective set is a read on its handle. nil means the group never declared a set and receives the whole stream:

binding, err := client.Stream[OrderPlaced]("orders").Consumer("email-receipts").Binding().Get(ctx)
if err != nil {
	return err
}
if binding != nil {
	fmt.Println(binding.Status, binding.Patterns)
}

The same read from the shell is sqlstreams consumer binding get orders email-receipts.

It’s all rows

Each stream owns a physical binding_config table, named by the stream’s id (table design). The pattern column keeps the pattern as you wrote it; the POSIX translation it matches with sits beside it as pattern_regex:

SELECT g.name AS consumer_group, b.pattern
FROM sqlstreams.binding_config_1 b JOIN sqlstreams.consumer_group_config g ON g.id = b.consumer_group_id
ORDER BY 1;

Every declaration attempt — installed and waiting alike — is also appended to the stream’s binding_config_log table with who declared it and when, so “who changed the routing and when” is a query, not a mystery.

What routing is not (yet)

Matching is on the routing key only. Header or content matching (“region=eu AND tier=gold” over a JSONB attribute set) is a designed follow-up that waits for a real workload — today, encode what you route on into the key.