Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wzphx-004Hdb-1Z for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 06:04:57 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wzphv-0069T8-35 for pgsql-hackers@arkaria.postgresql.org; Fri, 28 Aug 2026 06:04:55 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wzphv-0069T0-1h for pgsql-hackers@lists.postgresql.org; Fri, 28 Aug 2026 06:04:55 +0000 Received: from mail-pf1-x42b.google.com ([2607:f8b0:4864:20::42b]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wzpht-00000002nfW-23cw for pgsql-hackers@postgresql.org; Fri, 28 Aug 2026 06:04:54 +0000 Received: by mail-pf1-x42b.google.com with SMTP id d2e1a72fcca58-8568e3ed034so227120b3a.0 for ; Thu, 27 Aug 2026 23:04:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787897092; x=1788501892; darn=postgresql.org; h=references:to:cc:in-reply-to:date:subject:mime-version:content-type :message-id:from:from:to:cc:subject:date:message-id:reply-to :content-type; bh=ZGAr3JEiOepyCqZjo7EfVdvhPFmHX1TY2OFMcbUasBI=; b=ecqkRXFKO885O7ySDXwMLda+lUazlkhROSa1yPiLeQZ8BoXkczSyDKOeTUjMe4CG4d JZENlI9UvaUzW/0n4o/313xXJaCh1wP0sFOJsKxVIgia9PmwNZ29b9GrsejdCY1eO+BX mZTyzqRdT1rUW7zjWpqcc1Qw9xcMiF4cMbsxbR93HfDFE5l7qqUiZ2F/Xqb3fN0SW6wq iKGhcFe5MURm9CrFL0z9dpxhUmnZluf96RW6pvH+LWwieISm4RabYmeaJefZ0paA1k2r aCvEGjTythWsdQxgaG9g9LebCiUJv8hzKr12VBdfq19aO/iEWqHwqFrkS5K/gFyov7av PGmw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787897092; x=1788501892; h=references:to:cc:in-reply-to:date:subject:mime-version:content-type :message-id:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=ZGAr3JEiOepyCqZjo7EfVdvhPFmHX1TY2OFMcbUasBI=; b=k8YZbr1c8H8Be2OodUkbBlVjzt0JB32MMZpOZ2SCue9DIk85SFXjIl9KX6Eotgl3sY bHjjqqVY5DrO03NgYiNjb03F37/5pF4WpEFGDvk1U0wOl9Zn1bLx6UMS0fyYvtXSfHTm SHTVFpe3dTlz2oj7Ek88Uyd37m88uy1OtNfl0/6mi+5B6zA+PIVrVXClXkhrIm57gwBF ggw8nGDjz3nzwRZvcBJReE0Zdrcu2uNMPm78D4mQ9qyvReoAVbqe4up0ult2K5dsiiL0 ayiSiY5blHaxinUTJC3NE/HC4Ou1syVSX3uFQ7/Ise4V8WRXqkq/BBzVcqaGGd4bO+eM s9+Q== X-Forwarded-Encrypted: i=1; AHgh+Rp6Fq8HIWowDGk9FwUp91g9q5utDdvNv3kiADHv+xAVYfC4E+lTP3WPJjXIjVpNCpBV1v5z0un3dNQcg/ZA@postgresql.org X-Gm-Message-State: AFuF++m61EZjtXpqASoh9TUP0GAjplFs6UOhdeACTPSHk/+Ft6ExVlG8 cN9Hp44qGLMUqEv3ysNDYxJeFl1NoOPfW4jhamiZaaHrZGYfpH9gOxICt6RzFg== X-Gm-Gg: AR+sD13qSB5+0i67WeT1FUWMmmO9+aMo9WSpFlBTPKj7tQIv3pBzc5QdYGGuXjiyTVF MlbW9HATl1MzWJWOAhlLc9ZQiUfEF29SpwDqWYrwTS4WvLpGB6lwJWhuvkDZcx9lw4zyiasocPJ uDQfYhowVvrorAVsTEvg9/PE0T25WndQBkw+jWXB7kcAOjoDvdhurt+VLi339iucSB9UWKo6RP/ JrcYcJf0kNgHr3RDtSvhIJG2QoZkHjZ3e8XlMV9lS5yfZhGxqlmixkOxUmE6mQjOUP7BL5zLO0B 7eevrRSbCCWUNVNzBvCXRxja1dlPyUqNsFKtdyEK+7SI/YTvw6ahg4ucoeTRT3HLn8UwxKLwE8c rjMAsG82uijkSidGWqHobWrxJWhYZOR2mtnOaXbocY81Ye7t3xtcchKD4rmKbrd0ww3ltNiyCyz r3NBrozzgsRQWWqLxKhy/BX3gfv5vPci4CoTdMPVvIn24hk3mZCh7MDQpWpzyny+ocOdmXHA== X-Received: by 2002:a05:6a20:7faa:b0:3cc:92d9:80cb with SMTP id adf61e73a8af0-3d2686a3287mr7412597637.12.1787897092383; Thu, 27 Aug 2026 23:04:52 -0700 (PDT) Received: from smtpclient.apple ([185.135.79.161]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3286f9e2a95sm2000843eec.23.2026.08.27.23.04.49 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 27 Aug 2026 23:04:51 -0700 (PDT) From: Chao Li Message-Id: <54DABC65-787E-4DA9-895C-140A3CF862CF@gmail.com> Content-Type: multipart/mixed; boundary="Apple-Mail=_C2609B80-795B-4F9F-AC2B-D81A2E6FF9A7" Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.700.51.1.1\)) Subject: Re: REPACK (CONCURRENTLY) fails when replica identity index is dropped Date: Fri, 28 Aug 2026 14:04:15 +0800 In-Reply-To: Cc: Nathan Bossart , pgsql-hackers@postgresql.org, alvherre@kurilemu.de To: Matthias van de Meent References: X-Mailer: Apple Mail (2.3864.700.51.1.1) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --Apple-Mail=_C2609B80-795B-4F9F-AC2B-D81A2E6FF9A7 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On Aug 28, 2026, at 03:31, Matthias van de Meent = wrote: >=20 > On Thu, 27 Aug 2026 at 20:26, Nathan Bossart = wrote: >>=20 >> I don't fully understand the mechanics of this one, but here is a >> reproducer: >>=20 >> CREATE TABLE t (a INT PRIMARY KEY, b INT, c TEXT); >> INSERT INTO t SELECT g, g, repeat('x', 1000) FROM = generate_series(1, 1000000) g; >> CREATE UNIQUE INDEX i ON t (a); >> ALTER TABLE t REPLICA IDENTITY USING INDEX i; >> DROP INDEX i; >> REPACK (CONCURRENTLY) t; >>=20 >> -- in a separate session, while REPACK is still running >> DELETE FROM t WHERE a =3D 1; >>=20 >> This produces the following ERROR from the REPACK command: >>=20 >> ERROR: incomplete delete info >> CONTEXT: slot "pg_repack_34213", output plugin "pgrepack", in the = change callback, associated LSN 0/A6CC1ED0 >> REPACK decoding worker >>=20 >> I think this can be addressed by verifying the index exists in >> check_concurrent_repack_requirements() and erroring out if it = doesn't. >=20 > I think the issue is caused by the following: REPACK's check has the > incorrect assumption that REPLICA IDENTITY USING INDEX either reverts > to DEFAULT or falls back to the behaviour of DEFAULT if the identity > index gets dropped, and thus uses GetRelationIdentityOrPK(), which > hides a lack of replica identity index. The issue shows up due to the > following garden path of data flows: >=20 > 1. A table with REPLICA IDENTITY USING INDEX doesn't fall back to > REPLICA IDENTITY DEFAULT once the identity index is dropped. > 2. In the catcache, the table won't fall back to rd_replidindex =3D > pkeyIndex when replident=3D'i', but instead will set > rd_replidindex=3DInvalidOid. > See the tail end of RelationGetIndexList. > 3. Then, in heap_delete, it calls ExtractReplicaIdentity() to find the > key of the deleted tuple. > 3a. ExtractR_I_() checks the identity key attributes from > RelationGetIndexAttrBitmap(..., INDEX_ATTR_BITMAP_IDENTITY_KEY), which > also only uses rd_replidindex, and doesn't fall back to the primary > key index's attributes. > 3b. If ExtractR_I_() doesn't have identity key attributes, it returns = NULL > 3c. heap_delete thus doesn't have any logical identity attributes to > log, and treats the delete operation as any non-logical deletion when > logging the data. > 4. Finally, the DELETE record gets decoded, and the logical plugin > finds out that no logical key data was included, and promptly ERRORs > out. >=20 > The attached patch is a blind shot that I suspect will fix the issue. >=20 >=20 > Kind regards, >=20 > Matthias van de Meent > Databricks (https://www.databricks.com) > After dropping the index, pg_class.relreplident is still 'i', but the = corresponding pg_index entry is deleted, so the table is left in a stale = state. If we only check whether the REPLICA IDENTITY index is valid in = REPACK, that prevents REPACK from starting, but doesn=E2=80=99t resolve = the stale state itself. We cannot assume the intended replacement replica identity after = removing an explicitly selected index. For example, the user might want = DEFAULT, FULL, or maybe another index. Should we instead prevent = dropping of an index while it is used as REPLICA IDENTITY? The attached diff makes a change in the direction, like this: ``` evantest=3D# CREATE TABLE t (a INT PRIMARY KEY, b INT, c TEXT); CREATE TABLE evantest=3D# CREATE UNIQUE INDEX i ON t (a); CREATE INDEX evantest=3D# ALTER TABLE t REPLICA IDENTITY USING INDEX i; ALTER TABLE evantest=3D# DROP INDEX i; ERROR: cannot drop index "i" because it is used as replica identity HINT: Use ALTER TABLE ... REPLICA IDENTITY to change the table's = replica identity first. ``` Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/ --Apple-Mail=_C2609B80-795B-4F9F-AC2B-D81A2E6FF9A7 Content-Disposition: attachment; filename=block_drop_index.diff Content-Type: application/octet-stream; x-unix-mode=0644; name="block_drop_index.diff" Content-Transfer-Encoding: 7bit diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index fd144d783d9..ec3b374f6fa 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1685,6 +1685,17 @@ RemoveRelations(DropStmt *drop) continue; } + /* + * An explicitly selected replica identity must remain available until + * the table's replica identity is changed. + */ + if (drop->removeType == OBJECT_INDEX && get_index_isreplident(relOid)) + ereport(ERROR, + errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("cannot drop index \"%s\" because it is used as replica identity", + rel->relname), + errhint("Use ALTER TABLE ... REPLICA IDENTITY to change the table's replica identity first.")); + /* * Decide if concurrent mode needs to be used here or not. The * callback retrieved the rel's persistence for us. diff --git a/src/test/regress/expected/replica_identity.out b/src/test/regress/expected/replica_identity.out index 1560cd04125..bcfcf1a1b4b 100644 --- a/src/test/regress/expected/replica_identity.out +++ b/src/test/regress/expected/replica_identity.out @@ -133,6 +133,10 @@ SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass 1 (1 row) +-- An explicitly selected replica identity index cannot be dropped. +DROP INDEX test_replica_identity_keyab_key; +ERROR: cannot drop index "test_replica_identity_keyab_key" because it is used as replica identity +HINT: Use ALTER TABLE ... REPLICA IDENTITY to change the table's replica identity first. ---- -- Make sure non index cases work ---- @@ -143,6 +147,7 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; d (1 row) +DROP INDEX test_replica_identity_keyab_key; SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; count ------- @@ -169,7 +174,6 @@ Indexes: "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) "test_replica_identity_hash" hash (nonkey) "test_replica_identity_keyab" btree (keya, keyb) - "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE @@ -200,7 +204,6 @@ Indexes: "test_replica_identity_expr" UNIQUE, btree (keya, keyb, (3)) "test_replica_identity_hash" hash (nonkey) "test_replica_identity_keyab" btree (keya, keyb) - "test_replica_identity_keyab_key" UNIQUE, btree (keya, keyb) "test_replica_identity_nonkey" UNIQUE, btree (keya, nonkey) "test_replica_identity_partial" UNIQUE, btree (keya, keyb) WHERE keyb <> '3'::text "test_replica_identity_unique_defer" UNIQUE CONSTRAINT, btree (keya, keyb) DEFERRABLE diff --git a/src/test/regress/sql/replica_identity.sql b/src/test/regress/sql/replica_identity.sql index 4ebb097f282..b8d29ca8fa5 100644 --- a/src/test/regress/sql/replica_identity.sql +++ b/src/test/regress/sql/replica_identity.sql @@ -65,11 +65,15 @@ SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; \d test_replica_identity SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; +-- An explicitly selected replica identity index cannot be dropped. +DROP INDEX test_replica_identity_keyab_key; + ---- -- Make sure non index cases work ---- ALTER TABLE test_replica_identity REPLICA IDENTITY DEFAULT; SELECT relreplident FROM pg_class WHERE oid = 'test_replica_identity'::regclass; +DROP INDEX test_replica_identity_keyab_key; SELECT count(*) FROM pg_index WHERE indrelid = 'test_replica_identity'::regclass AND indisreplident; ALTER TABLE test_replica_identity REPLICA IDENTITY FULL; --Apple-Mail=_C2609B80-795B-4F9F-AC2B-D81A2E6FF9A7--