12.5. Logical Decoding Output Plugins: pgoutput

Alpha Version: Work in progress.

The primary role of an output plugin is to serialize the change data—which has been organized and reordered by the ReorderBuffer — into a format compatible with sending to a subscriber.

The specific tasks performed by the plugin include:

  • Data Transformation and Filtering: The plugin performs a final validation of the binary data within the ReorderBuffer against the publication’s configuration. It determines whether the target table or specific actions (e.g., INSERT, TRUNCATE) are included in the subscription and evaluates row filters or column lists. Once irrelevant data is excluded, the plugin formats the remaining payload into the appropriate data types and structures (e.g., text or binary) dictated by the replication protocol.
  • Message Generation: The plugin constructs a logical sequence of messages, such as BEGIN, INSERT/UPDATE/DELETE/TRUNCATE, and COMMIT. These messages are passed to the walsender process, which manages the network sending.

As the name implies, this mechanism is pluggable. Custom plugins can be implemented to support unique output formats or specialized internal processing logic. In standard PostgreSQL logical replication, the officially supported pgoutput is used as the default plugin.

The pgoutput message format consists of a series of tagged data packets. The following sections describe the structure of these messages.

Info

While PostgreSQL supports multiple protocol versions (versions 1 through 4), this section primarily focuses on the core structures. For a comprehensive reference, please consult the official documentation: Logical Replication Message Formats.

12.5.1. TupleData (shared sub-message)

The TupleData structure is a common sub-message used within DML operations to represent tuple contents.

[num_cols: Int16]
  For each column:
    [kind: Byte1('n'|'u'|'t'|'b')]
    if kind = 't' or 'b':
      [length: Int32] [value: Byte*n*]
Item Type Description
num_cols Int16 Number of columns in the tuple.
For each column:
kind Byte1 ’n’ = NULL; ‘u’ = unchanged TOASTed value; ’t’ = text formatted; ‘b’ = binary formatted.
length Int32 Length of the column value in bytes. Present only when kind is ’t’ or ‘b’.
value Byten The actual column value. n matches the preceding length. Present only when kind is ’t’ or ‘b’.

12.5.2. Transaction Control

12.5.2.1. Begin (‘B’)

Identifies the start of a transaction. It provides the transaction’s LSN and commit timestamp, allowing the subscriber to maintain chronological order.

[Byte1('B')] [final_lsn: Int64] [commit_timestamp: Int64] [txid: Int32]
Item Type Description
‘B’ Byte1 Identifies the message as a BEGIN message.
final_lsn Int64 (XLogRecPtr) The final LSN of the transaction. (Matches the commit_lsn in ‘C’)
commit_timestamp Int64 (TimestampTz) The commit timestamp in microseconds since the PostgreSQL epoch (2000-01-01).
txid Int32 (TransactionId) The XID (Transaction ID) of the transaction.

12.5.2.2. Commit (‘C’)

Identifies the end of a transaction. Upon receiving this, the subscriber applies the accumulated changes locally as a single atomic unit.

[Byte1('C')] [flags: Int8] [commit_lsn: Int64] [end_lsn: Int64] [commit_timestamp: Int64]
Item Type Description
‘C’ Byte1 Identifies the message as a COMMIT message.
flags Int8(0) Reserved flags; currently unused.
commit_lsn Int64 (XLogRecPtr) The LSN of the commit.
end_lsn Int64 (XLogRecPtr) The end LSN of the transaction.
commit_timestamp Int64 (TimestampTz) The commit timestamp.

12.5.3. DML

12.5.3.1. Origin (‘O’)

Used in setups involving multi-node replication. It informs the subscriber where the transaction originally occurred to prevent replication loops.

[Byte1('O')] [origin_lsn: Int64] [origin_name: String]
item type description
‘O’ Byte1 Identifies the message as an origin message.
origin_lsn Int64 (XLogRecPtr) The LSN of the commit on the origin server.
origin_name String Name of the origin. Note that there can be multiple Origin messages inside a single transaction.

12.5.3.2. Relation (‘R’)

Provides the metadata for a specific table. This message is typically sent before the first DML message for a table in a session, mapping a unique ID to the table’s schema and columns.

[Byte1('R')] [rel_id: Int32] [namespace: String] [relname: String]
             [replica_identity: Int8] [num_columns: Int16]
  For each column:
    [flags: Int8] [name: String] [type_oid: Int32] [atttypmod: Int32]
item type description
‘R’ Byte1 Identifies the message as a relation message.
rel_id Int32 (Oid) OID of the relation.
namespace String Namespace (empty string for pg_catalog).
relname String Relation name.
replica_identity Int8 Replica identity configuration for the relation (same as relreplident in pg_class). ’d’=default, ’n’=nothing, ‘f’=full, ‘i’=index.
num_columns Int16 Number of columns.
For each column:
flags Int8 Flags for the column. 0 = no flags; 1 = column is part of the replica identity key.
name String Name of the column.
type_oid Int32 (Oid) OID of the column’s data type.
atttypmod Int32 Type modifier of the column (atttypmod).

12.5.3.3. Insert (‘I’)

Represents the insertion of a new tuple. It includes the target relation ID and the TupleData of the new tuple.

[Byte1('I')] [rel_id: Int32] [Byte1('N')] [new_tuple: TupleData]
item type description
‘I’ Byte1 Identifies the message as an insert message.
rel_id Int32 (Oid) OID of the relation corresponding to the ID in the relation message.
‘N’ Byte1 Identifies the following TupleData as a new tuple.
new_tuple TupleData TupleData representing the contents of the new tuple.

12.5.3.4. Update (‘U’)

Represents an update to an existing tuple. Depending on the REPLICA IDENTITY configuration and whether the key columns changed, it may include the old tuple’s values to allow the subscriber to identify the correct tuple to modify.

-- REPLICA IDENTITY DEFAULT or INDEX (key columns changed):
[Byte1('U')] [rel_id: Int32] [Byte1('K')] [old_tuple: TupleData] [Byte1('N')] [new_tuple: TupleData]

-- REPLICA IDENTITY FULL:
[Byte1('U')] [rel_id: Int32] [Byte1('O')] [old_tuple: TupleData] [Byte1('N')] [new_tuple: TupleData]

-- REPLICA IDENTITY DEFAULT or INDEX (key columns NOT changed):
[Byte1('U')] [rel_id: Int32] [Byte1('N')] [new_tuple: TupleData]
item type description
‘U’ Byte1 Identifies the message as an update message.
rel_id Int32 (Oid) OID of the relation corresponding to the ID in the relation message.
‘K’ Byte1 (Optional) Identifies the following TupleData as a key. Present only if the update changed data in any column that is part of the REPLICA IDENTITY index. Mutually exclusive with ‘O’.
‘O’ Byte1 (Optional) Identifies the following TupleData as an old tuple. Present only if the table has REPLICA IDENTITY set to FULL. Mutually exclusive with ‘K’.
old_tuple TupleData (Optional) TupleData for the old tuple or primary key. Present only if the preceding ‘K’ or ‘O’ marker is present.
‘N’ Byte1 Identifies the following TupleData as a new tuple.
new_tuple TupleData TupleData representing the contents of the new tuple.

12.5.3.5. Delete (‘D’)

Represents the deletion of a tuple. The subscriber uses the provided key or old tuple data to locate and remove the record.

-- REPLICA IDENTITY DEFAULT or INDEX:
[Byte1('D')] [rel_id: Int32] [Byte1('K')] [old_key_tuple: TupleData]

-- REPLICA IDENTITY FULL:
[Byte1('D')] [rel_id: Int32] [Byte1('O')] [old_tuple: TupleData]
item type description
‘D’ Byte1 Identifies the message as a delete message.
rel_id Int32 (Oid) OID of the relation corresponding to the ID in the relation message.
‘K’ Byte1 (Either ‘K’ or ‘O’, never both) Identifies the following TupleData as a key. Present if the table uses an index as REPLICA IDENTITY.
‘O’ Byte1 (Either ‘K’ or ‘O’, never both) Identifies the following TupleData as an old tuple. Present if the table has REPLICA IDENTITY set to FULL.
old_key_tuple TupleData TupleData representing the contents of the old tuple or primary key, depending on the preceding marker.

12.5.3.6. Truncate (‘T’)

Represents a bulk removal of all tuples in one or more tables.

[Byte1('T')] [num_relations: Int32] [options: Int8]
             [rel_id: Int32] ...   -- repeated num_relations times
item type description
‘T’ Byte1 Identifies the message as a truncate message.
num_relations Int32 Number of relations to be truncated.
options Int8 Option bits for TRUNCATE: bit 0 (1) = CASCADE; bit 1 (2) = RESTART IDENTITY.
rel_id Int32 (Oid) OID of the relation corresponding to the ID in the relation message. This field is repeated num_relations times.

12.5.4. Stream Control

The following messages are utilized when the publisher sends data in segments to support the Streaming of Large Transactions.

For further details, refer to Section 12.6.3.

12.5.4.1. Stream Start (‘S’)

Identifies the beginning of a stream segment. This is used when a large, ongoing transaction is split into multiple segments for sending.

[Byte1('S')] [txid: Int32] [first_segment: Int8]
Item Type Description
‘S’ Byte1 Identifies the message as a Stream Start message.
txid Int32 (TransactionId) The XID of the transaction.
first_segment Int8 Set to 1 if this is the first segment for this XID; otherwise 0.

12.5.4.2. Stream Stop (‘E’)

Identifies the end of a stream segment.

[Byte1('E')]
Item Type Description
‘E’ Byte1 Identifies the message as a Stream Stop message.

12.5.4.3. Stream Commit (‘c’)

Identifies the commit of a streamed transaction. It contains similar information to a standard Commit (‘C’) but operates within the streaming context.

[Byte1('c')] [txid: Int32] [flags: Int8] [commit_lsn: Int64] [end_lsn: Int64] [commit_timestamp: Int64]
Item Type Description
‘c’ Byte1 Identifies the message as a Stream Commit message.
txid Int32 (TransactionId) The XID of the transaction.
flags Int8(0) Reserved flags; currently unused.
commit_lsn Int64 (XLogRecPtr) The LSN of the commit.
end_lsn Int64 (XLogRecPtr) The end LSN of the transaction.
commit_timestamp Int64 (TimestampTz) The commit timestamp.

12.5.4.4. Stream Abort (‘A’)

Identifies the abort (rollback) of a streamed transaction. This is also used for the abort of subtransactions.

[Byte1('A')] [txid: Int32] [subxid: Int32] [abort_lsn: Int64]? [abort_timestamp: Int64]?
Item Type Description
‘A’ Byte1 Identifies the message as a Stream Abort message.
txid Int32 (TransactionId) The XID of the transaction.
subxid Int32 (TransactionId) The XID of the subtransaction (same as txid for top-level transactions).
abort_lsn Int64 (XLogRecPtr) The LSN of the abort operation. Present only if parallel streaming is enabled (Protocol v4+).
abort_timestamp Int64 (TimestampTz) The abort timestamp. Present only if parallel streaming is enabled (Protocol v4+).