Skip to content

Failure Classification and Status Lifecycle

Failure classification

Decided by the pg_relay Processor per attempt — pg_relay_notifier only records the verdict via set_status().

Class Examples Resolution
Transient Connection refused, timeouts, SMTP 4xx, Graph 429/5xx, token endpoint 5xx retry status plus a backoff retry row (3s / 5s / 10s), until the channel's max_retries is exhausted — then treated as permanent
Permanent SMTP 5xx, Graph 400/403/404, a 401 persisting after a token refresh, a rejected client secret, an unknown transport, a malformed profile/message, an _env:-referenced variable unset on the Processor host, an unknown notification id failed immediately — no retries burned on something that cannot possibly succeed

Timeout: profile.timeout_seconds (default 30, hard cap 120) bounds every external call. A hung server costs one Processor worker for at most the cap, and classifies as transient.

Status lifecycle

                    ┌────────────► sent        (terminal for the attempt chain)
 (attempt runs) ────┼────────────► retry       (a new queue row will follow)
                    ├────────────► failed      (terminal: permanent, or retries exhausted)
 (before any send) ─┼────────────► expired     (queue expire_at passed; terminal)
                    └────────────► invalid     (channel disabled/unregistered; terminal)

A notification with max_retries = 2 that keeps failing transiently sees: retry (attempt 1) → retry (attempt 2) → failed (attempt 3). One that succeeds on the second try: retrysent.

Unservable states and recovery

pg_relay is deliberately conservative when the notifier interface is unavailable:

  • Registry row present, but schema/functions missing or ungranted — preflight reports notifier:schema / notifier:functions / notifier:grants warnings, never a startup-blocking error. A broken notifier must not stop unrelated SQL event processing.
  • A notify event arrives while the last-known snapshot says the notifier is unservable — the Processor first withholds the event untouched and re-reads the registry once. If the notifier appeared since (installed after the Processor started), a full reload happens and the event processes normally — installation is never a lost-event window. If absence is confirmed against fresh data, the event resolves as a permanent error naming the cause.
  • Interface version unsupported — the same confirm-then-error flow, with the message naming both versions.

After installing or upgrading, pgrelay.request_reload() (called automatically by the install/upgrade scripts) makes every Processor instance pick the change up within about a second — no restarts.