The short answer
Logical replication on managed Postgres means setting wal_level = logical, which can only take effect at server start, then connecting with a role that has the REPLICATION attribute and reading from a replication slot.6 Each provider wraps the setting differently:
- Supabase: logical replication is on by default; connect directly, not through the pooler.2
- Neon: turn it on per project in Settings › Postgres. It restarts computes and can’t be reverted.1
- Amazon RDS: set the static parameter
rds.logical_replication = 1and reboot.3 - Google Cloud SQL: set the flag
cloudsql.logical_decoding = on; the instance restarts.4 - Azure flexible server: set
wal_level = logical,max_worker_processesto at least 16, and restart.5
On every one of them, a slot that nobody reads holds WAL until the disk fills. Plan for that before you flip the switch.
Deedglass
A database change, checked and put back
-
1The agent’s change
Change order 42
UPDATE orders … WHERE id = 42
The agent: “1 row updated” -
2What the database really did
301 rows
A hidden trigger changed 300 more
Doesn’t match -
3Put back
301 rows restored
Signed off by a person
Matches
What every provider needs
Underneath the provider settings, Postgres needs four things:
wal_level = logical. It adds the information logical decoding needs and is set at server start, so a restart is unavoidable on most platforms.6- A role with
REPLICATION. Streaming changes needs it. - A replication slot. It remembers how far the consumer has read.
max_replication_slotsdefaults to 10.6 - A direct connection. Connection poolers in transaction mode don’t carry the replication protocol.
For tables where you want the full old row on updates and deletes, you also set REPLICA IDENTITY FULL on each table; the replica identity entry explains when that matters.
Provider by provider
| Provider | How to turn it on | Restart | Who gets REPLICATION |
Notes |
|---|---|---|---|---|
| Supabase | On by default2 | No | Create a role WITH REPLICATION as postgres2 |
Direct connection only; IPv6 unless you add IPv42 |
| Neon | Settings › Postgres › enable1 | Computes restart1 | Console, CLI and API roles have it1 | Can’t be reverted; idle slots removed after about 40 hours1 |
| Amazon RDS | rds.logical_replication = 13 |
Reboot3 | rds_replication role3 |
Enabling needs rds_superuser3 |
| Cloud SQL | cloudsql.logical_decoding = on4 |
Yes4 | A user with REPLICATION4 |
Same slot warning4 |
| Azure flexible | wal_level = logical, max_worker_processes ≥ 165 |
Yes5 | ALTER ROLE … WITH REPLICATION5 |
Read-only at 95% storage5 |
A few details per provider are worth reading twice.
Supabase. Replication needs a direct connection, and the destination must support IPv6 unless you enable the IPv4 add-on. The guide suggests a dedicated role created as postgres with CREATE ROLE <user> WITH REPLICATION, and creates the slot with pg_create_logical_replication_slot('example_slot', 'pgoutput').2
Neon. The switch is one-way and restarts the project’s computes. Neon removes inactive slots after about 40 hours, and a connected subscriber keeps the compute from scaling to zero.1 That second point has a cost: a consumer that stays connected keeps a compute running.
Amazon RDS. Setting rds.logical_replication to 1 also sets wal_level, max_wal_senders, max_replication_slots and max_connections, and AWS notes it can increase WAL generation, so turn it on only when you use logical slots.3
Cloud SQL and Azure. Both are a flag plus a restart, as above.4 Azure adds a hard stop: the server switches to read-only when storage use reaches 95 percent or free space drops under 5 GiB, and past a threshold it drops an unused logical slot automatically.5 Your database stays up; your consumer loses its place.
Check that it worked
After the restart, connect with the role you will use and run:
Code A database command in SQL. The text around it says what it does.
SHOW wal_level; -- expect: logical
SELECT rolname, rolreplication
FROM pg_roles
WHERE rolname = current_user; -- expect: rolreplication = t
SELECT slot_name, plugin, active,
pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS wal_held
FROM pg_replication_slots; -- every slot, and how much WAL it holds
The last query is the one to keep. wal_held is how much WAL Postgres can’t recycle because of that slot. Put it on a dashboard.
The disk trap, and how to avoid it
A replication slot holds WAL until its consumer confirms it has read it. If the consumer stops, the WAL piles up. Every provider warns about this in its own words. AWS: a slot you don’t read from can quickly fill the instance’s storage.3 Google: an unused slot makes Postgres hold WAL segments indefinitely, so disk use can grow without limit.4 Microsoft adds that unconsumed WAL raises the risk of transaction ID wraparound.5
Three settings and habits limit the damage:
- Cap retained WAL.
max_slot_wal_keep_sizelimits how much WAL slots may keep; the default, -1, is unlimited, and a slot that falls further behind may no longer be able to continue.6 Losing a slot is better than losing the database. - Time out idle slots. PostgreSQL 18’s
idle_replication_slot_timeoutinvalidates slots inactive longer than the setting; 0, the default, disables it.6 Neon’s 40-hour cleanup does the same job for you.1 - Alert on
wal_heldand onactive = false. A slot that has been inactive for an hour is a warning; for a day, an incident.
Drop slots you no longer use: SELECT pg_drop_replication_slot('name');. The replication slot entry has more on how they work.
A checklist before you turn it on
- Schedule the restart. Neon, RDS, Cloud SQL and Azure all restart.13
- Create a dedicated replication role, not your admin user. Microsoft calls separating them good practice.5
- Use the direct host, not the pooler.2
- Set
max_slot_wal_keep_sizeto a size your disk can afford.6 - Add the
wal_heldquery to monitoring with an alert. - Decide
REPLICA IDENTITYper table. - Write down who owns each slot, so the next person knows which ones are safe to drop.
Why an audit tool wants this
The reason this post exists: logical decoding is how a tool can see what actually changed in a database, rather than what a client said it changed. Triggers and statement logs have their own trade-offs, compared in pgaudit, triggers or logical decoding.
Deedglass uses it for its strongest check. That check needs wal_level = logical, one replication slot per watched connector and REPLICA IDENTITY FULL on each watched table; Amazon RDS, Cloud SQL, Supabase, Neon and Azure have their own settings for it, and our docs give them as guidance.7 The checklist above applies to our slot as much as anyone’s: if the consumer stops, the slot still holds WAL. For the broader idea, see logical decoding for agent audit. To try the checks without installing anything, open the playground; to grade what an agent can reach on your own machine, run npx deedglass scan (the grade, explained).
Written with AI assistance and checked against our claims register.