Webhook icon
Schedule icon
Query icon
If icon
Fail icon
Queries icon
SlackIncomingWebhook icon
Pause icon
Loop icon
Sequential icon
Exit icon

Prevent PostgreSQL Transaction ID Wraparound and Sequence Exhaustion

Detect xid wraparound risk, the sessions and slots blocking freeze, and int sequences running out, then fix them.

Categories
DataInfrastructure

Two PostgreSQL limits stop writes with no gradual degradation:

  • Transaction ID wraparound. When a database's oldest unfrozen transaction ID gets within about 3 million of 2^31, PostgreSQL refuses new writes until it is vacuumed in single-user mode, which can take hours. Autovacuum normally prevents this. It fails when something holds back the xmin horizon: a session left idle in a transaction, a forgotten prepared transaction, an abandoned replication slot, or a standby with hot_standby_feedback.
  • Sequence exhaustion. An integer (serial) key stops at 2,147,483,647. Inserts then fail with "nextval: reached maximum value of sequence".

Both build up slowly and are easy to see days ahead, if someone looks. This blueprint looks every hour, explains the cause, and turns the safe fixes into a one-click decision.

How it works

  1. hourly_check (Schedule) or alertmanager (Webhook, for postgres_exporter alerts) starts a run.
  2. check_server fails fast if the database is unreachable and checks whether the role may terminate other sessions. version_guard (If) requires PostgreSQL 13+.
  3. databases measures transaction ID and MultiXact age for every database in the cluster, as a share of the 2^31 limit.
  4. holders lists what keeps the xmin horizon back, each with its age and the specific fix:
    • sessions with an open transaction;
    • prepared transactions;
    • replication slots;
    • standbys sending hot_standby_feedback.
  5. oldest_tables lists the tables of this database with the oldest relfrozenxid (TOAST included), their size, and whether autovacuum is working on them.
  6. sequences measures every ascending sequence against the smaller of its own maximum and its column's type. It keeps samples to compute the consumption rate and the days left, and flags sequences where only the sequence is too small because the column is already bigint.
  7. track turns levels (ok / warn / critical) into changes, so each database or sequence is reported once per change.
  8. propose asks on-call (Pause, 2 hours, multi-select):
    • Terminate blocking sessions;
    • Freeze the oldest tables with VACUUM (FREEZE);
    • Widen sequences with ALTER SEQUENCE ... AS bigint where the column already allows it.
  9. The chosen actions run, then recheck reads the ages again and notify_outcome reports the before and after, plus anything still holding the horizon back.

Error handling

  • Actions only touch what was shown, as it was shown. A session is terminated only if it is still in the same transaction (same xact_start): "skipped: the session already ended" or "skipped: it started a new transaction since the proposal".
  • Overlapping runs do not freeze a table twice. Right before each VACUUM (FREEZE), freeze_check skips a table that another run or autovacuum is vacuuming now, or that was frozen since the proposal (its age went down), and the report says "skipped".
  • Each freeze is isolated. Each VACUUM (FREEZE) runs in its own Sequential with allowFailure, so a table dropped meanwhile or a lock timeout is reported as "NOT frozen" while the others proceed. The run then ends WARNING.
  • Dangerous fixes stay manual. Prepared transactions and replication slots are never touched. The report gives the exact command (ROLLBACK PREPARED 'gid', drop the slot) because only a human can tell whether the data is still needed. Integer columns are never rewritten: the report says the table needs a planned migration to bigint.
  • No alert storms. A level change is reported once, a critical database is proposed again only after ask_again_hours, and a sequence that drops back under the warning level is reported as such. A conditional UPDATE keeps overlapping runs from reporting the same change twice.
  • Clear failures. check_server retries, unsupported versions fail with a message, and the errors alert names the task and cause.

What this blueprint teaches about Kestra

  • A Pause with a MULTISELECT form, where each choice gates its own If branch.
  • Turning several diagnostic queries into one change-detection step with jq inside Pebble and a single Postgres statement.
  • Running maintenance statements (VACUUM, ALTER SEQUENCE) from the JDBC plugin, one item at a time in a Loop with per-item isolation and outputs.
  • Keeping time series in a small table to turn a level into a forecast ("about 14.8 days left").

Prerequisites

  • PostgreSQL 13 or later.
  • A role with pg_monitor (diagnosis), pg_signal_backend (terminating sessions), and ownership of the tables and sequences it should freeze or widen (or the MAINTAIN privilege on PostgreSQL 17+).
  • A Slack incoming webhook.

Database ages are cluster-wide; tables and sequences are those of the database in POSTGRES_URL. Run a copy per database that holds large tables.

Secrets

  • POSTGRES_URL, POSTGRES_USER, POSTGRES_PASSWORD: the database to watch (two small state tables are created in it).
  • SLACK_WEBHOOK_URL: Slack incoming webhook.
  • PG_WRAPAROUND_WEBHOOK_KEY: a long random string, the key in the Alertmanager webhook URL.

Inputs

  • xid_warn_percent, xid_critical_percent (FLOAT, default 25 / 50): share of the 2^31 limit.
  • sequence_warn_percent, sequence_critical_percent (FLOAT, default 75 / 90).
  • holder_min_age (INT, default 10,000,000): report holders at least this many transactions old.
  • top_tables (INT, default 5), ask_again_hours (INT, default 6), kestra_url (STRING).

Outputs

  • databases (JSON): age and level of each database.
  • sequences (JSON): usage, rate, days left and level of each sequence.

Quick start

  1. Add the secrets and run the flow once: it creates wraparound_watch and sequence_samples and stays quiet if everything is healthy.
  2. Leave the hourly schedule on; after a day the sequence report includes consumption rates and days left.
  3. Add the PG_WRAPAROUND_WEBHOOK_KEY secret and point your postgres_exporter alerts at the alertmanager webhook (/api/v1/main/executions/webhook/company.team/postgres-wraparound-guard/<key>).
  4. When a sequence on an integer column passes the warning level, schedule the bigint migration while there is still time.

Plugins used

See How

New to Kestra?

Use blueprints to kickstart your first workflows.