Skip to content

Multi-Master and Notification IDs

Operational guidance for running pg_relay_notifier alongside a multi-master pg_relay deployment is in the DBA Guide. This page covers the implementation detail behind it.

Why notification_id isn't a plain identity sequence

pgrelay_notifier.notifications.notification_id is generated by DEFAULT pgrelay._next_id(), not GENERATED ALWAYS AS IDENTITY. pgrelay._next_id() is pg_relay's own snowflake-style generator — the same one pgrelay.queue.id and pgrelay.log.id use — packing three fields into one signed 63-bit bigint:

| 41-bit millisecond timestamp | 10-bit node id | 12-bit per-node sequence |
    (epoch 2026-01-01, ~69 yrs)   (0–1023)          (0–4095, cycling)

The node id comes from the pg_relay.node_id GUC (0 on a single-node, unset install). Because the node component differs across a Spock cluster, ids stay globally unique without coordination — no two nodes can ever generate the same value, and the timestamp-leading layout keeps ids roughly time-ordered, preserving index locality.

Reusing pg_relay's own generator, rather than introducing a second id scheme, means:

  • notification_id is always a real, meaningful, globally-unique value — useful for a follow-up get_status() call — under every --mode, with no special-casing anywhere in pg_relay_notifier's own code.
  • On a single-node deployment (the overwhelming common case), nothing observable changes except that ids are large numbers rather than small sequential ones — functionally equivalent to a growing integer, just less compact.

This choice only matters if pgrelay_notifier's own tables are ever replicated across a multi-master cluster (so a notification composed on one node is visible from another) — pgrelay.queue/pgrelay.log themselves are pg_relay's concern, not pg_relay_notifier's, and this extension never touches them directly (see The Interface Contract).

Why concurrency_mode = 'channel' doesn't hold cluster-wide under multi-node

pg_relay's --mode=multi-node partitions queue dispatch by owner_node: each node's Processor only dispatches rows it itself enqueued (owner_node = pgrelay._this_node()), so that every node can dispatch in parallel without a global coordinator. concurrency_mode's ranking (ROW_NUMBER() over the dispatch candidates) is computed within that same per-node partition — it was never designed to rank across nodes, because doing so would require exactly the cluster-wide coordination multi-node mode exists to avoid.

The consequence: a channel's 'channel' serialization guarantee — "at most one in-flight send" — only holds within one node's own partition under multi-node. Under --mode=leader, there is by definition only one dispatching node at a time, so the same ranking logic naturally becomes cluster-wide with no code difference at all — the guarantee is a property of topology, not of anything pg_relay_notifier or pg_relay_notifier's channels do differently.

Why node_restricted doesn't apply to notify channels

pg_relay 1.1's node_restricted channel flag pins a channel's queue rows to the node that created them, for companion applications with node-local side effects — the motivating case is a materialised-view refresh, where every node holds its own physical, non-replicated copy of the view's heap, so running the refresh on the wrong node is actively incorrect, not just suboptimal.

Email delivery has no analogous node-local state: sending a message from any node produces the identical external result. There is therefore no scenario where flagging a notify channel node_restricted fixes anything — it would only reduce dispatch parallelism for no correctness benefit. create_channel() passes through to pgrelay.register() with named arguments precisely so it stays forward-compatible with node_restricted (and any future channel-registration parameter) without a pg_relay_notifier code change, but there is no reason to set it for a notify channel today.