Managing Channels¶
Registering a channel¶
SELECT pgrelay.register(
'channel_name', -- the name you will use in notify()
'SELECT my_fn($1)', -- the SQL to run; $1 is the payload
'What this does', -- optional description
true, -- active immediately (default)
3, -- retry up to 3 times on transient failure (default 0)
'sql', -- action type (default 'sql')
'concurrent' -- concurrency mode (default 'concurrent')
);
Updating a channel¶
You can change the action SQL, the description, the retry count, the action type, or the concurrency mode without re-registering. You do not need to provide all of them — just the ones you want to change:
You cannot change the channel's name. If that is genuinely needed, disable the old one and register a new channel with the correct name instead.
Disabling and re-enabling¶
Disabling a channel prevents new events from being queued on it — pgrelay.notify() raises an error if you call it on a disabled channel. Any events already sitting in the queue when the channel was disabled are still processed, but with status invalid (no action is run; they are simply marked done and logged). Re-enabling the channel restores normal processing for new events.
Changes take effect immediately — there is no reload or restart needed.
Listing channels¶
SELECT channel, active, notes FROM pgrelay.list(); -- all channels
SELECT channel, active, notes FROM pgrelay.list(true); -- active only
SELECT * FROM pgrelay.get('channel_name'); -- one channel
Managing action types¶
pg_relay ships with one built-in action type: 'sql'. You can view registered action types without any extra permissions:
Additional action types are defined by your Processor configuration or by extensions built on pg_relay. Registering a new type does not automatically make the Processor able to handle it — a Processor that does not recognise an action type logs unsupported_action_type at error level and marks the event done anyway. Talk to the maintainer of your Processor deployment before registering a non-'sql' action type.
-- Register a new action type (requires an explicit grant, or superuser)
SELECT pgrelay.register_action_type('http', 'Send an HTTP request', 2);
-- Update description, run_order, or active flag
SELECT pgrelay.update_action_type('http', p_run_order := 3);
-- Delete (only if no channels reference it)
SELECT pgrelay.delete_action_type('http');
Removing a channel¶
This removes the channel's registration. Historical records in pgrelay.log are kept, and any queue entries still waiting will appear as invalid in the Processor's log.
Viewing the audit log¶
-- Recent events:
SELECT channel, payload, status, elapsed_ms, actioned_at
FROM pgrelay.log ORDER BY actioned_at DESC LIMIT 20;
-- Recent errors:
SELECT channel, payload, error, actioned_at
FROM pgrelay.log WHERE status = 'error' ORDER BY actioned_at DESC;
Queue depth and throughput¶
pgrelay.queue_stats() gives you an at-a-glance view of queue health and recent throughput per channel. It requires an explicit grant — superusers can call it directly, or you can grant it to a monitoring role:
-- Overall queue health (last 1 minute)
SELECT * FROM pgrelay.queue_stats();
-- Last 10 minutes of activity, sorted by backlog
SELECT * FROM pgrelay.queue_stats(p_minutes := 10, p_order_by := 'count');
-- Alert: any channel with events waiting more than 30 seconds
SELECT action_type, channel, oldest_pending
FROM pgrelay.queue_stats()
WHERE oldest_pending > interval '30 seconds';
| Column | Meaning |
|---|---|
pending |
Eligible to run right now — a non-zero value while the Processor is running means there is a backlog. |
future |
Events you have deliberately deferred to a later time. This is normal, not a sign of a problem. |
pending_restricted |
Events waiting to run on one specific node (see Node-restricted channels below — this only applies if you run more than one database). A steadily growing number here means that node is down or has been retired. |
oldest_pending |
The age of the oldest event still waiting to run. This is the number to keep an eye on for alerting. |
processed |
Events completed successfully (ok) within the time window. |
expired |
Events discarded because their expiry time passed before they could run. |
errors |
Events that failed (error, invalid, or unsupported_action_type) within the time window. |
Automatic retries¶
When you register a channel with max_retries greater than 0, a failed action is automatically retried — once after 3 seconds, again after 5 seconds, then after 10 seconds for any further attempts. Only failures caused by a genuine database conflict (such as a deadlock — two operations stuck waiting on each other) are retried. Permanent errors, such as a typo in your action SQL, are not retried, since retrying would just fail again in exactly the same way.
Controlling concurrency¶
By default, pg_relay dispatches every eligible event as soon as a worker is free — two events on the same channel can run at the very same instant. That is the right behaviour for most channels, but some actions must never run twice at once: refreshing the same materialised view, exporting the same report, or charging the same account. Rather than building defensive locking into your own action SQL, you can declare this on the channel itself, using p_concurrency_mode:
| Mode | Meaning |
|---|---|
concurrent (default) |
No restriction — every eligible event dispatches immediately. |
channel |
At most one event for this channel runs at a time, whatever its payload. Events run strictly in the order they were queued. |
channel_payload |
At most one event runs at a time per (channel, payload) pair. Events with different payloads still run at the same time as each other; events sharing a payload run strictly in queued order. |
-- One refresh per view at a time; different views may refresh in parallel:
SELECT pgrelay.register('refresh_view', 'SELECT app.refresh_view($1)',
p_concurrency_mode := 'channel_payload');
-- Change an existing channel:
SELECT pgrelay.update('nightly_export', p_concurrency_mode := 'channel');
A few things worth knowing:
- An event waiting for the one ahead of it is simply held back until that one finishes. This waiting is silent — it is not an error, it does not use up a retry attempt, and it writes nothing to the audit log.
- The guarantee holds no matter how many workers (
--workers) or Processor instances you run, because it is enforced inside the database when candidate events are selected — not inside the Processor program itself. - Ordering across different channels (or different payloads under
channel_payloadmode) is unchanged: those events remain independent and may finish in any order. - Channel matching is case-insensitive, as everywhere else in pg_relay; payload matching is exact (case-sensitive).
See Workers in the Technical Guide for the full mechanics of how this is enforced.
Node-restricted channels (multi-master only)¶
If you run pg_relay against a single database, you can skip this section — it changes nothing for you.
On a multi-master cluster (see the Multi-Master Deployment book), some actions have node-local side effects — refreshing a materialised view, for example, only updates the copy on the node where the refresh actually ran, because a materialised view's contents are not replicated between nodes. For channels like this, register with p_node_restricted := true:
SELECT pgrelay.register('refresh_view', 'SELECT app.refresh_view($1)',
p_concurrency_mode := 'channel_payload',
p_node_restricted := true);
-- Or flag an existing channel:
SELECT pgrelay.update('refresh_view', p_node_restricted := true);
Every event queued on that channel is then pinned to the node that created it: it runs on that node, exactly there, no matter which --mode you are running — it is never handed to another node — and duplicate suppression (p_deduplicate) applies per node rather than across the whole cluster, so each node keeps its own copy of the work. The full behaviour, trade-offs, and clean-up procedure for a retired node are covered in the Multi-Master Deployment book.
Continue to Running the Performance Test to see how fast pg_relay runs on your own hardware.