public inbox for [email protected]
help / color / mirror / Atom feedFrom: Chao Li <[email protected]>
To: Zhijie Hou (Fujitsu) <[email protected]>
Cc: PostgreSQL-development <[email protected]>
Subject: Re: repack: fix a bug to reject deferrable primary key fallback for concurrent mode
Date: Fri, 17 Apr 2026 14:17:15 +0800
Message-ID: <[email protected]> (raw)
In-Reply-To: <TYRPR01MB14195C23394DC17ABBD321B9694202@TYRPR01MB14195.jpnprd01.prod.outlook.com>
References: <[email protected]>
<TYRPR01MB14195C23394DC17ABBD321B9694202@TYRPR01MB14195.jpnprd01.prod.outlook.com>
> On Apr 17, 2026, at 11:46, Zhijie Hou (Fujitsu) <[email protected]> wrote:
>
> On Friday, April 17, 2026 11:35 AM Chao Li <[email protected]> wrote:
>> 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.
>>
> ...
>> 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.
>> ```
>
> Good catch!
>
> I think we can use the existing API to identify the index, for example:
>
> diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
> index 67364cc60e3..cc30236f493 100644
> --- a/src/backend/commands/repack.c
> +++ b/src/backend/commands/repack.c
> @@ -62,6 +62,7 @@
> #include "miscadmin.h"
> #include "optimizer/optimizer.h"
> #include "pgstat.h"
> +#include "replication/logicalrelation.h"
> #include "storage/bufmgr.h"
> #include "storage/lmgr.h"
> #include "storage/predicate.h"
> @@ -924,9 +925,7 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
> * repack work with a FULL replica identity; however that requires more
> * work and is not implemented yet.
> */
> - ident_idx = RelationGetReplicaIndex(rel);
> - if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex))
> - ident_idx = rel->rd_pkindex;
> + ident_idx = GetRelationIdentityOrPK();
> if (!OidIsValid(ident_idx))
> ereport(ERROR,
> errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
Thanks for pointing out GetRelationIdentityOrPK(). Looks like it wraps RelationGetReplicaIndex and excludes deferrable primary key.
Switched to use GetRelationIdentityOrPK() in v2.
>
> And it would be better to add a test for this.
>
I didn’t add the test because I saw there was only 1 test case of checking catalog table in cluster.sql for repack(concurrently). As you asked, I added tests for all checks of check_concurrent_repack_requirements().
PFA v2.
Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/
Attachments:
[application/octet-stream] v2-0001-Reject-deferrable-primary-key-fallback-in-REPACK-.patch (7.0K, 2-v2-0001-Reject-deferrable-primary-key-fallback-in-REPACK-.patch)
download | inline diff:
From 6ced3f3010d778dd18dd962b48dbebd0ecebbda3 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 17 Apr 2026 11:12:36 +0800
Subject: [PATCH v2] 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.
This change switches to use GetRelationIdentityOrPK() that excludes
deferrable primary key.
Author: Chao Li <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Discussion: https://postgr.es/m/[email protected]
---
src/backend/commands/repack.c | 15 +++++----
src/test/regress/expected/cluster.out | 44 ++++++++++++++++++++++++++-
src/test/regress/sql/cluster.sql | 39 +++++++++++++++++++++++-
3 files changed, 88 insertions(+), 10 deletions(-)
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 67364cc60e3..76d84180acc 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -62,6 +62,7 @@
#include "miscadmin.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
+#include "replication/logicalrelation.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
@@ -919,14 +920,12 @@ check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
/*
* Obtain the replica identity index -- either one that has been set
- * explicitly, or the primary key. If none of these cases apply, the
- * table cannot be repacked concurrently. It might be possible to have
- * repack work with a FULL replica identity; however that requires more
- * work and is not implemented yet.
- */
- ident_idx = RelationGetReplicaIndex(rel);
- if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex))
- ident_idx = rel->rd_pkindex;
+ * explicitly, or a non-deferrable primary key. If none of these cases
+ * apply, the table cannot be repacked concurrently. It might be possible
+ * to have repack work with a FULL replica identity; however that requires
+ * more work and is not implemented yet.
+ */
+ ident_idx = GetRelationIdentityOrPK(rel);
if (!OidIsValid(ident_idx))
ereport(ERROR,
errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 6127b215a86..0ba87972712 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -806,10 +806,52 @@ ORDER BY o.relname;
clstr_3
(2 rows)
--- concurrently
+--
+-- Check concurrent mode requirements
+--
+-- Doesn't support catalog tables
REPACK (CONCURRENTLY) pg_class;
ERROR: cannot repack relation "pg_class"
HINT: REPACK CONCURRENTLY is not supported for catalog relations.
+-- Only support permanent tables, temp and unlogged tables are not supported
+CREATE TEMP TABLE repack_conc_temp (i int PRIMARY KEY);
+REPACK (CONCURRENTLY) repack_conc_temp;
+ERROR: cannot repack relation "repack_conc_temp"
+HINT: REPACK CONCURRENTLY is only allowed for permanent relations.
+DROP TABLE repack_conc_temp;
+CREATE UNLOGGED TABLE repack_conc_unlogged (i int PRIMARY KEY);
+REPACK (CONCURRENTLY) repack_conc_unlogged;
+ERROR: cannot repack relation "repack_conc_unlogged"
+HINT: REPACK CONCURRENTLY is only allowed for permanent relations.
+DROP TABLE repack_conc_unlogged;
+-- Doesn't support tables without a primary key or replica identity index
+CREATE TABLE repack_conc_noident (i int);
+REPACK (CONCURRENTLY) repack_conc_noident;
+ERROR: cannot process relation "repack_conc_noident"
+HINT: Relation "repack_conc_noident" has no identity index.
+DROP TABLE repack_conc_noident;
+-- Doesn't support TOAST tables directly
+CREATE TABLE repack_conc_toast (t text);
+SELECT reltoastrelid::regclass AS toast_rel
+FROM pg_class WHERE oid = 'repack_conc_toast'::regclass \gset
+\set VERBOSITY sqlstate
+REPACK (CONCURRENTLY) :toast_rel;
+ERROR: 0A000
+\set VERBOSITY default
+DROP TABLE repack_conc_toast;
+-- Doesn't support tables with REPLICA IDENTITY NOTHING, even if they have a primary key
+CREATE TABLE repack_conc_nothing (i int PRIMARY KEY);
+ALTER TABLE repack_conc_nothing REPLICA IDENTITY NOTHING;
+REPACK (CONCURRENTLY) repack_conc_nothing;
+ERROR: cannot repack relation "repack_conc_nothing"
+HINT: Relation "repack_conc_nothing" has insufficient replication identity.
+DROP TABLE repack_conc_nothing;
+-- Doesn't support tables with deferrable primary keys
+CREATE TABLE repack_conc_deferrable (i int PRIMARY KEY DEFERRABLE);
+REPACK (CONCURRENTLY) repack_conc_deferrable;
+ERROR: cannot process relation "repack_conc_deferrable"
+HINT: Relation "repack_conc_deferrable" has no identity index.
+DROP TABLE repack_conc_deferrable;
-- clean up
DROP TABLE clustertest;
DROP TABLE clstr_1;
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index d14063a9683..99ff3739c60 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -386,9 +386,46 @@ JOIN relnodes_new n ON o.relname = n.relname
WHERE o.relfilenode <> n.relfilenode
ORDER BY o.relname;
--- concurrently
+--
+-- Check concurrent mode requirements
+--
+
+-- Doesn't support catalog tables
REPACK (CONCURRENTLY) pg_class;
+-- Only support permanent tables, temp and unlogged tables are not supported
+CREATE TEMP TABLE repack_conc_temp (i int PRIMARY KEY);
+REPACK (CONCURRENTLY) repack_conc_temp;
+DROP TABLE repack_conc_temp;
+CREATE UNLOGGED TABLE repack_conc_unlogged (i int PRIMARY KEY);
+REPACK (CONCURRENTLY) repack_conc_unlogged;
+DROP TABLE repack_conc_unlogged;
+
+-- Doesn't support tables without a primary key or replica identity index
+CREATE TABLE repack_conc_noident (i int);
+REPACK (CONCURRENTLY) repack_conc_noident;
+DROP TABLE repack_conc_noident;
+
+-- Doesn't support TOAST tables directly
+CREATE TABLE repack_conc_toast (t text);
+SELECT reltoastrelid::regclass AS toast_rel
+FROM pg_class WHERE oid = 'repack_conc_toast'::regclass \gset
+\set VERBOSITY sqlstate
+REPACK (CONCURRENTLY) :toast_rel;
+\set VERBOSITY default
+DROP TABLE repack_conc_toast;
+
+-- Doesn't support tables with REPLICA IDENTITY NOTHING, even if they have a primary key
+CREATE TABLE repack_conc_nothing (i int PRIMARY KEY);
+ALTER TABLE repack_conc_nothing REPLICA IDENTITY NOTHING;
+REPACK (CONCURRENTLY) repack_conc_nothing;
+DROP TABLE repack_conc_nothing;
+
+-- Doesn't support tables with deferrable primary keys
+CREATE TABLE repack_conc_deferrable (i int PRIMARY KEY DEFERRABLE);
+REPACK (CONCURRENTLY) repack_conc_deferrable;
+DROP TABLE repack_conc_deferrable;
+
-- clean up
DROP TABLE clustertest;
DROP TABLE clstr_1;
--
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], [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