From 7c14f8406af7b1784db0b9f0ef95870773fa244a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 05:37:07 +0000 Subject: [PATCH] doc: Add a user documentation section for replica identity The information about replica identity was spread over the ALTER TABLE reference page and a short subsection of the publication documentation, which left several user-visible aspects undocumented, for example how the subscriber searches for the row to modify, what a replica identity costs, and that ALTER TABLE ... REPLICA IDENTITY does not recurse to partitions. Add a dedicated "Replica Identity" section to the logical replication chapter, describing the available modes and what each of them writes to WAL, how to set and examine the replica identity, the requirements on the publisher and on the subscriber, the handling of partitioned tables, and the trade-offs of REPLICA IDENTITY FULL. The previous subsection in the publication documentation is replaced by a reference to the new section, and ALTER TABLE ... REPLICA IDENTITY now points to it as well. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KhWjMve4rzoc4qyCuhSvgy --- doc/src/sgml/logical-replication.sgml | 427 +++++++++++++++++++++++--- doc/src/sgml/ref/alter_table.sgml | 2 + 2 files changed, 378 insertions(+), 51 deletions(-) diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 3a61e2d688932..34da42eacf539 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -150,57 +150,13 @@ snapshot once the transaction has committed. - - Replica Identity - - - 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. - - - - By default, this is the primary key, if there is one. Another unique index - (with certain additional requirements) can also be set to be the replica - identity. If the table does not have any suitable key, then it can be set - to replica identity FULL, which means the entire row - becomes the key. When replica identity FULL is - specified, indexes can be used on the subscriber side for searching the - rows. Candidate indexes must be btree or hash, non-partial, and the - leftmost index field must be a column (not an expression) that references - the published table column. These restrictions on the non-unique index - properties adhere to some of the restrictions that are enforced for - primary keys. If there are no such suitable indexes, the search on the - subscriber side can be very inefficient, therefore replica identity - FULL should only be used as a fallback if no other - solution is possible. - - - - If a replica identity other than FULL is set on the - publisher side, a replica identity comprising the same or fewer columns - must also be set on the subscriber side. - - - - Tables with a replica identity defined as NOTHING, - DEFAULT without a primary key, or USING - INDEX with a dropped index, cannot support - UPDATE or DELETE operations when - included in a publication replicating these actions. Attempting such - operations will result in an error on the publisher. - - - - INSERT operations can proceed regardless of any replica identity. - - - - See ALTER TABLE...REPLICA IDENTITY - for details on how to set the replica identity. - - + + To be able to replicate UPDATE and + DELETE operations, a published table must have a + replica identity configured, so that the rows to be updated or deleted can + be identified on the subscriber side. See + for details. + @@ -846,6 +802,375 @@ HINT: To initiate replication, you must manually create the replication slot, e + + Replica Identity + + + replica identity + + + + Logical replication transfers changes row by row, so the apply process on + the subscriber has to determine which row of its copy of a published table + corresponds to the row that was updated or deleted on the publisher. The + replica identity of a table provides the information + needed for that: it determines which old column values are written to the + write-ahead log when a row is updated or deleted, so that those values can + be decoded later and sent to the subscriber together with the change. + + + + Every table has a replica identity, which is recorded in + pg_class.relreplident, + but it only has an effect when the table's changes are decoded logically, + that is, when is set to + logical. Only UPDATE and + DELETE depend on it: INSERT + replicates the complete new row anyway, and TRUNCATE + does not refer to individual rows, so those operations can be replicated + regardless of the replica identity. + + + + Replica Identity Modes + + + The replica identity of a table is set using + ALTER TABLE ... REPLICA IDENTITY, + which supports the four modes summarized in + . + + + + Replica Identity Modes + + + + + + + Mode + Old values written to WAL + Notes + + + + + DEFAULT + the columns of the primary key + + This is the default for user tables. If the table has no primary + key, the behavior is the same as NOTHING. + + + + USING INDEX index_name + the columns covered by the named index + + The index must be unique, must not be partial, must not be + deferrable, must not be an expression index, and must contain only + columns marked NOT NULL. If the index is dropped, + the behavior is the same as NOTHING. + + + + FULL + all columns of the old row + + A fallback for tables that have no suitable key; it is more expensive + on both sides, see + . + + + + NOTHING + nothing + + This is the default for system catalogs. UPDATE + and DELETE cannot be replicated. + + + + +
+ + + With DEFAULT and USING INDEX, the + old values of the key columns are written for every + DELETE, but for UPDATE they are + only written if at least one key column was modified; however, if the old + value of a key column is stored externally (see + ), it is always written, whether or not it + changed. With FULL, the complete old row is written + for every UPDATE and DELETE. + + + + Note that a table can be in a state where no old values are available at + all, namely when its replica identity is NOTHING, + DEFAULT without a primary key, or USING + INDEX with an index that has since been dropped. Such a table + cannot replicate UPDATE or DELETE; + see . + +
+ + + Setting and Examining the Replica Identity + + + The following example gives a table a replica identity based on a unique + index, because the table has no primary key: + +CREATE TABLE t (a int NOT NULL, b text); +CREATE UNIQUE INDEX t_a_key ON t (a); +ALTER TABLE t REPLICA IDENTITY USING INDEX t_a_key; + + + + In psql, an index that is used as the replica + identity is marked as such in the table description, while the + FULL and NOTHING modes are reported + by \d+: + +\d t + Table "public.t" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------- + a | integer | | not null | + b | text | | | +Indexes: + "t_a_key" UNIQUE, btree (a) REPLICA IDENTITY + + The setting can also be queried directly from the system catalogs: + +SELECT relreplident FROM pg_class WHERE relname = 't'; + relreplident +-------------- + i +(1 row) + + + + Changing the replica identity of a table requires an + ACCESS EXCLUSIVE lock on it, but it neither rewrites + the table nor affects data that has already been written to the + write-ahead log; it only changes what is logged for subsequent + operations. It is therefore best to set the replica identity of a table + before adding it to a publication. + + + + + Requirements on the Publisher + + + If a table is part of a publication that replicates + UPDATE or DELETE operations, and no + old values would be available for it, the operation fails on the + publisher: + +UPDATE t SET b = 'x'; +ERROR: cannot update table "t" because it does not have a replica identity and publishes updates +HINT: To enable updating the table, set REPLICA IDENTITY using ALTER TABLE. + + The error is raised when the operation is executed, not when the table is + added to the publication, so a table that is only ever inserted into can + be published without any replica identity at all. + + + + In addition, the replica identity restricts which columns may be used by + other publication features, and it must itself be fully replicated: + + + + + + A row filter of a publication that publishes + UPDATE or DELETE may only + reference columns covered by the replica identity. See + . + + + + + + A column list of such a publication must include all columns of the + replica identity. See . + + + + + + Generated columns that are part of the replica identity must be + published, that is, the publication must either set + publish_generated_columns + to stored or list those columns in its column list. + See . + + + + + + Violations of these rules are likewise reported when an + UPDATE or DELETE is executed on the + published table. + + + + + Requirements on the Subscriber + + + The apply process uses the old values received from the publisher to + search for the local row to update or delete. The target table on the + subscriber therefore has to be searchable with exactly those values: + + + + + + If the published table has a replica identity other than + FULL, the target table must have a primary key or a + replica identity index, and its columns must all be covered by the + replica identity of the published table. In other words, a replica + identity comprising the same or fewer columns must be set on the + subscriber side. Otherwise the apply process reports an error such as: + +ERROR: publisher did not send replica identity column expected by the logical replication target relation "public.t" + + + + + + + If the target table has neither a primary key nor a replica identity + index, the published table must have replica identity + FULL, so that the whole old row is available for the + search. Otherwise the apply process reports: + +ERROR: logical replication target relation "public.t" has neither REPLICA IDENTITY index nor PRIMARY KEY and published relation does not have REPLICA IDENTITY FULL + + + + + + + The search is performed with a primary key or replica identity index if + the target table has one. When the published table has replica identity + FULL and the target table has no such index, any other + suitable index may be used instead. Candidate indexes must be btree or + hash, non-partial, and the leftmost index field must be a column (not an + expression) that references the published table column. These + restrictions on the non-unique index properties adhere to some of the + restrictions that are enforced for primary keys. If there is no suitable + index, the search falls back to a sequential scan of the whole target + table for every change, which can be very slow. + + + + + A search that is not based on a unique index can match several rows, for + example when a table with replica identity FULL + contains duplicate rows. In that case one of the matching rows is + updated or deleted, and which one that is, is not determined. + + + + + + Partitioned Tables + + + Rows are physically stored in leaf partitions, and that is also where the + old values are written to the write-ahead log. The replica identity of + each partition therefore determines whether an UPDATE or + DELETE can be replicated, also when the publication + parameter + publish_via_partition_root + is enabled. + + + + Unlike most other ALTER TABLE forms, + ALTER TABLE ... REPLICA IDENTITY does not recurse to + the partitions of a partitioned table, and partitions created later do + not inherit the setting either. The replica identity has to be set on + each partition individually: + +CREATE TABLE p (a int NOT NULL, b text) PARTITION BY RANGE (a); +CREATE TABLE p1 PARTITION OF p FOR VALUES FROM (0) TO (10); +ALTER TABLE p REPLICA IDENTITY FULL; +ALTER TABLE p1 REPLICA IDENTITY FULL; + + Note that a primary key or a unique constraint defined on the partitioned + table does exist on every partition, so replica identity + DEFAULT and a partitioned primary key work without any + per-partition setup. + + + + When publish_via_partition_root is enabled, changes + are reported using the identity and schema of the root table, and the + replica identity of that root table is announced to the subscriber. In + that case the root table and its partitions should be given matching + replica identities. + + + + + Using <literal>REPLICA IDENTITY FULL</literal> + + + Replica identity FULL makes the entire row the key, so + it works for any table. It comes at a price on both sides of the + replication setup, and should only be used as a fallback if no other + solution is possible: + + + + + + On the publisher, every UPDATE and + DELETE writes a copy of the complete old row into + the write-ahead log, which increases the amount of WAL generated, and + with it the work of decoding and of sending the changes. Values that + are stored externally are written inline, so wide rows are especially + expensive. + + + + + + On the subscriber, the search for the row to update or delete compares + all columns. Unless a suitable index exists, this requires a + sequential scan per change, as described in + . + + + + + + UPDATE and DELETE cannot be + applied on the subscriber if the table includes attributes with data + types (such as point or box) that do not have + a default operator class for btree or hash, because the old and the new + row cannot be compared. Defining a primary key or another replica + identity for the table avoids this limitation. + + + + + + So, in order of preference, use a primary key, or a unique index on + NOT NULL columns via USING INDEX, + and resort to FULL only where neither is available. + + + +
+ Row Filters diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index ff7071bef5b4d..7a2bf78e92cfd 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -986,6 +986,8 @@ WITH ( MODULUS numeric_literal, REM from the new value; however, if the old value is stored externally, it is always logged regardless of whether it changed. This option has no effect except when logical replication is in use. + See for more + information about how the replica identity is used. DEFAULT