Skip to main content
Release Date: August 12th, 2026 pgstream v1.4.0 is a minor release. It adds a native Prometheus scrape endpoint, granular object type filtering for Postgres-to-Postgres snapshots and DDL replication, and validation of transformation rules against unique indexes before a run starts instead of failing mid-load. Target connection pool limits become configurable, webhook delivery gains retries and idempotency headers, and a DDL parsing bug that silently dropped RENAME COLUMN and DROP COLUMN changes is fixed. Pipelines with a lossy transformer on a unique-indexed column need attention before upgrading — see the upgrade notes.

Prometheus Scrape Endpoint

PR #1008, contributed by @pauliyobo in their first contribution. instrumentation.metrics.prometheus.enabled exposes a /metrics endpoint on the existing health server, wired into the OpenTelemetry meter provider independently of the OTLP exporter. Metrics can be scraped directly, with no collector in between:
The endpoint returns 404 when disabled.

Granular Object Type Filtering

PR #735, by @tsg. Both the schema snapshot and DDL replication for Postgres-to-Postgres pipelines can be restricted to a subset of object categories — tables, sequences, types, indexes, functions, views, triggers and others, 16 in total — via include_object_types (allowlist) or exclude_object_types (denylist), independently for snapshot and replication. A DDL statement that touches several object types (for example CREATE TABLE with a primary key) is only skipped when every object it touches is excluded. This does not resolve inter-object dependencies: excluding a category that surviving objects depend on will make the snapshot or DDL replay fail, so use it with a good understanding of your schema.

Transformation Rules Validated Against Unique Indexes

PR #1082, PR #1084, PR #1085 and PR #1086, by @kvch. Anonymization is lossy by design: masking with type: id keeps a 6-character prefix, so every value sharing that prefix collapses to the same masked value. Applied to a column covered by a unique index, this previously surfaced as duplicate key value violates unique constraint hours into a data load. Every transformer is now classified as preserved (distinct inputs stay distinct), not_guaranteed (random or hashed output, collisions possible) or lossy (collides by construction — masking, the greenmask and neosync name and choice generators, literal_string). validate rules and pipeline startup read pg_index for the tables in scope and check the configured rules against unique indexes, primary keys and unique constraints. With a Postgres target, a lossy transformer on a covered column fails at parse time with transformation rules break a unique index; with other targets (Kafka, Elasticsearch/OpenSearch, webhooks) the same finding is a warning, since there is no unique index to violate. A not_guaranteed transformer always warns. Set allow_uniqueness_loss: true on a column rule to keep a lossy transformer anyway. Related: a custom mask configured to mask zero characters (mask_begin equal to mask_end, or unmask_begin: 0) is now rejected at construction rather than silently passing values through unmasked (PR #1083) — use noop to pass a column through on purpose.

Configurable Connection Pool Limits

PR #1048, contributed by @subotac in their first contribution, with PR #1092 by @kvch. target.postgres.max_connections (PGSTREAM_POSTGRES_WRITER_MAX_CONNECTIONS) controls the writer pool size, defaulting to 50 and honoring pool_max_conns from the target URL when set explicitly. Previously this setting also sized the schema observer’s pool, so a target capped at 90 connections to stay under a 100-connection server limit could still open up to 180. The schema observer is now sized separately, at min(writer pool, 16) — its catalog lookups are a startup and DDL burst rather than sustained COPY concurrency — so the process opens at most the configured writer limit plus 16.

Webhook Delivery

PR #1080, by @kvch. A failed webhook POST is now retried with configurable backoff (network errors, 429, 5xx); other non-2xx responses are treated as permanent and not retried. A delivery that is still failing after retries is no longer checkpointed away — it is logged and dropped so one broken subscriber cannot block delivery to everyone else, and at-least-once semantics apply on restart. New X-Pgstream-LSN and Idempotency-Key headers let subscribers dedupe redelivered events (both omitted for snapshot events, which share a zero LSN). DISABLE_RETRIES now actually disables retries.

CREATEROLE Preflight Check

PR #1055, contributed by @lghuy05. pgstream check verifies that the target connection role has CREATEROLE (or is a superuser) when roles_snapshot_mode is enabled or no_passwords, catching a missing privilege before the restore fails.

Bug Fixes

  • RENAME COLUMN and DROP COLUMN are no longer silently dropped (PR #1078). The regex used to extract column names matched the trailing statement terminator as part of a bare identifier, so RENAME COLUMN pii_email TO email; was parsed as renaming to email; — the lookup then missed and the DDL event produced an empty schema diff, dropping the change from replication.

Documentation

  • Transformer uniqueness and the unique index check are documented (PR #1088).
  • The destructive --reset snapshot behaviour is documented (PR #1059).

New Contributors

Upgrade

⚠️ Breaking: Postgres-to-Postgres pipelines with a lossy transformer on a unique-indexed column now fail at startup. This is the intended outcome of the new uniqueness check — it replaces a mid-load duplicate key value violates unique constraint failure with an immediate, actionable one — but existing configurations that rely on this combination need either a uniqueness-preserving transformer or allow_uniqueness_loss: true on the affected column before upgrading. ⚠️ Breaking: a zero-width custom mask (masking) is now rejected at startup. If you have a mask_begin/mask_end pair or unmask_begin: 0 that masks no characters, switch to noop or a real mask range. Connection pools. No action needed for most setups. If you were relying on max_connections to size the schema observer’s pool as well as the writer’s, the observer is now capped at min(writer pool, 16) regardless — reduce the writer setting if you need a lower total connection count.

Support