Skip to content

Retry Policy

What triggers a retry

Only SQLSTATE class 40 errors trigger an automatic retry. SQLSTATE is PostgreSQL's own standard error-code system; class 40 covers transient conflicts between concurrent operations:

  • 40001 — Serialization failure (two transactions conflicted in a way that could not both succeed)
  • 40P01 — Deadlock detected (two operations were each waiting on the other)

These are transient infrastructure problems, where trying again after a short pause is likely to succeed. Every other kind of error — type errors, constraint violations, permission errors, a syntax error in your action SQL — is a permanent failure. These are logged as error immediately, with no retry, because the same error would simply happen again on every attempt.

Retry backoff schedule

When a retry is scheduled, a new row is inserted into pgrelay.queue with a future run_at, based on the new row's retry_count:

retry_count Delay before next attempt
1 3 seconds
2 5 seconds
3 and above 10 seconds

The original queue row is marked done, and a new row is created to represent the retry. The parent_id column links the retry row back to the original.

Log entries for retried events

A single event that is retried twice creates three log rows in total:

  1. retry_scheduled — first failure, a retry is queued (the underlying error appears in the error column, alongside the ID of the new retry row).
  2. retry_scheduled — second failure, another retry is queued.
  3. ok (or error) — the final outcome.

If the retry limit is reached on the last attempt, that final row is max_retries_exceeded instead of retry_scheduled.

How max_retries is set

-- Allow up to 3 retry attempts (4 total attempts):
SELECT pgrelay.register('my_channel', 'SELECT my_fn($1)', max_retries := 3);

-- Change an existing channel to allow 5 retries:
SELECT pgrelay.update('my_channel', p_max_retries := 5);

Default max_retries is 0 — no retries.

Note

The retry mechanism described above (a queue-side row linked by parent_id) is shared infrastructure used by every action type. What actually triggers a retry differs by action type: SQL events retry on a SQLSTATE class 40 error, as described here; 'notify' events (SMTP, Microsoft 365, or webhook, via the pg_relay_notifier companion extension) retry on transient provider failures instead — see pg_relay_notifier compatibility — using the identical backoff schedule and the same max_retries setting.


Continue to Log Levels and Format to understand every message the Processor can write.