Replica Identity
Replica identity is a PostgreSQL table setting that decides how a row is identified — and how much of its previous content is recorded — when an update or delete is replicated. The documentation states the requirement directly: “A published table must have a replica identity configured in order to be able to replicate UPDATE and DELETE operations, so that appropriate rows to update or delete can be identified on the subscriber side.” Descriptions here follow PostgreSQL 18 documentation checked in September 2026.
The settings
| Setting | What identifies the row | What a CDC consumer sees of the old row |
|---|---|---|
DEFAULT | The primary key, if there is one | Previous values of the primary key columns |
USING INDEX | A specified unique index meeting the documented requirements | Previous values of that index’s columns |
FULL | The entire row becomes the key | Previous values of all columns |
NOTHING | Nothing | No previous values; updates and deletes cannot be published |
The consumer-side effect is documented by Debezium for its PostgreSQL connector: with the default setting, update and delete events contain the previous values of the primary key columns; with FULL, they contain the previous values of every column.
Getting it wrong is an error, not a silent gap
PostgreSQL does not quietly publish a partial change. Tables whose replica identity is NOTHING, or DEFAULT without a primary key, or USING INDEX with a dropped index, cannot support updates and deletes in a publication that replicates those actions, and the documentation says attempting them “will result in an error on the publisher.” Inserts proceed regardless of replica identity. The practical consequence is worth stating plainly: enabling logical replication on a table without a primary key can make writes that used to succeed start failing on the source. That is a change to the source system, and it belongs in the conversation with whoever owns it.
The same decision in MySQL
MySQL exposes the equivalent choice as binlog_row_image, documented with three values: full logs all columns in both the before and after image; minimal logs only the before-image columns needed to identify the row plus the after-image columns actually assigned or auto-generated; noblob is full minus BLOB and TEXT columns that are neither needed for identification nor changed. The default is full. Descriptions follow MySQL 8.4 documentation.
Why a pipeline should check it first
Several downstream capabilities depend on having the old values, and none of them can be added later for changes already captured:
- Keeping a history of what a row contained before each change, not just its latest state.
- Reversing an aggregate — subtracting the old amount and adding the new one — without re-reading the source.
- Auditing deletions, where “row 42 was removed” is far less useful than what row 42 held.
- Detecting which columns actually changed, when only some changes should trigger downstream work.
The cost of the generous settings is paid on the source: more data written to the log for every update, and correspondingly more to retain and transfer. And the setting belongs to the source team, not the pipeline team. Confirm what it is before promising anything downstream that depends on before-values. How this fits the rest of change capture is worked through in Incremental Loading and CDC: How a Pipeline Knows What Changed.
References: PostgreSQL 18 documentation, Logical Replication: Publication; Debezium documentation, Debezium connector for PostgreSQL; MySQL 8.4 Reference Manual, Binary Logging Options and Variables.
Discover more from Insightful Data Lab
Subscribe to get the latest posts sent to your email.
