repack with verbose output: not showing the number of removable row versions when using USING INDEX or CONCURRENTLY option

Started by Jochen Bandhauer5 days ago2 messagesbugs
Jump to latest

Hello,

on: PostgreSQL 19beta3 (development build (./configure --enable-debug
--enable-cassert CFLAGS='-O0 -g3'))
when using repack with the option CONCURRENTLY or when using USING INDEX
the verbose output does not show the number of removable row versions.
When using the command without these options the number of removable
rows are shown.

Complete Version: PostgreSQL 19beta3 on x86_64-pc-linux-gnu, compiled by
gcc (GCC) 14.3.1 20251022 (Red Hat 14.3.1-4), 64-bit

Test Case:

-- create table and data
drop table if exists t;
create table t (id bigint primary key, col1 bigint);
create index i1 on t (col1);
insert into t select generate_series(1, 100000);

-- commands
repack (verbose) t;  -- found 0 removable => ok
update t set col1=1;
repack (verbose) t;  -- found 100000 removable => ok
update t set col1=1;
repack (verbose,concurrently) t;  -- found 0 removable => is this correct?
update t set col1=1;
repack (verbose) t using index i1;  -- found 0 removable => is this correct?
update t set col1=1;
repack (verbose,concurrently) t using index i1;  -- found 0 removable =>
is this correct?

-- output:
mydb=# repack (verbose) t;
INFO:  repacking "public.t" in physical order
INFO:  "public.t": found 0 removable, 100000 nonremovable row versions
in 443 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.14 s, system: 0.00 s, elapsed: 0.14 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose) t;
INFO:  repacking "public.t" in physical order
INFO:  "public.t": found 100000 removable, 100000 nonremovable row
versions in 984 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.23 s, system: 0.00 s, elapsed: 0.23 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose,concurrently) t;
INFO:  repacking "public.t" in physical order
INFO:  "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.37 s, system: 0.00 s, elapsed: 0.38 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose) t using index i1;
INFO:  repacking "public.t" using index scan on "i1"
INFO:  "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.39 s, system: 0.00 s, elapsed: 0.40 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose,concurrently) t using index i1;
INFO:  repacking "public.t" using index scan on "i1"
INFO:  "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL:  0 dead row versions cannot be removed yet.
CPU: user: 0.56 s, system: 0.01 s, elapsed: 0.59 s.
REPACK
mydb=#

Thanks
Jochen

--
Mit freundlichen Grüßen

Jochen Bandhauer
+49 (0)176 29390012
www.jbitc.de

Attachments:

jb.vcftext/vcard; charset=UTF-8; name=jb.vcfDownload
#2Srinath Reddy Sadipiralla
srinath2133@gmail.com
In reply to: Jochen Bandhauer (#1)
Re: repack with verbose output: not showing the number of removable row versions when using USING INDEX or CONCURRENTLY option

Hi Jochen,

On Wed, Aug 19, 2026 at 4:33 PM Jochen Bandhauer <jb@jbitc.de> wrote:

Hello,

on: PostgreSQL 19beta3 (development build (./configure --enable-debug
--enable-cassert CFLAGS='-O0 -g3'))
when using repack with the option CONCURRENTLY or when using USING INDEX
the verbose output does not show the number of removable row versions.
When using the command without these options the number of removable
rows are shown.

Complete Version: PostgreSQL 19beta3 on x86_64-pc-linux-gnu, compiled by
gcc (GCC) 14.3.1 20251022 (Red Hat 14.3.1-4), 64-bit

Test Case:

-- create table and data
drop table if exists t;
create table t (id bigint primary key, col1 bigint);
create index i1 on t (col1);
insert into t select generate_series(1, 100000);

-- commands
repack (verbose) t; -- found 0 removable => ok
update t set col1=1;
repack (verbose) t; -- found 100000 removable => ok
update t set col1=1;
repack (verbose,concurrently) t; -- found 0 removable => is this correct?
update t set col1=1;
repack (verbose) t using index i1; -- found 0 removable => is this
correct?
update t set col1=1;
repack (verbose,concurrently) t using index i1; -- found 0 removable =>
is this correct?

-- output:
mydb=# repack (verbose) t;
INFO: repacking "public.t" in physical order
INFO: "public.t": found 0 removable, 100000 nonremovable row versions
in 443 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.14 s, system: 0.00 s, elapsed: 0.14 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose) t;
INFO: repacking "public.t" in physical order
INFO: "public.t": found 100000 removable, 100000 nonremovable row
versions in 984 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.23 s, system: 0.00 s, elapsed: 0.23 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose,concurrently) t;
INFO: repacking "public.t" in physical order
INFO: "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.37 s, system: 0.00 s, elapsed: 0.38 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose) t using index i1;
INFO: repacking "public.t" using index scan on "i1"
INFO: "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.39 s, system: 0.00 s, elapsed: 0.40 s.
REPACK
mydb=# update t set col1=1;
UPDATE 100000
mydb=# repack (verbose,concurrently) t using index i1;
INFO: repacking "public.t" using index scan on "i1"
INFO: "public.t": found 0 removable, 100000 nonremovable row versions
in 1082 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.56 s, system: 0.01 s, elapsed: 0.59 s.
REPACK
mydb=#

Thanks for the super-clear reproducer; I can reproduce it on master as well.
The code in copy_table_data(), where VERBOSE bumps the log level to INFO
so the message becomes visible. The counts it prints (removable,
nonremovable,
and "dead row versions cannot be removed yet") are only ever computed in the
non-concurrent path: that path scans the old heap with SnapshotAny and
classifies
every tuple with HeapTupleSatisfiesVacuum(), which is what populates
tups_vacuumed
and tups_recently_dead. The concurrent path instead scans with a regular
MVCC
snapshot, so the scan itself returns only the rows visible to that
snapshot, and those
two counters stay at 0. So for the concurrent case it doesn't make sense to
report
removable/nonremovable counts, since they aren't tracked there. I think
it's more
useful to report the number of tuples copied into the new table. Small
patch below,
thoughts?

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index edff54e734e..b780dbf2ff9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -1438,16 +1438,23 @@ copy_table_data(Relation NewHeap, Relation OldHeap,
Relation OldIndex,
        num_pages = RelationGetNumberOfBlocks(NewHeap);
        /* Log what we did */
-       ereport(elevel,
-                       (errmsg("\"%s.%s\": found %.0f removable, %.0f
nonremovable row versions in %u pages",
-                                       nspname,
-                                       RelationGetRelationName(OldHeap),
-                                       tups_vacuumed, num_tuples,
-                                       RelationGetNumberOfBlocks(OldHeap)),
-                        errdetail("%.0f dead row versions cannot be
removed yet.\n"
-                                          "%s.",
-                                          tups_recently_dead,
-                                          pg_rusage_show(&ru0))));
+       if (!concurrent)
+               ereport(elevel,
+                               (errmsg("\"%s.%s\": found %.0f removable,
%.0f nonremovable row versions in %u pages",
+                                               nspname,
+
RelationGetRelationName(OldHeap),
+                                               tups_vacuumed, num_tuples,
+
RelationGetNumberOfBlocks(OldHeap)),
+                               errdetail("%.0f dead row versions cannot be
removed yet.\n"
+                                               "%s.",
+                                               tups_recently_dead,
+                                               pg_rusage_show(&ru0))));
+       else
+               ereport(elevel,
+                               (errmsg("\"%s.%s\": copied %.0f row
versions in %u pages",
+                                               nspname,
RelationGetRelationName(OldHeap),
+                                               num_tuples, num_pages),
+                               errdetail("%s.", pg_rusage_show(&ru0))));

/* Update pg_class to reflect the correct values of pages and
tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);

--
Thanks :)
Srinath Reddy Sadipiralla
EDB: https://www.enterprisedb.com/