Skip to content

CONCEPT Cited by 2 sources

Postgres notice warning channel

The Postgres NoticeResponse frame is a wire-protocol message that delivers informational, warning, and debug messages from the server to the driver in-band alongside query results, without raising an error or interrupting the query. PlanetScale Traffic Control uses NoticeResponse as the Warn-mode observability piggyback channel: over-budget events in Warn mode surface as notices rather than as SQLSTATE 53000 errors, letting applications observe budget pressure "from within your application without any user-facing effects."

Wire-protocol mechanics

Postgres's frontend/backend protocol defines seven server-to- client message types besides row data. NoticeResponse (message type N) carries a severity field (NOTICE, WARNING, DEBUG, INFO, LOG, NOTIFY) and a human-readable message. Unlike ErrorResponse (type E), a notice:

  • Does not interrupt the query. Rows continue to stream.
  • Does not trigger transaction rollback. The transaction state is unchanged.
  • Does not show up in standard error-handling code paths. Drivers surface it through a separate callback, not through err != nil returns.

pgx/v5 integration shape

Canonical Go integration via the jackc/pgx/v5 driver (Source: sources/2026-04-21-planetscale-patterns-for-postgres-traffic-control):

config, err := pgx.ParseConfig(dsn)
if err != nil { log.Fatal(err) }

config.OnNotice = func(c *pgconn.PgConn, notice *pgconn.Notice) {
    if strings.Contains(notice.Message, "[PGINSIGHTS] Traffic Control:") {
        log.Printf("traffic control warning: %s", notice.Message)
        // Increment a metric, write to a structured log, etc.
    }
}

The OnNotice hook fires for every notice on every connection in the pool. The Notice struct carries Severity, Code, Message, Detail, Hint, and position information — the same fields as PgError because they share the wire format.

Prefix-filtering on [PGINSIGHTS] Traffic Control: is recommended because the application's database may emit unrelated notices (NOTICE: CREATE TABLE will create implicit sequence …, WARNING: a long-running transaction is blocking autovacuum, etc.) — treating them all as Traffic Control signal pollutes the telemetry.

Why a driver callback, not an error return

Notices carry advisory / observability signal, not actionable failure signal. A query that triggers a Warn-mode budget violation still succeeded — it returned rows, the transaction is fine, the caller has no remediation to perform. Surfacing it as an error would:

  • Force every db.QueryContext call site to unwrap a typed error to check whether it was "real".
  • Conflict with standard database/sql semantics (no rows returned + err == nil + notice) where errors are fail-loud.
  • Couple observability to error-handling rather than to a metrics / logging pipeline.

The driver callback isolates Warn-mode observation above the query-response code path, matching the actual semantics.

Canonical Warn-mode observability loop

The 4-step lifecycle the Brown + Dicken posts both canonicalise:

  1. Ship the budget in Warn modeOnNotice fires on every over-budget query, logs accumulate.
  2. Aggregate — forward the log line to a metrics pipeline (counter per budget name, percentile over matched-query frequency, etc.).
  3. Tune — read the aggregates, adjust budget dials (server-share, per-query-limit, concurrency cap) until only pathological cases trigger.
  4. Flip to Enforce — now over-budget queries become SQLSTATE 53000 errors and callers retry-or-degrade.

Generalises beyond Traffic Control

Any Postgres extension that wants to piggyback warnings on query responses can use the same channel. pgx's OnNotice will catch them all; prefix-based discrimination is how multiple extensions coexist on one pool without crossing wires.

Not all Go drivers expose a notice handler. lib/pq and the standard database/sql interface don't pass notices through; applications bound to those must inspect the Postgres log file or poll pg_stat_activity — both cruder and lossier.

Seen in

Last updated · 378 distilled / 1,213 read