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:
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 COLUMNandDROP COLUMNare no longer silently dropped (PR #1078). The regex used to extract column names matched the trailing statement terminator as part of a bare identifier, soRENAME COLUMN pii_email TO email;was parsed as renaming toemail;— 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
--resetsnapshot behaviour is documented (PR #1059).
New Contributors
- @pauliyobo in PR #1008
- @subotac in PR #1048
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-loadduplicate 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
- Issues: https://github.com/xataio/pgstream/issues
- Documentation: https://github.com/xataio/pgstream/tree/main/docs
- Discussions: https://github.com/xataio/pgstream/discussions