Skip to content

Replication and standbys

Vault tables replicate normally. There is one configuration requirement, and the way it fails is quiet, so it is worth reading properly.


Physical (streaming) replication

Works exactly as it does for ordinary tables.

Vault tables produce ordinary WAL records, so a standby replays them without the extension being involved at all. Row data, deletions and retention deadlines all arrive unchanged — a retention window means the same moment on the primary and on every replica.

Writes on a standby are refused by recovery, as they would be for any table.


Every standby needs the setting

shared_preload_libraries = 'pg_vault_tables'

on the standby's own postgresql.conf, not just the primary's.

The failure is silent

Because replay does not involve the extension, a standby without the setting keeps replicating perfectly. Lag is zero, everything looks healthy, no error appears anywhere.

It fails only when somebody queries a vault table on that replica — and at that point the rows turn out to have been there all along. Ordinary tables on the same replica work normally, which makes it very easy to misdiagnose as a problem with the table.

The data is never at risk. Restore the setting, restart, and everything is intact.

How this usually goes wrong

A replica built with pg_basebackup inherits postgresql.conf from the primary, so it gets the setting automatically.

A replica built from a templated or generated configuration may not. That is the realistic failure path — make sure whatever builds your replicas sets it.

Failover

Put it in the failover checklist. Promoting a standby that lacks the setting gives you a primary whose vault tables cannot be opened. Safe, but not a working database.


Logical replication

Behaves differently from physical replication in two ways that matter.

Logical replication does not replay blocks; the subscriber applies real inserts, updates and deletes. So it goes through enforcement like any other client.

1. The subscriber's permissions apply, not the publisher's.

If the subscriber's copy of the table grants less than the stream carries — say insert where the publisher sends updates — the apply is refused, a violation is logged, and the subscription stalls. It does not silently drop the change.

Declare both sides the same unless you specifically mean not to.

2. The retention clock restarts on the subscriber.

Rows arrive as fresh inserts, so their deadlines are calculated on arrival. A logically replicated copy therefore keeps rows longer than the origin, and the two do not expire together.

That is safe — nothing expires early — but if you are relying on a logical replica to hold the same retention window as the primary, it will not.


Running the purge

purge_vault deletes rows, so it runs on the primary only. On a standby it fails as a read-only transaction, which is the correct answer and needs no special handling.

Just make sure a scheduled purge is pinned to the primary, or tolerant of being run somewhere it cannot act. See Scheduling the Purge.