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

SettingWhat identifies the rowWhat a CDC consumer sees of the old row
DEFAULTThe primary key, if there is onePrevious values of the primary key columns
USING INDEXA specified unique index meeting the documented requirementsPrevious values of that index’s columns
FULLThe entire row becomes the keyPrevious values of all columns
NOTHINGNothingNo 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.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.