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: retry → sent.
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:grantswarnings, 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.