The four settings
ALTER TABLE … REPLICA IDENTITY changes the information written to the write-ahead log to identify rows that are updated or deleted, and has no effect except when logical replication is in use.1 The options:
- DEFAULT records the old values of the primary key columns. It is the default for ordinary tables, and on a table with no primary key it behaves like
NOTHING.1 - USING INDEX records the old values of the columns in a named unique index, which must be unique, not partial, not deferrable, and cover only
NOT NULLcolumns.1 - FULL records the old values of all columns in the row.1
- NOTHING records no information about the old row.1
In the example, both statements first ran under DEFAULT: the update of row 42 carries only its new values, and the delete says that row 43 is gone, not what it held. After FULL, the update of row 41 carries the old status, 'paid', next to the new one. The empty transaction 757 is the ALTER TABLE itself: logical decoding doesn’t carry schema changes.3
When it breaks writes
Replica identity isn’t only about what a reader sees. A table whose replica identity is NOTHING, DEFAULT without a primary key, or USING INDEX with a dropped index can’t support UPDATE or DELETE while it is in a publication that replicates them: those statements fail on the publisher. INSERT still works.2 Adding a table without a primary key to a publication can therefore make an application’s updates start failing.
Why audits and undo want FULL
An audit of what an agent changed has to answer two questions for every row: what is it now, and what was it before? Under DEFAULT, an update that doesn’t touch the key carries the new row and nothing of the old values, and a delete carries only the key. You can see that something changed, but not from what.
FULL answers both, which is also what undo needs: to put a row back, you need its old values. The trade-off is volume. Every update and delete on the table writes the whole old row to the WAL, so busy, wide tables produce more WAL and more for a reader to process. Set FULL on the tables an agent can change, not on every table in the database.
Checking a table
SELECT relname,
CASE relreplident WHEN 'd' THEN 'default' WHEN 'f' THEN 'full'
WHEN 'i' THEN 'index' WHEN 'n' THEN 'nothing' END AS replica_identity
FROM pg_class
WHERE relkind = 'r' AND relnamespace = 'public'::regnamespace;
Tables without a primary key deserve a second look either way: under DEFAULT they log nothing about old rows, and many tools that read the change stream can’t tell their rows apart.