v4-0003-WIP-conflict-log-table-docs.patch
application/octet-stream
Filename: v4-0003-WIP-conflict-log-table-docs.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v4-0003
Subject: WIP: conflict log table docs
| File | + | − |
|---|---|---|
| doc/src/sgml/logical-replication.sgml | 124 | 2 |
| doc/src/sgml/ref/alter_subscription.sgml | 9 | 2 |
| doc/src/sgml/ref/create_subscription.sgml | 19 | 0 |
From d8a5e11dcc63406c86584cf08abd4f5a3c314605 Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumarb@google.com>
Date: Mon, 17 Nov 2025 11:44:55 +0530
Subject: [PATCH v4 3/3] WIP: conflict log table docs
---
doc/src/sgml/logical-replication.sgml | 126 +++++++++++++++++++++-
doc/src/sgml/ref/alter_subscription.sgml | 11 +-
doc/src/sgml/ref/create_subscription.sgml | 19 ++++
3 files changed, 152 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml
index d64ed9dc36b..951a9bc0e3c 100644
--- a/doc/src/sgml/logical-replication.sgml
+++ b/doc/src/sgml/logical-replication.sgml
@@ -248,7 +248,9 @@
The subscription is added using <link linkend="sql-createsubscription"><command>CREATE SUBSCRIPTION</command></link> and
can be stopped/resumed at any time using the
<link linkend="sql-altersubscription"><command>ALTER SUBSCRIPTION</command></link> command and removed using
- <link linkend="sql-dropsubscription"><command>DROP SUBSCRIPTION</command></link>.
+ <link linkend="sql-dropsubscription"><command>DROP SUBSCRIPTION</command></link>. If a
+ <literal>conflict_log_table</literal> was specified for the subscription, that internally
+ managed table is automatically dropped along with the subscription.
</para>
<para>
@@ -284,6 +286,18 @@
option of <command>CREATE SUBSCRIPTION</command> for details.
</para>
+ <para>
+ Conflicts that occur during replication are typically logged as plain text
+ in the server log, which can be difficult for automated monitoring and
+ analysis. The <command>CREATE SUBSCRIPTION</command> command provides the
+ <link linkend="sql-createsubscription-params-with-conflict-log-table"><literal>conflict_log_table</literal></link>
+ option to specify a table name where detailed conflict information
+ is recorded in a structured, queryable format, significantly improving
+ post-mortem analysis and operational visibility of the replication setup.
+ This table is created and managed internally by the system and is owned
+ by the subscription owner.
+ </para>
+
<sect2 id="logical-replication-subscription-slot">
<title>Replication Slot Management</title>
@@ -1762,7 +1776,9 @@ Publications:
<para>
Additional logging is triggered, and the conflict statistics are collected (displayed in the
<link linkend="monitoring-pg-stat-subscription-stats"><structname>pg_stat_subscription_stats</structname></link> view)
- in the following <firstterm>conflict</firstterm> cases:
+ in the following <firstterm>conflict</firstterm> cases (If the subscription was created with the
+ <literal>conflict_log_table</literal> option, detailed conflict information is also inserted
+ into the specified table, providing a structured record of all conflicts).
<variablelist>
<varlistentry id="conflict-insert-exists" xreflabel="insert_exists">
<term><literal>insert_exists</literal></term>
@@ -1871,6 +1887,104 @@ Publications:
log.
</para>
+ <para>
+ When the <literal>conflict_log_table</literal> option is enabled, the system automatically creates
+ a new table with a predefined schema to log conflict details. This table is created in the
+ specified schema, is **owned by the subscription owner**, and logs system fields. The schema of this table is
+ detailed in <xref linkend="logical-replication-conflict-log-schema"/>.
+ </para>
+
+ <table id="logical-replication-conflict-log-schema">
+ <title>Conflict Log History Table Schema</title>
+ <tgroup cols="3">
+ <thead>
+ <row>
+ <entry>Column</entry>
+ <entry>Type</entry>
+ <entry>Description</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>relid</literal></entry>
+ <entry><type>oid</type></entry>
+ <entry>The OID of the local table where the conflict occurred.</entry>
+ </row>
+ <row>
+ <entry><literal>local_xid</literal></entry>
+ <entry><type>xid</type></entry>
+ <entry>The local transaction ID involved in the conflict (NULL if local tuple missing).</entry>
+ </row>
+ <row>
+ <entry><literal>remote_xid</literal></entry>
+ <entry><type>xid</type></entry>
+ <entry>The remote transaction ID that caused the conflict.</entry>
+ </row>
+ <row>
+ <entry><literal>remote_commit_lsn</literal></entry>
+ <entry><type>pg_lsn</type></entry>
+ <entry>The final LSN of the remote transaction.</entry>
+ </row>
+ <row>
+ <entry><literal>local_commit_ts</literal></entry>
+ <entry><type>timestamptz</type></entry>
+ <entry>The local commit timestamp of the local conflicting row (NULL if local tuple missing).</entry>
+ </row>
+ <row>
+ <entry><literal>remote_commit_ts</literal></entry>
+ <entry><type>timestamptz</type></entry>
+ <entry>The remote commit timestamp of the remote transaction.</entry>
+ </row>
+ <row>
+ <entry><literal>table_schema</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>The schema name of the conflicting table.</entry>
+ </row>
+ <row>
+ <entry><literal>table_name</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>The name of the conflicting table.</entry>
+ </row>
+ <row>
+ <entry><literal>conflict_type</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>The type of conflict that occurred (e.g., <literal>insert_exists</literal>).</entry>
+ </row>
+ <row>
+ <entry><literal>local_origin</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>The origin of the local transaction (if applicable).</entry>
+ </row>
+ <row>
+ <entry><literal>remote_origin</literal></entry>
+ <entry><type>text</type></entry>
+ <entry>The origin of the remote transaction.</entry>
+ </row>
+ <row>
+ <entry><literal>key_tuple</literal></entry>
+ <entry><type>json</type></entry>
+ <entry>The JSON representation of the replica identity or primary key tuple involved.</entry>
+ </row>
+ <row>
+ <entry><literal>local_tuple</literal></entry>
+ <entry><type>json</type></entry>
+ <entry>The JSON representation of the local row before the conflict (NULL if missing).</entry>
+ </row>
+ <row>
+ <entry><literal>remote_tuple</literal></entry>
+ <entry><type>json</type></entry>
+ <entry>The JSON representation of the incoming remote row that caused the conflict.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The conflicting row data, including the original local tuple and
+ the remote tuple, is stored in <type>JSON</type> columns (<literal>local_tuple</literal>
+ and <literal>remote_tuple</literal>) for flexible querying and analysis.
+ </para>
+
<para>
The log format for logical replication conflicts is as follows:
<synopsis>
@@ -2163,6 +2277,14 @@ CONTEXT: processing remote data for replication origin "pg_16395" during "INSER
key or replica identity defined for it.
</para>
</listitem>
+
+ <listitem>
+ <para>
+ The conflict log history table created using the <literal>conflict_log_table</literal>
+ option on a subscription is not published even if the publication is defined with
+ <literal>FOR ALL TABLES</literal>.
+ </para>
+ </listitem>
</itemizedlist>
</sect1>
diff --git a/doc/src/sgml/ref/alter_subscription.sgml b/doc/src/sgml/ref/alter_subscription.sgml
index 8ab3b7fbd37..e221fba8c57 100644
--- a/doc/src/sgml/ref/alter_subscription.sgml
+++ b/doc/src/sgml/ref/alter_subscription.sgml
@@ -265,8 +265,9 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
<link linkend="sql-createsubscription-params-with-origin"><literal>origin</literal></link>,
<link linkend="sql-createsubscription-params-with-failover"><literal>failover</literal></link>,
<link linkend="sql-createsubscription-params-with-two-phase"><literal>two_phase</literal></link>,
- <link linkend="sql-createsubscription-params-with-retain-dead-tuples"><literal>retain_dead_tuples</literal></link>, and
- <link linkend="sql-createsubscription-params-with-max-retention-duration"><literal>max_retention_duration</literal></link>.
+ <link linkend="sql-createsubscription-params-with-retain-dead-tuples"><literal>retain_dead_tuples</literal></link>,
+ <link linkend="sql-createsubscription-params-with-max-retention-duration"><literal>max_retention_duration</literal></link> and,
+ <link linkend="sql-createsubscription-params-with-conflict-log-table"><literal>conflict_log_table</literal></link>.
Only a superuser can set <literal>password_required = false</literal>.
</para>
@@ -324,6 +325,12 @@ ALTER SUBSCRIPTION <replaceable class="parameter">name</replaceable> RENAME TO <
<quote><literal>pg_conflict_detection</literal></quote>, created to retain
dead tuples for conflict detection, will be dropped.
</para>
+
+ <para>
+ When altering the <link linkend="sql-createsubscription-params-with-conflict-log-table"><literal>conflict_log_table</literal></link>, the target table must not
+ exist in the specified schema; attempting to set the parameter to an
+ existing table name will result in an error.
+ </para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/create_subscription.sgml b/doc/src/sgml/ref/create_subscription.sgml
index ed82cf1809e..0923f2fc801 100644
--- a/doc/src/sgml/ref/create_subscription.sgml
+++ b/doc/src/sgml/ref/create_subscription.sgml
@@ -268,6 +268,25 @@ CREATE SUBSCRIPTION <replaceable class="parameter">subscription_name</replaceabl
</listitem>
</varlistentry>
+ <varlistentry id="sql-createsubscription-params-with-conflict-log-table">
+ <term><literal>conflict_log_table</literal> (<type>string</type>)</term>
+ <listitem>
+ <para>
+ Specifies the qualified table name where detailed logical replication
+ conflict information will be recorded. The table specified by this option
+ must not exist when the subscription is created; if it does, an error will
+ be raised. This table is automatically created by the system, and it is
+ owned by the subscription owner. The table's predefined schema includes
+ fields for transaction details, LSNs, and JSON columns for the conflicting
+ local and remote tuples etc.
+ </para>
+ <para>
+ If this option is used, the table is automatically dropped when the subscription
+ is dropped.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="sql-createsubscription-params-with-streaming">
<term><literal>streaming</literal> (<type>enum</type>)</term>
<listitem>
--
2.49.0