public inbox for [email protected]
help / color / mirror / Atom feedFrom: Chao Li <[email protected]>
To: PostgreSQL-development <[email protected]>
Subject: repack: fix a bug to reject deferrable primary key fallback for concurrent mode
Date: Fri, 17 Apr 2026 11:35:14 +0800
Message-ID: <[email protected]> (raw)
Hi,
I am continuing to test REPACK, and I found another issue.
In check_concurrent_repack_requirements(), if a table has no replica identity index, the code falls back to using the primary key if one exists. The problem is that a deferrable primary key cannot be used for this purpose. WAL generation does not consider a deferrable primary key to be a replica identity, so concurrent mode may not receive enough old tuple information to replay concurrent changes.
I tested this with the following procedure.
1 - Create a table
```
create table t (id int, v text, primary key (id) deferrable initially deferred);
insert into t values (1, 'a');
```
2 - Attach a debugger to session 1's backend process. I used vscode. Add a breakpoint at the first process_concurrent_changes() call. This blocks the REPACK process and gives session 2 time to issue a DELETE.
3 - In session 1, issue a repack, it will stop at the breakpoint
```
repack (concurrently) t;
```
4 - In session 2
```
delete from t where id=1;
```
5 - Detach session 1 from the debugger, so that repack continues and tries to re-apply the delete from session 2.
6 - repack fails with:
```
evantest=# repack (concurrently) t;
ERROR: incomplete delete info
CONTEXT: slot "repack_96468", output plugin "pgrepack", in the change callback, associated LSN 0/2A5717F0
REPACK decoding worker
```
The error comes from this code in pgrepack.c:
```
case REORDER_BUFFER_CHANGE_DELETE:
{
HeapTuple oldtuple;
oldtuple = change->data.tp.oldtuple;
if (oldtuple == NULL)
elog(ERROR, "incomplete delete info");
repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple);
}
break;
```
The root cause is that repack.c assumes rel->rd_pkindex is usable as an identity index, but logical decoding does not treat a deferrable primary key as replica identity. As a result, the decoding worker may not get the old tuple needed to re-apply the delete.
To fix the problem, I think we should just not fall back to deferrable primary key in the first place. See the attached patch.
With this patch, repack will quickly for the test:
```
evantest=# repack (concurrently) t;
ERROR: cannot process relation "t"
HINT: Relation "t" has a deferrable primary key.
```
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Attachments:
[application/octet-stream] v1-0001-Reject-deferrable-primary-key-fallback-in-REPACK-.patch (2.1K, 2-v1-0001-Reject-deferrable-primary-key-fallback-in-REPACK-.patch)
download | inline diff:
From d67bb43755896909663daa62fb0ae19b32b9d75e Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 17 Apr 2026 11:12:36 +0800
Subject: [PATCH v1] Reject deferrable primary key fallback in REPACK
CONCURRENTLY
REPACK CONCURRENTLY uses logical decoding to collect concurrent
changes and then replays them on the new heap. To locate rows for
UPDATE and DELETE replay, it requires an identity index.
When RelationGetReplicaIndex() returned InvalidOid, the code fell
back to rel->rd_pkindex if a primary key existed. That is not safe for
deferrable primary keys. Such indexes are not considered replica
identity indexes by WAL generation, so logical decoding may not provide
the old tuple needed by the repack output plugin.
This can make replay fail later with errors such as "incomplete delete
info" from the decoding worker.
Prevent the fallback when the primary key is deferrable, and report the
problem during the initial REPACK CONCURRENTLY checks instead.
Author: Chao Li <[email protected]>
Reviewed-by:
Discussion: https://postgr.es/m/
---
src/backend/commands/repack.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 67364cc60e3..2c89cd17db4 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -926,7 +926,21 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
*/
ident_idx = RelationGetReplicaIndex(rel);
if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex))
+ {
+ /*
+ * A deferrable primary key is not considered a replica identity
+ * index, so WAL may not contain the old tuple needed for replay.
+ */
+ if (rel->rd_ispkdeferrable)
+ ereport(ERROR,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot process relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errhint("Relation \"%s\" has a deferrable primary key.",
+ RelationGetRelationName(rel)));
+
ident_idx = rel->rd_pkindex;
+ }
if (!OidIsValid(ident_idx))
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
--
2.50.1 (Apple Git-155)
view thread (10+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: repack: fix a bug to reject deferrable primary key fallback for concurrent mode
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox