BUG #19686: Rolling back SET TABLESPACE

Started by Alexandre Felipe4 days ago1 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliestests failedCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253815
psql -h localhost -U postgres

Built from patchset v1 (message #1), September 19, 2026 at 10:19 PM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253815_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253815_1 && git checkout t253815_1

Patchset v1 (message #1) is on t253815_1

Jump to latest
#1Alexandre Felipe
o.alexandre.felipe@gmail.com

This patchset addresses the issue reported on the pgsql-bugs [1]/messages/by-id/19686-30f4da834585129f@postgresql.org

The root cause is that after the relation files are copied to a new
tablespace queries
update the index in place but the heap is updated only in the tablespace
copy. If
the transaction is rolled back, the index and the heap becomes inconsistent.

First I tried to fix the crash, easy for btree, manageable for hash, but
for GiST that
would not be feasible, AFAIK would have to perform array searches possibly
over
multiple pages. Later thinking about this I noticed something I didn't
realise on my
first read.

Even without inserting duplicates, and no crashes, it can produce incorrect
results.

SET enable_seqscan = off;
SET enable_bitmapscan = off;
SET allow_in_place_tablespaces = true;
CREATE TABLESPACE ts LOCATION '';
CREATE TABLE t(a int);
CREATE INDEX ON t(a);
BEGIN;
ALTER TABLE t SET TABLESPACE ts;
INSERT INTO t VALUES (0); -- this adds (0, 1) | 0 to the index in the ts
copy
ROLLBACK;
INSERT INTO t VALUES (41); -- this adds (0, 1) | 41 in the default
tablespace
SELECT ctid, a FROM t WHERE a = 0;
ctid | a
-------+----
(0,1) | 41
(1 row)

So, I decided to fix the root cause: modifying a non-durable copy of the
file.
I thought it would be way harder, but the code was architected well enough
that I could save a list of deferred copies, and keep modifying the the
table
in place. If the transaction is rolled back all the tuples in the index
will have
its (possibly dead) in the heap, effectively reserving those TID, this
prevents
both the insertion of duplicates, and the resuscitation of dead tuples by
later changes.

[1]: /messages/by-id/19686-30f4da834585129f@postgresql.org
/messages/by-id/19686-30f4da834585129f@postgresql.org

Attachments:

t253815_1
v1-0001-nbtree-skip-insertion-of-existing-tuples.patchapplication/octet-stream; name=v1-0001-nbtree-skip-insertion-of-existing-tuples.patchDownload+31-11
v1-0002-logging-and-testcase.patchapplication/octet-stream; name=v1-0002-logging-and-testcase.patchDownload+192-1
v1-0004-establish-expected-tablespace.out.patchapplication/octet-stream; name=v1-0004-establish-expected-tablespace.out.patchDownload+226-2
v1-0003-fix-deferred-relation-copy.patchapplication/octet-stream; name=v1-0003-fix-deferred-relation-copy.patchDownload+170-2