From 1518b985d977b9929e22da4d50345dff585055b7 Mon Sep 17 00:00:00 2001 From: Nisha Moond Date: Wed, 21 Oct 2026 13:56:51 +0530 Subject: [PATCH v86 3/3] Document and comment that replica identity rendering can still exceed 1GB The 64kB cap on replica identity values is checked after a key value's text is produced, so in rare cases the text itself can exceed 1GB and conflict logging fails with an error. Mention this in the docs, and add an XXX comment in build_index_key_json() listing when it can happen. --- doc/src/sgml/logical-replication.sgml | 18 +++++++++++++++++- src/backend/replication/logical/conflict.c | 14 ++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/logical-replication.sgml b/doc/src/sgml/logical-replication.sgml index 8ae0fad65ef..ad08a710a3c 100644 --- a/doc/src/sgml/logical-replication.sgml +++ b/doc/src/sgml/logical-replication.sgml @@ -2502,9 +2502,25 @@ SELECT * FROM ... WHERE (replica_identity->>'r')::regclass[] = '{pg_class}'; -- SELECT * FROM pg_conflict.pg_conflict_log_16392 WHERE has_omitted_values; A value omitted this way cannot be recovered from the conflict log table. + + + Some limit is needed because replica_identity is a json value, which cannot exceed 1GB, and the text of a value - can be much longer than the value itself. + can be much longer than the value itself. However, the 64kB limit is + applied only after a key value's text representation has been produced, + so it limits how much text is kept in the conflict log table but does not + stop that text from exceeding 1GB in the first place. Normally this + cannot happen, because the publisher had to produce the same text, within + the same 1GB limit, in order to send the value. That does not hold when, + for example, the key column has a different data type on the subscriber, + the subscription uses + binary + mode so the value is not sent as text, or the subscriber's + or + setting differs from the + publisher's. In such cases the text can exceed 1GB, and the apply worker + fails with an error when it tries to log the conflict. diff --git a/src/backend/replication/logical/conflict.c b/src/backend/replication/logical/conflict.c index 9791a023201..9f71901fac4 100644 --- a/src/backend/replication/logical/conflict.c +++ b/src/backend/replication/logical/conflict.c @@ -1119,8 +1119,9 @@ build_index_key_json(EState *estate, Relation localrel, * a json representation -- so its result is unrelated to the size of the * value we received, and a tiny key could render a json value above the * 1GB limit and error out the apply worker. Using the output function - * keeps us to the same representation the publisher already produced, as - * the server log does in BuildIndexValueDescription(). + * keeps us to the same representation the publisher already produced + * (when the column types and output settings match, see below), as the + * server log does in BuildIndexValueDescription(). * * Only the key attributes are recorded. FormIndexDatum() above also * fills in any non-key (INCLUDE) columns, but those are not part of the @@ -1171,6 +1172,15 @@ build_index_key_json(EState *estate, Relation localrel, * instead index's tupleDesc. */ getTypeOutputInfo(indexDesc->rd_opcintype[i], &outfuncoid, &typisvarlena); + + /* + * XXX The cap below is checked only after the value is rendered. + * The publisher rendered the same value to send it, which bounds + * its length, unless the column types differ, binary = true, or + * the apply worker's bytea_output or quote_all_identifiers + * differs. Then the render can exceed 1GB and error out the apply + * worker. + */ outputstr = OidOutputFunctionCall(outfuncoid, values[i]); outputlen = strlen(outputstr); -- 2.49.0