Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Allow old WAL recycling during REPACK CONCURRENTLY
- 45b02984e2fa 19 (unreleased) landed
-
Advance restart_lsn more eagerly in LogicalConfirmReceivedLocation
- 38470c2c1ea7 19 (unreleased) landed
-
Remove unnecessary signal handler change
- 5d48d3b14e0e 19 (unreleased) landed
-
Improve REPACK (CONCURRENTLY) error messages some more
- 378dffaf8c80 19 (unreleased) landed
-
Revert "Allow logical replication snapshots to be database-specific"
- 01a80f062146 19 (unreleased) landed
-
Move REPACK (CONCURRENTLY) test out of stock regression tests
- 4b2aa4b39cba 19 (unreleased) landed
-
REPACK: do not require REPLICATION or LOGIN
- 5dbb63fc82b7 19 (unreleased) landed
-
Add missing initialization
- 05c401d5786a 19 (unreleased) landed
-
Simplify declaration of memcpy target
- 2cff363715ef 19 (unreleased) landed
-
Reserve replication slots specifically for REPACK
- e76d8c749c31 19 (unreleased) landed
-
doc: Add an example of REPACK (CONCURRENTLY)
- 8fb95a8ab6e5 19 (unreleased) landed
-
Allow logical replication snapshots to be database-specific
- 0d3dba38c777 19 (unreleased) landed
-
Avoid different-size pointer-to-integer cast
- a3b069ef90bd 19 (unreleased) landed
-
Fix valgrind failure
- 5bcc3fbd196c 19 (unreleased) landed
-
Add CONCURRENTLY option to REPACK
- 28d534e2ae0a 19 (unreleased) landed
-
Rename cluster.c to repack.c (and corresponding .h)
- c0b53ec06309 19 (unreleased) landed
-
Allow index_create to suppress index_build progress reporting
- caec9d9fadf1 19 (unreleased) landed
-
Make index_concurrently_create_copy more general
- 33bf7318f94c 19 (unreleased) landed
-
Document the 'command' column of pg_stat_progress_repack
- a630ac5c2016 19 (unreleased) landed
-
Introduce the REPACK command
- ac58465e0618 19 (unreleased) landed
-
Toggle logical decoding dynamically based on logical slot presence.
- 67c20979ce72 19 (unreleased) cited
-
Split vacuumdb to create vacuuming.c/h
- c4067383cb2c 19 (unreleased) landed
-
Remove ReorderBufferTupleBuf structure.
- 08e6344fd642 17.0 cited
-
Revert changes to CONCURRENTLY that "sped up" Xmin advance
- 042b584c7f7d 14.4 cited
-
VACUUM: ignore indexing operations with CONCURRENTLY
- d9d076222f5b 14.0 cited
-
Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-07-26T21:56:04Z
Hello, Here's a patch to add REPACK and eventually the CONCURRENTLY flag to it. This is coming from [1]. The ultimate goal is to have an in-core tool to allow concurrent table rewrite to get rid of bloat; right now, VACUUM FULL does that, but it's not concurrent. Users have resorted to using the pg_repack third-party tool, which is ancient and uses a weird internal implementation, as well as pg_squeeze, which uses logical decoding to capture changes that occur during the table rewrite. The patch submitted here, largely by Antonin Houska with some changes by me, is based on the the pg_squeeze code which he authored, and first introduces a new command called REPACK to absorb both VACUUM FULL and CLUSTER, followed by addition of a CONCURRENTLY flag to allow some forms of REPACK to operate online using logical decoding. Essentially, this first patch just reshuffles the CLUSTER code to create the REPACK command. I made a few changes from Antonin's original at [2]. First, I modified the grammar to support "REPACK [tab] USING INDEX" without specifying the index name. With this change, all possibilities of the old commands are covered, which gives us the chance to flag them as obsolete. (This is good, because having VACUUM FULL do something completely different from regular VACUUM confuses users all the time; and on the other hand, having a command called CLUSTER which is at odds with what most people think of as a "database cluster" is also confusing.) Here's a list of existing commands, and how to write them in the current patch's proposal for REPACK: -- re-clusters all tables that have a clustered index set CLUSTER -> REPACK USING INDEX -- clusters the given table using the given index CLUSTER tab USING idx -> REPACK tab USING INDEX idx -- clusters this table using a clustered index; error if no index clustered CLUSTER tab -> REPACK tab USING INDEX -- vacuum-full all tables VACUUM FULL -> REPACK -- vacuum-full the specified table VACUUM FULL tab -> REPACK tab My other change to Antonin's patch is that I made REPACK USING INDEX set the 'indisclustered' flag to the index being used, so REPACK behaves identically to CLUSTER. We can discuss whether we really want this. For instance we could add an option so that by default REPACK omits persisting the clustered index, and instead it only does that when you give it some special option, say something like "REPACK (persist_clustered_index=true) tab USING INDEX idx" Overall I'm not sure this is terribly interesting, since clustered indexes are not very useful for most users anyway. I made a few other minor changes not worthy of individual mention, and there are a few others pending, such as updates to the pg_stat_progress_repack view infrastructure, as well as phasing out pg_stat_progress_cluster (maybe the latter would offer a subset of the former; not yet sure about this.) Also, I'd like to work on adding a `repackdb` command for completeness. On repackdb: I think is going to be very similar to vacuumdb, mostly in that it is going to need to be able to run tasks in parallel; but there are things it doesn't have to deal with, such as analyze-in-stages, which I think is a large burden. I estimate about 1k LOC there, extremely similar to vacuumdb. Maybe it makes sense to share the source code and make the new executable a symlink instead, with some additional code to support the two different modes. Again, I'm not sure about this -- I like the idea, but I'd have to see the implementation. I'll be rebasing the rest of Antonin's patch series afterwards, including the logical decoding changes necessary for CONCURRENTLY. In the meantime, if people want to review those, which would be very valuable, they can go back to branch master from around the time he submitted it and apply the old patches there. [1] https://postgr.es/m/76278.1724760050@antos [2] https://postgr.es/m/152010.1751307725@localhost -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-07-27T01:59:10Z
On Sat, Jul 26, 2025 at 5:56 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hello, > > Here's a patch to add REPACK and eventually the CONCURRENTLY flag to it. > This is coming from [1]. The ultimate goal is to have an in-core tool > to allow concurrent table rewrite to get rid of bloat; right now, VACUUM > FULL does that, but it's not concurrent. Users have resorted to using > the pg_repack third-party tool, which is ancient and uses a weird > internal implementation, as well as pg_squeeze, which uses logical > decoding to capture changes that occur during the table rewrite. The > patch submitted here, largely by Antonin Houska with some changes by me, > is based on the the pg_squeeze code which he authored, and first > introduces a new command called REPACK to absorb both VACUUM FULL and > CLUSTER, followed by addition of a CONCURRENTLY flag to allow some forms > of REPACK to operate online using logical decoding. > > Essentially, this first patch just reshuffles the CLUSTER code to create > the REPACK command. > Thanks for keeping this ball rolling. > > My other change to Antonin's patch is that I made REPACK USING INDEX set > the 'indisclustered' flag to the index being used, so REPACK behaves > identically to CLUSTER. We can discuss whether we really want this. > For instance we could add an option so that by default REPACK omits > persisting the clustered index, and instead it only does that when you > give it some special option, say something like > "REPACK (persist_clustered_index=true) tab USING INDEX idx" > Overall I'm not sure this is terribly interesting, since clustered > indexes are not very useful for most users anyway. > I think I would lean towards having it work like CLUSTER (preserve the index), since that helps people making the transition, and it doesn't feel terribly useful to invent new syntax for a feature that I would agree isn't very useful for most people. > I made a few other minor changes not worthy of individual mention, and > there are a few others pending, such as updates to the > pg_stat_progress_repack view infrastructure, as well as phasing out > pg_stat_progress_cluster (maybe the latter would offer a subset of the > former; not yet sure about this.) Also, I'd like to work on adding a > `repackdb` command for completeness. > > On repackdb: I think is going to be very similar to vacuumdb, mostly in > that it is going to need to be able to run tasks in parallel; but there > are things it doesn't have to deal with, such as analyze-in-stages, > which I think is a large burden. I estimate about 1k LOC there, > extremely similar to vacuumdb. Maybe it makes sense to share the source > code and make the new executable a symlink instead, with some additional > code to support the two different modes. Again, I'm not sure about > this -- I like the idea, but I'd have to see the implementation. > > I'll be rebasing the rest of Antonin's patch series afterwards, > including the logical decoding changes necessary for CONCURRENTLY. In > the meantime, if people want to review those, which would be very > valuable, they can go back to branch master from around the time he > submitted it and apply the old patches there. > For clarity, are you intending to commit this patch before having the other parts ready? (If that sounds like an objection, it isn't) After a first pass, I think there's some confusing bits in the new docs that could use straightening out, but there likely going to overlap changes once concurrently is brought in, so it might make sense to hold off on those. Either way I definitely want to dive into this a bit deeper with some fresh eyes, there's a lot to digest... speaking of, for this bit in src/backend/commands/cluster.c + switch (cmd) + { + case REPACK_COMMAND_REPACK: + return "REPACK"; + case REPACK_COMMAND_VACUUMFULL: + return "VACUUM"; + case REPACK_COMMAND_CLUSTER: + return "VACUUM"; + } + return "???"; The last one should return "CLUSTER" no? Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Fujii Masao <masao.fujii@gmail.com> — 2025-07-27T06:00:51Z
On Sun, Jul 27, 2025 at 6:56 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hello, > > Here's a patch to add REPACK and eventually the CONCURRENTLY flag to it. > This is coming from [1]. The ultimate goal is to have an in-core tool > to allow concurrent table rewrite to get rid of bloat; +1 > right now, VACUUM > FULL does that, but it's not concurrent. Users have resorted to using > the pg_repack third-party tool, which is ancient and uses a weird > internal implementation, as well as pg_squeeze, which uses logical > decoding to capture changes that occur during the table rewrite. The > patch submitted here, largely by Antonin Houska with some changes by me, > is based on the the pg_squeeze code which he authored, and first > introduces a new command called REPACK to absorb both VACUUM FULL and > CLUSTER, followed by addition of a CONCURRENTLY flag to allow some forms > of REPACK to operate online using logical decoding. Does this mean REPACK CONCURRENTLY requires wal_level = logical, while plain REPACK (without CONCURRENTLY) works with any wal_level setting? If we eventually deprecate VACUUM FULL and CLUSTER, I think plain REPACK should still be allowed with wal_level = minimal or replica, so users with those settings can perform equivalent processing. + if (!cluster_is_permitted_for_relation(tableOid, userid, + CLUSTER_COMMAND_CLUSTER)) As for the patch you attached, it seems to be an early WIP and might not be ready for review yet?? BTW, I got the following compilation failure and probably CLUSTER_COMMAND_CLUSTER the above should be GetUserId(). ----------------- cluster.c:455:14: error: use of undeclared identifier 'CLUSTER_COMMAND_CLUSTER' 455 | CLUSTER_COMMAND_CLUSTER)) | ^ 1 error generated. ----------------- Regards, -- Fujii Masao -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-07-31T16:50:11Z
On 2025-Jul-26, Robert Treat wrote: > For clarity, are you intending to commit this patch before having the > other parts ready? (If that sounds like an objection, it isn't) After > a first pass, I think there's some confusing bits in the new docs that > could use straightening out, but there likely going to overlap changes > once concurrently is brought in, so it might make sense to hold off on > those. I'm aiming at getting 0001 committed during the September commitfest, and the CONCURRENTLY flag addition later in the pg19 cycle. But I'd rather have good-enough docs at every step of the way. They don't have to be *perfect* if we want to get everything in pg19, but I'd rather not leave anything openly confusing even transiently. That said, I did not review the docs this time around, so here's them the same as they were in the previous post. But if you want to suggest changes for the docs in 0001, please do. Just don't get too carried away. > speaking of, for this bit in src/backend/commands/cluster.c > > + switch (cmd) > + { > + case REPACK_COMMAND_REPACK: > + return "REPACK"; > + case REPACK_COMMAND_VACUUMFULL: > + return "VACUUM"; > + case REPACK_COMMAND_CLUSTER: > + return "VACUUM"; > + } > + return "???"; > > The last one should return "CLUSTER" no? Absolutely -- my blunder. On 2025-Jul-27, Fujii Masao wrote: > > The patch submitted here, largely by Antonin Houska with some > > changes by me, is based on the the pg_squeeze code which he > > authored, and first introduces a new command called REPACK to absorb > > both VACUUM FULL and CLUSTER, followed by addition of a CONCURRENTLY > > flag to allow some forms of REPACK to operate online using logical > > decoding. > > Does this mean REPACK CONCURRENTLY requires wal_level = logical, while > plain REPACK (without CONCURRENTLY) works with any wal_level setting? > If we eventually deprecate VACUUM FULL and CLUSTER, I think plain > REPACK should still be allowed with wal_level = minimal or replica, so > users with those settings can perform equivalent processing. Absolutely. One of the later patches in the series, which I have not included yet, intends to implement the idea of transiently enabling wal_level=logical for the table being repacked concurrently, so that you can still use the concurrent mode if you have a non-logical-wal_level instance. > + if (!cluster_is_permitted_for_relation(tableOid, userid, > + CLUSTER_COMMAND_CLUSTER)) > > As for the patch you attached, it seems to be an early WIP and > might not be ready for review yet?? BTW, I got the following > compilation failure and probably CLUSTER_COMMAND_CLUSTER > the above should be GetUserId(). This was a silly merge mistake, caused by my squashing Antonin's 0004 (trivial code restructuring) into 0001 at the last minute and failing to "git add" the compile fixes before doing git-format-patch. Here's v17. (I decided that calling my previous one "v1" after Antonin had gone all the way to v15 was stupid on my part.) The important part here is that I rebased Antonin 0004's, that is, the addition of the CONCURRENTLY flag, plus 0005 regression tests. The only interesting change here is that I decided to not mess with the grammar by allowing an unparenthesized CONCURRENTLY keyword; if you want concurrent, you have to say "REPACK (CONCURRENTLY)". This is at odds with the way we use the keyword in other commands, but ISTM we don't _need_ to support that legacy syntax. Anyway, this is easy to put back afterwards, if enough people find it not useless. I've not reviewed 0003 in depth yet, just rebased it. But it works to the point that CI is happy with it. I've not yet included Antonin's 0006 and 0007. TODO list for 0001: - addition of src/bin/scripts/repackdb - clean up the progress report infrastructure - doc review -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest "foo" someone someday shall type "supercalifragilisticexpialidocious" (5th Commandment for C programmers) -
Re: Adding REPACK [concurrently]
Fujii Masao <masao.fujii@gmail.com> — 2025-08-01T11:07:05Z
On Fri, Aug 1, 2025 at 1:50 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > One of the later patches in the series, which I have not included yet, > intends to implement the idea of transiently enabling wal_level=logical > for the table being repacked concurrently, so that you can still use > the concurrent mode if you have a non-logical-wal_level instance. Sounds good to me! > Here's v17. I just tried REPACK command and observed a few things: When I repeatedly ran REPACK on the regression database while make installcheck was running, I got the following error: ERROR: StartTransactionCommand: unexpected state STARTED "REPACK (VERBOSE);" failed with the following error. ERROR: syntax error at or near ";" REPACK (CONCURRENTLY) USING INDEX failed with the following error, while the same command without CONCURRENTLY completed successfully: =# REPACK (CONCURRENTLY) parallel_vacuum_table using index regular_sized_index ; ERROR: cannot process relation "parallel_vacuum_table" HINT: Relation "parallel_vacuum_table" has no identity index. When I ran REPACK (CONCURRENTLY) on a table that's also a logical replication target, I saw the following log messages. Is this expected? =# REPACK (CONCURRENTLY) t; LOG: logical decoding found consistent point at 1/00021F20 DETAIL: There are no running transactions. STATEMENT: REPACK (CONCURRENTLY) t; Regards, -- Fujii Masao -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-04T23:21:00Z
Hello Álvaro, Should we skip non-ready indexes in build_new_indexes? Also, in the current implementation, concurrent mode is marked as non-MVCC safe. From my point of view, this is a significant limitation for practical use. Should we consider an option to exchange non-MVCC issues to short exclusive lock of ProcArrayLock + cancellation of some transactions with older xmin? Best regards, Mikhail
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-05T08:58:44Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > I made a few changes from Antonin's original at [2]. First, I modified > the grammar to support "REPACK [tab] USING INDEX" without specifying the > index name. With this change, all possibilities of the old commands are > covered, ... > Here's a list of existing commands, and how to write them in the current > patch's proposal for REPACK: > > -- re-clusters all tables that have a clustered index set > CLUSTER -> REPACK USING INDEX > > -- clusters the given table using the given index > CLUSTER tab USING idx -> REPACK tab USING INDEX idx > > -- clusters this table using a clustered index; error if no index clustered > CLUSTER tab -> REPACK tab USING INDEX > > -- vacuum-full all tables > VACUUM FULL -> REPACK > > -- vacuum-full the specified table > VACUUM FULL tab -> REPACK tab > Now that we want to cover the CLUSTER/VACUUM FULL completely, I've checked the options of VACUUM FULL. I found two items not supported by REPACK (but also not supported by by CLUSTER): ANALYZE and SKIP_DATABASE_STATS. Maybe just let's mention that in the user documentation of REPACK? (Besides that, VACUUM FULL accepts TRUNCATE and INDEX_CLEANUP options, but I think these have no effect.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-09T12:55:00Z
Hello! One more thing - I think build_new_indexes and index_concurrently_create_copy are very close in semantics, so it might be a good idea to refactor them a bit. I’m still concerned about MVCC-related issues. For multiple applications, this is a dealbreaker, because in some cases correctness is a higher priority than availability. Possible options: 1) Terminate connections with old snapshots. Add a flag to terminate all connections with snapshots during the ExclusiveLock period for the swap. From the application’s perspective, this is not a big deal - it's similar to a primary switch. We would also need to prevent new snapshots from being taken during the swap transaction, so a short exclusive lock on ProcArrayLock would also be required. 2) MVCC-safe two-phase approach (inspired by CREATE INDEX). - copy the data from T1 to the new table T2. - apply the log. - take a table-exclusive lock on T1 - apply the log again. - instead of swapping, mark the T2 as a kind of shadow table - any transaction applying changes to T1 must also apply them to T2, while reads still use T1 as the source of truth. - commit (and record the transaction ID as XID1). - at this point, all changes are applied to both tables with the same XIDs because of the "shadow table" mechanism. - wait until older snapshots no longer treat XID1 as uncommitted. - now the tables are identical from the MVCC perspective. - take an exclusive lock on both T1 and T2. - perform the swap and drop T1. - commit. This is more complex and would require implementing some sort of "shadow table" mechanism, so it might not be worth the effort. Option 1 feels more appealing to me. If others think this is a good idea, I might try implementing a proof of concept. Best regards, Mikhail
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-09T13:33:31Z
On 2025-Aug-09, Mihail Nikalayeu wrote: > Hello! > > One more thing - I think build_new_indexes and > index_concurrently_create_copy are very close in semantics, so it > might be a good idea to refactor them a bit. > > I’m still concerned about MVCC-related issues. For multiple > applications, this is a dealbreaker, because in some cases correctness > is a higher priority than availability. Please note that Antonin already implemented this. See his patches here: https://www.postgresql.org/message-id/77690.1725610115%40antos I proposed to leave this part out initially, which is why it hasn't been reposted. We can review and discuss after the initial patches are in. Because having an MVCC-safe mode has drawbacks, IMO we should make it optional. But you're welcome to review that part specifically if you're so inclined, and offer feedback on it. (I suggest to rewind back your checked-out tree to branch master at the time that patch was posted, for easy application. We can deal with a rebase later.) -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ Officer Krupke, what are we to do? Gee, officer Krupke, Krup you! (West Side Story, "Gee, Officer Krupke")
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-11T14:22:00Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > One more thing - I think build_new_indexes and > index_concurrently_create_copy are very close in semantics, so it > might be a good idea to refactor them a bit. You're right. I think I even used the latter for reference when writing the first. 0002 in the attached series tries to fix that. build_new_indexes() (in 0004) is simpler now. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-15T12:32:09Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > > One more thing - I think build_new_indexes and > > index_concurrently_create_copy are very close in semantics, so it > > might be a good idea to refactor them a bit. > > You're right. I think I even used the latter for reference when writing the > first. > > 0002 in the attached series tries to fix that. build_new_indexes() (in 0004) > is simpler now. This is v18 again. Parts 0001 through 0004 are unchanged, however 0005 is added. It implements a new client application pg_repackdb. (If I posted 0005 alone its regression tests would not work. I wonder if the cfbot handles the repeated occurence of the 'v18-' prefix correctly.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-15T12:48:17Z
On 2025-Aug-15, Antonin Houska wrote: > This is v18 again. Thanks for this! > Parts 0001 through 0004 are unchanged, however 0005 is added. It > implements a new client application pg_repackdb. (If I posted 0005 > alone its regression tests would not work. I wonder if the cfbot > handles the repeated occurence of the 'v18-' prefix correctly.) Yeah, the cfbot is just going to take the attachments from the latest email in the thread that has any, and assume they are the whole that make up the patch. It wouldn't work to post just v18-0005 and assume that the bot is going grab patches 0001 through 0004 from a previous email, if that's what you're thinking. In short, what you did is correct and necessary. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-08-16T13:41:57Z
On Tue, Aug 5, 2025 at 4:59 AM Antonin Houska <ah@cybertec.at> wrote: > > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > I made a few changes from Antonin's original at [2]. First, I modified > > the grammar to support "REPACK [tab] USING INDEX" without specifying the > > index name. With this change, all possibilities of the old commands are > > covered, > > ... > > > Here's a list of existing commands, and how to write them in the current > > patch's proposal for REPACK: > > > > -- re-clusters all tables that have a clustered index set > > CLUSTER -> REPACK USING INDEX > > > > -- clusters the given table using the given index > > CLUSTER tab USING idx -> REPACK tab USING INDEX idx > > > > -- clusters this table using a clustered index; error if no index clustered > > CLUSTER tab -> REPACK tab USING INDEX > > In the v18 patch, the docs say that repack doesn't remember the index, but it seems we are still calling mark_index_clustered, so I think the above is true but we need to update the docs(?). > > -- vacuum-full all tables > > VACUUM FULL -> REPACK > > > > -- vacuum-full the specified table > > VACUUM FULL tab -> REPACK tab > > > > Now that we want to cover the CLUSTER/VACUUM FULL completely, I've checked the > options of VACUUM FULL. I found two items not supported by REPACK (but also > not supported by by CLUSTER): ANALYZE and SKIP_DATABASE_STATS. Maybe just > let's mention that in the user documentation of REPACK? > I would note that both pg_repack and pg_squeeze analyze by default, and running "vacuum full analyze" is the recommended behavior, so not having analyze included is a step backwards. > (Besides that, VACUUM FULL accepts TRUNCATE and INDEX_CLEANUP options, but I > think these have no effect.) > Yeah, these seem safe to ignore. Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-19T12:22:47Z
On 2025-Aug-16, Robert Treat wrote: > On Tue, Aug 5, 2025 at 4:59 AM Antonin Houska <ah@cybertec.at> wrote: > > Now that we want to cover the CLUSTER/VACUUM FULL completely, I've checked the > > options of VACUUM FULL. I found two items not supported by REPACK (but also > > not supported by by CLUSTER): ANALYZE and SKIP_DATABASE_STATS. Maybe just > > let's mention that in the user documentation of REPACK? > > I would note that both pg_repack and pg_squeeze analyze by default, > and running "vacuum full analyze" is the recommended behavior, so not > having analyze included is a step backwards. Make sense to add ANALYZE as an option to repack, yeah. So if I repack a single table with REPACK (ANALYZE) table USING INDEX; then do you expect that this would first cluster the table under AccessExclusiveLock, then release the lock to do the analyze step, or would the analyze be done under the same lock? This is significant for a query that starts while repack is running, because if we release the AEL then the query is planned when there are no stats for the table, which might be bad. I think the time to run the analyze step should be considerable shorter than the time to run the repacking step, so running both together under the same lock should be okay. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Computing is too important to be left to men." (Karen Spärck Jones)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-19T12:23:32Z
On 2025-Aug-16, Robert Treat wrote: > In the v18 patch, the docs say that repack doesn't remember the index, > but it seems we are still calling mark_index_clustered, so I think the > above is true but we need to update the docs(?). Yes, the docs are obsolete on this point, I'm in the process of updating them. Thanks for pointing this out. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "La victoria es para quien se atreve a estar solo"
-
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-08-19T18:53:32Z
Hello, Here's a second cut of the initial REPACK work. Antonin added an implementation of pg_repackdb, and there's also a couple of bug fixes that were reported in the thread. I also added support for the ANALYZE option as noted by Robert Treat, though it only works if you specify a single non-partitioned table. Adding for the multi-table case is likely easy, but I didn't try. I purposefully do not include the CONCURRENTLY work yet -- I want to get this part commitable-clean first, then we can continue work on the logical decoding work on top of that. Note choice of shell command name: though all the other programs in src/bin/scripts do not use the "pg_" prefix, this one does; we thought it made no sense to follow the old programs as precedent because there seems to be a lament for the lack of pg_ prefix in those, and we only keep what they are because of their long history. This one has no history. Still on pg_repackdb, the implementation here is to install a symlink called pg_repackdb which points to vacuumdb, and make the program behave differently when called in this way. The amount of additional code for this is relatively small, so I think this is a worthy technique -- assuming it works. If it doesn't, Antonin proposed a separate binary that just calls some functions from vacuumdb. Or maybe we could have a common source file that both utilities call. I edited the docs a bit, limiting the exposure of CLUSTER and VACUUM FULL, and instead redirecting the user to the REPACK docs. In the REPACK docs I modified things for additional clarity. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Selbst das größte Genie würde nicht weit kommen, wenn es alles seinem eigenen Innern verdanken wollte." (Johann Wolfgang von Goethe) Ni aún el genio más grande llegaría muy lejos si quisiera sacarlo todo de su propio interior. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-20T08:33:49Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2025-Aug-16, Robert Treat wrote: > > > On Tue, Aug 5, 2025 at 4:59 AM Antonin Houska <ah@cybertec.at> wrote: > > > > Now that we want to cover the CLUSTER/VACUUM FULL completely, I've checked the > > > options of VACUUM FULL. I found two items not supported by REPACK (but also > > > not supported by by CLUSTER): ANALYZE and SKIP_DATABASE_STATS. Maybe just > > > let's mention that in the user documentation of REPACK? > > > > I would note that both pg_repack and pg_squeeze analyze by default, > > and running "vacuum full analyze" is the recommended behavior, so not > > having analyze included is a step backwards. > > Make sense to add ANALYZE as an option to repack, yeah. > > So if I repack a single table with > REPACK (ANALYZE) table USING INDEX; > > then do you expect that this would first cluster the table under > AccessExclusiveLock, then release the lock to do the analyze step, or > would the analyze be done under the same lock? This is significant for > a query that starts while repack is running, because if we release the > AEL then the query is planned when there are no stats for the table, > which might be bad. > > I think the time to run the analyze step should be considerable shorter > than the time to run the repacking step, so running both together under > the same lock should be okay. AFAICS, VACUUM FULL first releases the AEL, then it analyzes the table. If users did not complain so far, I'd assume that vacuum_rel() (effectively cluster_rel() in the FULL case) does not change the stats that much. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-20T08:53:13Z
Álvaro Herrera <alvherre@kurilemu.de> wrote: > Still on pg_repackdb, the implementation here is to install a symlink > called pg_repackdb which points to vacuumdb, and make the program behave > differently when called in this way. The amount of additional code for > this is relatively small, so I think this is a worthy technique -- > assuming it works. If it doesn't, Antonin proposed a separate binary > that just calls some functions from vacuumdb. Or maybe we could have a > common source file that both utilities call. There's an issue with the symlink, maybe some meson expert can help. In particular, the CI on Windows ends up with the following error: ERROR: Tried to install symlink to missing file C:/cirrus/build/tmp_install/usr/local/pgsql/bin/vacuumdb (The reason it does not happen on other platforms might be that the build is slower on Windows, and thus it's more prone to some specific race conditions.) It appears that the 'point_to' argument of the 'install_symlink()' function [1] is only a string rather than a "real target" [2]. That's likely the reason the function does not wait for the creation of the 'vacuumdb' executable. I could not find another symlink of this kind in the tree. (AFAICS, the postmaster->postgres symlink had been removed before Meson has been introduced.) Does anyone happen to have a clue? Thanks. [1] https://mesonbuild.com/Reference-manual_functions.html#install_symlink [2] https://mesonbuild.com/Reference-manual_returned_tgt.html -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-08-20T12:07:37Z
On 2025-Aug-20, Antonin Houska wrote: > There's an issue with the symlink, maybe some meson expert can help. In > particular, the CI on Windows ends up with the following error: > > ERROR: Tried to install symlink to missing file C:/cirrus/build/tmp_install/usr/local/pgsql/bin/vacuumdb Hmm, that's not the problem I see in the CI run from the commitfest app: https://cirrus-ci.com/task/5608274336153600 [19:11:00.642] FAILED: [code=2] src/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj [19:11:00.642] "cl" "-Isrc\bin\scripts\vacuumdb.exe.p" "-Isrc\include" "-I..\src\include" "-Ic:\openssl\1.1\include" "-I..\src\include\port\win32" "-I..\src\include\port\win32_msvc" "-Isrc/interfaces/libpq" "-I..\src\interfaces\libpq" "/MDd" "/nologo" "/showIncludes" "/utf-8" "/W2" "/Od" "/Zi" "/Zc:preprocessor" "/DWIN32" "/DWINDOWS" "/D__WINDOWS__" "/D__WIN32__" "/D_CRT_SECURE_NO_DEPRECATE" "/D_CRT_NONSTDC_NO_DEPRECATE" "/wd4018" "/wd4244" "/wd4273" "/wd4101" "/wd4102" "/wd4090" "/wd4267" "/Fdsrc\bin\scripts\vacuumdb.exe.p\vacuumdb.c.pdb" /Fosrc/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj "/c" ../src/bin/scripts/vacuumdb.c [19:11:00.642] ../src/bin/scripts/vacuumdb.c(186): error C2059: syntax error: '}' [19:11:00.642] ../src/bin/scripts/vacuumdb.c(197): warning C4034: sizeof returns 0 The real problem here seems to be the empty long_options_repack array. I removed it and started a new run to see what happens. Running now: https://cirrus-ci.com/build/4961902171783168 -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-20T14:22:41Z
Álvaro Herrera <alvherre@kurilemu.de> wrote: > On 2025-Aug-20, Antonin Houska wrote: > > > There's an issue with the symlink, maybe some meson expert can help. In > > particular, the CI on Windows ends up with the following error: > > > > ERROR: Tried to install symlink to missing file C:/cirrus/build/tmp_install/usr/local/pgsql/bin/vacuumdb > > Hmm, that's not the problem I see in the CI run from the commitfest app: > > https://cirrus-ci.com/task/5608274336153600 I was referring to the other build that you shared off-list (probably independent from cfbot): https://cirrus-ci.com/build/4726227505774592 > [19:11:00.642] FAILED: [code=2] src/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj > [19:11:00.642] "cl" "-Isrc\bin\scripts\vacuumdb.exe.p" "-Isrc\include" "-I..\src\include" "-Ic:\openssl\1.1\include" "-I..\src\include\port\win32" "-I..\src\include\port\win32_msvc" "-Isrc/interfaces/libpq" "-I..\src\interfaces\libpq" "/MDd" "/nologo" "/showIncludes" "/utf-8" "/W2" "/Od" "/Zi" "/Zc:preprocessor" "/DWIN32" "/DWINDOWS" "/D__WINDOWS__" "/D__WIN32__" "/D_CRT_SECURE_NO_DEPRECATE" "/D_CRT_NONSTDC_NO_DEPRECATE" "/wd4018" "/wd4244" "/wd4273" "/wd4101" "/wd4102" "/wd4090" "/wd4267" "/Fdsrc\bin\scripts\vacuumdb.exe.p\vacuumdb.c.pdb" /Fosrc/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj "/c" ../src/bin/scripts/vacuumdb.c > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(186): error C2059: syntax error: '}' > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(197): warning C4034: sizeof returns 0 > > The real problem here seems to be the empty long_options_repack array. > I removed it and started a new run to see what happens. Running now: > https://cirrus-ci.com/build/4961902171783168 The symlink issue occurred at "Windows - Server 2019, MinGW64 - Meson", where the code compiled well. The compilation failure mentioned above comes from "Windows - Server 2019, VS 2019 - Meson & ninja". I think it's still possible that the symlink issue will occur there once the compilation is fixed. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2025-08-20T16:11:39Z
Hi, On 2025-08-20 16:22:41 +0200, Antonin Houska wrote: > Álvaro Herrera <alvherre@kurilemu.de> wrote: > > > On 2025-Aug-20, Antonin Houska wrote: > > > > > There's an issue with the symlink, maybe some meson expert can help. In > > > particular, the CI on Windows ends up with the following error: > > > > > > ERROR: Tried to install symlink to missing file C:/cirrus/build/tmp_install/usr/local/pgsql/bin/vacuumdb > > > > Hmm, that's not the problem I see in the CI run from the commitfest app: > > > > https://cirrus-ci.com/task/5608274336153600 > > I was referring to the other build that you shared off-list (probably > independent from cfbot): > > https://cirrus-ci.com/build/4726227505774592 > > > [19:11:00.642] FAILED: [code=2] src/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj > > [19:11:00.642] "cl" "-Isrc\bin\scripts\vacuumdb.exe.p" "-Isrc\include" "-I..\src\include" "-Ic:\openssl\1.1\include" "-I..\src\include\port\win32" "-I..\src\include\port\win32_msvc" "-Isrc/interfaces/libpq" "-I..\src\interfaces\libpq" "/MDd" "/nologo" "/showIncludes" "/utf-8" "/W2" "/Od" "/Zi" "/Zc:preprocessor" "/DWIN32" "/DWINDOWS" "/D__WINDOWS__" "/D__WIN32__" "/D_CRT_SECURE_NO_DEPRECATE" "/D_CRT_NONSTDC_NO_DEPRECATE" "/wd4018" "/wd4244" "/wd4273" "/wd4101" "/wd4102" "/wd4090" "/wd4267" "/Fdsrc\bin\scripts\vacuumdb.exe.p\vacuumdb.c.pdb" /Fosrc/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj "/c" ../src/bin/scripts/vacuumdb.c > > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(186): error C2059: syntax error: '}' > > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(197): warning C4034: sizeof returns 0 > > > > The real problem here seems to be the empty long_options_repack array. > > I removed it and started a new run to see what happens. Running now: > > https://cirrus-ci.com/build/4961902171783168 > > The symlink issue occurred at "Windows - Server 2019, MinGW64 - Meson", where > the code compiled well. The compilation failure mentioned above comes from > "Windows - Server 2019, VS 2019 - Meson & ninja". I think it's still possible > that the symlink issue will occur there once the compilation is fixed. FWIW, I don't think it's particularly wise to rely on symlinks on windows - IIRC they will often not be enabled outside of development environments. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-20T23:44:00Z
Hello everyone! Alvaro Herrera <alvherre@alvh.no-ip.org>: > Please note that Antonin already implemented this. See his patches > here: > https://www.postgresql.org/message-id/77690.1725610115%40antos > I proposed to leave this part out initially, which is why it hasn't been > reposted. We can review and discuss after the initial patches are in. I think it is worth pushing it at least in the same release cycle. > But you're welcome to review that part specifically if you're so > inclined, and offer feedback on it. (I suggest to rewind back your > checked-out tree to branch master at the time that patch was posted, for > easy application. We can deal with a rebase later.) I have rebased that on top of v18 (attached). Also, I think I found an issue (or lost something during rebase): we must preserve xmin,cmin during initial copy to make sure that data is going to be visible by snapshots of concurrent changes later: static void reform_and_rewrite_tuple(......) ..... /*It is also crucial to stamp the new record with the exact same xid and cid, * because the tuple must be visible to the snapshot of the applied concurrent * change later. */ CommandId cid = HeapTupleHeaderGetRawCommandId(tuple->t_data); TransactionId xid = HeapTupleHeaderGetXmin(tuple->t_data); heap_insert(NewHeap, copiedTuple, xid, cid, HEAP_INSERT_NO_LOGICAL, NULL); I'll try to polish that part a little bit. > Because having an MVCC-safe mode has drawbacks, IMO we should make it > optional. Do you mean some option for the command? Like REPACK (CONCURRENTLY, SAFE)? Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-21T18:07:04Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Also, I think I found an issue (or lost something during rebase): we > must preserve xmin,cmin during initial copy > to make sure that data is going to be visible by snapshots of > concurrent changes later: > > static void > reform_and_rewrite_tuple(......) > ..... > /*It is also crucial to stamp the new record with the exact same > xid and cid, > * because the tuple must be visible to the snapshot of the > applied concurrent > * change later. > */ > CommandId cid = HeapTupleHeaderGetRawCommandId(tuple->t_data); > TransactionId xid = HeapTupleHeaderGetXmin(tuple->t_data); > > heap_insert(NewHeap, copiedTuple, xid, cid, HEAP_INSERT_NO_LOGICAL, NULL); When posting version 12 of the patch [1] I raised a concern that the the MVCC safety is too expensive when it comes to logical decoding. Therefore, I abandoned the concept for now, and v13 [2] uses plain heap_insert(). Once we implement the MVCC safety, we simply rewrite the tuple like v12 did - that's the simplest way to preserve fields like xmin, cmin, ... [1] https://www.postgresql.org/message-id/178741.1743514291%40localhost [2] https://www.postgresql.org/message-id/97795.1744363522%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-21T18:14:14Z
Andres Freund <andres@anarazel.de> wrote: > Hi, > > On 2025-08-20 16:22:41 +0200, Antonin Houska wrote: > > Álvaro Herrera <alvherre@kurilemu.de> wrote: > > > > > On 2025-Aug-20, Antonin Houska wrote: > > > > > > > There's an issue with the symlink, maybe some meson expert can help. In > > > > particular, the CI on Windows ends up with the following error: > > > > > > > > ERROR: Tried to install symlink to missing file C:/cirrus/build/tmp_install/usr/local/pgsql/bin/vacuumdb > > > > > > Hmm, that's not the problem I see in the CI run from the commitfest app: > > > > > > https://cirrus-ci.com/task/5608274336153600 > > > > I was referring to the other build that you shared off-list (probably > > independent from cfbot): > > > > https://cirrus-ci.com/build/4726227505774592 > > > > > [19:11:00.642] FAILED: [code=2] src/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj > > > [19:11:00.642] "cl" "-Isrc\bin\scripts\vacuumdb.exe.p" "-Isrc\include" "-I..\src\include" "-Ic:\openssl\1.1\include" "-I..\src\include\port\win32" "-I..\src\include\port\win32_msvc" "-Isrc/interfaces/libpq" "-I..\src\interfaces\libpq" "/MDd" "/nologo" "/showIncludes" "/utf-8" "/W2" "/Od" "/Zi" "/Zc:preprocessor" "/DWIN32" "/DWINDOWS" "/D__WINDOWS__" "/D__WIN32__" "/D_CRT_SECURE_NO_DEPRECATE" "/D_CRT_NONSTDC_NO_DEPRECATE" "/wd4018" "/wd4244" "/wd4273" "/wd4101" "/wd4102" "/wd4090" "/wd4267" "/Fdsrc\bin\scripts\vacuumdb.exe.p\vacuumdb.c.pdb" /Fosrc/bin/scripts/vacuumdb.exe.p/vacuumdb.c.obj "/c" ../src/bin/scripts/vacuumdb.c > > > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(186): error C2059: syntax error: '}' > > > [19:11:00.642] ../src/bin/scripts/vacuumdb.c(197): warning C4034: sizeof returns 0 > > > > > > The real problem here seems to be the empty long_options_repack array. > > > I removed it and started a new run to see what happens. Running now: > > > https://cirrus-ci.com/build/4961902171783168 > > > > The symlink issue occurred at "Windows - Server 2019, MinGW64 - Meson", where > > the code compiled well. The compilation failure mentioned above comes from > > "Windows - Server 2019, VS 2019 - Meson & ninja". I think it's still possible > > that the symlink issue will occur there once the compilation is fixed. > > FWIW, I don't think it's particularly wise to rely on symlinks on windows - > IIRC they will often not be enabled outside of development environments. ok, installing a copy of the same executable with a different name seems more reliable. At least that's how the postmaster->postgres link used to be handled, if I read Makefile correctly. Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2025-08-21T18:16:28Z
Hi, On 2025-08-21 20:14:14 +0200, Antonin Houska wrote: > ok, installing a copy of the same executable with a different name seems more > reliable. At least that's how the postmaster->postgres link used to be > handled, if I read Makefile correctly. Thanks. I have not followed this thread, but I don't think the whole thing of having a single executable with multiple names is worth doing. Just make whatever an option, instead of having multiple "executables". Greetings, Andres
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-08-21T22:06:13Z
On Tue, Aug 19, 2025 at 2:53 PM Álvaro Herrera <alvherre@kurilemu.de> wrote: > Note choice of shell command name: though all the other programs in > src/bin/scripts do not use the "pg_" prefix, this one does; we thought > it made no sense to follow the old programs as precedent because there > seems to be a lament for the lack of pg_ prefix in those, and we only > keep what they are because of their long history. This one has no > history. > > Still on pg_repackdb, the implementation here is to install a symlink > called pg_repackdb which points to vacuumdb, and make the program behave > differently when called in this way. The amount of additional code for > this is relatively small, so I think this is a worthy technique -- > assuming it works. If it doesn't, Antonin proposed a separate binary > that just calls some functions from vacuumdb. Or maybe we could have a > common source file that both utilities call. > What's the plan for clusterdb? It seems like we'd ideally create a stand alone pg_repackdb which replaces clusterdb and also allows us to remove the FULL options from vacuumdb. Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-08-22T09:40:40Z
On 2025-Aug-21, Robert Treat wrote: > What's the plan for clusterdb? It seems like we'd ideally create a > stand alone pg_repackdb which replaces clusterdb and also allows us to > remove the FULL options from vacuumdb. I don't think we should remove clusterdb, to avoid breaking any scripts that work today. As you say, I would create the standalone pg_repackdb to do what we need it to do (namely: run the REPACK commands) and leave vacuumdb and clusterdb alone. Removing the obsolete commands and options can be done in a few years. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Euler Taveira <euler@eulerto.com> — 2025-08-22T20:32:34Z
On Fri, Aug 22, 2025, at 6:40 AM, Álvaro Herrera wrote: > On 2025-Aug-21, Robert Treat wrote: > >> What's the plan for clusterdb? It seems like we'd ideally create a >> stand alone pg_repackdb which replaces clusterdb and also allows us to >> remove the FULL options from vacuumdb. > > I don't think we should remove clusterdb, to avoid breaking any scripts > that work today. As you say, I would create the standalone pg_repackdb > to do what we need it to do (namely: run the REPACK commands) and leave > vacuumdb and clusterdb alone. Removing the obsolete commands and > options can be done in a few years. > I would say that we need to plan the removal of these binaries (clusterdb and vacuumdb). We can start with a warning into clusterdb saying they should use pg_repackdb. In a few years, we can remove clusterdb. There were complaints about binary names without a pg_ prefix in the past [1]. I don't think we need to keep vacuumdb. Packagers can keep a symlink (vacuumdb) to pg_repackdb. We can add a similar warning message saying they should use pg_repackdb if the symlink is used. [1] https://www.postgresql.org/message-id/CAJgfmqXYYKXR%2BQUhEa3cq6pc8dV0Hu7QvOUccm7R0TkC%3DT-%2B%3DA%40mail.gmail.com -- Euler Taveira EDB https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Michael Banck <mbanck@gmx.net> — 2025-08-23T06:56:12Z
Hi, On Fri, Aug 22, 2025 at 05:32:34PM -0300, Euler Taveira wrote: > On Fri, Aug 22, 2025, at 6:40 AM, Álvaro Herrera wrote: > > On 2025-Aug-21, Robert Treat wrote: > >> What's the plan for clusterdb? It seems like we'd ideally create a > >> stand alone pg_repackdb which replaces clusterdb and also allows us to > >> remove the FULL options from vacuumdb. > > > > I don't think we should remove clusterdb, to avoid breaking any scripts > > that work today. As you say, I would create the standalone pg_repackdb > > to do what we need it to do (namely: run the REPACK commands) and leave > > vacuumdb and clusterdb alone. Removing the obsolete commands and > > options can be done in a few years. > > I would say that we need to plan the removal of these binaries (clusterdb and > vacuumdb). We can start with a warning into clusterdb saying they should use > pg_repackdb. In a few years, we can remove clusterdb. There were complaints > about binary names without a pg_ prefix in the past [1]. Yeah. > I don't think we need to keep vacuumdb. Packagers can keep a symlink (vacuumdb) > to pg_repackdb. We can add a similar warning message saying they should use > pg_repackdb if the symlink is used. Unless pg_repack has the same (or a superset of) CLI and behaviour as vacuumdb (I haven't checked, but doubt it?), I think replacing vacuumdb with a symlink to pg_repack will lead to much more breakage in existing scripts/automation than clusterdb, which I guess is used orders of magnitude less frequently than vacumdb. Michael
-
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-08-23T14:22:11Z
On 2025-08-23, Michael Banck wrote: > On Fri, Aug 22, 2025 at 05:32:34PM -0300, Euler Taveira wrote: >> I don't think we need to keep vacuumdb. Packagers can keep a symlink (vacuumdb) >> to pg_repackdb. We can add a similar warning message saying they should use >> pg_repackdb if the symlink is used. > > Unless pg_repack has the same (or a superset of) CLI and behaviour as > vacuumdb (I haven't checked, but doubt it?), I think replacing vacuumdb > with a symlink to pg_repack will lead to much more breakage in existing > scripts/automation than clusterdb, which I guess is used orders of > magnitude less frequently than vacumdb. Yeah, I completely disagree with the idea of getting rid of vacuumdb. We can, maybe, in a distant future, get rid of the --full option to vacuumdb. But the rest of the vacuumdb behavior must stay, I think, because REPACK is not VACUUM — it is only VACUUM FULL. And we want to make that distinction very clear. We can also, in a few years, get rid of clusterdb. But I don't think we need to deprecate it just yet. -- Álvaro Herrera
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-24T16:52:00Z
Hello, Antonin! > When posting version 12 of the patch [1] I raised a concern that the the MVCC > safety is too expensive when it comes to logical decoding. Therefore, I > abandoned the concept for now, and v13 [2] uses plain heap_insert(). Once we > implement the MVCC safety, we simply rewrite the tuple like v12 did - that's > the simplest way to preserve fields like xmin, cmin, ... Thanks for the explanation. I was looking into catalog-related logical decoding features, and it seems like they are clearly overkill for the repack case. We don't need CID tracking or even a snapshot for each commit if we’re okay with passing xmin/xmax as arguments. What do you think about the following approach for replaying: * use the extracted XID as the value for xmin/xmax. * use SnapshotSelf to find the tuple for update/delete operations. SnapshotSelf seems like a good fit here: * it sees the last "existing" version. * any XID set as xmin/xmax in the repacked version is already committed - so each update/insert is effectively "committed" once written. * it works with multiple updates of the same tuple within a single transaction - SnapshotSelf sees the last version. * all updates are ordered and replayed sequentially - so the last version is always the one we want. If I'm not missing anything, this looks like something worth including in the patch set. If so, I can try implementing a test version. Best regards, Mikhail
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-25T13:09:18Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > I was looking into catalog-related logical decoding features, and it > seems like they are clearly overkill for the repack case. > We don't need CID tracking or even a snapshot for each commit if we’re > okay with passing xmin/xmax as arguments. I assume you are concerned with the patch part 0005 of the v12 patch ("Preserve visibility information of the concurrent data changes."), aren't you? > What do you think about the following approach for replaying: > * use the extracted XID as the value for xmin/xmax. > * use SnapshotSelf to find the tuple for update/delete operations. > > SnapshotSelf seems like a good fit here: > * it sees the last "existing" version. > * any XID set as xmin/xmax in the repacked version is already > committed - so each update/insert is effectively "committed" once > written. > * it works with multiple updates of the same tuple within a single > transaction - SnapshotSelf sees the last version. > * all updates are ordered and replayed sequentially - so the last > version is always the one we want. > > If I'm not missing anything, this looks like something worth including > in the patch set. > If so, I can try implementing a test version. Not sure I understand in all details, but I don't think SnapshotSelf is the correct snapshot. Note that HeapTupleSatisfiesSelf() does not use its 'snapshot' argument at all. Instead, it considers the set of running transactions as it is at the time the function is called. One particular problem I imagine is replaying an UPDATE to a row that some later transaction will eventually delete, but the transaction that ran the UPDATE obviously had to see it. When looking for the old version during the replay, HeapTupleSatisfiesMVCC() will find the old version as long as we pass the correct snapshot to it. However, at the time we're replaying the UPDATE in the new table, the tuple may have been already deleted from the old table, and the deleting transaction may already have committed. In such a case, HeapTupleSatisfiesSelf() will conclude the old version invisible and the we'll fail to replay the UPDATE. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-25T14:15:16Z
Hi, Antonin! > I assume you are concerned with the patch part 0005 of the v12 patch > ("Preserve visibility information of the concurrent data changes."), aren't > you? Yes, of course. I got an idea while trying to find a way to optimize it. > Not sure I understand in all details, but I don't think SnapshotSelf is the > correct snapshot. Note that HeapTupleSatisfiesSelf() does not use its > 'snapshot' argument at all. Instead, it considers the set of running > transactions as it is at the time the function is called. Yes, and it is almost the same behavior when a typical MVCC snapshot encounters a tuple created by its own transaction. So, how it works in the non MVCC-safe case (current patch behaviour): 1) we have a whole initial table snapshot with all the xmin = repack XID 2) appling transaction sees ALL the self-alive (no xmax) tuples in it because all tuples created\deleted by transaction itself 3) each update/delete during the replay selects the last existing tuple version, updates it xmax and inserts a new one 4) so, there is no any real MVCC involved - just find the latest version and create a new version 5) and it works correctly because all ordering issues were resolved by locking mechanisms on the original table or by reordering buffer How it maps to MVCC-safe case (SnapshotSelf): 1) we have a whole initial table snapshot with all xmin copied from the original table. All such xmin are committed. 2) appling transaction sees ALL the self-alive (no xmax) tuple in it because its xmin\xmax is committed and SnapshotSelf is happy with it 3) each update/delete during the replay selects the last existing tuple version, updates it xmax=original xid and inserts a new one keeping with xmin=orignal xid 4) --//-- 5) --//-- > However, at the time we're replaying the UPDATE in the new table, the tuple > may have been already deleted from the old table, and the deleting transaction > may already have committed. In such a case, HeapTupleSatisfiesSelf() will > conclude the old version invisible and the we'll fail to replay the UPDATE. No, it will see it - because its xmax will be empty in the repacked version of the table. From your question I feel you understood the concept - but feel free to ask for an additional explanation/scheme. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-25T15:42:15Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > Not sure I understand in all details, but I don't think SnapshotSelf is the > > correct snapshot. Note that HeapTupleSatisfiesSelf() does not use its > > 'snapshot' argument at all. Instead, it considers the set of running > > transactions as it is at the time the function is called. > > Yes, and it is almost the same behavior when a typical MVCC snapshot > encounters a tuple created by its own transaction. > > So, how it works in the non MVCC-safe case (current patch behaviour): > > 1) we have a whole initial table snapshot with all the xmin = repack XID > 2) appling transaction sees ALL the self-alive (no xmax) tuples in it > because all tuples created\deleted by transaction itself > 3) each update/delete during the replay selects the last existing > tuple version, updates it xmax and inserts a new one > 4) so, there is no any real MVCC involved - just find the latest > version and create a new version > 5) and it works correctly because all ordering issues were resolved by > locking mechanisms on the original table or by reordering buffer ok > How it maps to MVCC-safe case (SnapshotSelf): > > 1) we have a whole initial table snapshot with all xmin copied from > the original table. All such xmin are committed. > 2) appling transaction sees ALL the self-alive (no xmax) tuple in it > because its xmin\xmax is committed and SnapshotSelf is happy with it How does HeapTupleSatisfiesSelf() recognize the status of any XID w/o using a snapshot? Do you mean by checking the commit log (TransactionIdDidCommit) ? > 3) each update/delete during the replay selects the last existing > tuple version, updates it xmax=original xid and inserts a new one > keeping with xmin=orignal xid > 4) --//-- > 5) --//-- > > However, at the time we're replaying the UPDATE in the new table, the tuple > > may have been already deleted from the old table, and the deleting transaction > > may already have committed. In such a case, HeapTupleSatisfiesSelf() will > > conclude the old version invisible and the we'll fail to replay the UPDATE. > > No, it will see it - because its xmax will be empty in the repacked > version of the table. You're right, it'll be empty in the new table. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-08-25T16:03:03Z
On Sat, Aug 23, 2025 at 10:23 AM Álvaro Herrera <alvherre@kurilemu.de> wrote: > On 2025-08-23, Michael Banck wrote: > > On Fri, Aug 22, 2025 at 05:32:34PM -0300, Euler Taveira wrote: > > >> I don't think we need to keep vacuumdb. Packagers can keep a symlink (vacuumdb) > >> to pg_repackdb. We can add a similar warning message saying they should use > >> pg_repackdb if the symlink is used. > > > > Unless pg_repack has the same (or a superset of) CLI and behaviour as > > vacuumdb (I haven't checked, but doubt it?), I think replacing vacuumdb > > with a symlink to pg_repack will lead to much more breakage in existing > > scripts/automation than clusterdb, which I guess is used orders of > > magnitude less frequently than vacumdb. > > Yeah, I completely disagree with the idea of getting rid of vacuumdb. We can, maybe, in a distant future, get rid of the --full option to vacuumdb. But the rest of the vacuumdb behavior must stay, I think, because REPACK is not VACUUM — it is only VACUUM FULL. And we want to make that distinction very clear. > Or to put it the other way, VACUUM FULL is not really VACUUM either, it is really a form of "repack". > We can also, in a few years, get rid of clusterdb. But I don't think we need to deprecate it just yet. > Yeah, ISTM the long term goal should be two binaries, one of which manages aspects of clustering/repacking type of activities, and one which manages vacuum type activities. I don't think that's different that what Alvaro is proposing, FWIW my original question was about confirming that was the end goal, but also trying to understand the coordination of when these changes would take place, because the changes to the code, changes to the SQL commands and their docs, and changes to the command line tools, seem to be working at different cadences. Which can be fine if it's on purpose, but maybe needs to be tightened up if not; for example, the current patchset doesn't make any changes to clusterdb, which one might expect to emit a warning about being deprecated in favor of pg_repackdb, if not just a complete punting to use pg_repackdb instead. Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-25T16:23:09Z
Hi, Antonin > How does HeapTupleSatisfiesSelf() recognize the status of any XID w/o using a > snapshot? Do you mean by checking the commit log (TransactionIdDidCommit) ? Yes, TransactionIdDidCommit. Another option is just invent a new snapshot type - SnapshotBelieveEverythingCommitted - for that particular case it should work - because all xmin/xmax written into the new table are committed by design.
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-08-25T16:36:10Z
On Mon, Aug 25, 2025 at 10:15 AM Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > 1) we have a whole initial table snapshot with all xmin copied from > the original table. All such xmin are committed. > 2) appling transaction sees ALL the self-alive (no xmax) tuple in it > because its xmin\xmax is committed and SnapshotSelf is happy with it > 3) each update/delete during the replay selects the last existing > tuple version, updates it xmax=original xid and inserts a new one > keeping with xmin=orignal xid > 4) --//-- > 5) --//-- > Advancing the tables min xid to at least repack XID is a pretty big feature, but the above scenario sounds like it would result in any non-modified pre-existing tuples ending up with their original xmin rather than repack XID, which seems like it could lead to weird side-effects. Maybe I am mis-thinking it though? Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-25T16:54:12Z
Robert Treat <rob@xzilla.net> wrote: > On Mon, Aug 25, 2025 at 10:15 AM Mihail Nikalayeu > <mihailnikalayeu@gmail.com> wrote: > > 1) we have a whole initial table snapshot with all xmin copied from > > the original table. All such xmin are committed. > > 2) appling transaction sees ALL the self-alive (no xmax) tuple in it > > because its xmin\xmax is committed and SnapshotSelf is happy with it > > 3) each update/delete during the replay selects the last existing > > tuple version, updates it xmax=original xid and inserts a new one > > keeping with xmin=orignal xid > > 4) --//-- > > 5) --//-- > > > > Advancing the tables min xid to at least repack XID is a pretty big > feature, but the above scenario sounds like it would result in any > non-modified pre-existing tuples ending up with their original xmin > rather than repack XID, which seems like it could lead to weird > side-effects. Maybe I am mis-thinking it though? What we discuss here is how to keep visibility information of tuples (xmin, xmax, ...) unchanged. Both CLUSTER and VACUUM FULL already do that. However it's not trivial to ensure that REPACK with the CONCURRENTLY option does as well. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-25T17:22:14Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hi, Antonin > > > How does HeapTupleSatisfiesSelf() recognize the status of any XID w/o using a > > snapshot? Do you mean by checking the commit log (TransactionIdDidCommit) ? > > Yes, TransactionIdDidCommit. I think the problem is that HeapTupleSatisfiesSelf() uses TransactionIdIsInProgress() instead of checking the snapshot: ... else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) return false; else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) ... When decoding (and replaying) data changes, you deal with the database state as it was (far) in the past. However TransactionIdIsInProgress() is not suitable for this purpose. And since CommitTransaction() updates the commit log before removing the transaction from ProcArray, I can even imagine race conditions: if a transaction is committed and decoded fast enough, TransactionIdIsInProgress() might still return true. In such a case, HeapTupleSatisfiesSelf() returns false instead of calling TransactionIdDidCommit(). > Another option is just invent a new > snapshot type - SnapshotBelieveEverythingCommitted - for that > particular case it should work - because all xmin/xmax written into > the new table are committed by design. I'd prefer optimization of the logical decoding for REPACK CONCURRENTLY, and using the MVCC snapshots. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-25T18:18:17Z
Antonin Houska <ah@cybertec.at>: > I think the problem is that HeapTupleSatisfiesSelf() uses > TransactionIdIsInProgress() instead of checking the snapshot: Yes, some issues might be possible for SnapshotSelf. Possible solution is to override TransactionIdIsCurrentTransactionId to true (like you did with nParallelCurrentXids but just return true). IIUC, in that case all checks are going to behave the same way as in v5 version. > I'd prefer optimization of the logical decoding for REPACK CONCURRENTLY, and > using the MVCC snapshots. It is also possible, but it is much more complex and feels like overkill to me. We need just a way to find the latest version of row in the world of all-committed transactions without any concurrent writers - I am pretty sure it is possible to achieve in a more simple and effective way.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-26T08:46:00Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Antonin Houska <ah@cybertec.at>: > > I think the problem is that HeapTupleSatisfiesSelf() uses > > TransactionIdIsInProgress() instead of checking the snapshot: > > Yes, some issues might be possible for SnapshotSelf. > Possible solution is to override TransactionIdIsCurrentTransactionId > to true (like you did with nParallelCurrentXids but just return true). > IIUC, in that case all checks are going to behave the same way as in v5 version. I assume you mean v12-0005. Yes, that modifies TransactionIdIsCurrentTransactionId(), so that the the transaction being replayed recognizes if it (or its subtransaction) performed particular change itself. Although it could work, I think it'd be confusing to consider the transactions being replayed as "current" from the point of view of the backend that executes REPACK CONCURRENTLY. But the primary issue is that in v12-0005, TransactionIdIsCurrentTransactionId() gets the information on "current transactions" from snapshots - see the calls of SetRepackCurrentXids() before each scan. It's probably not what you want. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-26T09:02:01Z
Antonin Houska <ah@cybertec.at>: > Although it could work, I think it'd be confusing to consider the transactions > being replayed as "current" from the point of view of the backend that > executes REPACK CONCURRENTLY. Just realized SnapshotDirty is the thing that fits into the role - it respects not-yet committed transactions, giving enough information to wait for them. It is already used in a similar pattern in check_exclusion_or_unique_constraint and RelationFindReplTupleByIndex. So, it is easy to detect the case of the race you described previously and retry + there is no sense to hack around TransactionIdIsCurrentTransactionId. BWT, btree + SnapshotDirty has issue [0], but it is a different story and happens only with concurrent updates which are not present in the current scope. [0]: https://www.postgresql.org/message-id/flat/CADzfLwXGhH_qD6RGqPyEeKdmHgr-HpA-tASYdi5onP%2BRyP5TCw%40mail.gmail.com#77f6426ef2d282198f2d930d5334e3fa
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-26T13:31:33Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Antonin Houska <ah@cybertec.at>: > > > Although it could work, I think it'd be confusing to consider the transactions > > being replayed as "current" from the point of view of the backend that > > executes REPACK CONCURRENTLY. > > Just realized SnapshotDirty is the thing that fits into the role - it > respects not-yet committed transactions, giving enough information to > wait for them. > It is already used in a similar pattern in > check_exclusion_or_unique_constraint and RelationFindReplTupleByIndex. > > So, it is easy to detect the case of the race you described previously > and retry + there is no sense to hack around > TransactionIdIsCurrentTransactionId. Where exactly should HeapTupleSatisfiesDirty() conclude that the tuple is visible? TransactionIdIsCurrentTransactionId() will not do w/o the modifications that you proposed earlier [1] and TransactionIdIsInProgress() is not suitable as I explained in [2]. I understand your idea that there are no transaction aborts in the new table, which makes things simpler. I cannot judge if it's worth inventing a new kind of snapshot. Anyway, I think you'd then also need to hack HeapTupleSatisfiesUpdate(). Isn't that too invasive? [1] https://www.postgresql.org/message-id/CADzfLwUqyOmpkLmciecBy4aBN1sohQVZ2Hgc6m-tjSUqDRHwyQ%40mail.gmail.com [2] https://www.postgresql.org/message-id/24483.1756142534%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-27T00:38:00Z
Hello, Antonin! Antonin Houska <ah@cybertec.at>: > > Where exactly should HeapTupleSatisfiesDirty() conclude that the tuple is > visible? TransactionIdIsCurrentTransactionId() will not do w/o the > modifications that you proposed earlier [1] and TransactionIdIsInProgress() is > not suitable as I explained in [2]. HeapTupleSatisfiesDirty is designed to respect even not-yet-committed transactions and provides additional related information. else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) { /* * Return the speculative token to caller. Caller can worry about * xmax, since it requires a conclusively locked row version, and * a concurrent update to this tuple is a conflict of its * purposes. */ if (HeapTupleHeaderIsSpeculative(tuple)) { snapshot->speculativeToken = HeapTupleHeaderGetSpeculativeToken(tuple); Assert(snapshot->speculativeToken != 0); } snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple); /* XXX shouldn't we fall through to look at xmax? */ return true; /* in insertion by other */ } So, it returns true when TransactionIdIsInProgress is true. However, that alone is not sufficient to trust the result in the common case. You may check check_exclusion_or_unique_constraint or RelationFindReplTupleByIndex for that pattern: if xmin is set in the snapshot (a special hack in SnapshotDirty to provide additional information from the check), we wait for the ongoing transaction (or one that is actually committed but not yet properly reflected in the proc array), and then retry the entire tuple search. So, the race condition you explained in [2] will be resolved by a retry, and the changes to TransactionIdIsInProgress described in [1] are not necessary. > I understand your idea that there are no transaction aborts in the new table, > which makes things simpler. I cannot judge if it's worth inventing a new kind > of snapshot. Anyway, I think you'd then also need to hack > HeapTupleSatisfiesUpdate(). Isn't that too invasive? It seems that HeapTupleSatisfiesUpdate is also fine as it currently exists (we'll see the committed version after retry).. The solution appears to be non-invasive: * uses the existing snapshot type * follows the existing usage pattern * leaves TransactionIdIsInProgress and HeapTupleSatisfiesUpdate unchanged The main change is that xmin/xmax values are forced from the arguments - but that seems unavoidable in any case. I'll try to make some kind of prototype this weekend + cover race condition you mentioned in specs. Maybe some corner cases will appear. By the way, there's one more optimization we could apply in both MVCC-safe and non-MVCC-safe cases: setting the HEAP_XMIN_COMMITTED / HEAP_XMAX_COMMITTED bit in the new table: * in the MVCC-safe approach, the transaction is already committed. * in the non-MVCC-safe case, it isn’t committed yet - but no one will examine that bit before it commits (though this approach does feel more fragile). This could help avoid potential storms of full-page writes caused by SetHintBit after the table switch. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-27T06:16:07Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello, Antonin! > > Antonin Houska <ah@cybertec.at>: > > > > Where exactly should HeapTupleSatisfiesDirty() conclude that the tuple is > > visible? TransactionIdIsCurrentTransactionId() will not do w/o the > > modifications that you proposed earlier [1] and TransactionIdIsInProgress() is > > not suitable as I explained in [2]. > > HeapTupleSatisfiesDirty is designed to respect even not-yet-committed > transactions and provides additional related information. > > else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) > { > /* > * Return the speculative token to caller. Caller can worry about > * xmax, since it requires a conclusively locked row version, and > * a concurrent update to this tuple is a conflict of its > * purposes. > */ > if (HeapTupleHeaderIsSpeculative(tuple)) > { > snapshot->speculativeToken = > HeapTupleHeaderGetSpeculativeToken(tuple); > > Assert(snapshot->speculativeToken != 0); > } > > snapshot->xmin = HeapTupleHeaderGetRawXmin(tuple); > /* XXX shouldn't we fall through to look at xmax? */ > return true; /* in insertion by other */ > } > > So, it returns true when TransactionIdIsInProgress is true. > However, that alone is not sufficient to trust the result in the common case. > > You may check check_exclusion_or_unique_constraint or > RelationFindReplTupleByIndex for that pattern: > if xmin is set in the snapshot (a special hack in SnapshotDirty to > provide additional information from the check), we wait for the > ongoing transaction (or one that is actually committed but not yet > properly reflected in the proc array), and then retry the entire tuple > search. > > So, the race condition you explained in [2] will be resolved by a > retry, and the changes to TransactionIdIsInProgress described in [1] > are not necessary. I insist that this is a misuse of TransactionIdIsInProgress(). When dealing with logical decoding, only WAL should tell whether particular transaction is still running. AFAICS this is how reorderbuffer.c works. A new kind of snapshot seems like (much) cleaner solution at the moment. > I'll try to make some kind of prototype this weekend + cover race > condition you mentioned in specs. > Maybe some corner cases will appear. No rush. First, the MVCC safety is not likely to be included in v19 [1]. Second, I think it's good to let others propose their ideas before writing code. > By the way, there's one more optimization we could apply in both > MVCC-safe and non-MVCC-safe cases: setting the HEAP_XMIN_COMMITTED / > HEAP_XMAX_COMMITTED bit in the new table: > * in the MVCC-safe approach, the transaction is already committed. > * in the non-MVCC-safe case, it isn’t committed yet - but no one will > examine that bit before it commits (though this approach does feel > more fragile). > > This could help avoid potential storms of full-page writes caused by > SetHintBit after the table switch. Good idea, thanks. [1] https://www.postgresql.org/message-id/202504040733.ysuy5gad55md%40alvherre.pgsql -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-27T08:22:24Z
Antonin Houska <ah@cybertec.at>: > I insist that this is a misuse of TransactionIdIsInProgress(). When dealing > with logical decoding, only WAL should tell whether particular transaction is > still running. AFAICS this is how reorderbuffer.c works. Hm... Maybe, but at the same time we already have SnapshotDirty used in that way and it even deals with the same race.... But I agree - a special kind of snapshot is a more accurate solution. > A new kind of snapshot seems like (much) cleaner solution at the moment. Do you mean some kind of snapshot which only uses TransactionIdDidCommit/Abort ignoring TransactionIdIsCurrentTransactionId/TransactionIdIsInProgress? Actually it behaves like SnapshotBelieveEverythingCommitted in that particular case, but TransactionIdDidCommit/Abort may be used as some kind of assert/error source to be sure everything is going as designed. And, yes, for the new snapshot we need to have HeapTupleSatisfiesUpdate to be modified. Also, to deal with that particular race we may just use XactLockTableWait(xid, NULL, NULL, XLTW_None) before starting transaction replay. > No rush. First, the MVCC safety is not likely to be included in v19 [1]. That worries me - it is not the behaviour someone expects from a database by default. At least the warning should be much more visible and obvious. I think most of user will expect the same guarantees as [CREATE|RE] INDEX CONCURRENTLY provides.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-27T10:11:45Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > A new kind of snapshot seems like (much) cleaner solution at the moment. > > Do you mean some kind of snapshot which only uses > TransactionIdDidCommit/Abort ignoring > TransactionIdIsCurrentTransactionId/TransactionIdIsInProgress? > Actually it behaves like SnapshotBelieveEverythingCommitted in that > particular case, but TransactionIdDidCommit/Abort may be used as some > kind of assert/error source to be sure everything is going as > designed. Given that there should be no (sub)transaction aborts in the new table, I think you only need to check that the tuple has valid xmin and invalid xmax. I think the XID should be in the commit log at the time the transaction is being replayed, so it should be legal to use TransactionIdDidCommit/Abort in Assert() statements. (And as long as REPACK CONCURRENTLY will use ShareUpdateExclusiveLock, which conflicts with VACUUM, pg_class(relfrozenxid) for given table should not advance during the processing, and therefore the replayed XIDs should not be truncated from the commit log while REPACK CONCURRENTLY is running.) > And, yes, for the new snapshot we need to have > HeapTupleSatisfiesUpdate to be modified. > > Also, to deal with that particular race we may just use > XactLockTableWait(xid, NULL, NULL, XLTW_None) before starting > transaction replay. Do you mean the race related to TransactionIdIsInProgress()? Not sure I understand, as you suggested above that you no longer need the function. > > No rush. First, the MVCC safety is not likely to be included in v19 [1]. > > That worries me - it is not the behaviour someone expects from a > database by default. At least the warning should be much more visible > and obvious. > I think most of user will expect the same guarantees as [CREATE|RE] > INDEX CONCURRENTLY provides. It does not really worry me. The pg_squeeze extension is not MVCC-safe and I remember there were only 1 or 2 related complaints throughout its existence. (pg_repack isn't MVCC-safe as well, but I don't keep track of its issues.) Of course, user documentation should warn about the problem, in a way it does for other commands (typically ALTER TABLE). -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-27T10:55:12Z
Antonin Houska <ah@cybertec.at>: > Do you mean the race related to TransactionIdIsInProgress()? Not sure I > understand, as you suggested above that you no longer need the function. The "lightweight" approaches I see so far: * XactLockTableWait before replay + SnapshotSelf(GetLatestSnapshot?) * SnapshotDirty + retry logic * SnapshotBelieveEverythingCommitted + modification of HeapTupleSatisfiesUpdate (because it called by heap_update and looks into TransactionIdIsInProgress) > It does not really worry me. The pg_squeeze extension is not MVCC-safe and I > remember there were only 1 or 2 related complaints throughout its > existence. (pg_repack isn't MVCC-safe as well, but I don't keep track of its > issues.) But pg_squeeze and pg_repack are extensions. If we are moving that mechanics into core I'd expect some improvements over pg_squeeze. MVCC-safety of REINDEX CONCURRENTLY makes it possible to run it on a regular basis as some kind of background job. It would be nice to have something like this for the heap. I agree the initial approach is too invasive, complex and performance-heavy to push it forward now. But, any of "lightweight" feels like a good candidate to be shipped with the feature itself - relatively easy and non-invasive. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-28T21:39:49Z
On 2025-Aug-21, Mihail Nikalayeu wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org>: > > I proposed to leave this part out initially, which is why it hasn't been > > reposted. We can review and discuss after the initial patches are in. > > I think it is worth pushing it at least in the same release cycle. If others are motivated enough to certify it, maybe we can consider it. But I don't think I'm going to have time to get this part reviewed and committed in time for 19, so you might need another committer. > > Because having an MVCC-safe mode has drawbacks, IMO we should make it > > optional. > > Do you mean some option for the command? Like REPACK (CONCURRENTLY, SAFE)? Yes, exactly that. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "La grandeza es una experiencia transitoria. Nunca es consistente. Depende en gran parte de la imaginación humana creadora de mitos" (Irulan)
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-29T00:32:00Z
Hello, Álvaro! > If others are motivated enough to certify it, maybe we can consider it. > But I don't think I'm going to have time to get this part reviewed and > committed in time for 19, so you might need another committer. I don't think it is realistic to involve another committer - it is just a well-known curse of all non-committers :) > > > Because having an MVCC-safe mode has drawbacks, IMO we should make it > > > optional. As far as I can see, the proposed "lightweight" solutions don't introduce any drawbacks - unless something has been overlooked. > > Do you mean some option for the command? Like REPACK (CONCURRENTLY, SAFE)? > Yes, exactly that. To be honest that approach feels a little bit strange for me. I work in the database-consumer (not database-developer) industry and 90% of DevOps engineers (or similar roles who deal with database maintenance now) have no clue what MVCC is - and it is industry standard nowadays. From my perspective - it is better to have a less performant, but MVCC-safe approach by default, with some "more performance, less safety" flag for those who truly understand the trade-offs. All kinds of safety and correctness is probably the main reason to use classic databases instead of storing data in s3\elastis\mongo\etc these days. In case of some incident related to that (in a large well-known company) the typical takeaway for readers of tech blogs will simply be "some command in Postgres is broken". And maybe also "the database with a name starting with 'O' is not affected by that flaw". Yes, some ALTER table-rewriting commands are not MVCC-safe, but those typically block everything for hours - so they're avoided unless absolutely necessary, and usually performed during backend outages to prevent systems from getting stuck waiting on millions of locks. "CONCURRENTLY" is something that feels like a "working in background, don't worry, do not stop your 100k RPS" thing, especially in Postgres because of CIC. I also have personal experience debugging incidents caused by incorrect Postgres behavior - and it’s absolute hell. Sorry, I made a drama here.... I fully understand resource limitations... Maybe Postgres 19 could introduce something like REPACK (CONCURRENTLY, UNSAFE) first, and later add a safer REPACK (CONCURRENTLY)? Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-08-29T07:41:56Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > In case of some incident related to that (in a large well-known > company) the typical takeaway for readers of tech blogs will simply be > "some command in Postgres is broken". For _responsible_ users, the message will rather be that "some tech bloggers do not bother to read user documentation". -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-30T17:50:56Z
Hello, Here's v19 of this patchset. This is mostly Antonin's v18. I added a preparatory v19-0001 commit, which splits vacuumdb.c to create a new file, vacuuming.c (and its header file vacuuming.h). If you look at it under 'git show --color-moved=zebra' you should notice that most of it is just code movement; there's hardly any code changes. v19-0002 has absorbed Antonin's v18-0005 (the pg_repackdb binary) together with the introduction of the REPACK command proper; but instead of using a symlink, I just created a separate pg_repackdb.c source file for it and we compile that small new source file with vacuuming.c to create a regular binary. BTW the meson.build changes look somewhat duplicative; maybe there's a less dumb way to go about this. (For instance, maybe just have libscripts.a include vacuuming.o, though it's not used by any of the other programs in that subdir.) I'm not wedded to the name "vacuuming.c"; happy to take suggestions. After 0002, the pg_repackdb utility should be ready to take clusterdb's place, and also vacuumdb --full, with one gotcha: if you try to use pg_repackdb with an older server version, it will fail, claiming that REPACK is not supported. This is not ideal. Instead, we should make it run VACUUM FULL (or CLUSTER); so if you have a fleet including older servers you can use the new utils there too. All the logic for vacuumdb to select tables to operate on has been moved to vacuuming.c verbatim. This means this logic applies to pg_repackdb as well. As long as you stick to repacking a single table this is okay (read: it won't be used at all), but if you want to use parallel mode (say to process multiple schemas), we might need to change it. For the same reason, I think we should add an option to it (--index[=indexname]) to select whether to use the USING INDEX clause or not, and optionally indicate which index to use; right now there's no way to select which logic (cluster's or vacuum full's) to use. Then v19-0003 through v19-0005 are Antonin's subsequent patches to add the CONCURRENTLY option; I have not reviewed these at all, so I'm including them here just for completion. I also included v18-0006 as posted by Mihail previously, though I have little faith that we're going to include it in this release. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Pensar que el espectro que vemos es ilusorio no lo despoja de espanto, sólo le suma el nuevo terror de la locura" (Perelandra, C.S. Lewis)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-31T12:09:29Z
Apparently I mismerged src/bin/scripts/meson.build. This v20 is identical to v19, where that mistake has been corrected. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ Al principio era UNIX, y UNIX habló y dijo: "Hello world\n". No dijo "Hello New Jersey\n", ni "Hello USA\n".
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-08-31T15:29:00Z
Hello! I started an attempt to make a "lightweight" MVCC-safe prototype and stuck into the "it is not working" issue. After some debugging I realized Antonin's variant (catalog-mode based) seems to be broken also... And after a few more hours I realized non-MVCC is broken as well :) This is a patch with a test to reproduce the issue related to repack + concurrent modifications. Seems like some updates may be lost. I hope the patch logic is clear - but feel free to ask if not. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-08-31T17:43:17Z
On 2025-Aug-31, Mihail Nikalayeu wrote: > I started an attempt to make a "lightweight" MVCC-safe prototype and > stuck into the "it is not working" issue. > After some debugging I realized Antonin's variant (catalog-mode based) > seems to be broken also... > > And after a few more hours I realized non-MVCC is broken as well :) Ugh. Well, obviously we need to get this fixed if we want CONCURRENTLY at all :-) Please don't post patches that aren't the commitfest item's main patch as attachment with .patch extension. This confuses the CFbot into thinking your patch is the patch-of-record (which it isn't) and reports that the patch fails CI. See here: https://cirrus-ci.com/github/postgresql-cfbot/postgresql/cf%2F5117 (For the same reason, it isn't useful to number them as if they were part of the patch series). If you want to post secondary patches, please rename them to end in something like .txt or .nocfbot or whatever. See here: https://wiki.postgresql.org/wiki/Cfbot#Which_attachments_are_considered_to_be_patches? Thanks for your interest in this topic, -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Michael Paquier <michael@paquier.xyz> — 2025-09-01T00:16:42Z
On Wed, Aug 27, 2025 at 10:22:24AM +0200, Mihail Nikalayeu wrote: > That worries me - it is not the behaviour someone expects from a > database by default. At least the warning should be much more visible > and obvious. > I think most of user will expect the same guarantees as [CREATE|RE] > INDEX CONCURRENTLY provides. Having a unified path for the handling of the waits and the locking sounds to me like a pretty good argument in favor of a basic implementation. In my experience, users do not really care about the time it takes to complete a operation involving CONCURRENTLY if we allow concurrent reads and writes in parallel of it. I have not looked at the proposal in details, but before trying a more folkloric MVCC approach, relying on basics that we know have been working for some time seems like a good and sufficient initial step in terms of handling the waits and the locks with table AMs (aka heap or something else). Just my 2c. -- Michael
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-09-01T05:12:11Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello! > > I started an attempt to make a "lightweight" MVCC-safe prototype and > stuck into the "it is not working" issue. > After some debugging I realized Antonin's variant (catalog-mode based) > seems to be broken also... > > And after a few more hours I realized non-MVCC is broken as well :) > > This is a patch with a test to reproduce the issue related to repack + > concurrent modifications. > Seems like some updates may be lost. > > I hope the patch logic is clear - but feel free to ask if not. Are you sure the test is complete? I see no occurrence of the REPACK command in it. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-09-01T09:06:35Z
Hello! Antonin Houska <ah@cybertec.at>: > Are you sure the test is complete? I see no occurrence of the REPACK command > in it. Oops, send invalid file. The correct one in attachment.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-09-01T13:00:39Z
Hello, Álvaro! Alvaro Herrera <alvherre@alvh.no-ip.org>: > If you want to post secondary patches, please rename them to end in > something like .txt or .nocfbot or whatever. See here: > https://wiki.postgresql.org/wiki/Cfbot#Which_attachments_are_considered_to_be_patches? Sorry, I missed that. But now it is possible to send ".patch" without changing the extension [0]. > It also ignores any files that start with "nocfbot". [0]: https://discord.com/channels/1258108670710124574/1328362897189113867/1412021226528051250
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-09-01T15:30:18Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Antonin Houska <ah@cybertec.at>: > > Are you sure the test is complete? I see no occurrence of the REPACK command > > in it. > Oops, send invalid file. The correct one in attachment. Thanks! The problem was that when removing the original "preserve visibility patch" v12-0005 [1] from the series, I forgot to change the value of 'need_full_snapshot' argument of CreateInitDecodingContext(). v12 and earlier treated the repacked table like system catalog, so it was o.k. to pass need_full_snapshot=false. However, it must be true now, otherwise the snapshot created for the initial copy does not see commits of transactions that do not change regular catalogs. The fix is as simple as diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index f481a3cec6d..7866ac01278 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -502,6 +502,7 @@ SnapBuildInitialSnapshotForRepack(SnapBuild *builder) StringInfo buf = makeStringInfo(); Assert(builder->state == SNAPBUILD_CONSISTENT); + Assert(builder->building_full_snapshot); snap = SnapBuildBuildSnapshot(builder); I'll apply it to the next version of the "Add CONCURRENTLY option to REPACK command" patch. [1] https://www.postgresql.org/message-id/flat/CAFj8pRDK89FtY_yyGw7-MW-zTaHOCY4m6qfLRittdoPocz+dMQ@mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-09-02T10:44:27Z
Hello! Antonin Houska <ah@cybertec.at>: > I'll apply it to the next version of the "Add CONCURRENTLY option to REPACK > command" patch. I have added it to the v21 patchset. Also, I’ve updated the MVCC-safe patch: * it uses the "XactLockTableWait before replay + SnapshotSelf" approach from [0] * it includes a TAP test to ensure MVCC safety - not intended to be committed in its current form (too heavy) * documentation has been updated. It's now much simpler and does not negatively impact performance. It is less aggressive in tuple freezing, but can be updated to match the non-MVCC-safe version if needed. While testing MVCC-safe version with stress-tests 007_repack_concurrently_mvcc.pl I encountered some random crashes with such logs: 25-09-02 12:24:40.039 CEST client backend[261907] 007_repack_concurrently_mvcc.pl ERROR: relcache reference 0x7715b9f394a8 is not owned by resource owner TopTransaction 2025-09-02 12:24:40.039 CEST client backend[261907] 007_repack_concurrently_mvcc.pl STATEMENT: REPACK (CONCURRENTLY) tbl1 USING INDEX tbl1_pkey; TRAP: failed Assert("rel->rd_refcnt > 0"), File: "../src/backend/utils/cache/relcache.c", Line: 6992, PID: 261907 postgres: CIC_test: nkey postgres [local] REPACK(ExceptionalCondition+0xbe)[0x5b7ac41d79f9] postgres: CIC_test: nkey postgres [local] REPACK(+0x852d2e)[0x5b7ac41cbd2e] postgres: CIC_test: nkey postgres [local] REPACK(+0x8aa4a6)[0x5b7ac42234a6] postgres: CIC_test: nkey postgres [local] REPACK(+0x8aad3b)[0x5b7ac4223d3b] postgres: CIC_test: nkey postgres [local] REPACK(+0x8aac69)[0x5b7ac4223c69] postgres: CIC_test: nkey postgres [local] REPACK(ResourceOwnerRelease+0x32)[0x5b7ac4223c26] postgres: CIC_test: nkey postgres [local] REPACK(+0x1f43bf)[0x5b7ac3b6d3bf] postgres: CIC_test: nkey postgres [local] REPACK(+0x1f4dfa)[0x5b7ac3b6ddfa] postgres: CIC_test: nkey postgres [local] REPACK(AbortCurrentTransaction+0xe)[0x5b7ac3b6dd6b] postgres: CIC_test: nkey postgres [local] REPACK(PostgresMain+0x57d)[0x5b7ac3fd7238] postgres: CIC_test: nkey postgres [local] REPACK(+0x654102)[0x5b7ac3fcd102] postgres: CIC_test: nkey postgres [local] REPACK(postmaster_child_launch+0x191)[0x5b7ac3eceb7a] postgres: CIC_test: nkey postgres [local] REPACK(+0x55c8c1)[0x5b7ac3ed58c1] postgres: CIC_test: nkey postgres [local] REPACK(+0x559d1e)[0x5b7ac3ed2d1e] postgres: CIC_test: nkey postgres [local] REPACK(PostmasterMain+0x168a)[0x5b7ac3ed25f8] postgres: CIC_test: nkey postgres [local] REPACK(main+0x3a1)[0x5b7ac3da2bd6] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x7715b962a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x7715b962a28b] This time I was clever and tried to attempt to reproduce the issue on a non-MVCC safe version at first - and it is reproducible. Just comment \if :p_t1 != :p_t2 (and its internals, because they catching non-mvcc behaviour which is expected without 0006 patch); and set '--no-vacuum --client=30 --jobs=4 --exit-on-abort --transactions=25000' It takes about a minute on my PC to get the crash. [0]: https://www.postgresql.org/message-id/flat/CADzfLwXCTXNdxK-XGTKmObvT%3D_QnaCviwgrcGtG9chsj5sYzrg%40mail.gmail.com#6d6ccf34e6debcd386d735db593379d8 Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-09-03T09:55:34Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > While testing MVCC-safe version with stress-tests > 007_repack_concurrently_mvcc.pl I encountered some random crashes with > such logs: > > 25-09-02 12:24:40.039 CEST client backend[261907] > 007_repack_concurrently_mvcc.pl ERROR: relcache reference > 0x7715b9f394a8 is not owned by resource owner TopTransaction > ... > This time I was clever and tried to attempt to reproduce the issue on > a non-MVCC safe version at first - and it is reproducible. Thanks again for a thorough testing! I think this should be fixed separately [1]. [1] https://www.postgresql.org/message-id/119497.1756892972%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-09-23T15:51:06Z
Hello, Barring further commentary, I intend to get 0001 committed tomorrow, and 0002 some time later -- perhaps by end of this week, or sometime next week. Regards -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-09-25T18:12:41Z
After looking at this some more, I realized that 0001 had been written a bit too hastily and that it could use with some more cleanup -- in particular, we don't need to export most of the function prototypes other than vacuuming_main() (and the trivial escape_quotes helper). I made the other functions static. Also, prepare_vacuum_command() also needs the encoding in order to do fmtIdEnc() on a given index name (for `pg_repackdb -t table --index=foobar`), so I changed it to take the PGconn instead of just the serverVersion. I realized that it makes no sense that objfilter is a global variable instead of living inside `main` and be passed as argument where needed. (Heck, maybe it should be inside vacuumingOpts). Lastly, it seemed weird coding that the functions would sometimes exit(1) instead of returning a result code, so I made them do that and have the callers react appropriately. These are all fairly straightforward changes. So here's v22 with those and rebased to current sources. Only the first two patches this time, which are the ones I would be glad to receive input on. I also wonder if analyze_only and analyze_in_stages should be new values in RunMode rather than separate booleans ... I think that might make the code simpler. I didn't try though. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Los dioses no protegen a los insensatos. Éstos reciben protección de otros insensatos mejor dotados" (Luis Wu, Mundo Anillo)
-
Re: Adding REPACK [concurrently]
Marcos Pegoraro <marcos@f10.com.br> — 2025-09-25T20:20:34Z
Em qui., 25 de set. de 2025 às 15:12, Álvaro Herrera <alvherre@kurilemu.de> escreveu: Some typos I've found on usage of pg_repackdb. + printf(_(" -n, --schema=SCHEMA repack tables in the specified schema(s) only\n")); + printf(_(" -N, --exclude-schema=SCHEMA do not repack tables in the specified schema(s)\n")); both options can point to a single schema, so "(s)" should be removed. "in the specified schema(s)" should be "in the specified schema" Same occurs on this one, which should be table, not table(s) + printf(_(" -t, --table='TABLE' repack specific table(s) only\n")); regards Marcos -
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-09-25T21:31:35Z
On Thu, Sep 25, 2025 at 4:21 PM Marcos Pegoraro <marcos@f10.com.br> wrote: > > Em qui., 25 de set. de 2025 às 15:12, Álvaro Herrera <alvherre@kurilemu.de> escreveu: > > Some typos I've found on usage of pg_repackdb. > > + printf(_(" -n, --schema=SCHEMA repack tables in the specified schema(s) only\n")); > + printf(_(" -N, --exclude-schema=SCHEMA do not repack tables in the specified schema(s)\n")); > both options can point to a single schema, so "(s)" should be removed. > "in the specified schema(s)" should be "in the specified schema" > > Same occurs on this one, which should be table, not table(s) > + printf(_(" -t, --table='TABLE' repack specific table(s) only\n")); > This pattern is used because you can pass more than one argument, for example, something like pg_repackdb -d pagila -v -n public -n legacy While I agree that the wording is a little awkward; I'd prefer "repack tables only in the specified schema(s)"; but this follows the same pattern as pg_dump and friends. Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Marcos Pegoraro <marcos@f10.com.br> — 2025-09-25T21:46:48Z
Em qui., 25 de set. de 2025 às 18:31, Robert Treat <rob@xzilla.net> escreveu: > This pattern is used because you can pass more than one argument, for > example, something like I know that > > While I agree that the wording is a little awkward, this follows the same > pattern as pg_dump and friends. > well, I think pg_dump looks wrong too. Because if you explain that it's a single table or single schema on docs, why you write on plural on usage ? + Repack or analyze all tables in + <replaceable class="parameter">schema</replaceable> only. Multiple + schemas can be repacked by writing multiple <option>-n</option> + switches. instead of + printf(_(" -n, --schema=SCHEMA repack tables in the specified schema(s) only\n")); maybe this ? + printf(_(" -n, --schema=SCHEMA repack tables in the specified schema, can be used several times\n")); regards Marcos -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-09-26T14:27:12Z
Hello! Álvaro Herrera <alvherre@kurilemu.de>: > So here's v22 with those and rebased to current sources. Only the first > two patches this time, which are the ones I would be glad to receive > input on. > get_tables_to_repack_partitioned(RepackCommand cmd, MemoryContext cluster_context, > Oid relid, bool rel_is_index) Should we rename it to repack_context to be aligned with the calling side? --------- 'cmd' in > static List *get_tables_to_repack(RepackCommand cmd, bool usingindex, > MemoryContext permcxt); but 'command' in > get_tables_to_repack(RepackCommand command, bool usingindex, > MemoryContext permcxt) --------- > cmd == REPACK_COMMAND_CLUSTER ? "CLUSTER" : "REPACK", May be changed to RepackCommandAsString ----------- if (cmd == REPACK_COMMAND_REPACK) pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, PROGRESS_REPACK_COMMAND_REPACK); else if (cmd == REPACK_COMMAND_CLUSTER) { pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, PROGRESS_CLUSTER_COMMAND_CLUSTER); } else .... '{' and '}' looks a little bit weird. -------- Documentation of pg_repackdb contains a lot of "analyze" and even "--analyze" parameter - but I can't see anything related in the code. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-09-26T17:30:09Z
On Thu, Sep 25, 2025 at 2:12 PM Álvaro Herrera <alvherre@kurilemu.de> wrote: > So here's v22 with those and rebased to current sources. Only the first > two patches this time, which are the ones I would be glad to receive > input on. > A number of small issues I noticed. I don't know that they all need addressing right now, but seems worth asking the questions... #1 "pg_repackdb --help" does not mention the --index option, although the flag is accepted. I'm not sure if this is meant to match clusterdb, but since we need the index option to invoke the clustering behavior, I think it needs to be there. #2 [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t customer --index=idx_last_name pg_repackdb: repacking database "pagila" INFO: clustering "public.customer" using sequential scan and sort [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t customer pg_repackdb: repacking database "pagila" INFO: vacuuming "public.customer" This was less confusing once I figured out we could pass the --index option, but even with that it is a little confusing, I think mostly because it looks like we are "vacuuming" the table, which in a world of repack and vacuum (ie. no vacuum full) doesn't make sense. I think the right thing to do here would be to modify it to be "repacking %s" in both cases, with the "using sequential scan and sort" as the means to understand which version of repack is being executed. #3 pg_repackdb does not offer an --analyze option, which istm it should to match the REPACK command #4 SQL level REPACK help shows: where option can be one of: VERBOSE [ boolean ] ANALYSE | ANALYZE but SQL level VACUUM does VERBOSE [ boolean ] ANALYZE [ boolean ] These operate the same way, so I would expect it to match the language in vacuum. #5 [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t film --index pg_repackdb: repacking database "pagila" In the above scenario, I am repacking without having previously specified an index. At the SQL level this would throw an error, at the command line it gives me a heart attack. :-) It's actually not that bad, because we don't actually do anything, but maybe we should throw an error? #6 On the individual command pages (like sql-repack.html), I think there should be more cross-linking, ie. repack should probably say "see also cluster" and vice versa. Likely similarly with vacuum and repack. #7 Is there some reason you chose to intermingle the repack regression tests with the existing tests? I feel like it'd be easier to differentiate potential regressions and new functionality if these were separated. Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-10-07T14:05:33Z
On 2025-Sep-26, Mihail Nikalayeu wrote: > Should we rename it to repack_context to be aligned with the calling side? Sure, done. > > cmd == REPACK_COMMAND_CLUSTER ? "CLUSTER" : "REPACK", > > May be changed to RepackCommandAsString Oh, of course. > Documentation of pg_repackdb contains a lot of "analyze" and even > "--analyze" parameter - but I can't see anything related in the code. Hmm, yeah, that was missing. I added it. In doing so I noticed that because vacuumdb allows a column list to be given, then we should do likewise here, both in pg_repackdb and in the REPACK command, so I added support for that. This changed the grammar a little bit. Note that we still don't allow multiple tables to be given to the SQL command REPACK, so if you want to repack multiple tables, you need to call it without giving a name or give the name of a partitioned table. The pg_repackdb utility allows you to give multiple -t switches, and in that case it calls REPACK once for each name. Also, if you give a column list to pg_repackdb, then you must pass -z. This is consistent with vacuumdb via VACUUM ANALYZE. On 2025-Sep-26, Robert Treat wrote: > #1 > "pg_repackdb --help" does not mention the --index option, although the > flag is accepted. I'm not sure if this is meant to match clusterdb, > but since we need the index option to invoke the clustering behavior, > I think it needs to be there. Oops, yes, added. > #2 > [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t customer > --index=idx_last_name > pg_repackdb: repacking database "pagila" > INFO: clustering "public.customer" using sequential scan and sort > > [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t customer > pg_repackdb: repacking database "pagila" > INFO: vacuuming "public.customer" > > This was less confusing once I figured out we could pass the --index > option, but even with that it is a little confusing, I think mostly > because it looks like we are "vacuuming" the table, which in a world > of repack and vacuum (ie. no vacuum full) doesn't make sense. I think > the right thing to do here would be to modify it to be "repacking %s" > in both cases, with the "using sequential scan and sort" as the means > to understand which version of repack is being executed. I changed these messages to always say "repacking", but it will say "using sequential scan and sort", or "using index", or "following physical order", respectively. That said, on this topic, I've always been bothered by our usage of command names as verbs, because they are (IMO) horrible for translation. For instance, in this version of the patch I am making this change: if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot cluster a shared catalog"))); + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot run %s on a shared catalog", + RepackCommandAsString(cmd))); In the old version, the message is not very translatable because you have to find a native word to say "to cluster" or "to vacuum", and that doesn't always work very well in a direct translation. For instance, in the Spanish message catalog you find this sort of thing: msgid "vacuuming \"%s.%s.%s\"" msgstr "haciendo vacuum a «%s.%s.%s»" which is pretty clear ... but the reason it works, is that I have turned the phrase around before translating it. I would struggle if I had to find a Spanish verb that means "to repack" without contorting the message or saying something absurd and/or against Spanish language rules, such as "ejecutando repack en table XYZ" or "repaqueando tabl XYZ" (that's not a word!) or "reempaquetando tabla XYZ" (this is correct, but far enough from "repack" that it's annoying and potentially confusing). So I would rather the original used "running REPACK on table using method XYZ", which is very very easy to translate, and then the translator doesn't have to editorialize. > #3 > pg_repackdb does not offer an --analyze option, which istm it should > to match the REPACK command Added, as mentioned above. > #4 Fixed. > #5 > [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t film --index > pg_repackdb: repacking database "pagila" > > In the above scenario, I am repacking without having previously > specified an index. At the SQL level this would throw an error, at the > command line it gives me a heart attack. :-) > It's actually not that bad, because we don't actually do anything, but > maybe we should throw an error? Yeah, I think this is confusing. I think we should make pg_repackdb explicitly indicate what has been done, in all cases, without requiring -v. Otherwise it's too confusing, particularly for the using-index mode which determines which tables to process based on the existance of an index marked indiscluster. > #6 > On the individual command pages (like sql-repack.html), I think there > should be more cross-linking, ie. repack should probably say "see also > cluster" and vice versa. Likely similarly with vacuum and repack. Hmm, I don't necessarily agree -- I think the sql-cluster page should be mostly empty and reference the sql-repack page. We don't need any incoming links to sql-cluster, I think. All the useful info should be in the sql-repack page only. The same applies for VACUUM FULL: an outgoing link in sql-vacuum to sql-repack is good to have, but we don't need links from sql-repack to sql-vacuum. > #7 > Is there some reason you chose to intermingle the repack regression > tests with the existing tests? I feel like it'd be easier to > differentiate potential regressions and new functionality if these > were separated. I admit I haven't paid too much attention to these tests. I think I would rather create a separate src/test/regress/sql/repack.sql file with the tests for this command. Let's consider this part a WIP for now -- clearly more tests are needed both for the SQL command CLUSTER and for pg_repackdb. In the meantime, this version has been rebased to current sources. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-10-09T06:38:15Z
Álvaro Herrera <alvherre@kurilemu.de> wrote: > On 2025-Sep-26, Mihail Nikalayeu wrote: > > > Should we rename it to repack_context to be aligned with the calling side? > > Sure, done. > > > > cmd == REPACK_COMMAND_CLUSTER ? "CLUSTER" : "REPACK", > > > > May be changed to RepackCommandAsString > > Oh, of course. > > > Documentation of pg_repackdb contains a lot of "analyze" and even > > "--analyze" parameter - but I can't see anything related in the code. > > Hmm, yeah, that was missing. I added it. In doing so I noticed that > because vacuumdb allows a column list to be given, then we should do > likewise here, both in pg_repackdb and in the REPACK command, so I added > support for that. + /* + * Make sure ANALYZE is specified if a column list is present. + */ + if ((params->options & CLUOPT_ANALYZE) == 0 && stmt->relation->va_cols != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ANALYZE option must be specified when a column list is provided"))); Shouldn't the user documentation mention this restriction? -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Álvaro Herrera <alvherre@kurilemu.de> — 2025-10-09T11:49:13Z
On 2025-Oct-09, Antonin Houska wrote: > + /* > + * Make sure ANALYZE is specified if a column list is present. > + */ > + if ((params->options & CLUOPT_ANALYZE) == 0 && stmt->relation->va_cols != NIL) > + ereport(ERROR, > + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("ANALYZE option must be specified when a column list is provided"))); > > Shouldn't the user documentation mention this restriction? Hmm, yeah, I guess it should. Will add. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "¿Cómo puedes confiar en algo que pagas y que no ves, y no confiar en algo que te dan y te lo muestran?" (Germán Poo) -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-10-10T14:11:32Z
Hello, Here's patch v24. I was hoping to push this today, but I think there were too many changes from v23 for that. Here's what I did: - pg_stat_progress_cluster is no longer a view on top of the low-level pg_stat_get_progress_info() function. Instead, it's a view on top of pg_stat_progress_repack. The only change it applies on top of that one is change the command from REPACK to one of VACUUM FULL or CLUSTER, depending on whether an index is being used or not. This should keep the behavior identical to previous versions. Alternatively we could just hide rows where the command is REPACK, but I don't think that would be any better. This way, we maintain compatibility with tools reading pg_stat_progress_cluster. Maybe this is useless and we should just drop the view, not sure, we can discuss separately. - pg_stat_progress_repack itself now shows the command. Also I got rid of the separate enum values for the command, and instead used the values from the parse node (RepackCommand); this removes about a dozen lines of C code. To forestall potentially bogus usage of value 0, I made the enum start from 1. - I noticed that you can do "CLUSTER pg_class ON some_index" and it will happily modify pg_index.indisclustered, which is a bit weird considering that allow_system_table_mods is off -- if you later try ALTER TABLE .. SET WITHOUT CLUSTER, it won't let you. I think this is bogus and we should change it so that CLUSTER refuses to change the clustered index on a system catalog, unless allow_system_table_mods is on. However, that would be a change from longstanding behavior which is specifically tested for in regression tests, so I didn't do it. We can discuss such a change separately. But I did make REPACK refuse to do that, because we don't need to propagate bogus historical behavior. So REPACK will fail if you try to change the indisclustered index, but it will work fine if you repack based on the same index as before, or repack with no index. - pg_repackdb: if you try with a non-superuser without specifying a table name, it will fail as soon as it hits the first catalog table or whatever with "ERROR: cannot lock this table". This is sorta fine for vacuumdb, but only because VACUUM itself will instead say "WARNING: cannot lock table XYZ, skipping", so it's not an error and vacuumdb keeps running. IMO this is bogus: vacuumdb should not try to process tables that it doesn't have privileges to. However, not wanting to change longstanding behavior, I left that alone. For pg_repackdb, I added a condition in the WHERE clause there to only fetch tables that the current user has MAINTAIN privilege over. Then you can do a "pg_repackdb -U foobar" and it will nicely process the tables that that user is allowed to process. We can discuss changing the vacuumdb behavior separately. - Added some additional tests for pg_repackdb and REPACK. - Updated the docs. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-10-13T00:03:18Z
On Tue, Oct 7, 2025 at 10:05 AM Álvaro Herrera <alvherre@kurilemu.de> wrote: > On 2025-Sep-26, Robert Treat wrote: <snip> > That said, on this topic, I've always been bothered by our usage of > command names as verbs, because they are (IMO) horrible for translation. > For instance, in this version of the patch I am making this change: > > if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared) > ereport(ERROR, > - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > - errmsg("cannot cluster a shared catalog"))); > + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > + errmsg("cannot run %s on a shared catalog", > + RepackCommandAsString(cmd))); > > In the old version, the message is not very translatable because you > have to find a native word to say "to cluster" or "to vacuum", and that > doesn't always work very well in a direct translation. For instance, in > the Spanish message catalog you find this sort of thing: > > msgid "vacuuming \"%s.%s.%s\"" > msgstr "haciendo vacuum a «%s.%s.%s»" > > which is pretty clear ... but the reason it works, is that I have turned > the phrase around before translating it. I would struggle if I had to > find a Spanish verb that means "to repack" without contorting the > message or saying something absurd and/or against Spanish language > rules, such as "ejecutando repack en table XYZ" or "repaqueando tabl > XYZ" (that's not a word!) or "reempaquetando tabla XYZ" (this is > correct, but far enough from "repack" that it's annoying and potentially > confusing). So I would rather the original used "running REPACK on > table using method XYZ", which is very very easy to translate, and then > the translator doesn't have to editorialize. > I see you didn't do this in the current patch, but +1 for this idea from me. And if you think it'd help, I'm also +1 on the idea for the main docs as well, for example doing something like + <para> - <application>pg_repackdb</application> is a utility for repacking a + <application>pg_repackdb</application> is a utility for running REPACK on a + <productname>PostgreSQL</productname> database. I'd be inclined to leave the internal comments alone though, since they aren't translated. > > #5 > > [xzilla@zebes] pgsql/bin/pg_repackdb -d pagila -v -t film --index > > pg_repackdb: repacking database "pagila" > > > > In the above scenario, I am repacking without having previously > > specified an index. At the SQL level this would throw an error, at the > > command line it gives me a heart attack. :-) > > It's actually not that bad, because we don't actually do anything, but > > maybe we should throw an error? > > Yeah, I think this is confusing. I think we should make pg_repackdb > explicitly indicate what has been done, in all cases, without requiring > -v. Otherwise it's too confusing, particularly for the using-index mode > which determines which tables to process based on the existance of an > index marked indiscluster. > At the moment, clusterdb runs silently, but vacuumdb emits output, so there is an argument for either way as default behavior. That said, I think the current behavior of vacuum, which is what we are currently following in pg_repackdb, is the worst of the two: [xzilla@zebes] pgsql/bin/vacuumdb -t actor pagila vacuumdb: vacuuming database "pagila" Without any additional information, the information we do give is misleading; I would rather not say anything. We could of course try to make this more verbose, but I think clusterdb actually gets this right... - say nothing by default (follow the "rule of silence.") - if we want to see commands, pass -e - if we want to see the details, pass -v - if we do something that causes an error, return the error - if we don't want errors, pass -q This is also how reindexdb works, and I think most of the other utilities, and I'd argue this is how vacuumdb should work... to the extent I almost consider it a bug that it doesn't (I leave a little room since I am not sure why it doesn't operate like the other utilities). vacuum is a bit outside the purview of what we are doing here, but I do think following clusterdb/reindexdb is the behavior we should follow for pg_repackdb. > I admit I haven't paid too much attention to these tests. I think I > would rather create a separate src/test/regress/sql/repack.sql file with > the tests for this command. Let's consider this part a WIP for now -- > clearly more tests are needed both for the SQL command CLUSTER and for > pg_repackdb. Yeah, istm as long as we have all 3 commands (repack, cluster, vacuum full) we need regression tests for all 3. > - pg_stat_progress_cluster is no longer a view on top of the low-level > pg_stat_get_progress_info() function. Instead, it's a view on top of > pg_stat_progress_repack. The only change it applies on top of that > one is change the command from REPACK to one of VACUUM FULL or > CLUSTER, depending on whether an index is being used or not. This > should keep the behavior identical to previous versions. > Alternatively we could just hide rows where the command is REPACK, but > I don't think that would be any better. This way, we maintain > compatibility with tools reading pg_stat_progress_cluster. Maybe this > is useless and we should just drop the view, not sure, we can discuss > separately. > I think this mostly depends on how aggressive you want to be in moving people away from cluster and toward repack. If we remove _progress_cluster, it will force people to update monitoring which probably encourages people to switch to pg_repackdb. We probably need to have at least one "bridge" release though, and I think you've got the right balance for that. > - I noticed that you can do "CLUSTER pg_class ON some_index" and it will > happily modify pg_index.indisclustered, which is a bit weird > considering that allow_system_table_mods is off -- if you later try > ALTER TABLE .. SET WITHOUT CLUSTER, it won't let you. I think this is > bogus and we should change it so that CLUSTER refuses to change the > clustered index on a system catalog, unless allow_system_table_mods is > on. However, that would be a change from longstanding behavior which > is specifically tested for in regression tests, so I didn't do it. > We can discuss such a change separately. But I did make REPACK refuse > to do that, because we don't need to propagate bogus historical > behavior. So REPACK will fail if you try to change the indisclustered > index, but it will work fine if you repack based on the same index as > before, or repack with no index. > Since cluster will presumably be deprecated with this release, I'd leave the existing behavior and move forward with repack as you've laid out. > - pg_repackdb: if you try with a non-superuser without specifying a > table name, it will fail as soon as it hits the first catalog table or > whatever with "ERROR: cannot lock this table". This is sorta fine for > vacuumdb, but only because VACUUM itself will instead say "WARNING: > cannot lock table XYZ, skipping", so it's not an error and vacuumdb > keeps running. IMO this is bogus: vacuumdb should not try to process > tables that it doesn't have privileges to. However, not wanting to > change longstanding behavior, I left that alone. For pg_repackdb, I > added a condition in the WHERE clause there to only fetch tables that > the current user has MAINTAIN privilege over. Then you can do a > "pg_repackdb -U foobar" and it will nicely process the tables that > that user is allowed to process. We can discuss changing the vacuumdb > behavior separately. Again, vacuumdb seems to be a good example of what not to do, but I'll leave that for another thread. In general I like this idea, but it does make for a weird corner case where if I specify a table with -t that I don't have permission to repack, repack returns silently whilst doing nothing. I suppose one way to handle that would be to check if the table passed in -t is found in the list of tables with MAINTAIN privileges, and if not to issue a WARNING like "%s not found. Make sure that the table exists and that you have MAINTAIN privileges". Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-10-30T23:17:05Z
Hello, Here's a new installment of this series, v25, including the CONCURRENTLY part, which required some conflict fixes on top of the much-changed v24-0001 patch. After the talk on this subject for PGConf.EU, there were some reservations about this whole project, and if I understand correctly, they can be summarized in these three points: 1. Would the spill files for reorderbuffers occupy as much disk space as it takes to copy the initial contents of the table, for each active logical decoding replication slot? Antonin claims (I haven't verified this) that there are some hacks in place to avoid this problem, or that it is easy to install some -- and if so, then this patch would already be better than pg_repack. This perhaps merits more testing. 2. Is the concurrent REPACK operation MVCC-safe? At the moment, with the present implementation, no it is not. There are discussions on getting this fixed, and Mihail has proposed some patches which at least are quite short, though their safety is something we need to assess in more depth. 3. Would the xmin horizon remain stuck at the spot where REPACK started, thereby preventing VACUUM from cleaning up recently-dead rows in other tables? As I understand, with the current implementation, yes it would, and we cannot easily apply hacks such as PROC_IN_VACUUM to prevent it, because it would introduce the same problems it did for CREATE INDEX CONCURRENTLY that was fixed in pg14 (commit 042b584c7f7d62). Mihail and Antonin have discussed possible ways to ease this, but we don't have code for that yet. This is, again, no worse than VACUUM FULL or CLUSTER, so lack of this wouldn't be a killer for this project, though of course it would be much better to do better. I have not yet addressed Robert Treat's feedback from October 12th. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ Officer Krupke, what are we to do? Gee, officer Krupke, Krup you! (West Side Story, "Gee, Officer Krupke")
-
Re: Adding REPACK [concurrently]
jian he <jian.universality@gmail.com> — 2025-11-01T12:42:47Z
On Fri, Oct 31, 2025 at 7:17 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hello, > > Here's a new installment of this series, v25, including the CONCURRENTLY > part, which required some conflict fixes on top of the much-changed > v24-0001 patch. > hi. if (params.options & CLUOPT_ANALYZE) ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot %s multiple tables", "REPACK (ANALYZE)")); for this error case, adding a simple test case would be better? + /* Do an analyze, if requested */ + if (params->options & CLUOPT_ANALYZE) + { + VacuumParams vac_params = {0}; + + vac_params.options |= VACOPT_ANALYZE; + if (params->options & CLUOPT_VERBOSE) + vac_params.options |= VACOPT_VERBOSE; + analyze_rel(RelationGetRelid(rel), NULL, vac_params, + stmt->relation->va_cols, true, NULL); + } Looking at the comments in struct VacuumParams, some fields have nonzero default values — for example, log_vacuum_min_duration. Do we need to explicitly set these fields to their default values? (see ExecVacuum) repack.sgml can also add a <refsect1> <title>See Also</title> similar to analyze.sgml, vacuum.sgml doc/src/sgml/ref/repack.sgml synopsis section missing syntax: REPACK USING INDEX I am wondering, can we also support REPACK opt_utility_option_list USING INDEX MATERIALIZED VIEW: create materialized view a_________ as select * from t2; repack (verbose); INFO: repacking "public.a_________" in physical order INFO: "public.a_________": found 0 removable, 10 nonremovable row versions in 1 pages DETAIL: 0 dead row versions cannot be removed yet. CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. cluster (verbose); won't touch materialized view a_________ but materialized views don't have bloat, nothing can be removed. So here we are waste cycles to repack materialized view? -
Re: Adding REPACK [concurrently]
Sergei Kornilov <sk@zsrv.org> — 2025-11-01T12:53:33Z
Hello! > but materialized views don't have bloat, nothing can be removed. REFRESH MATERIALIZED VIEW CONCURRENTLY does not replace relation completely but updates the relation using insert and delete queries (refresh_by_match_merge in src/backend/commands/matview.c) - so there may be bloat. regards, Sergei
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-11-01T18:16:00Z
Hello! On Fri, Oct 31, 2025 at 12:17 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Here's a new installment of this series, v25, including the CONCURRENTLY > part, which required some conflict fixes on top of the much-changed > v24-0001 patch. > * cluster.c > * CLUSTER a table on an index. This is now also used for VACUUM FULL. Should we add something about repack here? > ii_ExclusinOps typo here. > * index is inserted into catalogs and needs to be built later on. Now it is only in case concurrently == true > * Build the index information for the new index. Note that rebuild of > * indexes with exclusion constraints is not supported, hence there is no > * need to fill all the ii_Exclusion* fields. Now the function supports its in !concurrently mode. Should we fill ii_Exclusion? Also, it says > If !concurrently, ii_ExclusinOps is currently not needed. But it is not clear - why not? > newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs, > oldInfo->ii_NumIndexKeyAttrs, > oldInfo->ii_Am, > indexExprs, > indexPreds, > oldInfo->ii_Unique, > oldInfo->ii_NullsNotDistinct, > false, /* not ready for inserts */ > true, > indexRelation->rd_indam->amsummarizing, > oldInfo->ii_WithoutOverlaps); Is it ok we pass isready == false if !concurrent? Also, we pass concurrent == true even if concurrently == false - feels strange and probably wrong. > This difference does has no impact on XidInMVCCSnapshot(). Should it be "This difference has no impact"? > * pgoutput_cluster.c > * src/backend/replication/pgoutput_cluster/pgoutput_cluster.c it is pgoutput_trepack.c :) Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-11-03T07:56:40Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello! > > On Fri, Oct 31, 2025 at 12:17 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Here's a new installment of this series, v25, including the CONCURRENTLY > > part, which required some conflict fixes on top of the much-changed > > v24-0001 patch. > > > * cluster.c > > * CLUSTER a table on an index. This is now also used for VACUUM FULL. ok > Should we add something about repack here? > > > ii_ExclusinOps > typo here. ok > > * index is inserted into catalogs and needs to be built later on. > Now it is only in case concurrently == true ok > > * Build the index information for the new index. Note that rebuild of > > * indexes with exclusion constraints is not supported, hence there is no > > * need to fill all the ii_Exclusion* fields. > > Now the function supports its in !concurrently mode. Should we fill > ii_Exclusion? Also, it says > > > If !concurrently, ii_ExclusinOps is currently not needed. > But it is not clear - why not? Right, makeIndexInfo() needs to be adjusted. > > > newInfo = makeIndexInfo(oldInfo->ii_NumIndexAttrs, > > oldInfo->ii_NumIndexKeyAttrs, > > oldInfo->ii_Am, > > indexExprs, > > indexPreds, > > oldInfo->ii_Unique, > > oldInfo->ii_NullsNotDistinct, > > false, /* not ready for inserts */ > > true, > > indexRelation->rd_indam->amsummarizing, > > oldInfo->ii_WithoutOverlaps); > > Is it ok we pass isready == false if !concurrent? > Also, we pass concurrent == true even if concurrently == false - feels > strange and probably wrong. You're right, both arguments are wrong. > > This difference does has no impact on XidInMVCCSnapshot(). > Should it be "This difference has no impact"? ok > > * pgoutput_cluster.c > > * src/backend/replication/pgoutput_cluster/pgoutput_cluster.c > it is pgoutput_trepack.c :) ok I'll fix all the problems in the next version. Thanks! -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
jian he <jian.universality@gmail.com> — 2025-11-05T02:48:21Z
On Fri, Oct 31, 2025 at 7:17 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hello, > > Here's a new installment of this series, v25, including the CONCURRENTLY > part, which required some conflict fixes on top of the much-changed > v24-0001 patch. > <refnamediv> <refname>pg_repackdb</refname> <refpurpose>repack and analyze a <productname>PostgreSQL</productname> database</refpurpose> </refnamediv> but with --all option specified, it's doing repack whole cluster. (more than one database). I am not fully sure this description is OK. I think pg_repackdb Synopsis section: pg_repackdb [connection-option...] [option...] [ -t | --table table [( column [,...] )] ] ... [ dbname | -a | --all ] pg_repackdb [connection-option...] [option...] [ -n | --schema schema ] ... [ dbname | -a | --all ] pg_repackdb [connection-option...] [option...] [ -N | --exclude-schema schema ] ... [ dbname | -a | --all ] can be simplified the same way as as pg_dump: pg_repackdb [connection-option...] [option...] [ dbname | -a | --all ] ------------------------ [-d] dbname [--dbname=]dbname what do you think to expand it as below: dbname -d dbname --dbname=dbname -------------------- + printf(_(" --index[=INDEX] repack following an index\n")); should it be + printf(_("--index[=INDEX] repack following an index\n")); ? similar to pg_dump: printf(_("\nIf no database name is supplied, then the PGDATABASE environment\n" "variable value is used.\n\n")); in pg_repackdb help section, we can mention: printf(_("\nIf no database name is supplied and --all option not specified then the PGDATABASE environment\n" "variable value is used.\n\n")); Do you think it's necessary? what the expectation of pg_repackdb --index=index_name, the doc is not very helpful. pg_repackdb --analyze --index=zz --verbose pg_repackdb: repacking database "src3" pg_repackdb: error: processing of database "src3" failed: ERROR: "zz" is not an index for table "tenk1" select pg_get_indexdef ('zz'::regclass); pg_get_indexdef --------------------------------------------------- CREATE INDEX zz ON public.tenk2 USING btree (two) ------ jian he EDB: http://www.enterprisedb.com -
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-11-05T05:10:47Z
On Tue, Nov 4, 2025 at 9:48 PM jian he <jian.universality@gmail.com> wrote: > On Fri, Oct 31, 2025 at 7:17 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > > Hello, > > > > Here's a new installment of this series, v25, including the CONCURRENTLY > > part, which required some conflict fixes on top of the much-changed > > v24-0001 patch. > > > > <refnamediv> > <refname>pg_repackdb</refname> > <refpurpose>repack and analyze a <productname>PostgreSQL</productname> > database</refpurpose> > </refnamediv> > > but with --all option specified, it's doing repack whole cluster. > (more than one database). > I am not fully sure this description is OK. > This wording came from vacuumdb, which operates the same way, and I don't think it's lead to confusion. And while I don't think we need to take away the option, I see no reason to encourage the idea that people should be doing cluster wide full database repacks. On that note, I'd take the "and analyze" from the refpurpose as well; the more I look at it, I see pg_repackdb as a replacement for clusterdb, with selected bells and whistles from vacuum full or external repack-type tooling, but at the end of the day that's a simpler model for operators, and helps draw a distinction for which features we DONT need to include, like -Z (ie. analyze only; if you want that, use vacuumdb, not pg_repackdb) > > I think pg_repackdb Synopsis section: > pg_repackdb [connection-option...] [option...] [ -t | --table table [( > column [,...] )] ] ... [ dbname | -a | --all ] > pg_repackdb [connection-option...] [option...] [ -n | --schema schema > ] ... [ dbname | -a | --all ] > pg_repackdb [connection-option...] [option...] [ -N | --exclude-schema > schema ] ... [ dbname | -a | --all ] > > can be simplified the same way as as pg_dump: > > pg_repackdb [connection-option...] [option...] [ dbname | -a | --all ] > I think it's laid out that way in vacuumdb to indicate that those options are exclusive of one another. I'm not sure how convincing that is, but the above would need to do more to make the switch imo. > ------------------------ > [-d] dbname > [--dbname=]dbname > > what do you think to expand it as below: > dbname > -d dbname > --dbname=dbname not sure i am following this one, but the brackets are the standard way we should items to be optional, which in either case they are. > -------------------- > > + printf(_(" --index[=INDEX] repack following an index\n")); > should it be > + printf(_("--index[=INDEX] repack following an index\n")); > ? > I believe this is included for alignment, since this option has no shorthand version. > > similar to pg_dump: > printf(_("\nIf no database name is supplied, then the PGDATABASE > environment\n" > "variable value is used.\n\n")); > > in pg_repackdb help section, we can mention: > printf(_("\nIf no database name is supplied and --all option not > specified then the PGDATABASE environment\n" > "variable value is used.\n\n")); > Do you think it's necessary? > no. (again, looking first at clusterdb, and also vacuumdb, neither of which have it). > > what the expectation of > pg_repackdb --index=index_name, the doc is not very helpful. > > pg_repackdb --analyze --index=zz --verbose > pg_repackdb: repacking database "src3" > pg_repackdb: error: processing of database "src3" failed: ERROR: "zz" > is not an index for table "tenk1" > > select pg_get_indexdef ('zz'::regclass); > pg_get_indexdef > --------------------------------------------------- > CREATE INDEX zz ON public.tenk2 USING btree (two) > Hmm... yes, this is a bit confusing. I didn't verify it in the code, but from memory I think the --index option is meant to be used only in conjunction with --table, in which case it would repack the table using the specified index. I could be overlooking something though. Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-11-05T07:12:19Z
Robert Treat <rob@xzilla.net> wrote: > On Tue, Nov 4, 2025 at 9:48 PM jian he <jian.universality@gmail.com> wrote: > > what the expectation of > > pg_repackdb --index=index_name, the doc is not very helpful. > > > > pg_repackdb --analyze --index=zz --verbose > > pg_repackdb: repacking database "src3" > > pg_repackdb: error: processing of database "src3" failed: ERROR: "zz" > > is not an index for table "tenk1" > > > > select pg_get_indexdef ('zz'::regclass); > > pg_get_indexdef > > --------------------------------------------------- > > CREATE INDEX zz ON public.tenk2 USING btree (two) > > > > Hmm... yes, this is a bit confusing. I didn't verify it in the code, > but from memory I think the --index option is meant to be used only in > conjunction with --table, in which case it would repack the table > using the specified index. I could be overlooking something though. The corresponding code is: + /* + * In REPACK mode, if the 'using_index' option was given but no index + * name, filter only tables that have an index with indisclustered set. + * (If an index name is given, we trust the user to pass a reasonable list + * of tables.) + * + * XXX it may be worth printing an error if an index name is given with no + * list of tables. + */ + if (vacopts->mode == MODE_REPACK && + vacopts->using_index && !vacopts->indexname) + { + appendPQExpBufferStr(&catalog_query, + " AND EXISTS (SELECT 1 FROM pg_catalog.pg_index\n" + " WHERE indrelid = c.oid AND indisclustered)\n"); + } I'm not sure if it's worth allowing the --index option to have an argument. Since the user can specify multiple tables, he should also be able to specify multiple indexes. And then the question would be: what should happen if the user forgot to specify (or just mistyped) the index name for a table which does not yet have the clustering index set? Skip that table (and print out a warning)? Or consider it an error? -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
jian he <jian.universality@gmail.com> — 2025-11-05T08:46:04Z
On Wed, Nov 5, 2025 at 1:11 PM Robert Treat <rob@xzilla.net> wrote: > > -------------------- > > > > + printf(_(" --index[=INDEX] repack following an index\n")); > > should it be > > + printf(_("--index[=INDEX] repack following an index\n")); > > ? > > > > I believe this is included for alignment, since this option has no > shorthand version. > if you compare pg_dump --help, pg_repackdb --help then you will see the inconsistency. This is legacy behavior, but can we move some of the error checks in do_analyze_rel to an earlier point? we call cluster_rel before analyze_rel, cluster_rel is way more time-consuming, a failure in analyze_rel means all the previous work (cluster_rel) is wasted. + else if (HeadMatches("REPACK", "(*") && + !HeadMatches("REPACK", "(*)")) + { + /* + * This fires if we're in an unfinished parenthesized option list. + * get_previous_words treats a completed parenthesized option list as + * one word, so the above test is correct. + */ + if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) + COMPLETE_WITH("VERBOSE"); + else if (TailMatches("VERBOSE")) + COMPLETE_WITH("ON", "OFF"); + } this part can also support the ANALYZE option? ClusterStmt should be removed from src/tools/pgindent/typedefs.list? doc/src/sgml/ref/clusterdb.sgml <para> <application>clusterdb</application> has been superceded by <application>pg_repackdb</application>. </para> google told me, "superceded" should be "superseded" -- jian he EDB: http://www.enterprisedb.com -
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2025-11-09T22:13:43Z
On Wed, Nov 5, 2025 at 2:12 AM Antonin Houska <ah@cybertec.at> wrote: > Robert Treat <rob@xzilla.net> wrote: > > On Tue, Nov 4, 2025 at 9:48 PM jian he <jian.universality@gmail.com> wrote: > > > > what the expectation of > > > pg_repackdb --index=index_name, the doc is not very helpful. > > > > > > pg_repackdb --analyze --index=zz --verbose > > > pg_repackdb: repacking database "src3" > > > pg_repackdb: error: processing of database "src3" failed: ERROR: "zz" > > > is not an index for table "tenk1" > > > > > > select pg_get_indexdef ('zz'::regclass); > > > pg_get_indexdef > > > --------------------------------------------------- > > > CREATE INDEX zz ON public.tenk2 USING btree (two) > > > > > > > Hmm... yes, this is a bit confusing. I didn't verify it in the code, > > but from memory I think the --index option is meant to be used only in > > conjunction with --table, in which case it would repack the table > > using the specified index. I could be overlooking something though. > > The corresponding code is: > > + /* > + * In REPACK mode, if the 'using_index' option was given but no index > + * name, filter only tables that have an index with indisclustered set. > + * (If an index name is given, we trust the user to pass a reasonable list > + * of tables.) > + * > + * XXX it may be worth printing an error if an index name is given with no > + * list of tables. > + */ > + if (vacopts->mode == MODE_REPACK && > + vacopts->using_index && !vacopts->indexname) > + { > + appendPQExpBufferStr(&catalog_query, > + " AND EXISTS (SELECT 1 FROM pg_catalog.pg_index\n" > + " WHERE indrelid = c.oid AND indisclustered)\n"); > + } > > I'm not sure if it's worth allowing the --index option to have an > argument. Since the user can specify multiple tables, he should also be able > to specify multiple indexes. And then the question would be: what should > happen if the user forgot to specify (or just mistyped) the index name for a > table which does not yet have the clustering index set? Skip that table (and > print out a warning)? Or consider it an error? > Ah, yes, this is something completely different. So, we do need a way to differentiate between "vacuum full" vs "cluster" all tables... as well as "vacuum full" vs "cluster" of a specific table (including the idea of "vacuum full" of a previously clustered table, and the existing code handles all that (though I might quibble with the option name). As for having an --index= option, I'd love to hear the use case; something like partitions or maybe some client per schema situation comes to mind, but ISTM in all those cases the user would also know (or be expected to know) the table name, so I agree with Antonin that the extra complexity doesn't seem worth supporting to me. (It's even worse the more you think about it, what if some table has the index named above, but is clustered on a different index, then what should we do?) As for the use case I was thinking of, specifying a table and index in order to repack using that index (and setting indisclustered if not already); while I feel like that would be a useful option, if it isn't currently supported I don't see a strong argument for adding it now. Robert Treat https://xzilla.net -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-02T00:50:00Z
Hello, Antonin! On Mon, Nov 3, 2025 at 8:56 AM Antonin Houska <ah@cybertec.at> wrote: > I'll fix all the problems in the next version. Thanks! A few more moments I mentioned: > switch ((vis = HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))) vis is unused, also to double braces. > LockBuffer(buf, BUFFER_LOCK_UNLOCK); > continue; > } > /* > * In the concurrent case, we have a copy of the tuple, so we > * don't worry whether the source tuple will be deleted / updated > * after we release the lock. > */ > LockBuffer(buf, BUFFER_LOCK_UNLOCK); >} I think locking and comments are a little bit confusing here. I think we may use single LockBuffer(buf, BUFFER_LOCK_UNLOCK); before `if (isdead)` as it was before. Also, I am not sure "we have a copy" is the correct point here, I think motivation is mostly the same as in heapam_index_build_range_scan. Also, I think it is a good idea to add tests for index-based and sort-based repack. Also, for sort-based I think we need to also call repack_decode_concurrent_changes during insertion phase > is_system_catalog && !concurrent 2 places, always true, feels strange. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-02T16:14:57Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello, Antonin! > > On Mon, Nov 3, 2025 at 8:56 AM Antonin Houska <ah@cybertec.at> wrote: > > I'll fix all the problems in the next version. Thanks! > > A few more moments I mentioned: > > > switch ((vis = HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf))) > vis is unused, also to double braces. > > > LockBuffer(buf, BUFFER_LOCK_UNLOCK); > > continue; > > } > > > /* > > * In the concurrent case, we have a copy of the tuple, so we > > * don't worry whether the source tuple will be deleted / updated > > * after we release the lock. > > */ > > LockBuffer(buf, BUFFER_LOCK_UNLOCK); > >} > > I think locking and comments are a little bit confusing here. > I think we may use single LockBuffer(buf, BUFFER_LOCK_UNLOCK); before > `if (isdead)` as it was before. > Also, I am not sure "we have a copy" is the correct point here, I > think motivation is mostly the same as in > heapam_index_build_range_scan. All these problems are due to incorrect separation of the "preserve visibility" part of the patch series. Will be fixed in the next version. > Also, I think it is a good idea to add tests for index-based and > sort-based repack. Not sure, cluster.sql already seems to do the same. > Also, for sort-based I think we need to also call > repack_decode_concurrent_changes during insertion phase I miss the point. The current coding is such that this part if (concurrent) { XLogRecPtr end_of_wal; end_of_wal = GetFlushRecPtr(NULL); if ((end_of_wal - end_of_wal_prev) > wal_segment_size) { repack_decode_concurrent_changes(decoding_ctx, end_of_wal); end_of_wal_prev = end_of_wal; } } gets called regardless the value of 'tuplesort' above. > > is_system_catalog && !concurrent > 2 places, always true, feels strange. ok -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-02T16:22:47Z
Hi! On Tue, Dec 2, 2025 at 5:14 PM Antonin Houska <ah@cybertec.at> wrote: > Not sure, cluster.sql already seems to do the same. I think in the case of CONCURRENTLY it may behave a little bit different, but not sure. > I miss the point. The current coding is such that this part I mean call it periodically in both loops: scan loop and insertion loop. Best greetings, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-03T07:56:01Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Tue, Dec 2, 2025 at 5:14 PM Antonin Houska <ah@cybertec.at> wrote: > > Not sure, cluster.sql already seems to do the same. > I think in the case of CONCURRENTLY it may behave a little bit > different, but not sure. > > > I miss the point. The current coding is such that this part > I mean call it periodically in both loops: scan loop and insertion loop. ok, that makes sense. I'll add that to the next version. Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-04T13:36:12Z
jian he <jian.universality@gmail.com> wrote: > if (params.options & CLUOPT_ANALYZE) > ereport(ERROR, > errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > errmsg("cannot %s multiple tables", "REPACK (ANALYZE)")); > for this error case, adding a simple test case would be better? More options should probably be tested, currently we have only very basic regression test for pg_repackdb. TBD > + /* Do an analyze, if requested */ > + if (params->options & CLUOPT_ANALYZE) > + { > + VacuumParams vac_params = {0}; > + > + vac_params.options |= VACOPT_ANALYZE; > + if (params->options & CLUOPT_VERBOSE) > + vac_params.options |= VACOPT_VERBOSE; > + analyze_rel(RelationGetRelid(rel), NULL, vac_params, > + stmt->relation->va_cols, true, NULL); > + } > > Looking at the comments in struct VacuumParams, some fields have nonzero default > values — for example, log_vacuum_min_duration. > Do we need to explicitly set these fields to their default values? > (see ExecVacuum) Perhaps, TBD. > repack.sgml can also add a > <refsect1> <title>See Also</title> > similar to analyze.sgml, vacuum.sgml ok, added this in v26 (to be posted today): <refsect1> <title>See Also</title> <simplelist type="inline"> <member><xref linkend="app-pgrepackdb"/></member> <member><xref linkend="repack-progress-reporting"/></member> </simplelist> </refsect1> (Not added reference to VACUUM FULL and CLUSTER intentionally: whoever uses REPACK should not need them. > doc/src/sgml/ref/repack.sgml > synopsis section missing syntax: > REPACK USING INDEX ok, added in v26. > I am wondering, can we also support > REPACK opt_utility_option_list USING INDEX I agree, added that in v26 (Hopefully I haven't broken anything, the syntax is not trival anymore.) > MATERIALIZED VIEW: > create materialized view a_________ as select * from t2; > > repack (verbose); > INFO: repacking "public.a_________" in physical order > INFO: "public.a_________": found 0 removable, 10 nonremovable row > versions in 1 pages > DETAIL: 0 dead row versions cannot be removed yet. > CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s. > > cluster (verbose); > won't touch materialized view a_________ > > but materialized views don't have bloat, nothing can be removed. > So here we are waste cycles to repack materialized view? Answered in https://www.postgresql.org/message-id/3436011762001613%40a7af8471-b1b8-48c2-9ff7-631187067407 -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-04T17:43:27Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Here's a new installment of this series, v25, including the CONCURRENTLY > part, which required some conflict fixes on top of the much-changed > v24-0001 patch. v26 attached here. It's been rebased and reflects most of the feedback. A few incomplete items are marked as TBD here [1] and [2] is another thing that needs discussion. Besides that, I've done some refactoring in 0004: 1) move more code to setup_logical_decoding(), and 2) reduced the number of arguments of process_concurrent_changes() by using a new structure. Both these changes are a preparation for a background worker that will perform the logical decoding, but seem to be useful as such. (I have a PoC of the worker but will post it later, it doesn't seem to be the priority for now.) I've also removed support for decoding TRUNCATE because I realized that this command uses AccessExclusiveLock, so it cannot be executed on a table that REPACK (CONCURRENTLY) is just processing. Also I tried to fix TAB completion in psql. > I have not yet addressed Robert Treat's feedback from October 12th. These are still pending. [1] https://www.postgresql.org/message-id/23631.1764855372%40localhost [2] https://www.postgresql.org/message-id/CAJSLCQ2_jX8WmNOC4eu6hL5QyNHceOkgPbGhKHFw2X5onVEKDQ%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-05T00:03:00Z
Hello, Antonin! On Thu, Dec 4, 2025 at 6:43 PM Antonin Houska <ah@cybertec.at> wrote: > v26 attached here. It's been rebased and reflects most of the feedback. Some comments on 0001-0002: 1) > cluster_rel(stmt->command, rel, indexOid, params); cluster_rel closes relation, and after it is dereferenced a few lines after. Technically it may be correct, but feels a little bit strange. 2) > if (vacopts->mode == MODE_VACUUM) I think for better compatibility it is better to handle new value in if - (vacopts->mode == MODE_REPACK) to keep old cases unchanged 3) > case T_RepackStmt: > tag = CMDTAG_REPACK; > break; should we use instead: case T_RepackStmt: if (((RepackStmt *) parsetree)->command == REPACK_COMMAND_CLUSTER) tag = CMDTAG_CLUSTER; else tag = CMDTAG_REPACK; break; or delete CMDTAG_CLUSTER - since it not used anymore 4) "has been superceded by" typo Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-06T18:16:00Z
Hello, Antonin! Some comments for 0003: > /* allocate in transaction context */ It may be any context now, because it is a function now. > result = CopySnapshot(snapshot); > /* Restore the original values so the source is intact. */ > snapshot->xip = oldxip; > snapshot->xcnt = oldxcnt; I think it is worth to call pfree(newxip) here. > "This difference does has no impact" should be "This difference has no impact"? Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-07T16:03:00Z
Hello, comments so far on 0004: --- > ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old); I think the biggest issue we have so far - repack_decode_concurrent_changes is not called while new indexes are built (the build itself creates a huge amount of WAL and takes days sometimes). Looks like a way to catastrophic scenarios :) Some small parts of it may be related to reset snapshots tech in CIC case: 1) if we build new indexes concurrently in REPACK case 2) and reset snapshots every so often 3) we may use the same callback to also process WAL every so often 4) but it still not applies to some phases of index building (batch insertion phase, for example) Or should we move repack_decode_concurrent_changes calls into some kind of worker instead? --- > if (OldHeap->rd_rel->reltoastrelid) > LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); I think we should pass mode from rebuild_relation here - because AccessExclusiveLock will break "CONCURRENTLY" totally. And also upgrade before swap probably. --- > cluster_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) Should be check CheckSlotPermissions(); here? Aso, maybe it is worth mentioning in docs. --- > REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; Some paths (without index) are not covered in any way in tests at the moment. Also, I think some TOAST-related scenarios too. > * Alternatively, we can lock all the indexes now in a mode that blocks > * all the ALTER INDEX commands (ShareUpdateExclusiveLock ?), and keep I think it's better to lock. --- > rebuild_relation(RepackCommand cmd, Relation OldHeap, Relation index, "cmd" is not used. --- > apply_concurrent_update > apply_concurrent_delete > apply_concurrent_insert "change" is not used, but I think it is intentionally for the MVCC-safe case. --- > rebuild_relation(RepackCommand cmd, Relation OldHeap, Relation index, > bool verbose, bool concurrent) "concurrent" is "concurrently" in definition. --- > TM_FailureData *tmfd, bool changingPart, > bool wal_logical); Maybe "walLogical" to keep it aligned with "changingPart"? --- > subtransacion typo --- > Should we check a the end "a" is "at"? --- > Note that <command>REPACK</command> with the > the <literal>CONCURRENTLY</literal> option does not try to order the double "the" --- > if (size >= 0x3FFFFFFF) if (size >= MaxAllocSize) --- > extern bool HeapTupleMVCCInserted(HeapTuple htup, Snapshot snapshot, > Buffer buffer); > extern bool HeapTupleMVCCNotDeleted(HeapTuple htup, Snapshot snapshot, > Buffer buffer); Looks like this from another patch. --- src/backend/utils/cache/relcache.c > #include "commands/cluster.h" may be removed --- > during any of the preceding > phase. "phases" --- > # Prefix the system columns with underscore as they are not allowed as column > # names. Should it be removed? --- > "Failed to find target tuple" This and multiple other new error messages should start with lowercase --- > Copyright (c) 2012-2024, PostgreSQL Global Development Group in pgoutput_repack - maybe it is time to adjust. --- src/test/modules/injection_points/logical.conf Better to add newline --- > SELECT injection_points_detach('repack-concurrently-before-lock'); Uses spaces, need to be tabs. Next step in my plan - rebase MVCC-safe commit and test it with some amount of stress tests. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-08T07:35:53Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Thu, Dec 4, 2025 at 6:43 PM Antonin Houska <ah@cybertec.at> wrote: > > v26 attached here. It's been rebased and reflects most of the feedback. > > Some comments on 0001-0002: > 1) > > > cluster_rel(stmt->command, rel, indexOid, params); > cluster_rel closes relation, and after it is dereferenced a few lines after. > Technically it may be correct, but feels a little bit strange. ok, will be fixed in the next version (supposedly later today). > 2) > > > if (vacopts->mode == MODE_VACUUM) > I think for better compatibility it is better to handle new value in > if - (vacopts->mode == MODE_REPACK) to keep old cases unchanged I suppose you mean vacuuming.c. We're considering removal of pg_repackdb from the patchset, so let's decide on this later. > 3) > > > case T_RepackStmt: > > tag = CMDTAG_REPACK; > > break; > > should we use instead: > > case T_RepackStmt: > if (((RepackStmt *) parsetree)->command == REPACK_COMMAND_CLUSTER) > tag = CMDTAG_CLUSTER; > else > tag = CMDTAG_REPACK; > break; > > or delete CMDTAG_CLUSTER - since it not used anymore LGTM, will include it in the next version. > 4) > "has been superceded by" > typo ok. (This may also be removed, as it's specific to pg_repackdb.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-08T09:51:36Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Some comments for 0003: > > > /* allocate in transaction context */ > It may be any context now, because it is a function now. Inaccuracy not introduced by REPACK, but I think it's o.k. if the next version of this patch will remove the comment. > > result = CopySnapshot(snapshot); > > > /* Restore the original values so the source is intact. */ > > snapshot->xip = oldxip; > > snapshot->xcnt = oldxcnt; > > I think it is worth to call pfree(newxip) here. ok > > "This difference does has no impact" > > should be "This difference has no impact"? Right, thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-09T18:52:37Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello, comments so far on 0004: > > --- > > ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old); > > I think the biggest issue we have so far - > repack_decode_concurrent_changes is not called while new indexes are > built (the build itself creates a huge amount of WAL and takes days > sometimes). Looks like a way to catastrophic scenarios :) Indeed, that may be a problem. > Some small parts of it may be related to reset snapshots tech in CIC case: > 1) if we build new indexes concurrently in REPACK case > 2) and reset snapshots every so often > 3) we may use the same callback to also process WAL every so often > 4) but it still not applies to some phases of index building (batch > insertion phase, for example) I prefer not to depend on other improvements. > Or should we move repack_decode_concurrent_changes calls into some > kind of worker instead? Worker makes more sense to me - the initial implementation is in 0005. > --- > > if (OldHeap->rd_rel->reltoastrelid) > > LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); > > I think we should pass mode from rebuild_relation here - because > AccessExclusiveLock will break "CONCURRENTLY" totally. Good point, I missed this. > And also upgrade before swap probably. rebuild_relation_finish_concurrent() already does that. > --- > > cluster_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) > > Should be check CheckSlotPermissions(); here? Aso, maybe it is worth > mentioning in docs. setup_logical_decoding() does that, but I'm not sure if we should really require the REPLICATION user attribute for REPACK. I need to think about this, perhaps ACL_MAINTAIN is enough. > --- > > REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; > > Some paths (without index) are not covered in any way in tests at the moment. > Also, I think some TOAST-related scenarios too. I added test for TOAST to "injection_points" and hit a serious problem: when applying concurrent changes to the new table, REPACK tried to delete rows from the new one. The point is that the "swap TOAST by content" technique cannot be used here. Fixed, thanks for this suggestion! > > * Alternatively, we can lock all the indexes now in a mode that blocks > > * all the ALTER INDEX commands (ShareUpdateExclusiveLock ?), and keep > > I think it's better to lock. ok, changed > --- > > rebuild_relation(RepackCommand cmd, Relation OldHeap, Relation index, > > "cmd" is not used. Fixed (not specific to 0004). > --- > > apply_concurrent_update > > apply_concurrent_delete > > apply_concurrent_insert > > "change" is not used, but I think it is intentionally for the MVCC-safe case. Not sure if it's necessary for the MVCC-safe case, I consider it leftover from some previous version. Removed. > --- > > rebuild_relation(RepackCommand cmd, Relation OldHeap, Relation index, > > bool verbose, bool concurrent) > > "concurrent" is "concurrently" in definition. Fixed. > --- > > > TM_FailureData *tmfd, bool changingPart, > > bool wal_logical); > Maybe "walLogical" to keep it aligned with "changingPart"? ok > --- > > subtransacion > typo > I removed the related code. It was a workaround for plan_cluster_use_sort() not to leave locks behind. However, as REPACK (CONCURRENTLY) does not unlock the relation anymore, this is not needed as well. > --- > > Should we check a the end > > "a" is "at"? Removed when addressing one of the previous comments. > > --- > > Note that <command>REPACK</command> with the > > the <literal>CONCURRENTLY</literal> option does not try to order the > > double "the" Fixed. > --- > > if (size >= 0x3FFFFFFF) > if (size >= MaxAllocSize) Fixed. > --- > > extern bool HeapTupleMVCCInserted(HeapTuple htup, Snapshot snapshot, > > Buffer buffer); > > extern bool HeapTupleMVCCNotDeleted(HeapTuple htup, Snapshot snapshot, > > Buffer buffer); > > Looks like this from another patch. Right, this is from the "MVCC safety part". > --- > src/backend/utils/cache/relcache.c > > #include "commands/cluster.h" > > may be removed Yes, this belongs to some of the following patches of the series. > --- > > during any of the preceding > > phase. > > "phases" Fixed. > --- > > # Prefix the system columns with underscore as they are not allowed as column > > # names. > > Should it be removed? Done. (Belongs to the "MVCC-safety" part, where the test check xmin, xmax, ...) > --- > > "Failed to find target tuple" > > This and multiple other new error messages should start with lowercase Fixed. > --- > > Copyright (c) 2012-2024, PostgreSQL Global Development Group > > in pgoutput_repack - maybe it is time to adjust. Done. > --- > src/test/modules/injection_points/logical.conf > > Better to add newline Done. > --- > > SELECT injection_points_detach('repack-concurrently-before-lock'); > > Uses spaces, need to be tabs. ok Thanks for the review! -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-12-09T19:22:09Z
Hello, many thanks for the new version. Here's a very quick proposal for a new top-of-file comment on cluster.c, * cluster.c * Implementation of REPACK [CONCURRENTLY], also known as CLUSTER and * VACUUM FULL. * * There are two somewhat different ways to rewrite a table. In non- * concurrent mode, it's easy: take AccessExclusiveLock, create a new * transient relation, copy the tuples over to the relfilenode of the * new relation, swap the relfilenodes, then drop the old relation. * * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock, * then do an initial copy as above. However, while the tuples are being * copied, concurrent transactions could modify the table, and to cope * with those changes, we rely on logical decoding to obtain them from WAL. * A bgworker consumes WAL while the initial copy is ongoing (to prevent * excessive WAL from being reserved), and accumulates the changes in * a tuplestore. Once the initial copy is complete, we read the changes * from the tuplestore and re-apply them on the new heap. Then we * upgrade our ShareUpdateExclusiveLock to AccessExclusiveLock and swap * the relfilenodes. This way, the time we hold a strong lock on the * table is much reduced, and the bloat is greatly reduced. I haven't read build_relation_finish_concurrent() yet to understand how exactly do we do the lock upgrade, which I think is an important point we should address in this comment. Also not addressed is how exactly we handle indexes. Feel free to correct this, reword it or include any additional details that you think are important. (At this point we could just as well rename the file to repack.c, since very little of the original remains. But let's discuss that later.) Thanks, -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Doing what he did amounts to sticking his fingers under the hood of the implementation; if he gets his fingers burnt, it's his problem." (Tom Lane)
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-11T20:38:00Z
Hello, Antonin! On Tue, Dec 9, 2025 at 7:52 PM Antonin Houska <ah@cybertec.at> wrote: > Worker makes more sense to me - the initial implementation is in 0005. Comments for 0005, so far: --- > export_initial_snapshot Hm, should we use ExportSnapshot instead? And ImportSnapshort to import it. --- > get_initial_snapshot Should we check if a worker is still alive while waiting? Also is "process_concurrent_changes". And AFAIU RegisterDynamicBackgroundWorker does not guarantee new workers to be started (in case of some fork-related issues). --- > Assert(res = SHM_MQ_DETACHED); == --- > /* Wait a bit before we retry reading WAL. */ > (void) WaitLatch(MyLatch, > WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, > 1000L, > WAIT_EVENT_REPACK_WORKER_MAIN); Looks like we need ResetLatch(MyLatch); here. --- > * - decoding_ctx - logical decoding context, to capture concurrent data Need to be removed together with parameters. --- > hpm_context = AllocSetContextCreate(TopMemoryContext, > "ProcessParallelMessages", > ALLOCSET_DEFAULT_SIZES); "ProcessRepacklMessages" --- > if (XLogRecPtrIsInvalid(lsn_upto)) > { > SpinLockAcquire(&shared->mutex); > lsn_upto = shared->lsn_upto; > /* 'done' should be set at the same time as 'lsn_upto' */ > done = shared->done; > SpinLockRelease(&shared->mutex); > > /* Check if the work happens to be complete. */ > continue; > } May be moved to the start of the loop to avoid duplication. --- > SpinLockAcquire(&shared->mutex); > valid = shared->sfs_valid; > SpinLockRelease(&shared->mutex); Better to remember last_exported here to avoid any races/misses. --- > shared->lsn_upto = InvalidXLogRecPtr; I think it is better to clear it once it is read (after removing duplication). --- > bool done; bool exit_after_lsn_upto? --- > bool sfs_valid; Do we really need it? I think it is better to leave only last_exported and in process_concurrent_changes wait add argument (last_processed_file) and wait for last_exported to become higher. --- What if we reverse roles of leader-worker? Leader gets a snapshot, transfers it to workers (multiple probably for parallel scan) using already ready mechanics - workers are processing the scan of the table in parallel. Leader decodes the WAL. Also, workers may be assigned with a list of indexes they need to build. Feels like it reuses more from current infrastructure and also needs less different synchronization logic. But I'm not sure about the indexes phase - maybe it is not so easy to do. --- Also, should we add some kind of back pressure between building indexes/new heap and num of WAL we have? But probably it is out of scope of the patch. --- To build N indexes we need to scan table N times. What is about building multiple indexes during a single heap scan? -- Just a gentle reminder about the XMIN_COMMITTED flag and WAL storm after the switch. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-13T18:45:28Z
Hello, everyone. Stress tests for REPACK concurrently in attachment. So far I can't break anything (except MVCC of course). A rebased version of the MVCC-safe "light" version with its own stress test is attached also. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-13T18:48:39Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Hello, many thanks for the new version. Here's a very quick proposal > for a new top-of-file comment on cluster.c, The comment matches 0005, but I had to adjust it for 0004 (no background worker there). Also, the worker writes the changes to a file rather than tuplestore (storage/sharedfileset.h seems to me an easier way to pass the data from one process to another) Besides that I made the following changes: "bloat is greatly reduced" -> "bloat is eliminated" and "table, and to cope with" -> "table. To cope with" > I haven't read build_relation_finish_concurrent() yet to understand how > exactly do we do the lock upgrade, which I think is an important point > we should address in this comment. Also not addressed is how exactly we > handle indexes. Feel free to correct this, reword it or include any > additional details that you think are important. ok, I'll get back to the earlier parts of the set, including this, in the beginning of January. Regarding indexes, one thing I've noticed recently that they get locked in build_new_indexes(), but maybe it should happen earlier. > (At this point we could just as well rename the file to repack.c, since > very little of the original remains. But let's discuss that later.) ok. Do you mean only the file or the functions as well? (I'm not going to do that now, w/o that discussion.) Attached here is a new version of the patch set. Its rebased and extended one more time: 0006 is a PoC of the "snapshot resetting" technique, as discussed elsewhere with Mihail Nikalayeu and Matthias van de Meent. The way snapshot are generated here is different though: we need the snapshots from logical replication's snapbuild.c, not those from procarray.c. More information is in the commit message. I do not insist that this should go to PG 19, just needed some confidence that it's doable, as well as some feedback. There are no tests for this yet, but I've played with it for a while and checked the behavior using debugger. I'm curious to hear if the design is sound. While working on that, I fixed some problems in 0004 and 0005 too. It shouldn't be difficult to identify them using git, if needed. Even if 0005 and 0006 won't land in PG19, these parts show that some refactoring may be needed regarding the AM callback table_relation_copy_for_cluster(). The parts 0004, 0005 and 0006 each change the argument list. It wouldn't be perfect if both PG 19 and 20 changed the API. I think we should reconsider which arguments are generic and which are rather AM-specific. Maybe we should then add an opaque pointer (void *) for the AM-specific information. REPACK could then use it to pass the CONCURRENTLY-specific information. I'm now going to prioritize the parts <= 0004. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-13T18:59:30Z
Once it was sent, I realized MVCC-safe fails with 007_repack_concurrently.pl with TRANSACTION ISOLATION LEVEL REPEATABLE READ uncommented. Don't know why it fails - but happy it fails :) On Sat, Dec 13, 2025 at 7:45 PM Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > Hello, everyone. > > Stress tests for REPACK concurrently in attachment. > So far I can't break anything (except MVCC of course). > > A rebased version of the MVCC-safe "light" version with its own stress > test is attached also. > > Best regards, > Mikhail.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-13T19:01:52Z
Hello, Antonin! On Sat, Dec 13, 2025 at 7:48 PM Antonin Houska <ah@cybertec.at> wrote: > Attached here is a new version of the patch set. Its rebased and extended one > more time: 0006 is a PoC of the "snapshot resetting" technique, as discussed > elsewhere with Mihail Nikalayeu and Matthias van de Meent. The way snapshot > are generated here is different though: we need the snapshots from logical > replication's snapbuild.c, not those from procarray.c. More information is in > the commit message. Have you seen my feedback for 0004? Do you plan to check it? Asking to understand if it is worth reviewing now or later. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2025-12-13T19:39:21Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Tue, Dec 9, 2025 at 7:52 PM Antonin Houska <ah@cybertec.at> wrote: > > Worker makes more sense to me - the initial implementation is in 0005. > > Comments for 0005, so far: Thanks! > --- > > export_initial_snapshot > > Hm, should we use ExportSnapshot instead? And ImportSnapshort to import it. There is at least one thing that I don't want: ImportSnapshot calls SetTransactionSnapshot() at the end. I chose the way leader process uses to serialize and pass snapshot to background workers. > --- > > get_initial_snapshot > > Should we check if a worker is still alive while waiting? Also is > "process_concurrent_changes". ConditionVariableSleep() should handle that - see the WL_EXIT_ON_PM_DEATH flag in ConditionVariableTimedSleep(). > And AFAIU RegisterDynamicBackgroundWorker does not guarantee new > workers to be started (in case of some fork-related issues). Yes, user will get ERROR in such a case. This is different from parallel workers in query processing: if parallel worker cannot be started, the leader (AFAICS) still executes the query. I'm not sure though if we should implement REPACK (CONCURRENTLY) in such a way that it works even w/o the worker. The code would be more complex and the behaviour quite different (I mean the possibly huge amount of unprocessed WAL that you pointed out earlier.) > --- > > Assert(res = SHM_MQ_DETACHED); > > == Thanks! > --- > > /* Wait a bit before we retry reading WAL. */ > > (void) WaitLatch(MyLatch, > > WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, > > 1000L, > > WAIT_EVENT_REPACK_WORKER_MAIN); > > Looks like we need ResetLatch(MyLatch); here. You seem to be right. > --- > > * - decoding_ctx - logical decoding context, to capture concurrent data > > Need to be removed together with parameters. Do you mean in 0005? (It'd help if you pasted the hunk headers.) This should be fixed in v28 [1] > --- > > hpm_context = AllocSetContextCreate(TopMemoryContext, > > "ProcessParallelMessages", > > ALLOCSET_DEFAULT_SIZES); > > "ProcessRepacklMessages" ok, the copy and pasting is a problem that needs to be addressed (mentioned in the last paragraph of the commit message of 0005). > --- > > if (XLogRecPtrIsInvalid(lsn_upto)) > > { > > SpinLockAcquire(&shared->mutex); > > lsn_upto = shared->lsn_upto; > > /* 'done' should be set at the same time as 'lsn_upto' */ > > done = shared->done; > > SpinLockRelease(&shared->mutex); > > > > /* Check if the work happens to be complete. */ > > continue; > > } > > May be moved to the start of the loop to avoid duplication. I found more problems in this part when working on v28, maybe check that. > --- > > SpinLockAcquire(&shared->mutex); > > valid = shared->sfs_valid; > > SpinLockRelease(&shared->mutex); > > Better to remember last_exported here to avoid any races/misses. What races/misses exactly? > --- > > shared->lsn_upto = InvalidXLogRecPtr; > > I think it is better to clear it once it is read (after removing duplication). Maybe, I'll think about it. > --- > > bool done; > > bool exit_after_lsn_upto? Not sure. > --- > > bool sfs_valid; > > Do we really need it? I think it is better to leave only last_exported > and in process_concurrent_changes wait add argument > (last_processed_file) and wait for last_exported to become higher. I'll consider that (The variable is replaced in the 0006 part of v28, but the idea should still be applicable.) > --- > What if we reverse roles of leader-worker? > > Leader gets a snapshot, transfers it to workers (multiple probably for > parallel scan) using already ready mechanics - workers are processing > the scan of the table in parallel. Leader decodes the WAL. Insertion into a table by multiple workers is a special thing, but maybe it'd be doable in this case, but ... > Also, workers may be assigned with a list of indexes they need to build. > > Feels like it reuses more from current infrastructure and also needs > less different synchronization logic. But I'm not sure about the > indexes phase - maybe it is not so easy to do. ... my feelings were the opposite, i.e. I thought require higher amount of code rearrangement. Moreover, the part 0006 of v28 (snapshot switching) would be trickier. It processes one range of blocks after another, and parallelism would make it more difficult. > --- > Also, should we add some kind of back pressure between building > indexes/new heap and num of WAL we have? > But probably it is out of scope of the patch. Do you mean that the decoding worker should be less active if the amount of WAL doesn't grow too fast? > --- > To build N indexes we need to scan table N times. What is about > building multiple indexes during a single heap scan? That sounds like a separate feature, and similarly difficult as enhancing CREATE INDEX so it can create multiple indexes at a time. > -- > Just a gentle reminder about the XMIN_COMMITTED flag and WAL storm > after the switch. ok, I have it in my notes, moved it more to the top :-) [1] https://www.postgresql.org/message-id/210036.1765651719%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2025-12-15T14:25:22Z
On 2025-Dec-13, Antonin Houska wrote: > From 6279394135f2b693b6fffd174822509e0a067cbf Mon Sep 17 00:00:00 2001 > From: Antonin Houska <ah@cybertec.at> > Date: Sat, 13 Dec 2025 19:27:18 +0100 > Subject: [PATCH 4/6] Add CONCURRENTLY option to REPACK command. > diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c > index cc03f0706e9..a956892f42f 100644 > --- a/src/backend/replication/logical/decode.c > +++ b/src/backend/replication/logical/decode.c > @@ -472,6 +473,88 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) > + /* > + * Second, skip records which do not contain sufficient information for > + * the decoding. > + * > + * The problem we solve here is that REPACK CONCURRENTLY generates WAL > + * when doing changes in the new table. Those changes should not be useful > + * for any other user (such as logical replication subscription) because > + * the new table will eventually be dropped (after REPACK CONCURRENTLY has > + * assigned its file to the "old table"). > + */ > + switch (info) > + { > + case XLOG_HEAP_INSERT: > + { > + xl_heap_insert *rec; > + > + rec = (xl_heap_insert *) XLogRecGetData(buf->record); > + > + /* > + * This does happen when 1) raw_heap_insert marks the TOAST > + * record as HEAP_INSERT_NO_LOGICAL, 2) REPACK CONCURRENTLY > + * replays inserts performed by other backends. > + */ > + if ((rec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) == 0) > + return; > + > + break; > + } > + > + case XLOG_HEAP_HOT_UPDATE: > + case XLOG_HEAP_UPDATE: > + { > + xl_heap_update *rec; > + > + rec = (xl_heap_update *) XLogRecGetData(buf->record); > + if ((rec->flags & > + (XLH_UPDATE_CONTAINS_NEW_TUPLE | > + XLH_UPDATE_CONTAINS_OLD_TUPLE | > + XLH_UPDATE_CONTAINS_OLD_KEY)) == 0) > + return; > + > + break; > + } > + > + case XLOG_HEAP_DELETE: > + { > + xl_heap_delete *rec; > + > + rec = (xl_heap_delete *) XLogRecGetData(buf->record); > + if (rec->flags & XLH_DELETE_NO_LOGICAL) > + return; > + break; > + } > + } I'm confused as to the purpose of this addition. I took this whole block out, and no tests seem to fail. Moreover, some of the cases that are being skipped because of this, would already be skipped by code in DecodeInsert / DecodeUpdate anyway. The case for XLOG_HEAP_DELETE seems to have no effect (that is, the "return" there never hits for any tests as far as I can tell.) The reason I ask is that the line immediately below does this: > ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr); which means the Xid is tracked for snapshot building purposes. Which is probably important, because of what the comment right below it says: /* * If we don't have snapshot or we are just fast-forwarding, there is no * point in decoding data changes. However, it's crucial to build the base * snapshot during fast-forward mode (as is done in * SnapBuildProcessChange()) because we require the snapshot's xmin when * determining the candidate catalog_xmin for the replication slot. See * SnapBuildProcessRunningXacts(). */ So what happens here is that we would skip processing the Xid of a xlog record during snapshot-building, on the grounds that it doesn't contain logical changes. I'm not sure this is okay. If we do indeed need this, then perhaps it should be done after ReorderBufferProcessXid(). Or did you intend to make this conditional on the backend running REPACK? -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-18T01:47:00Z
Hello! On Sat, Dec 13, 2025 at 7:45 PM Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Stress tests for REPACK concurrently in attachment. To run: ninja && meson test --suite setup && meson test --print-errorlogs --suite amcheck *007* ninja && meson test --suite setup && meson test --print-errorlogs --suite amcheck *008* Results for v28: Up to " v28-0005-Use-background-worker-to-do-logical-decoding.patch": Technically it passes, but sometimes I saw 0% CPU usage for long periods with such stacks (looks like it happens for 0008 more often): epoll_wait 0x000078b99512a037 WaitEventSetWaitBlock waiteventset.c:1192 WaitEventSetWait waiteventset.c:1140 WaitLatch latch.c:196 decode_concurrent_changes cluster.c:2702 repack_worker_internal cluster.c:3777 RepackWorkerMain cluster.c:3725 BackgroundWorkerMain bgworker.c:850 postmaster_child_launch launch_backend.c:268 StartBackgroundWorker postmaster.c:4168 maybe_start_bgworkers postmaster.c:4334 LaunchMissingBackgroundProcesses postmaster.c:3408 ServerLoop postmaster.c:1728 PostmasterMain postmaster.c:1403 main main.c:231 epoll_wait 0x000078b99512a037 WaitEventSetWaitBlock waiteventset.c:1192 WaitEventSetWait waiteventset.c:1140 WaitLatch latch.c:196 ConditionVariableTimedSleep condition_variable.c:165 ConditionVariableSleep condition_variable.c:100 process_concurrent_changes cluster.c:3042 rebuild_relation_finish_concurrent cluster.c:3303 rebuild_relation cluster.c:1121 cluster_rel cluster.c:731 process_single_relation cluster.c:2405 ExecRepack cluster.c:391 standard_ProcessUtility utility.c:864 ProcessUtility utility.c:525 PortalRunUtility pquery.c:1148 PortalRunMulti pquery.c:1306 PortalRun pquery.c:783 exec_simple_query postgres.c:1280 PostgresMain postgres.c:4779 BackendMain backend_startup.c:124 postmaster_child_launch launch_backend.c:268 BackendStartup postmaster.c:3598 ServerLoop postmaster.c:1713 PostmasterMain postmaster.c:1403 main main.c:231 Probably it is because > 100000L, /* XXX Tune the delay. */ 100 seconds is clearly too much. For "v28-0006-Use-multiple-snapshots-to-copy-the-data.patch": 0007: crash with TRAP: failed Assert("portal->portalSnapshot == GetActiveSnapshot()"), File: "../src/backend/tcop/pquery.c", Line: 1169, PID: 178414 postgres: CIC_test: nkey postgres [local] REPACK(ExceptionalCondition+0xbe)[0x5743f9a955bb] postgres: CIC_test: nkey postgres [local] REPACK(+0x67fac4)[0x5743f98a7ac4] postgres: CIC_test: nkey postgres [local] REPACK(+0x67fced)[0x5743f98a7ced] postgres: CIC_test: nkey postgres [local] REPACK(PortalRun+0x346)[0x5743f98a7107] postgres: CIC_test: nkey postgres [local] REPACK(+0x6773bb)[0x5743f989f3bb] postgres: CIC_test: nkey postgres [local] REPACK(PostgresMain+0xc1c)[0x5743f98a4f58] postgres: CIC_test: nkey postgres [local] REPACK(+0x6726c6)[0x5743f989a6c6] postgres: CIC_test: nkey postgres [local] REPACK(postmaster_child_launch+0x191)[0x5743f979678c] postgres: CIC_test: nkey postgres [local] REPACK(+0x5755ca)[0x5743f979d5ca] postgres: CIC_test: nkey postgres [local] REPACK(+0x572972)[0x5743f979a972] postgres: CIC_test: nkey postgres [local] REPACK(PostmasterMain+0x168a)[0x5743f979a225] postgres: CIC_test: nkey postgres [local] REPACK(main+0x3a1)[0x5743f9662176] /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca)[0x77f80402a1ca] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x8b)[0x77f80402a28b] postgres: CIC_test: nkey postgres [local] REPACK(_start+0x25)[0x5743f9311eb5] 0008: pass Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2025-12-18T02:05:00Z
Hello, Antonin! On Sat, Dec 13, 2025 at 8:39 PM Antonin Houska <ah@cybertec.at> wrote: > > --- > > > SpinLockAcquire(&shared->mutex); > > > valid = shared->sfs_valid; > > > SpinLockRelease(&shared->mutex); > > > > Better to remember last_exported here to avoid any races/misses. > > What races/misses exactly? Just as some way to reduce a number of potential scenarios/states between parallel actors. > > --- > > > bool done; > > > > bool exit_after_lsn_upto? > > Not sure. I think it should be named in some way to signal it is a request, not a report. > > Also, should we add some kind of back pressure between building > > indexes/new heap and num of WAL we have? > > But probably it is out of scope of the patch. > > Do you mean that the decoding worker should be less active if the amount of > WAL doesn't grow too fast? In the previous version (without background) we have some kind of back-pressure during the scan part (if we have too muchWAL delayed because of us - we process it). But it is not more true with a background worker. At the same time - it never was during the index building phase... Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-05T10:31:27Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2025-Dec-13, Antonin Houska wrote: > > > From 6279394135f2b693b6fffd174822509e0a067cbf Mon Sep 17 00:00:00 2001 > > From: Antonin Houska <ah@cybertec.at> > > Date: Sat, 13 Dec 2025 19:27:18 +0100 > > Subject: [PATCH 4/6] Add CONCURRENTLY option to REPACK command. > > > diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c > > index cc03f0706e9..a956892f42f 100644 > > --- a/src/backend/replication/logical/decode.c > > +++ b/src/backend/replication/logical/decode.c > > @@ -472,6 +473,88 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) > > > + /* > > + * Second, skip records which do not contain sufficient information for > > + * the decoding. > > + * > > + * The problem we solve here is that REPACK CONCURRENTLY generates WAL > > + * when doing changes in the new table. Those changes should not be useful > > + * for any other user (such as logical replication subscription) because > > + * the new table will eventually be dropped (after REPACK CONCURRENTLY has > > + * assigned its file to the "old table"). > > + */ > > + switch (info) > > + { > > + case XLOG_HEAP_INSERT: > > + { > > + xl_heap_insert *rec; > > + > > + rec = (xl_heap_insert *) XLogRecGetData(buf->record); > > + > > + /* > > + * This does happen when 1) raw_heap_insert marks the TOAST > > + * record as HEAP_INSERT_NO_LOGICAL, 2) REPACK CONCURRENTLY > > + * replays inserts performed by other backends. > > + */ > > + if ((rec->flags & XLH_INSERT_CONTAINS_NEW_TUPLE) == 0) > > + return; > > + > > + break; > > + } > > + > > + case XLOG_HEAP_HOT_UPDATE: > > + case XLOG_HEAP_UPDATE: > > + { > > + xl_heap_update *rec; > > + > > + rec = (xl_heap_update *) XLogRecGetData(buf->record); > > + if ((rec->flags & > > + (XLH_UPDATE_CONTAINS_NEW_TUPLE | > > + XLH_UPDATE_CONTAINS_OLD_TUPLE | > > + XLH_UPDATE_CONTAINS_OLD_KEY)) == 0) > > + return; > > + > > + break; > > + } > > + > > + case XLOG_HEAP_DELETE: > > + { > > + xl_heap_delete *rec; > > + > > + rec = (xl_heap_delete *) XLogRecGetData(buf->record); > > + if (rec->flags & XLH_DELETE_NO_LOGICAL) > > + return; > > + break; > > + } > > + } > > I'm confused as to the purpose of this addition. I took this whole > block out, and no tests seem to fail. This is just an optimization, to avoid unnecessary decoding of data changes that the output plugin would ignore anyway. Note that REPACK (CONCURRENTLY) can generate a huge amount of WAL itself. > Moreover, some of the cases that > are being skipped because of this, would already be skipped by code in > DecodeInsert / DecodeUpdate anyway. By checking earlier I tried to avoid calling ReorderBufferProcessXid() unnecessarily. > The case for XLOG_HEAP_DELETE seems > to have no effect (that is, the "return" there never hits for any tests > as far as I can tell.) The current tests do not cover this, but it should be hit by backends performing logical decoding unrelated to REPACK. The typical case is that WAL sender involved in logical replication reads a DELETE record generated by REPACK (CONCURRENTLY) due to replaying a DELETE statement on the new relation. > The reason I ask is that the line immediately below does this: > > > ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr); > > which means the Xid is tracked for snapshot building purposes. Which is > probably important, because of what the comment right below it says: > > /* > * If we don't have snapshot or we are just fast-forwarding, there is no > * point in decoding data changes. However, it's crucial to build the base > * snapshot during fast-forward mode (as is done in > * SnapBuildProcessChange()) because we require the snapshot's xmin when > * determining the candidate catalog_xmin for the replication slot. See > * SnapBuildProcessRunningXacts(). > */ > > So what happens here is that we would skip processing the Xid of a xlog > record during snapshot-building, on the grounds that it doesn't contain > logical changes. I'm not sure this is okay. I think I missed the fact that SnapBuildProcessChange() relies on ReorderBufferProcessXid() having been called. > If we do indeed need this, > then perhaps it should be done after ReorderBufferProcessXid(). ... and after SnapBuildProcessChange(). Thus the changes being discussed here should be removed from the patch. I'll do that in the next version. Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-05T10:40:32Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > If we do indeed need this, > > then perhaps it should be done after ReorderBufferProcessXid(). > > ... and after SnapBuildProcessChange(). Thus the changes being discussed here > should be removed from the patch. I'll do that in the next version. Thanks. Actually the check of XLH_DELETE_NO_LOGICAL should not be discarded. I think it should be added to DecodeDelete(). -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-05T10:54:07Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello! > > On Sat, Dec 13, 2025 at 7:45 PM Mihail Nikalayeu > <mihailnikalayeu@gmail.com> wrote: > > Stress tests for REPACK concurrently in attachment. > > To run: > ninja && meson test --suite setup && meson test --print-errorlogs > --suite amcheck *007* > ninja && meson test --suite setup && meson test --print-errorlogs > --suite amcheck *008* Thanks for running the tests! > Results for v28: > > Up to " v28-0005-Use-background-worker-to-do-logical-decoding.patch": > > Technically it passes, but sometimes I saw 0% CPU usage for long > periods with such stacks (looks like it happens for 0008 more often): ... > Probably it is because > > 100000L, /* XXX Tune the delay. */ > > 100 seconds is clearly too much. I confused milliseconds with microseconds. Since I was only running the code with debugger, the long delays didn't appear to be a problem. Instead of tuning the timeout, I'm thinking of introducing a condition variable that signals WAL flushing. > For "v28-0006-Use-multiple-snapshots-to-copy-the-data.patch": > > 0007: crash with > > TRAP: failed Assert("portal->portalSnapshot == GetActiveSnapshot()"), > File: "../src/backend/tcop/pquery.c", Line: 1169, PID: 178414 Thanks. I'll check when I have time for this part. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-01-05T14:07:00Z
On 2026-Jan-05, Antonin Houska wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > Probably it is because > > > 100000L, /* XXX Tune the delay. */ > > > > 100 seconds is clearly too much. > > I confused milliseconds with microseconds. Since I was only running the code > with debugger, the long delays didn't appear to be a problem. > > Instead of tuning the timeout, I'm thinking of introducing a condition > variable that signals WAL flushing. I think there is a patch that adds support for this in the queue already -- see this message: https://postgr.es/m/CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "¿Qué importan los años? Lo que realmente importa es comprobar que a fin de cuentas la mejor edad de la vida es estar vivo" (Mafalda)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-05T14:59:32Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Jan-05, Antonin Houska wrote: > > > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > > > Probably it is because > > > > 100000L, /* XXX Tune the delay. */ > > > > > > 100 seconds is clearly too much. > > > > I confused milliseconds with microseconds. Since I was only running the code > > with debugger, the long delays didn't appear to be a problem. > > > > Instead of tuning the timeout, I'm thinking of introducing a condition > > variable that signals WAL flushing. > > I think there is a patch that adds support for this in the queue already > -- see this message: > https://postgr.es/m/CAPpHfds-KiZRuCruc0jHxLSxLqzKcHJGwOFFA0b_RgaJvtUOEQ@mail.gmail.com Thanks for the hint! It seems that the already committed WaitForLSN() function does what I need. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-08T16:59:00Z
Antonin Houska <ah@cybertec.at> wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > > If we do indeed need this, > > > then perhaps it should be done after ReorderBufferProcessXid(). > > > > ... and after SnapBuildProcessChange(). Thus the changes being discussed here > > should be removed from the patch. I'll do that in the next version. Thanks. > > Actually the check of XLH_DELETE_NO_LOGICAL should not be discarded. I think > it should be added to DecodeDelete(). v29 tries to fix the problem. Besides that, it reflects the recent Mihail's comments [1], [2]. [1] https://www.postgresql.org/message-id/CADzfLwXp4c-MJx7yVDxAGNNxPbX4o9dqyivxavtHvmUsdXYqBQ@mail.gmail.com [2] https://www.postgresql.org/message-id/CADzfLwWNz_jwi7KVOmJ9D97+zwxsiwDSqSUUJ9oqUCOqkbGnRA@mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-10T17:37:06Z
Hello, Antonin! On Thu, Jan 8, 2026 at 7:59 PM Antonin Houska <ah@cybertec.at> wrote: > v29 tries to fix the problem. Some comments for 0001-0004. ------ 0001 ----- > src/bin/scripts/t/103_repackdb.pl:1: > # Copyright (c) 2021-2025 Update year for 2026. > * FIXME: this is missing a way to specify the index to use to repack one > * table, or whether to pass a WITH INDEX clause when multiple tables are > * used. Something like --index[=indexname]. Adding that bleeds into > * vacuuming.c as well. Comments look stale. > return "???"; I think it is better to add Assert(false); before (done that way in a few places). > command <link linkend="sql-repack"><command>REPACK</command></link> There need . > “An utility” Should be “A utility” > else if (pg_strcasecmp(cmd, "CLUSTER") == 0) > cmdtype = PROGRESS_COMMAND_CLUSTER; Should we set PROGRESS_COMMAND_REPACK here? Because cluster is not used anywhere. Probably we may even delete PROGRESS_COMMAND_CLUSTER. > CLUOPT_RECHECK_ISCLUSTERED It is not set anymore... Probably something is wrong here or we need to just remove that constant and check for it. ------ 0002 ----- > rebuild_relation(Relation OldHeap, Relation index, bool verbose) It removes unused cmd parameter, but I think it is better to not add it in the previous commit. ------ 0003 ----- > int newxcnt = 0; I think it is better to use uint32 for consistency here. Also, I think it is worth adding Assert(snapshot->snapshot_type == SNAPSHOT_HISTORIC_MVCC) ------ 0004 ----- > /* Is REPACK (CONCURRENTLY) being run by this backend? */ > if (am_decoding_for_repack()) We should check change_useless_for_repack here - to avoid looking and TRUNCATE of unrelated tables. > /* For the same reason, unlock TOAST relation. */ > if (OldHeap->rd_rel->reltoastrelid) > LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); Hm, we are locking here instead of unlocking ;) > (errhint("Relation \"%s\" has no identity index.", > RelationGetRelationName(rel))))); One level of braces may be removed. > * to decode on behalf of REPACK (CONCURRENT)? CONCURRENTLY > * If recheck is required, it must have been preformed on the source "performed" > * On exit,'*scan_p' contains the scan descriptor used. The caller must close > * it when he no longer needs the tuple returned. There is no scan_p argument here. > * Copyright (c) 2012-2025, PostgreSQL Global Development Group 2026 > newtuple = change->data.tp.newtuple != NULL ? > change->data.tp.newtuple : NULL; > oldtuple = change->data.tp.oldtuple != NULL ? > change->data.tp.oldtuple : NULL; > newtuple = change->data.tp.newtuple != NULL ? > change->data.tp.newtuple : NULL; Hm, should it be just x = y ? > apply_concurrent_insert Double newline at function start. > heap2_decode Should we check for change_useless_for_repack here also? (for multi insert, for example). Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-12T16:33:30Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Thu, Jan 8, 2026 at 7:59 PM Antonin Houska <ah@cybertec.at> wrote: > > v29 tries to fix the problem. > > Some comments for 0001-0004. Thanks. > ------ 0001 ----- > > * FIXME: this is missing a way to specify the index to use to repack one > > * table, or whether to pass a WITH INDEX clause when multiple tables are > > * used. Something like --index[=indexname]. Adding that bleeds into > > * vacuuming.c as well. > > Comments look stale. This is an open question, see [1]. > > return "???"; > I think it is better to add Assert(false); before (done that way in a > few places). This is not really uncommon, see for example event_trigger.c. Added the comment /* keep compiler quiet */ > > “An utility” > Should be “A utility” Not sure it *should be* [2], but "a utility" appears to be much more common in the tree. Changed. > > else if (pg_strcasecmp(cmd, "CLUSTER") == 0) > > cmdtype = PROGRESS_COMMAND_CLUSTER; > > Should we set PROGRESS_COMMAND_REPACK here? Because cluster is not > used anywhere. Probably we may even delete PROGRESS_COMMAND_CLUSTER. Good point. Actually we do not need this branch at all as there's no pg_stat_get_progress_info('CLUSTER') call in system_views.sql. Removed. > > CLUOPT_RECHECK_ISCLUSTERED > It is not set anymore... Probably something is wrong here or we need > to just remove that constant and check for it. Yes, it got lost somehow. I added it where I think it's appropriate. > ------ 0002 ----- > > > rebuild_relation(Relation OldHeap, Relation index, bool verbose) > It removes unused cmd parameter, but I think it is better to not add > it in the previous commit. Yes, the changes were not correctly split into diffs. Fixed. > ------ 0003 ----- > > > int newxcnt = 0; > > I think it is better to use uint32 for consistency here. This diff only moves code across functions, I'm not going to do other changes now. > Also, I think it is worth adding Assert(snapshot->snapshot_type == > SNAPSHOT_HISTORIC_MVCC) ok > ------ 0004 ----- > > > /* Is REPACK (CONCURRENTLY) being run by this backend? */ > > if (am_decoding_for_repack()) > > We should check change_useless_for_repack here - to avoid looking and > TRUNCATE of unrelated tables. In v29, if XLOG_HEAP_TRUNCATE of an unrelated table is seen here, we'll raise ERROR unnecessarily instead of truncating the table. That's obviously wrong as well. On the other hand, it's not trivial to teach change_useless_for_repack() to filter the TRUNCATE records by file locator. So besides adding a call of change_useless_for_repack(), I removed that ereport(ERROR) from heap_decode() and added a comment to plugin_change() explaining why TRUNCATE on the table being repacked should fire the Assert() statement: TRUNCATE shouldn't appear here due to locking. > > /* For the same reason, unlock TOAST relation. */ > > if (OldHeap->rd_rel->reltoastrelid) > > LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); > > Hm, we are locking here instead of unlocking ;) Copy-pasto, the lock level is incorrect as well. Actually the whole idea of unlocking index and TOAST relation is probably wrong: if some transaction already locked the table with a lock that does not conflict with ShareUpdateExclusiveLock, it should not need to wait for ShareUpdateExclusiveLock on index / TOAST relation. At least I don't recall a case where index / TOAST requires stronger lock than the main table. So I removed the unlocking altogether. > > (errhint("Relation \"%s\" has no identity index.", > > RelationGetRelationName(rel))))); > > One level of braces may be removed. > > * to decode on behalf of REPACK (CONCURRENT)? > > CONCURRENTLY > > * If recheck is required, it must have been preformed on the source > > "performed" > > * On exit,'*scan_p' contains the scan descriptor used. The caller must close > > * it when he no longer needs the tuple returned. > > There is no scan_p argument here. > > * Copyright (c) 2012-2025, PostgreSQL Global Development Group > > 2026 ok > > newtuple = change->data.tp.newtuple != NULL ? > > change->data.tp.newtuple : NULL; > > > oldtuple = change->data.tp.oldtuple != NULL ? > > change->data.tp.oldtuple : NULL; > > newtuple = change->data.tp.newtuple != NULL ? > > change->data.tp.newtuple : NULL; > > Hm, should it be just x = y ? Thanks for spotting this. The reason is that significant portion of this patch is copied from the pg_squeeze extension, and there it originally looked like: oldtuple = change->data.tp.oldtuple != NULL ? &change->data.tp.oldtuple->tuple : NULL; I failed to notice the unnecessary complexity when adapting the code to the commit 08e6344fd642 in postgres core, and then copied it to the REPACK patch. Fixed now. > > apply_concurrent_insert > > Double newline at function start. ok > > heap2_decode > > Should we check for change_useless_for_repack here also? (for multi > insert, for example). Yes, done, thanks. While doing that, I've done the same for XLOG_HEAP_CONFIRM in heap_decode() as it'd be bad for reorderbuffer.c to receive the CONFIRM record w/o previously receiving the actual (speculative) INSERT. [1] https://www.postgresql.org/message-id/7224.1762326739%40localhost [2] https://en.wiktionary.org/wiki/an#Usage_notes -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-12T18:20:35Z
Hello, Antonin! More comments - now for 0005 (but v29, but I think they are mostly up to date). --- 0005 --- > potentiallly extra 'l' in commit message > Memory the queue is located int. "in"? > again if its still eligible if it's still eligible > int initialized; probably better to be bool (as in shared) > DecodingWorkerState such type does not exists in commit > REPACK_WORKER_MAIN Not used in code anywhere. > int64 timeout = 0; > WaitLSNResult res; formatting issue here (tab vs space) > if (size >= MaxAllocSize) Seems like we lost that check, I think it may be executed on storing the data or before "tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len);" in apply_concurrent_changes > bool done; I still think it is a confusing name. > chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1; I think it is better to increment it once a snapshot is received. And rename to last_processed/last_improrted to be aligned with last_exported. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-12T18:54:56Z
Also, there are some crashes of stress tests for v30 (for both single snapshot and multiple snapshot versions). --------------------- Looks like something is leaking, but not sure. https://cirrus-ci.com/task/5577209672368128?logs=test_world#L277 (multiple snapshots) https://cirrus-ci.com/task/6439044873191424 (without multiple snapshots) [17:49:07.251] # Failed test 'concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY stderr /(?^:^$)/' [17:49:07.251] # at /tmp/cirrus-ci-build/contrib/amcheck/t/ 007_repack_concurrently.pl line 56. [17:49:07.251] # 'pgbench: error: client 0 script 0 aborted in command 6 query 0: ERROR: out of background worker slots [17:49:07.251] # HINT: You might need to increase "max_worker_processes". [17:49:07.251] # pgbench: error: Run was aborted due to an error in thread 0 ------------------- This one showed something goes wrong, the sum of the table is broken. It may be 0 because non-MVCC safe, but I checked the logs: 2026-01-12 18:41:11.656 UTC client backend[76247] 007_repack_concurrently.pl LOG: statement: SELECT (490588) / 0; And also backend[54349] 007_repack_concurrently.pl ERROR: could not create unique index "tbl_pkey_repacknew" 2026-01-12 18:41:12.477 UTC client backend[54349] 007_repack_concurrently.pl DETAIL: Key (i)=(942) is duplicated. 2026-01-12 18:41:12.477 UTC client backend[54349] 007_repack_concurrently.pl STATEMENT: REPACK (CONCURRENTLY) tbl; https://cirrus-ci.com/task/4521496594350080 (single snapshot version) https://cirrus-ci.com/task/6157569896480768 (single snapshot version) [18:36:17.466] # at /Users/admin/pgsql/contrib/amcheck/t/ 007_repack_concurrently.pl line 56. [18:36:17.466] # 'pgbench: error: client 21 script 0 aborted in command 31 query 0: ERROR: division by zero [18:36:17.466] # pgbench: error: Run was aborted due to an error in thread 2 --------------------- https://cirrus-ci.com/task/5682762788634624 (multiple snapshots) Failed test 'concurrent operations with REINDEX/CREATE INDEX CONCURRENTLY stderr /(?^:^$)/' [18:02:06.938] # at t/007_repack_concurrently.pl line 56. [18:02:06.938] # 'pgbench: error: client 6 aborted in command 4 (SQL) of script 0; perhaps the backend died while processing [18:02:06.938] # pgbench: error: Run was aborted due to an error in thread 0 [18:02:06.938] # WARNING: terminating connection because of crash of another server process Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-15T16:36:59Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Also, there are some crashes of stress tests for v30 (for both single snapshot and multiple snapshot versions). > > --------------------- > > Looks like something is leaking, but not sure. > > https://cirrus-ci.com/task/5577209672368128?logs=test_world#L277 (multiple snapshots) > https://cirrus-ci.com/task/6439044873191424 (without multiple snapshots) As the test runs pgbench with --client=30 and the default value of max_worker_processes is 8, I'm not sure this is a leak. I've increased this parameter I couldn't see the error anymore. > This one showed something goes wrong, the sum of the table is broken. It may be 0 because non-MVCC safe, but I checked the logs: > > 2026-01-12 18:41:11.656 UTC client backend[76247] 007_repack_concurrently.pl LOG: statement: SELECT (490588) / 0; I agree that this is due to the missing MVCC safety feature. I commented that check in the script for now. Besides that, I saw some deadlocks. I think this was due to the fact that multiple rows are updated per transaction, and that the keys are random, so it can happen that two transactions try to update the same rows in different order. I increased the number of rows in the test table to 10000 and don't see the deadlocks anymore. > backend[54349] 007_repack_concurrently.pl ERROR: could not create unique index "tbl_pkey_repacknew" > 2026-01-12 18:41:12.477 UTC client backend[54349] 007_repack_concurrently.pl DETAIL: Key (i)=(942) is duplicated. > 2026-01-12 18:41:12.477 UTC client backend[54349] 007_repack_concurrently.pl STATEMENT: REPACK (CONCURRENTLY) tbl; This is tricky. I could reproduce the problem on my FreeBSD box a few times, never on Linux (no idea if the OS makes the difference since HW is also quite different, but CI also seemed to fail more often on FreeBSD.) Something seems to be wrong about UPDATE, but I'm failing to understand how it could relate to REPACK. This is an example of a duplicate value i=6118 SELECT i, j, xmin, xmax, ctid FROM tbl WHERE i=6118; i | j | xmin | xmax | ctid ------+--------+--------+--------+--------- 6118 | 445435 | 102317 | 103702 | (1,216) 6118 | 391135 | 103702 | 0 | (56,62) According to log, xid=102317 is the transaction used by REPACK and xid=103702 one of the test. pageinspect shows that the old version has not only HEAP_XMIN_COMMITTED in t_infomask, but also HEAP_XMAX_INVALID. So far I could not reproduce the duplicities with the REPACK (CONCURRENTLY) command commented out in the test script, but that does not prove much (even with REPACK, not every run fails). Also I noticed that REPACK incorrectly sets cmin/cmax to 1 instead of 0 and it needs to be fixed, but I have no idea why this bug should cause exactly this weird behavior. I even added quite a few logging messages to reveal where in the code the HEAP_XMAX_INVALID flag is set for particular ctid, but after a failure I could not find the message for the problematic tuples. Ideas are appreciated. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-15T17:05:02Z
Hello! Antonin Houska <ah@cybertec.at>: As the test runs pgbench with --client=30 and the default value of max_worker_processes is 8, I'm not sure this is a leak. I've increased this parameter I couldn't see the error anymore. Hm, as far as I remember only single repack may be executed in test (because of locking on test itself and also REPACK). At least still feel suspicious. I agree that this is due to the missing MVCC safety feature. I commented that check in the script for now. I don't think so. In case of non-MVCC safety we should see 0 or correct sum. But script failed with 490588... But should see 500500 (if I correctly calculated sum of numbers from 1 to 1000)... Besides that, I saw some deadlocks. I think this was due to the fact that multiple rows are updated per transaction, and that the keys are random, so it can happen that two transactions try to update the same rows in different order. I increased the number of rows in the test table to 10000 and don't see the deadlocks anymore. I think better to use min/max in updates to be sure (update lower id first). This is tricky. I could reproduce the problem on my FreeBSD box a few times, never on Linux (no idea if the OS makes the difference since HW is also quite different, but CI also seemed to fail more often on FreeBSD.) You may try to play with no_hot parameter in test - maybe it will provide some clue. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-16T18:18:16Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: >Antonin Houska <ah@cybertec.at>: >> >> As the test runs pgbench with --client=30 and the default value of >> max_worker_processes is 8, I'm not sure this is a leak. I've increased this >> parameter I couldn't see the error anymore. > > Hm, as far as I remember only single repack may be executed in test (because > of locking on test itself and also REPACK). The only problem is that the logical decoding system needs to wait during the setup for all the running transactions to finish. So if REPACK (CONCURRENTLY) is already running, the next execution will not start until the first is done. However, that does not restrict the REPACK decoding workers from starting. >> I agree that this is due to the missing MVCC safety feature. I commented that >> check in the script for now. > > I don't think so. In case of non-MVCC safety we should see 0 or correct sum. But script failed with 490588... > But should see 500500 (if I correctly calculated sum of numbers from 1 to 1000)... I was referring to your statement "It may be 0 because non-MVCC safe". Regarding the non-zero values, I think I finally understand the issue and even could reproduce some weird behavior using debugger. Since it also affects logical replication, I'll provide more details (and hopefully propose a patch) in a separate thread early next week. In short, it looks like (hopefully very) rare race condition, such that the snapshot builder can build the initial snapshot before all the commits have been recorded in CLOG. When that happens, visibility checks don't work correctly. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-18T21:52:00Z
Hello, Antonin! Some comments for 0006: > SnapBuildSnapshotForRepack(SnapBuild *builder) Does it also "replays" previously processed WAL to the position that snapshot is ready to use? I am afraid we may see some non-yet processed parts of WAL leading to duplicate insertion. > first_block What is the reason for that variable? It seems to be always the first block of relation. Also, what if we have a huge amount of empty space at the start. In that case the first block will be the block of the first "filled" page. But insert may (and will) fill empty pages before first_block - out of the range. So, I think we should delete it and always use 0 instead. > DecodeMultiInsert ItemPointerSet-related logic seems missing. > tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); With SnapshotAny we are going to check the same tuple multiple times. Better to let scan logic handle it (and change snapshots used by scan code). See [0] for a way to reset snapshots during the scan. > if (blkno >= range_end) I don't think it is legal to switch a snapshot while holding the tuple. Nothing is protecting it from being pruned\reused. Snapshots need to be switched "between" pages. You may check how it is done at [0]. > PopActiveSnapshot(); > InvalidateCatalogSnapshot(); I think it is a good idea to add here assert for MyProc->xmin and MyProc->xid to be invalid. To ensure we really allow the horizon to advance. > /* Set to request a snapshot. */ > bool snapshot_requested; We know the end or region in advance, so it should be possible to filter before writing changes to file. So, it is some kind of "this is the end of region, create new file and store everything before + create new snapshot for me". > PopActiveSnapshot(); Sometimes without InvalidateCatalogSnapshot(). > PushActiveSnapshot(GetTransactionSnapshot()); GetLatestSnapshot() feels better here. > * The individual builds might still be a problem, but that's a > * separate issue. Opening the index may create a catalog snapshot, so it needs to be invalidated after. > * TODO Can we somehow use the fact that the new heap is not yet > * visible to other transaction, and thus cannot be vacuumed? Snapshot resetting [0] may work here (without CIC, just as part of the scan + some code to ensure catalog snapshot is managed correctly). Also, to correctly build a unique index - some tech from [0] is required (building a unique index with multiple snapshots is a little bit tricky). Or we may implement some super lightweight way - just SnapshotAny without any visibility checks (just assume everything is ok since it copied from another relation with the same index set). > This approach introduces one limitation though: if the USING INDEX clause is > specified, an explicit sort is always used. Index scan wouldn't work because > it does not return the tuples sorted by CTID. Technically we may just use keys (if they are comparable) as a way to specify regions. Instead of number of pages to switch snapshot - number of tuples or time. But because we don't know the region end in advance - we have to keep all the changes in file and filter only while applying. > Assert(XLogRecPtrIsInvalid(shared->lsn_upto)); > /* Initially we're expected to provide a snapshot and only that. */ > Assert(shared->snapshot_requested && > XLogRecPtrIsInvalid(shared->lsn_upto)); XLogRecPtrIsInvalid(shared->lsn_upto) assertion is duplicated here. > range_end = repack_blocks_per_snapshot; Should be repack_blocks_per_snapshot + ctx->first_block ? (but better to remove the first_block at all). > * XXX It might be worth Assert(CatalogSnapshot == NULL) > * here, however that symbol is not external. As said above - better assert for MyProc->xmin/xid + add InvalidateCatalogSnapshot. > extern Snapshot > extern void Some "externs" used in C files (not headers). > ctx->block_ranges = NIL; may be used with list_free? > * Remember here to which pages should applied to changes recorded in given > * file. "should apply" > of pages, so that VACUUM does not get block for too long "blocked" > there are no restriction on block "there is" or "restrictions" > repack_add_block_range extra new-line after function. > Is REPACKED (CONCURRENTLY) is being run by this backend? REPACK and double "is" [0]: https://commitfest.postgresql.org/patch/6401/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-19T16:31:19Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > >Antonin Houska <ah@cybertec.at>: > >> > >> As the test runs pgbench with --client=30 and the default value of > >> max_worker_processes is 8, I'm not sure this is a leak. I've increased this > >> parameter I couldn't see the error anymore. > > > > Hm, as far as I remember only single repack may be executed in test (because > > of locking on test itself and also REPACK). > > The only problem is that the logical decoding system needs to wait during the > setup for all the running transactions to finish. So if REPACK (CONCURRENTLY) > is already running, the next execution will not start until the first is done. > > However, that does not restrict the REPACK decoding workers from starting. > > >> I agree that this is due to the missing MVCC safety feature. I commented that > >> check in the script for now. > > > > I don't think so. In case of non-MVCC safety we should see 0 or correct sum. But script failed with 490588... > > But should see 500500 (if I correctly calculated sum of numbers from 1 to 1000)... > > I was referring to your statement "It may be 0 because non-MVCC > safe". Regarding the non-zero values, I think I finally understand the issue > and even could reproduce some weird behavior using debugger. Since it also > affects logical replication, I'll provide more details (and hopefully propose > a patch) in a separate thread early next week. This is the report: https://www.postgresql.org/message-id/85833.1768840165%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-20T15:39:10Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > if (size >= MaxAllocSize) > Seems like we lost that check, I think it may be executed on storing > the data The tuple we process in store_change was created elsewhere (I think in reorderbuffer.c), so I wouldn't re-check the size here. > or before "tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len);" > in apply_concurrent_changes It's essentially the same length that we write in store_change() so I wouldn't bother re-checking it here. In general, I doubt the constant is appropriate. Its meaning is much more generic than the size of memory for a tuple and even heap_form_tuple() does not use it. > > bool done; > I still think it is a confusing name. I don't. The last call of process_concurrent_changes() tells the worker "Give me the the next batch and we are done". Your proposal "exit_after_lsn_upto" seems to me too verbose: the worker itself is supposed to know that it has to reach the LSN passed via another argument. > > chgdst.file_seq = WORKER_FILE_SNAPSHOT + 1; > I think it is better to increment it once a snapshot is received. The 'chgdst' is only defined in rebuild_relation_finish_concurrent(), no need to use it where the snapshot is received (in rebuild_relation()). > And rename to last_processed/last_improrted to be aligned with > last_exported. While DecodingWorkerShared deals with multiple files (not all of them necessarily available for "consumer") and therefore it makes sense to distinguish if file is exported or not, each instance of ChangeDest is assigned particular file and the functions using it do not care if the file is the last in the sequence or not. Other proposals accepted, will be reflected in the next version. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-22T08:37:40Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Some comments for 0006: > > > SnapBuildSnapshotForRepack(SnapBuild *builder) > Does it also "replays" previously processed WAL to the position that snapshot is ready to use? > I am afraid we may see some non-yet processed parts of WAL leading to duplicate insertion. The changes present in WAL decoded prior the snapshot creation are not replayed - these changes are visible to the snapshot. (This is not really specific to the 0006 part.) > > first_block > What is the reason for that variable? It seems to be always the first block > of relation. Although scan usually starts at the first block, it does not have to, especially due to synchronized sequential scans. > Also, what if we have a huge amount of empty space at the start. In that case the first block will be the block of the first "filled" page. But > insert may (and will) fill empty pages before first_block - out of the range. Good catch! I think I used this "lazy initialization" because I couldn't find the start block in TableScanDesc, and missed the problem that you describe here. > So, I think we should delete it and always use 0 instead. No, we need to set first_block to heapScan->rs_startblock before the scan starts. > > tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); > With SnapshotAny we are going to check the same tuple multiple times. Better to let scan logic handle it (and change snapshots used by scan > code). The current API does not seem to support changing snapshot of an in-progress scan and I don't want to change that. Plus note that the current implementation of CLUSTER also uses SnapshotAny and then checks the visibility separately. Finally, SnapshotAny is not really an expensive visibility check, if it can be considered a visibility check at all. > > if (blkno >= range_end) > I don't think it is legal to switch a snapshot while holding the tuple. Nothing is protecting it from being pruned\reused. Snapshot protects the table as whole from pruning live (or recently dead) tuples, but when you have fetched a tuple, the containing buffer remains pinned. The buffer pin itself makes prunning of the page impossible. And especially with REPACK (CONCURRENTLY), page pruning is also restricted by the replication slot's xmin. This is increased by calling LogicalIncreaseXminForSlot() from the decoding worker, each time it has created a new snapshot. > > PopActiveSnapshot(); > > InvalidateCatalogSnapshot(); > I think it is a good idea to add here assert for MyProc->xmin and MyProc->xid to be invalid. To ensure we really allow the horizon to advance. I've added it only for xmin. xid is valid because REPACK is executed in a transaction. That reminds me that PROC_IN_VACUUM should be present in MyProc->statusFlags. Fixed. > > /* Set to request a snapshot. */ > > bool snapshot_requested; > We know the end or region in advance, so it should be possible to filter before writing changes to file. Yes, filtering before writing makes sense, I'll consider that. > So, it is some kind of "this is the end of region, create new file and store everything before + create new snapshot for me". The last se of changes does not have to be followed by a snapshot - that's the purpose of snapshot_requested. > > PopActiveSnapshot(); > Sometimes without InvalidateCatalogSnapshot(). [ It'd be a bit easier to find the code if you included hunk headers. ] heapam_relation_copy_for_cluster() does not access catalogs after the first invalidation. The following comment is related: /* * XXX It might be worth Assert(CatalogSnapshot == NULL) here, * however that symbol is not external. */ > > PushActiveSnapshot(GetTransactionSnapshot()); > GetLatestSnapshot() feels better here. What will then happen to code that uses GetActiveSnapshot() ? > > * The individual builds might still be a problem, but that's a > > * separate issue. > Opening the index may create a catalog snapshot, so it needs to be invalidated after. It'll be invalidated in the next iteration. The point of this invalidation is to use one snapshot per index. > > * TODO Can we somehow use the fact that the new heap is not yet > > * visible to other transaction, and thus cannot be vacuumed? > Snapshot resetting [0] may work here (without CIC, just as part of the scan + some code to ensure catalog snapshot is managed correctly). > Also, to correctly build a unique index - some tech from [0] is required (building a unique index with multiple snapshots is a little bit tricky). > Or we may implement some super lightweight way - just SnapshotAny without any visibility checks (just assume everything is ok since it > copied from another relation with the same index set). ok, I'll check your patch. > > This approach introduces one limitation though: if the USING INDEX clause is > > specified, an explicit sort is always used. Index scan wouldn't work because > > it does not return the tuples sorted by CTID. > > Technically we may just use keys (if they are comparable) as a way to specify regions. Instead of number of pages to switch snapshot - > number of tuples or time. > But because we don't know the region end in advance - we have to keep all the changes in file and filter only while applying. Good idea. Unfortunately it questions your proposal to filter the changes before writing, as suggested above. > > range_end = repack_blocks_per_snapshot; > Should be repack_blocks_per_snapshot + ctx->first_block ? Indeed, I also failed to avoid assuming first_block==0 :-) > > * XXX It might be worth Assert(CatalogSnapshot == NULL) > > * here, however that symbol is not external. > As said above - better assert for MyProc->xmin/xid + add InvalidateCatalogSnapshot. I proposed the Assert above, but still thinking about it. > > Is REPACKED (CONCURRENTLY) is being run by this backend? > REPACK and double "is" Other comments accepted. Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-22T11:30:00Z
Hello, Antonin! > The changes present in WAL decoded prior the snapshot creation are not > replayed - these changes are visible to the snapshot. (This is not really > specific to the 0006 part.) OK, just want to be sure it still works the same way if we build multiple snapshots for the same slot that way. > The current API does not seem to support changing snapshot of an in-progress > scan and I don't want to change that. Plus note that the current > implementation of CLUSTER also uses SnapshotAny and then checks the visibility > separately. Finally, SnapshotAny is not really an expensive visibility check, > if it can be considered a visibility check at all. But we will require a real check for each tuple. Including dead one, multiple versions of the same HOT, etc. > I've added it only for xmin. xid is valid because REPACK is executed in a > transaction. That reminds me that PROC_IN_VACUUM should be present in > MyProc->statusFlags. Fixed. Yes, xid is required for repack. I think it is better to introduce a new flag instead of PROC_IN_VACCUUM. > > > PushActiveSnapshot(GetTransactionSnapshot()); > > GetLatestSnapshot() feels better here. > What will then happen to code that uses GetActiveSnapshot() ? O, I mean PushActiveSnapshot(GetLatestSnapshot()) > > Also, to correctly build a unique index - some tech from [0] is required (building a unique index with multiple snapshots is a little bit tricky). > ok, I'll check your patch. I realized building a unique index is still done with a single snapshot, so it should be OK for that case. But still check the patch :) > I proposed the Assert above, but still thinking about it. Hm... Do we really need these asserts if PROC_IN_VACUUM is set? I was proposing a way it is used for index building (to ensure nothing is propagated into xmin). Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-25T16:31:00Z
Hello, Antonin! PART 1: I started rebasing the MVCC-safe version on top of the multi-snapshot version and realized it becomes complex. But, what's really bad about MVCC-unsafety is the ability to access *incorrect* data and break some logic (or even constraints). If we may *prevent* such data access with some kind of error (which is going to be very infrequent) - I don't see any sense to achieve true MVCC-safety. I remembered a way it works with indcheckxmin for indexes. And made something similar for pg_class: it records the rewriting transaction XID and causes the executor to raise an error if a transaction with an older snapshot attempts to access the rewritten relation. For the normal case - check is never executed, no performance regression here. Also, the flag is automatically cleared by VACUUM once the transaction ID is frozen. It also "fixes" ALTER TABLE, not only REPACK concurrently. Attached patch contains more details (some in the commit message). PART 2: I have continued working with stress tests. This time I added your WIP patch to fix the LR\CLOG race. I made the following configs: 1) just REPACK CONCURRENTLY - ok 2) + relcheckxmin (see PART1) - ok 3) + worker - ok 4) + multiple snapshots - broken in multiple ways. You may see example of run here - https://cirrus-ci.com/build/6359048020295680 Some examples: 1) 'pgbench: error: client 11 script 0 aborted in command 20 query 0: ERROR: could not read blocks 0..0 in file "base/5/16414": read only 0 of 8192 bytes 2) at /home/postgres/postgres/contrib/amcheck/t/008_repack_concurrently.pl line 51. [15:36:37.204] # 'pgbench: error: client 5 script 0 aborted in command 28 query 0: ERROR: division by zero 3) 'pgbench: error: client 12 script 0 aborted in command 6 query 0: ERROR: cache lookup failed for relation 17400
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-26T07:34:40Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > PART 1: > > I started rebasing the MVCC-safe version on top of the multi-snapshot version and realized it becomes complex. > But, what's really bad about MVCC-unsafety is the ability to access *incorrect* data and break some logic (or even constraints). > > If we may *prevent* such data access with some kind of error (which is going to be very infrequent) - I don't see any sense to achieve true > MVCC-safety. > > I remembered a way it works with indcheckxmin for indexes. And made something similar for pg_class: it records the rewriting transaction > XID and causes the executor to raise an error if a transaction with an older snapshot attempts to access the rewritten relation. > > For the normal case - check is never executed, no performance regression here. Also, the flag is automatically cleared by VACUUM once the > transaction ID is frozen. > > It also "fixes" ALTER TABLE, not only REPACK concurrently. > > Attached patch contains more details (some in the commit message). A few days ago, when thinking about it, I realized that my implementation of MVCC safety is not correct, as it does not preserve the whole HOT chains as CLUSTER / VACUUM FULL does. To resolve that, we should not allow access to the new table until the parts of HOT chains not copied by REPACK are DEAD. Better solution might be to improve rewriteheap.c (which does keep the HOT chains) so it 1) works w/o AccessExclusiveLock on the table, 2) does not copy tuples which will eventually be retrieved by the logical decoding output plugin, 3) allows snapshot switching/resetting in 2). I think these requirements are rather hard to implement. I've noticed recently that the MVCC safety patch you posted is not exactly what I wrote, but maybe the copying part is identical. So it's possible that the problem you saw is related to what I try to describe here. Let's see in the future if the demand for the MVCC safety will ever justify the effort to implement it. > PART 2: > > I have continued working with stress tests. This time I added your WIP patch to fix the LR\CLOG race. > > I made the following configs: > 1) just REPACK CONCURRENTLY - ok > 2) + relcheckxmin (see PART1) - ok > 3) + worker - ok > 4) + multiple snapshots - broken in multiple ways. > > You may see example of run here - https://cirrus-ci.com/build/6359048020295680 > > Some examples: > > 1) 'pgbench: error: client 11 script 0 aborted in command 20 query 0: ERROR: could not read blocks 0..0 in file "base/5/16414": read only 0 > of 8192 bytes > 2) at /home/postgres/postgres/contrib/amcheck/t/008_repack_concurrently.pl line 51. > [15:36:37.204] # 'pgbench: error: client 5 script 0 aborted in command 28 query 0: ERROR: division by zero > 3) 'pgbench: error: client 12 script 0 aborted in command 6 query 0: ERROR: cache lookup failed for relation 17400 Thanks, I'll check these. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-27T10:57:36Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > > PART 2: > > > > I have continued working with stress tests. This time I added your WIP patch to fix the LR\CLOG race. > > > > I made the following configs: > > 1) just REPACK CONCURRENTLY - ok > > 2) + relcheckxmin (see PART1) - ok > > 3) + worker - ok > > 4) + multiple snapshots - broken in multiple ways. > > > > You may see example of run here - https://cirrus-ci.com/build/6359048020295680 > > > > Some examples: > > > > 1) 'pgbench: error: client 11 script 0 aborted in command 20 query 0: ERROR: could not read blocks 0..0 in file "base/5/16414": read only 0 > > of 8192 bytes > > 2) at /home/postgres/postgres/contrib/amcheck/t/008_repack_concurrently.pl line 51. > > [15:36:37.204] # 'pgbench: error: client 5 script 0 aborted in command 28 query 0: ERROR: division by zero > > 3) 'pgbench: error: client 12 script 0 aborted in command 6 query 0: ERROR: cache lookup failed for relation 17400 > > Thanks, I'll check these. PROC_IN_VACUUM shouldn't be used for the same reason StartupDecodingContext() avoids setting PROC_IN_LOGICAL_DECODING in transaction. I've removed that and the tests work for me. Especially the "cache lookup failed" error is almost certainly related. Please let me know if you still get the other errors (Except for 2, which is probably due to the MVCC-unsafe behavior, as discussed earlier.) The 0006 part needs more work (definitely beyond PG 19). For now I've summarized the problem in the code this way: + * As there is no snapshot, our xmin should be invalid now. + * + * TODO xid can still be valid. We can mark our transaction with the + * PROC_IN_VACUUM flag, but at the same time we need to make sure that + * anything we write is ignored by VACUUM: since our xid is >= xmin of + * our replication slot, the slot does not help. Other transaction + * might use their RecentXmin to check if our xact is still running + * (see TransactionIdIsInProgress) before they check CLOG. By using + * PROC_IN_VACUUM we'd let their RecentXmin skip our xid. Thus our + * xact would appear not running anymore, but not yet marked committed + * in CLOG either, therefore aborted: it's o.k. for VACUUM to clean up + * tuples written by aborted transaction. + * + * Perhaps we can add a new field 'relisvalid' to pg_class and + * something alike to pg_index and make sure that neither queries nor + * VACUUM can use tables / indexes which do not have this flag set + * (The existing pg_index(indisvalid) field probably should not + * control whether VACUUM is allowed or not). Then we can do the + * catalog changes in separate transactions. Only the transaction that + * copies the heap would then use the PROC_IN_VACUUM flag. However, + * even then it would probably be appropriate to do regular + * (MVCC-safe) rewriting, i.e. avoid setting the xid of the rewriting + * transaction in the tuple headers. Thanks for your testing! -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-01-28T02:06:00Z
Hello! > PROC_IN_VACUUM shouldn't be used for the same reason StartupDecodingContext() > avoids setting PROC_IN_LOGICAL_DECODING in transaction. I've removed that and > the tests work for me. Especially the "cache lookup failed" error is almost > certainly related. Please let me know if you still get the other errors Yes, now it is passing. > (Except for 2, which is probably due to the MVCC-unsafe behavior, as discussed > earlier.) Not happening too. BTW, it was non MVCC-related, because in that case relcheckxmin would catch it. What if: 1) add new PROC_IN_REPACK flag 2) use it in catalog horizon, but not in data (like was done in [0] for PROC_IN_SAFE_IC) And after we have options: 3) do not "table_close(NewHeap, NoLock);" - keep ShareUpdateExclusiveLock all the time to prevent VACUUM enter 4) do not heap_page_prune_opt in repack transaction (just using simple flag) Or 3) preserve xmin/xmax of original transaction in repacked data 4) but better to keep ShareUpdateExclusiveLock anyway Seems to be enough. > The 0006 part needs more work (definitely beyond PG 19). This is sad, because if you are in a situation then you need REPACK - pinning the horizon for too long may just finish your DB.... And also, even with 0006 we still need to build indexes, which might pin it for long (even duration caused by a single index). Mikhail. [0]: https://github.com/postgres/postgres/commit/d9d076222f5b94a85e0e318339cfc44b8f26022d -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-01-30T19:33:56Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > PROC_IN_VACUUM shouldn't be used for the same reason StartupDecodingContext() > > avoids setting PROC_IN_LOGICAL_DECODING in transaction. I've removed that and > > the tests work for me. Especially the "cache lookup failed" error is almost > > certainly related. Please let me know if you still get the other errors > > Yes, now it is passing. > > > (Except for 2, which is probably due to the MVCC-unsafe behavior, as discussed > > earlier.) > > Not happening too. BTW, it was non MVCC-related, because in that case relcheckxmin would catch it. > > What if: > > 1) add new PROC_IN_REPACK flag > 2) use it in catalog horizon, but not in data (like was done in [0] for PROC_IN_SAFE_IC) > > And after we have options: > 3) do not "table_close(NewHeap, NoLock);" - keep ShareUpdateExclusiveLock all the time to prevent VACUUM enter > 4) do not heap_page_prune_opt in repack transaction (just using simple flag) > Or > 3) preserve xmin/xmax of original transaction in repacked data > 4) but better to keep ShareUpdateExclusiveLock anyway I've been thinking of another approach. Note that REPACK creates a new table only to eventually swap the relation files and drop it. Thus the transactions needs to get XID assigned very soon. I'm considering a special kind of relation whose catalog entries remain in the catalog cache and are never written to the catalog tables. (Unlike temporary relation, it'd be WAL logged so that REPACK can be replayed on standby.) If we eventually implement the MVCC safety, XID will neither be needed during data copying. And it shouldn't even be needed to build indexes, as long as their catalog entries are also "cache only". Thus the transaction REPACK is running in would not need XID until the data has been copied, indexes built and even (most of) the concurrent data changes replayed. Only the final catalog changes would require XID, but those should take very short time. Without XID and with the snapshot resetting, REPACK should not really block the progress of the VACUUM xmin horizon. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-01T19:46:31Z
Hello! PART 1: -------------- Something still wrong with 0006, check: 'pgbench: error: client 12 script 0 aborted in command 2 query 0: ERROR: attempted to overwrite invisible tuple https://cirrus-ci.com/task/6385612527239168?logs=test_world#L300 But it is hard to reproduce - happened once. -------------- Also, once I got [16:25:18.641] # at /tmp/cirrus-ci-build/contrib/amcheck/t/ 007_repack_concurrently.pl line 57. [16:25:18.641] # 'pgbench: error: client 6 script 0 aborted in command 2 query 0: ERROR: relation 21856 deleted while still in use https://cirrus-ci.com/task/4686014242881536?logs=test_world#L384 It was the PROC_IN_REPACK version (see below), but I think it is not related to it. But I'm not 100% sure. PART 2: > I'm considering a special kind of relation whose catalog entries remain in the > catalog cache and are never written to the catalog tables. (Unlike temporary > relation, it'd be WAL logged so that REPACK can be replayed on standby.) I think it is too complicated, especially including replication logic. Approach with catalog-only xid is much simpler, it was even committed (yes, reverted but because of another reason). Essentially we have two issues: 1) make sure catalog entities are not dropped because the vacuum 2) make sure data in new table is not vacuumed also For the first PROC_IN_REPACK is enough. For second - depends if MVCC-safe (original xmin/xmax) are preserved. If yes - looks like nothing more needed. If not - just prevent the vacuum from touching the table (but, looks like it is done already, because lock is held on NewHeap until commit). And additionally reset snapshots during the index building itself, but it is scope of another patch. I have implemented PROC_IN_REPACK POC in the attached patch. Also, I am still not sure if MVCC-safe implementation is worth its complexity compared with "relcheckxmin"approach [0]. [0]: https://www.postgresql.org/message-id/CADzfLwUEH5%2BLjCN%2B6kRfSsXwuou8rKXyVV42Wi-O_TG0360Kug%40mail.gmail.com Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-01T22:31:48Z
On 2026-Feb-01, Mihail Nikalayeu wrote: > Also, I am still not sure if MVCC-safe implementation is worth > its complexity compared with "relcheckxmin"approach [0]. I'm not sure it's acceptable to cause other sessions to raise errors if they query the table being repacked (or a table repacked recently). That sounds extremely unpleasant. Imagine a long-running transactions that runs enormous queries for many hours or even days, being killed near the end because some DBA decided to run REPACK on a table. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-01T22:37:37Z
Hello! > I'm not sure it's acceptable to cause other sessions to raise errors if > they query the table being repacked (or a table repacked recently). > That sounds extremely unpleasant. Imagine a long-running transactions > that runs enormous queries for many hours or even days, being killed > near the end because some DBA decided to run REPACK on a table. It will not. It raises an error only for the case table will be "empty" because REPACK switched to new with all tuples with REPACK xid and our transaction treats that xid as running. So, there is no regression here, it just changes from "see an empty table because of MVCC violation" to "get error" in the exact situation. I prefer the second. Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-02T07:25:48Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > The 0006 part needs more work (definitely beyond PG 19). > > This is sad, because if you are in a situation then you need REPACK - pinning the horizon for too long may just finish your DB.... > And also, even with 0006 we still need to build indexes, which might pin it for long (even duration caused by a single index). I suppose "to finish database" refers to XID wraparound - a problem that you keep mentioning again and again. (Yes, the wraparound is a problem, but not exactly a "final" state of the database.) As far as I know, it's not uncommon for DBAs to use the pg_repack extension, and this extension also restricts the progress of the VACUUM xmin horizon. Are you sure that users do complain about having ended up in the XID wraparound situation? I don't really pay attention to pg_repack, but I do pay quite some attention to the pg_squeeze extension (which I wrote and maintain). I recall that some users were surprised by the amount of disk space consumed (as the earlier versions of pg_squeeze were "too lazy" about WAL decoding), but I do not recall a single complaint about pg_squeeze causing the XID wraparound situation. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-02T09:17:42Z
> > I'm not sure it's acceptable to cause other sessions to raise errors if > > they query the table being repacked (or a table repacked recently). > > That sounds extremely unpleasant. Imagine a long-running transactions > > that runs enormous queries for many hours or even days, being killed > > near the end because some DBA decided to run REPACK on a table. > It will not. It raises an error only for the case table will be "empty" because REPACK switched to new with all tuples with REPACK xid and our transaction treats that xid as running. > So, there is no regression here, it just changes from "see an empty table because of MVCC violation" to "get error" in the exact situation. > I prefer the second. But I agree that a MVCC-safe solution is better. But to receive the error you need to be really unlucky (subtle race - in *first* access to a repacked table in transaction you need to get a statement snapshot in the moment of table swap) or use non READ-COMMITED isolation method. So, we prevent silent READ of MVCC-violated data - I think it is enough, at least for start. Mikhail.
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-02T09:18:01Z
Helllo! > I don't really pay attention to pg_repack, but I do pay quite some attention > to the pg_squeeze extension (which I wrote and maintain). I recall that some > users were surprised by the amount of disk space consumed (as the earlier > versions of pg_squeeze were "too lazy" about WAL decoding), but I do not > recall a single complaint about pg_squeeze causing the XID wraparound > situation. For "finish" I mean get out of space (in other write-heavy tables) or high CPU usage (due to slow index scan checking the same rows again and again). Also, you REPACK one table - and add a lot of bloat in others, in some cases with negative impact in total. But yes, agree about pg_squeeze here - if it is usable with such a long transaction - REPACK CONCURRENTLY will be too. Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-02T09:35:29Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > PART 1: > > -------------- > > Something still wrong with 0006, check: > > 'pgbench: error: client 12 script 0 aborted in command 2 query 0: ERROR: attempted to overwrite invisible tuple > https://cirrus-ci.com/task/6385612527239168?logs=test_world#L300 > > But it is hard to reproduce - happened once. > > -------------- > > Also, once I got > [16:25:18.641] # at /tmp/cirrus-ci-build/contrib/amcheck/t/007_repack_concurrently.pl line 57. > [16:25:18.641] # 'pgbench: error: client 6 script 0 aborted in command 2 query 0: ERROR: relation 21856 deleted while still in use > https://cirrus-ci.com/task/4686014242881536?logs=test_world#L384 > > It was the PROC_IN_REPACK version (see below), but I think it is not related to it. But I'm not 100% sure. I think it *is* related. My earlier patch version, which used the PROC_IN_VACUUM flag improperly [1] was also causing visibility issues. Please let me know if you manage to reproduce the issue with v32. > PART 2: > > > I'm considering a special kind of relation whose catalog entries remain in the > > catalog cache and are never written to the catalog tables. (Unlike temporary > > relation, it'd be WAL logged so that REPACK can be replayed on standby.) > > I think it is too complicated, especially including replication logic. I'm confused by hearing a complaint about complexity of code that I haven't posted yet. And I don't understand the relationship to "replication logic": REPACK (CONCURRENTLY) tries to avoid decoding of data changes in the *new* (transient) relation anyway. > Essentially we have two issues: > 1) make sure catalog entities are not dropped because the vacuum > 2) make sure data in new table is not vacuumed also 3) XID assigned early due to creation of catalog entries for the new table - that XID prevents the VACUUM xmin horizon from advancing till the end of the transaction, i.e. till the end of REPACK execution. > Also, I am still not sure if MVCC-safe implementation is worth its complexity compared with "relcheckxmin"approach [0]. IMO it's better for users to see the correct data than ERROR. But it still needs work. [1] https://www.postgresql.org/message-id/88003.1769511456%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-02T10:04:45Z
Hello! > I think it *is* related. My earlier patch version, which used the > PROC_IN_VACUUM flag improperly [1] was also causing visibility issues. Please > let me know if you manage to reproduce the issue with v32. Will try. Just to highlight - first error happened on v31 *without* PROC_IN_REPACK. Second error had PROC_IN_REPACK code, but it wasn't executed (flag wasn't set) - that's why I think it is not related. > I'm confused by hearing a complaint about complexity of code that I haven't > posted yet. And I don't understand the relationship to "replication logic": > REPACK (CONCURRENTLY) tries to avoid decoding of data changes in the *new* > (transient) relation anyway. I am not about complexity of code, but more about complexity of approach (introducing new things like cache-only relations). "Replication logic" - is about the fact you mentioned that such a relation is going to be replicated to standby (as result, some replication-related code is affected too, probably standby promotion also). Compared to the PROC_IN_REPACK flag - it feels overly complicated for me. PROC_IN_REPACK is the simplest thing here - just exclude XID from data-horizon, but keep it in catalog. That's all. Also, maybe I sound a little bit rude, sorry, it is just because of the language barrier. > 3) XID assigned early due to creation of catalog entries for the new table - > that XID prevents the VACUUM xmin horizon from advancing till the end of the > transaction, i.e. till the end of REPACK execution. Yes, but PROC_IN_REPACK covers it as well. That xid only in the catalog horizon. > IMO it's better for users to see the correct data than ERROR. But it still > needs work. Agreed, for me it is ordered like this (from bad to good): 1) silently see incorrect data in rear race 2) receive error instead in that race <----- acceptable for me 3) no error, data is correct Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-02T19:39:59Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > I think it *is* related. My earlier patch version, which used the > > PROC_IN_VACUUM flag improperly [1] was also causing visibility issues. Please > > let me know if you manage to reproduce the issue with v32. > > Will try. Just to highlight - first error happened on v31 *without* PROC_IN_REPACK. > Second error had PROC_IN_REPACK code, but it wasn't executed (flag wasn't set) - that's why I think it is not related. ok, v31 is the one that uses PROC_IN_VACUUM incorrectly. > > I'm confused by hearing a complaint about complexity of code that I haven't > > posted yet. And I don't understand the relationship to "replication logic": > > REPACK (CONCURRENTLY) tries to avoid decoding of data changes in the *new* > > (transient) relation anyway. > > I am not about complexity of code, but more about complexity of approach (introducing new things like cache-only relations). > "Replication logic" - is about the fact you mentioned that such a relation is going to be replicated to standby (as result, some > replication-related code is affected too, probably standby promotion also). I thought you mean logical replication. Regarding streaming replication, I mentioned it rather for the record. I need to check details to see if it requires special attention. > Compared to the PROC_IN_REPACK flag - it feels overly complicated for me. > PROC_IN_REPACK is the simplest thing here - just exclude XID from data-horizon, but keep it in catalog. That's all. My preference is to avoid hacking procarray.c if a reasonable alternative exists. > Also, maybe I sound a little bit rude, sorry, it is just because of the language barrier. No, that's fine. Since we've met at pgconf.eu, I think you're not a bad guy :-) Technical discussions are mostly about problems, so they tend to sound negative as such. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-06T15:08:27Z
Here's a v33, where pg_repackdb has been removed, per multiple discussions off-list. This is not a statement that we will never have such a tool; just that for the time being we should not let ourselves be distracted by it. If we have time to get something done about it for v19, then it's fine to bring it back; but I kinda doubt it. I think getting bits done including the addition of the CONCURRENTLY option trumps that. We can add it in v20 if we decide to; no great loss. I didn't include Antonin's 0006 "Use multiple snapshots to copy the data" either. It seems a bit too experimental yet. I think it would be good to have it in v19 also, but it seems less critical than the rest. I haven't looked at Mihail's patch downthread either. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Los cuentos de hadas no dan al niño su primera idea sobre los monstruos. Lo que le dan es su primera idea de la posible derrota del monstruo." (G. K. Chesterton) -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-06T16:29:58Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > I think it *is* related. My earlier patch version, which used the > > PROC_IN_VACUUM flag improperly [1] was also causing visibility issues. Please > > let me know if you manage to reproduce the issue with v32. > > Will try. Just to highlight - first error happened on v31 *without* PROC_IN_REPACK. I spent some time running the test with your branch (based on v32 as you told me off-list), but couldn't reproduce the problem. The related code is in heap_inplace_lock(): /* no known way this can happen */ ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg_internal("attempted to overwrite invisible tuple"))); The only path REPACK (CONCURRENTLY) uses in-place update seems to be: cluster.c:build_new_indexes() -> index_create_copy() -> index_create() -> index_build() -> index_update_stats() -> systable_inplace_update_begin() However I've got no idea how this can be related to REPACK. Since the new index is not visible to other transactions until REPACK is done, VACUUM should be the only process able to change the tuple before heap_inplace_lock(). Indeed, the server log seems to indicate relationship to VACUUM: 2026-02-01 16:44:58.878 UTC autovacuum worker[22589] LOG: automatic vacuum of table "postgres.pg_catalog.pg_class": index scans: 1 ... 2026-02-01 16:44:58.884 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: COMMIT; 2026-02-01 16:44:58.884 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: SELECT pg_try_advisory_lock(42)::integer AS gotlock 2026-02-01 16:44:58.884 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: SELECT pg_advisory_lock(43); 2026-02-01 16:44:58.884 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: BEGIN; 2026-02-01 16:44:58.884 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: INSERT INTO tbl(j) VALUES (nextval('last_j')) RETURNING j 2026-02-01 16:44:58.885 UTC client backend[12727] 008_repack_concurrently.pl LOG: statement: SELECT COUNT(*) AS count FROM tbl WHERE j <= 14148 2026-02-01 16:44:58.885 UTC client backend[12734] 008_repack_concurrently.pl LOG: statement: SELECT COUNT(*) AS count FROM tbl WHERE j <= 14145 2026-02-01 16:44:58.885 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: COMMIT; 2026-02-01 16:44:58.885 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: SELECT pg_advisory_unlock(43); 2026-02-01 16:44:58.887 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: BEGIN --TRANSACTION ISOLATION LEVEL REPEATABLE READ ; 2026-02-01 16:44:58.887 UTC client backend[12737] 008_repack_concurrently.pl LOG: statement: SELECT 1; 2026-02-01 16:44:58.891 UTC REPACK decoding worker[22621] FATAL: terminating background worker "REPACK decoding worker" due to administrator command 2026-02-01 16:44:58.896 UTC client backend[12740] 008_repack_concurrently.pl LOG: statement: SELECT COUNT(*) AS count FROM tbl WHERE j <= 14146 2026-02-01 16:44:58.896 UTC client backend[12722] 008_repack_concurrently.pl ERROR: attempted to overwrite invisible tuple 2026-02-01 16:44:58.896 UTC client backend[12722] 008_repack_concurrently.pl STATEMENT: REPACK (CONCURRENTLY) tbl USING INDEX tbl_pkey; However, VACUUM should not touch the tuple because the scan in systable_inplace_update_begin() should leave the containing buffer pinned. I wonder if you managed to hit another existing bug. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-07T14:16:27Z
Hi! > Indeed, the server log seems to indicate relationship to > VACUUM: > 2026-02-01 16:44:58.878 UTC autovacuum worker[22589] LOG: automatic vacuum of table "postgres.pg_catalog.pg_class": index scans: 1 O, it's a good clue! I have added some vacuum calls for pg_class in a stress test - and now it fails much more often (check attachment). It is "ERROR: cache lookup failed for relation" - but I think it may share the cause with "attempted to overwrite invisible tuple. See: https://cirrus-ci.com/build/4852126532239360 - with "Use multiple snapshots to copy the data." https://cirrus-ci.com/build/6429084491710464 - with "Use background worker to do logical decoding." But I am unable to reproduce the issue with only "Add CONCURRENTLY option to REPACK command." https://cirrus-ci.com/build/6467070524653568 Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-09T11:50:03Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > Indeed, the server log seems to indicate relationship to > > VACUUM: > > 2026-02-01 16:44:58.878 UTC autovacuum worker[22589] LOG: automatic vacuum of table "postgres.pg_catalog.pg_class": index scans: 1 > > O, it's a good clue! > > I have added some vacuum calls for pg_class in a stress test - and now it fails much more often (check attachment). > > It is "ERROR: cache lookup failed for relation" - but I think it may share the cause with "attempted to overwrite invisible tuple. I've just reported one issue [1] that causes this, but that does not seem to be related to the "attempted to overwrite invisible tuple" error. > See: > https://cirrus-ci.com/build/4852126532239360 - with "Use multiple snapshots to copy the data." > https://cirrus-ci.com/build/6429084491710464 - with "Use background worker to do logical decoding." > > But I am unable to reproduce the issue with only "Add CONCURRENTLY option to REPACK command." > https://cirrus-ci.com/build/6467070524653568 No idea why VACUUM makes the issue happen too often. Maybe it's related to the PD_ALL_VISIBLE flage, but I've got no detailed explanation. I also don't know why it does not reproduce w/o the logical decoding worker. Thanks again for your testing! [1] https://www.postgresql.org/message-id/61812.1770637345%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-15T16:03:00Z
Hello! Some feedback for v33. > else if (pg_strcasecmp(cmd, "REPACK") == 0) > cmdtype = PROGRESS_COMMAND_REPACK; src/backend/utils/adt/pgstatfuncs.c:290 I think we need to add "CLUSTER" here too to avoid regression. ----------- > ConditionVariablePrepareToSleep(&shared->cv); > for (;;) > { > bool initialized; > > SpinLockAcquire(&shared->mutex); > initialized = shared->initialized; > SpinLockRelease(&shared->mutex); src/backend/commands/cluster.c:3663 I think we should check GetBackgroundWorkerPid for worker status, to not wait forever in case of some issue with the worker. ----------- > /* Error queue. */ > shm_mq *error_mq; src/backend/commands/cluster.c:210. Not used anywhere. ----------- > finish_heap_swap(old_table_oid, new_table_oid, > is_system_catalog, > false, /* swap_toast_by_content */ > false, true, false, > frozenXid, cutoffMulti, > relpersistence); src/backend/commands/cluster.c I think we should add comments for other boolean parameters. ----------- > elog(ERROR, "Incomplete insert info."); > elog(ERROR, "Incomplete update info."); src/backend/replication/pgoutput_repack/pgoutput_repack.c:118,132 Should be non-capitalized? ----------- > # Copyright (c) 2022-2024, PostgreSQL Global Development Group src/backend/replication/pgoutput_repack/meson.build 2022-2026 ----------- > int newxcnt = 0; src/backend/replication/logical/snapbuild.c:53 uint32 is better here. Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-16T07:47:08Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Some feedback for v33. Thanks again for your review! > > else if (pg_strcasecmp(cmd, "REPACK") == 0) > > cmdtype = PROGRESS_COMMAND_REPACK; > src/backend/utils/adt/pgstatfuncs.c:290 > > I think we need to add "CLUSTER" here too to avoid regression. What kind of regression? There is no pg_stat_get_progress_info('CLUSTER') call in system_views.sql. > ----------- > > > ConditionVariablePrepareToSleep(&shared->cv); > > for (;;) > > { > > bool initialized; > > > > SpinLockAcquire(&shared->mutex); > > initialized = shared->initialized; > > SpinLockRelease(&shared->mutex); > src/backend/commands/cluster.c:3663 > > I think we should check GetBackgroundWorkerPid for worker status, to > not wait forever in case of some issue with the worker. ConditionVariableSleep() calls CHECK_FOR_INTERRUPTS(). That should process error messages from the worker. > ----------- > > > /* Error queue. */ > > shm_mq *error_mq; > src/backend/commands/cluster.c:210. > > Not used anywhere. ok > ----------- > > > finish_heap_swap(old_table_oid, new_table_oid, > > is_system_catalog, > > false, /* swap_toast_by_content */ > > false, true, false, > > frozenXid, cutoffMulti, > > relpersistence); > src/backend/commands/cluster.c > > I think we should add comments for other boolean parameters. Perhaps, it wouldn't hurt. > ----------- > > > elog(ERROR, "Incomplete insert info."); > > elog(ERROR, "Incomplete update info."); > src/backend/replication/pgoutput_repack/pgoutput_repack.c:118,132 > > Should be non-capitalized? ok > ----------- > > > # Copyright (c) 2022-2024, PostgreSQL Global Development Group > src/backend/replication/pgoutput_repack/meson.build > > 2022-2026 ok > ----------- > > > int newxcnt = 0; > src/backend/replication/logical/snapbuild.c:53 > > uint32 is better here. This was already discussed: https://www.postgresql.org/message-id/137668.1768235610%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-16T11:16:48Z
Hello! > What kind of regression? There is no pg_stat_get_progress_info('CLUSTER') call > in system_views.sql. I am about a direct call to that function (some monitoring tools may use it). -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-16T15:05:00Z
On 2026-Feb-16, Mihail Nikalayeu wrote: > Hello! > > > What kind of regression? There is no pg_stat_get_progress_info('CLUSTER') > > call in system_views.sql. > > I am about a direct call to that function (some monitoring tools may > use it). We don't promise compatibility using that interface, as far as I know. Tools can update to accomodate the new definition -- assuming any exist that actually do this. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-16T15:44:52Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Feb-16, Mihail Nikalayeu wrote: > > > Hello! > > > > > What kind of regression? There is no pg_stat_get_progress_info('CLUSTER') > > > call in system_views.sql. > > > > I am about a direct call to that function (some monitoring tools may > > use it). > > We don't promise compatibility using that interface, as far as I know. At least this particular function is not mentioned in the user documentation, AFAICS. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-16T19:56:18Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Here's a v33 ... This is v33 rebased, pgindented and with Mihail's review [1] incorporated. [1] https://www.postgresql.org/message-id/CADzfLwXJLypkRdpwapQr+pZQnv1-NvkJ9DpzWhNwudQgirCE0Q@mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-23T18:23:52Z
Rebased again, due to recent conflicting changes. I also noticed a leftover "See also" line for nonexistent pg_repackdb in ref/repack.sgml; removed. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ Tom: There seems to be something broken here. Teodor: I'm in sackcloth and ashes... Fixed. http://postgr.es/m/482D1632.8010507@sigaev.ru -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-24T18:29:17Z
On 2026-Feb-23, Alvaro Herrera wrote: Looking at this function in pgoutput_repack.c: > +/* Store concurrent data change. */ > +static void > +store_change(LogicalDecodingContext *ctx, ConcurrentChangeKind kind, > + HeapTuple tuple) > +{ [...] we have this: > + size = VARHDRSZ + SizeOfConcurrentChange; > + > + /* > + * ReorderBufferCommit() stores the TOAST chunks in its private memory > + * context and frees them after having called apply_change(). Therefore > + * we need flat copy (including TOAST) that we eventually copy into the > + * memory context which is available to decode_concurrent_changes(). > + */ > + if (HeapTupleHasExternal(tuple)) > + { > + /* > + * toast_flatten_tuple_to_datum() might be more convenient but we > + * don't want the decompression it does. > + */ > + tuple = toast_flatten_tuple(tuple, dstate->tupdesc); > + flattened = true; > + } > + > + size += tuple->t_len; > + if (size >= MaxAllocSize) > + elog(ERROR, "Change is too big."); > + > + /* Construct the change. */ > + change_raw = (char *) palloc0(size); > + SET_VARSIZE(change_raw, size); I wonder if this isn't problematic with large tuples. If a row has some very wide columns, each of which individually is less than 1 GB, then it might happen that the sum of their sizes exceeds 1 GB, causing palloc() to complain and abort the whole repack operation. This wouldn't be very nice, so I think we need to address it somehow. Another thing I'm not very keen on, is the fact that we have to memcpy() the tuple contents a few lines below: > + /* > + * Copy the tuple. > + * > + * Note: change->tup_data.t_data must be fixed on retrieval! > + */ > + memcpy(&change.tup_data, tuple, sizeof(HeapTupleData)); > + memcpy(dst, &change, SizeOfConcurrentChange); > + dst += SizeOfConcurrentChange; > + memcpy(dst, tuple->t_data, tuple->t_len); > + /* Store as tuple of 1 bytea column. */ > + values[0] = PointerGetDatum(change_raw); > + isnull[0] = false; > + tuplestore_putvalues(dstate->tstore, dstate->tupdesc_change, > + values, isnull); To make matters worse, tuplestore_putvalues does a heap_form_minimal_tuple() on this and copies the data again. This seems pretty wasteful. I think we need some new APIs to avoid all this copying. It appears that it all starts with reorderbuffer doing something unhelpful with the memory context of the TOAST chunks. Maybe we should address this by "fixing" reorderbuffer so that it doesn't do this, instead of playing so many games to cope. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-25T08:55:59Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Feb-23, Alvaro Herrera wrote: > > Looking at this function in pgoutput_repack.c: > > > +/* Store concurrent data change. */ > > +static void > > +store_change(LogicalDecodingContext *ctx, ConcurrentChangeKind kind, > > + HeapTuple tuple) > > +{ > > [...] we have this: > > > + size = VARHDRSZ + SizeOfConcurrentChange; > > + > > + /* > > + * ReorderBufferCommit() stores the TOAST chunks in its private memory > > + * context and frees them after having called apply_change(). Therefore > > + * we need flat copy (including TOAST) that we eventually copy into the > > + * memory context which is available to decode_concurrent_changes(). > > + */ > > + if (HeapTupleHasExternal(tuple)) > > + { > > + /* > > + * toast_flatten_tuple_to_datum() might be more convenient but we > > + * don't want the decompression it does. > > + */ > > + tuple = toast_flatten_tuple(tuple, dstate->tupdesc); > > + flattened = true; > > + } > > + > > + size += tuple->t_len; > > + if (size >= MaxAllocSize) > > + elog(ERROR, "Change is too big."); > > + > > + /* Construct the change. */ > > + change_raw = (char *) palloc0(size); > > + SET_VARSIZE(change_raw, size); In 0005 ("Use background worker to do logical decoding"), the function is a bit simpler because here the decoding worker uses temporary file to send the data to the REPACKing backend, rather than tuplestore. sharedtuplestore.h would also work but I think we do not need its functionality, and AFAICS it always writes the data into a file anyway (i.e. it does not use memory even if the amount of data is small). (Perhaps 0004 should use the file too, in case 0005 does not make it into PG 19.) > I wonder if this isn't problematic with large tuples. If a row has some > very wide columns, each of which individually is less than 1 GB, then it > might happen that the sum of their sizes exceeds 1 GB, causing palloc() > to complain and abort the whole repack operation. This wouldn't be very > nice, so I think we need to address it somehow. I agree. > I think we need some new APIs to avoid all this copying. It appears > that it all starts with reorderbuffer doing something unhelpful with the > memory context of the TOAST chunks. Maybe we should address this by > "fixing" reorderbuffer so that it doesn't do this, instead of playing so > many games to cope. What I see is that reorderbuffer.c collects the TOAST pointers (ReorderBufferToastAppendChunk) and then, before passing the tuple to the output plugin, it copies the TOAST chunks referenced by the tuple to memory and replaces the "on-disk" TOAST pointers in the tuple with "external indirect" ones, pointing to the in-memory TOAST chunks (ReorderBufferToastReplace). For REPACK, I suggest a variant of toast_flatten_tuple() that writes the output to a file, and a corresponding function that reads it while allocating separate chunks of memory for the individual TOASTed attributes - the restored tuple would reference the chunks using the "external indirect" TOAST pointers, as if it had been processed by ReorderBufferToastReplace(). Does that make sense to you? -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-25T13:54:09Z
On 2026-Feb-25, Antonin Houska wrote: > In 0005 ("Use background worker to do logical decoding"), the function is a > bit simpler because here the decoding worker uses temporary file to send the > data to the REPACKing backend, rather than tuplestore. Ah, that patch changes the implementation rather substantially then; I didn't realize that because I haven't been looking at it yet, as I wanted to have a good idea of the status of 0004 before proceeding further with the next one. However, given that these changes are so extensive, I think it might be better to review 0004+0005 squashed as one unit, rather than separately. > For REPACK, I suggest a variant of toast_flatten_tuple() that writes the > output to a file, and a corresponding function that reads it while allocating > separate chunks of memory for the individual TOASTed attributes - the restored > tuple would reference the chunks using the "external indirect" TOAST pointers, > as if it had been processed by ReorderBufferToastReplace(). Does that make > sense to you? Hmm, so on the apply side when reading the file, we would first reach each toast attribute value, which we know to insert directly to the toast table (keeping track of each individually toast pointer as we do so); then we reach the heap tuple itself, we [... somehow ...] interpret these external indirect toast pointers and substitute the toast pointers that we created. So we never have to construct the entire tuple, or indeed do anything else with the toasted values other than insert them into the toast table. Actually, can't we simply insert the toasted values directly in the decoding worker into the new toast table? That could save a lot of writing to the file, since we only save the raw heap tuples with no toasted contents; but it's not clear to me that this is valid. (And we might create extra bloat if a tuple is inserted and later deleted concurrently with the repack; but that would happen with the original approach as well, no?) -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "We’ve narrowed the problem down to the customer’s pants being in a situation of vigorous combustion" (Robert Haas, Postgres expert extraordinaire) -
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-02-25T13:55:42Z
Hello, I did stress testing on v35 patches, where I did concurrency test using pgbench with 50 concurrent clients, 4 threads with the below pgbench script (dual_chaos.sql) on the following table setup(setup.sql). I ran pgbench with 5M rows for 10 minutes and 50M for ~45 minutes multiple times. REPACK (concurrently) ran successfully except "once"(see below). I created a shadow/clone table to use for checking the correctness after doing the concurrency test.I used 4 checks to verify that data is intact and REPACK (concurrently) ran successfully. 1) table file OID(relfilenode) swapped? 2) bloat gone? victim relation size should be less than shadow relation size. 3) using FULL JOIN logic (borrowed from repack.spec, with small change) against the shadow table which goes under the same concurrent ops done on the victim table , basically doing dual writes (see dual_chaos.sql) to verify table data integrity. 4) Physical Index Integrity (amcheck) (borrowed from Mihail's tests) The concurrency test failed once. I tried to reproduce the below scenario but no luck,i think the reason the assert failure happened because after speculative insert there might be no spec CONFIRM or ABORT, thoughts? TRAP: failed Assert("!specinsert"), File: "reorderbuffer.c", Line: 2610, PID: 3956168 postgres: REPACK decoding worker for relation "stress_victim" (ExceptionalCondition+0x98)[0xaaaab1251188] postgres: REPACK decoding worker for relation "stress_victim" (+0x67b1cc)[0xaaaab0f4b1cc] postgres: REPACK decoding worker for relation "stress_victim" (+0x67b86c)[0xaaaab0f4b86c] postgres: REPACK decoding worker for relation "stress_victim" (ReorderBufferCommit+0x74)[0xaaaab0f4b8f0] postgres: REPACK decoding worker for relation "stress_victim" (+0x66229c)[0xaaaab0f3229c] postgres: REPACK decoding worker for relation "stress_victim" (xact_decode+0x1a0)[0xaaaab0f312bc] postgres: REPACK decoding worker for relation "stress_victim" (LogicalDecodingProcessRecord+0xd4)[0xaaaab0f30e60] postgres: REPACK decoding worker for relation "stress_victim" (+0x3372e4)[0xaaaab0c072e4] postgres: REPACK decoding worker for relation "stress_victim" (+0x339634)[0xaaaab0c09634] postgres: REPACK decoding worker for relation "stress_victim" (RepackWorkerMain+0x1ac)[0xaaaab0c094e8] postgres: REPACK decoding worker for relation "stress_victim" (BackgroundWorkerMain+0x2b0)[0xaaaab0efc440] postgres: REPACK decoding worker for relation "stress_victim" (postmaster_child_launch+0x1f0)[0xaaaab0f00398] postgres: REPACK decoding worker for relation "stress_victim" (+0x639ca4)[0xaaaab0f09ca4] postgres: REPACK decoding worker for relation "stress_victim" (+0x639f94)[0xaaaab0f09f94] postgres: REPACK decoding worker for relation "stress_victim" (+0x638714)[0xaaaab0f08714] postgres: REPACK decoding worker for relation "stress_victim" (+0x635978)[0xaaaab0f05978] postgres: REPACK decoding worker for relation "stress_victim" (PostmasterMain+0x160c)[0xaaaab0f050c8] postgres: REPACK decoding worker for relation "stress_victim" (main+0x3dc)[0xaaaab0d974d4] /lib/aarch64-linux-gnu/libc.so.6(+0x284c4)[0xffff867584c4] /lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x98)[0xffff86758598] postgres: REPACK decoding worker for relation "stress_victim" (_start+0x30)[0xaaaab09bc1f0] 2026-02-19 18:20:56.088 IST [3905812] LOG: checkpoint starting: wal 2026-02-19 18:21:10.683 IST [3905808] LOG: background worker "REPACK decoding worker" (PID 3956168) was terminated by signal 6: Aborted Crash Test: i did crash test using debugger using a breakpoint inside apply_concurrent_changes to simulate a crash while concurrent changes are being done, after few concurrent changes are done , i crashed the server using "pg_ctl -m immediate stop", then restarted the server, i observed that REPACK (concurrently) didn't completed (expected), files were not swapped and data on the victim table is intact checked using FULL JOIN with shadow table, but there are some leftovers of the transient table we used for REPACK (concurrently) such as 1) transient table's relation files - these consume extra space , i think this was the case with VACUUM FULL previously, so these has to be removed manually , but I think this time we have a "leverage" which we can use to remove the extra space. 2) transient table's WALs - these are generated because of concurrent changes done while applying the logical decoded changes on the new transient table, i think this won't be a problem until they only will get recycled but if they get archived , they are of no use instead they consume more space and time during the archival process. "Leverage" Idea: i think we can re-use these transient table's relation files and WALs during crash recovery, so that user don't have to re-run the REPACK (concurrently) after server has recovered, for this we might need to write a WAL for REPACK (concurrently) to let startup process know REPACK (concurrently) occurred which sets a flag, so at the end of startup process all the WALs of the transient table are already applied so transient table perfect now , at the end we can do swapping (finish_heap_swap) after checking the flag , these are all my initial thoughts on this idea to reuse the "residue" files of the transient table. I could be totally wrong :) Please correct me if I am. i think we need to update this statement in repack.sgml regarding wal_level <listitem> <para> The <link linkend="guc-wal-level"><varname>wal_level</varname></link> configuration parameter is less than <literal>logical</literal>. </para> </listitem> because of this commit POC: enable logical decoding when wal_level = 'replica' without a server restart (67c2097) -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-25T16:03:19Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Feb-25, Antonin Houska wrote: > > For REPACK, I suggest a variant of toast_flatten_tuple() that writes the > > output to a file, and a corresponding function that reads it while allocating > > separate chunks of memory for the individual TOASTed attributes - the restored > > tuple would reference the chunks using the "external indirect" TOAST pointers, > > as if it had been processed by ReorderBufferToastReplace(). Does that make > > sense to you? > > Hmm, so on the apply side when reading the file, we would first reach > each toast attribute value, which we know to insert directly to the > toast table (keeping track of each individually toast pointer as we do > so); then we reach the heap tuple itself, we [... somehow ...] interpret > these external indirect toast pointers and substitute the toast pointers > that we created. So we never have to construct the entire tuple, or > indeed do anything else with the toasted values other than insert them > into the toast table. Yes, that's what I mean. > Actually, can't we simply insert the toasted values directly in the > decoding worker into the new toast table? That could save a lot of > writing to the file, since we only save the raw heap tuples with no > toasted contents; but it's not clear to me that this is valid. (And we > might create extra bloat if a tuple is inserted and later deleted > concurrently with the repack; but that would happen with the original > approach as well, no?) The problem I see here is that for UPDATE you need the old tuple to determine if its TOAST value should be deleted or if the new tuple should reuse it - this is how I understand toast_tuple_init(). So the worker would have to store all the changes somewhere temporarily until it can fully apply the changes (i.e. until the initial copy and index build is complete). Besides that, if the worker had to switch between the past (for the decoding) and present (for the TOAST operations), it would have to invalidate system caches repeatedly. 0004 does that, but 0005 makes that unnecessary. (I don't know if the repeated cache invalidation would be a serious performance problem, but from coding perspective I find it more convenient if the worker only deals with decoding and does not have to do this time travel and invalidations at all.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-25T16:25:54Z
On 2026-Feb-25, Antonin Houska wrote: > > Hmm, so on the apply side when reading the file, we would first reach > > each toast attribute value, which we know to insert directly to the > > toast table (keeping track of each individually toast pointer as we do > > so); then we reach the heap tuple itself, we [... somehow ...] interpret > > these external indirect toast pointers and substitute the toast pointers > > that we created. So we never have to construct the entire tuple, or > > indeed do anything else with the toasted values other than insert them > > into the toast table. > > Yes, that's what I mean. Makes sense. Would you be able to try and implement that? > The problem I see here is that for UPDATE you need the old tuple to determine > if its TOAST value should be deleted or if the new tuple should reuse it - > this is how I understand toast_tuple_init(). So the worker would have to store > all the changes somewhere temporarily until it can fully apply the changes > (i.e. until the initial copy and index build is complete). Ah, you're right, that won't work. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ Tom: There seems to be something broken here. Teodor: I'm in sackcloth and ashes... Fixed. http://postgr.es/m/482D1632.8010507@sigaev.ru -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-25T19:04:13Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Feb-25, Antonin Houska wrote: > > > > Hmm, so on the apply side when reading the file, we would first reach > > > each toast attribute value, which we know to insert directly to the > > > toast table (keeping track of each individually toast pointer as we do > > > so); then we reach the heap tuple itself, we [... somehow ...] interpret > > > these external indirect toast pointers and substitute the toast pointers > > > that we created. So we never have to construct the entire tuple, or > > > indeed do anything else with the toasted values other than insert them > > > into the toast table. > > > > Yes, that's what I mean. > > Makes sense. Would you be able to try and implement that? Yes, I'll try in the following days. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-25T19:41:15Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > I did stress testing on v35 patches, where I did concurrency test using > pgbench with 50 concurrent clients, 4 threads with the below pgbench > script (dual_chaos.sql) on the following table setup(setup.sql). > I ran pgbench with 5M rows for 10 minutes and 50M for ~45 minutes > multiple times. REPACK (concurrently) ran successfully except "once"(see below). > I created a shadow/clone table to use for checking the correctness after doing > the concurrency test.I used 4 checks to verify that data is intact and > REPACK (concurrently) ran successfully. > > 1) table file OID(relfilenode) swapped? > 2) bloat gone? victim relation size should be less than > shadow relation size. > 3) using FULL JOIN logic (borrowed from repack.spec, with small change) > against the shadow table which goes under the same concurrent ops > done on the victim table , basically doing dual writes (see dual_chaos.sql) to > verify table data integrity. > 4) Physical Index Integrity (amcheck) (borrowed from Mihail's tests) Thanks! > The concurrency test failed once. I tried to reproduce the below scenario > but no luck,i think the reason the assert failure happened because > after speculative insert there might be no spec CONFIRM or ABORT, thoughts? Perhaps, I'll try. I'm not sure the REPACK decoding worker does anthing special regarding decoding. If you happen to see the problem again, please try to preserve the related WAL segments - if this is a bug in PG executor, pg_waldump might reveal that. > Crash Test: > i did crash test using debugger using a breakpoint inside apply_concurrent_changes > to simulate a crash while concurrent changes are being done, after few concurrent changes > are done , i crashed the server using "pg_ctl -m immediate stop", then restarted the server, > i observed that REPACK (concurrently) didn't completed (expected), files were not swapped and data > on the victim table is intact checked using FULL JOIN with shadow table, but there are > some leftovers of the transient table we used for REPACK (concurrently) such as > 1) transient table's relation files - these consume extra space , i think this was the > case with VACUUM FULL previously, so these has to be removed manually , but > I think this time we have a "leverage" which we can use to remove the extra space. > 2) transient table's WALs - these are generated because of concurrent changes done while > applying the logical decoded changes on the new transient table, i think this won't be a problem > until they only will get recycled but if they get archived , they are of no use instead they > consume more space and time during the archival process. VACUUM FULL / CLUSTER also produces (a lot of) WAL, so IMO there's nothing specific about REPACK. Regarding the transient table, I have a draft patch (for future versions) that creates the transient table in a separate transaction and commits it. (This is part of the effort to not block the progress of VACUUM xmin horizon. The point is that most of the time REPACK should not have XID assigned.) With this design, each time REPACK starts, it checks (in the pg_depend catalog) if the transient table exists for particular table, and if it does, it drops it. > "Leverage" Idea: > i think we can re-use these transient table's relation files and WALs during crash recovery, > so that user don't have to re-run the REPACK (concurrently) after server has recovered, > for this we might need to write a WAL for REPACK (concurrently) to let startup process > know REPACK (concurrently) occurred which sets a flag, so at the end of startup process > all the WALs of the transient table are already applied so transient table perfect now , > at the end we can do swapping (finish_heap_swap) after checking the flag , these are > all my initial thoughts on this idea to reuse the "residue" files of the transient table. > I could be totally wrong :) Please correct me if I am. I think it'd be quite difficult to restart REPACK exactly at the point it crashed. Especially if the tables are unlocked between server restart and the restart of REPACK. > i think we need to update this statement in repack.sgml regarding wal_level > <listitem> > <para> > The <link linkend="guc-wal-level"><varname>wal_level</varname></link> > configuration parameter is less than <literal>logical</literal>. > </para> > </listitem> > because of this commit POC: enable logical decoding when wal_level = 'replica' without a server restart (67c2097) I'm aware of this commit and already updated regression tests, however forgot to update the user documentation. Thanks for reminder. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-02-26T12:19:25Z
On 2026-Feb-25, Antonin Houska wrote: > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > 2) transient table's WALs - these are generated because of > > concurrent changes done while applying the logical decoded changes > > on the new transient table, i think this won't be a problem until > > they only will get recycled but if they get archived , they are of > > no use instead they consume more space and time during the archival > > process. > > VACUUM FULL / CLUSTER also produces (a lot of) WAL, so IMO there's > nothing specific about REPACK. Yeah, I don't think there's anything we can (or should) do about this. It's just writing to the common WAL stream, which means that any non-repack concurrent load is going to be producing WAL messages interspersed with what is being done for repack. So it's not possible to skip archiving those files, or anything of the sort. In fact, these messages being written to WAL is how REPACK is going to be transmitted to replicas. (Actually, now that I think about this, I realize that I don't know how this part really works, and I should. Back to code review it is then.) -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "At least to kernel hackers, who really are human, despite occasional rumors to the contrary" (LWN.net)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-02-27T18:38:29Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > On 2026-Feb-25, Antonin Houska wrote: > > > > > > Hmm, so on the apply side when reading the file, we would first reach > > > > each toast attribute value, which we know to insert directly to the > > > > toast table (keeping track of each individually toast pointer as we do > > > > so); then we reach the heap tuple itself, we [... somehow ...] interpret > > > > these external indirect toast pointers and substitute the toast pointers > > > > that we created. So we never have to construct the entire tuple, or > > > > indeed do anything else with the toasted values other than insert them > > > > into the toast table. > > > > > > Yes, that's what I mean. > > > > Makes sense. Would you be able to try and implement that? > > Yes, I'll try in the following days. My proposal is in the 0005 part of this series - a separate diff just for now, to make review easier (the diff also contains a few lines of related refactoring, I hope it's not too disturbing). The changes we had in 0005 ("Use background worker ...") so far are now in 0004 ("Add CONCURRENTLY option ..."). -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-02-28T15:16:00Z
Hello! Some review comments: ------------ > attrs = palloc0_array(Datum, desc->natts); > isnull = palloc0_array(bool, desc->natts); It looks like there is a memory leak with those arrays. ------------ > # TOAST pointer, wich we need to update typo ------------ > ident_idx = RelationGetReplicaIndex(rel); > if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) check_repack_concurrently_requirements uses rd_pkindex as fallback. But rebuild_relation_finish_concurrent does not contain such logic: > ident_idx_old = RelationGetReplicaIndex(OldHeap); ------------ > > > > > ConditionVariablePrepareToSleep(&shared->cv); > > > for (;;) > > > { > > > bool initialized; > > > > > > SpinLockAcquire(&shared->mutex); > > > initialized = shared->initialized; > > > SpinLockRelease(&shared->mutex); > > src/backend/commands/cluster.c:3663 > > > > I think we should check GetBackgroundWorkerPid for worker status, to > > not wait forever in case of some issue with the worker. > ConditionVariableSleep() calls CHECK_FOR_INTERRUPTS(). That should process > error messages from the worker. Hm, yes, and RepackWorkerShutdown will detach the queue. But ProcessRepackMessages does not react somehow to SHM_MQ_DETACHED - just ignores. Or am I missing something? And looks like it applies to all wait-loops related to repack. ------------ > build_identity_key > .... > n = ident_idx->indnatts; Should we use indnkeyatts here? ------------ > build_identity_key > .... > entry->sk_collation = att->attcollation; Should we use index collation (not heap) here? entry->sk_collation = ident_idx_rel->rd_indcollation[i]; ------------ > SnapBuildInitialSnapshotForRepack What is about to add defensive checks like SnapBuildInitialSnapshot does? > if (!builder->committed.includes_all_transactions) > elog(ERROR, "cannot build an initial slot snapshot, not all transactions are monitored anymore"); Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-02T14:39:01Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Some review comments: Thanks again! > ------------ > > > attrs = palloc0_array(Datum, desc->natts); > > isnull = palloc0_array(bool, desc->natts); > > It looks like there is a memory leak with those arrays. I suppose you mean store_change(). Yes, I tried to free the individual chunks and forgot these. The next version uses a new, per-change memory context. > > ident_idx = RelationGetReplicaIndex(rel); > > if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) > > check_repack_concurrently_requirements uses rd_pkindex as fallback. > > But rebuild_relation_finish_concurrent does not contain such logic: > > > ident_idx_old = RelationGetReplicaIndex(OldHeap); Good point. I added a new argument to rebuild_relation_finish_concurrent() so that the identity index is only determined once. > ------------ > > > > > > > > ConditionVariablePrepareToSleep(&shared->cv); > > > > for (;;) > > > > { > > > > bool initialized; > > > > > > > > SpinLockAcquire(&shared->mutex); > > > > initialized = shared->initialized; > > > > SpinLockRelease(&shared->mutex); > > > src/backend/commands/cluster.c:3663 > > > > > > I think we should check GetBackgroundWorkerPid for worker status, to > > > not wait forever in case of some issue with the worker. > > > ConditionVariableSleep() calls CHECK_FOR_INTERRUPTS(). That should process > > error messages from the worker. > > Hm, yes, and RepackWorkerShutdown will detach the queue. But > ProcessRepackMessages does not react somehow to SHM_MQ_DETACHED - just > ignores. Or am I missing something? On ERROR / FATAL, RepackWorkerShutdown() should send the message before detaching. elog.c does it via send_message_to_frontend(), due to the previous call of pq_redirect_to_shm_mq() in RepackWorkerMain(). ProcessRepackMessages() then only needs to care about the message, not about the worker's detaching. > ------------ > > > build_identity_key > > .... > > n = ident_idx->indnatts; > > Should we use indnkeyatts here? Definitely. I missed the addition of the INCLUDE columns feature during maintenance of pg_squeeze, and copied the bug to REPACK. Fixed. > ------------ > > > build_identity_key > > .... > > entry->sk_collation = att->attcollation; > > Should we use index collation (not heap) here? > entry->sk_collation = ident_idx_rel->rd_indcollation[i]; AFAIC they should be equal, but what you propose simplifies the code a bit. Done. > ------------ > > > SnapBuildInitialSnapshotForRepack > > What is about to add defensive checks like SnapBuildInitialSnapshot does? > > > if (!builder->committed.includes_all_transactions) > > elog(ERROR, "cannot build an initial slot snapshot, not all transactions are monitored anymore"); Initially I added a header comment (XXX) to SnapBuildInitialSnapshotForRepack() saying that some of the checks, including this one, could be adopted. The checks were problematic in the backend executing REPACK. However, they appear to be fine if the code is executed by the logical decoding worker. So what I'm trying now is to add a new argument "repack" to SnapBuildInitialSnapshot() and remove the original 0003 diff ("Move conversion of a historic to MVCC snapshot to a separate function.") from the series altogether. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-02T17:23:30Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > ------------ > > > > > attrs = palloc0_array(Datum, desc->natts); > > > isnull = palloc0_array(bool, desc->natts); > > > > It looks like there is a memory leak with those arrays. > > I suppose you mean store_change(). Yes, I tried to free the individual chunks > and forgot these. The next version uses a new, per-change memory context. I realize now that I forgot to reset the context at the end of the function. I'll fix that in the next version (which will probably be posted rather soon). -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-05T19:06:13Z
Antonin Houska <ah@cybertec.at> wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > > ------------ > > > > > > > attrs = palloc0_array(Datum, desc->natts); > > > > isnull = palloc0_array(bool, desc->natts); > > > > > > It looks like there is a memory leak with those arrays. > > > > I suppose you mean store_change(). Yes, I tried to free the individual chunks > > and forgot these. The next version uses a new, per-change memory context. > > I realize now that I forgot to reset the context at the end of the > function. I'll fix that in the next version (which will probably be posted > rather soon). This is it. One more diff added, to engage BulkInsertState. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-06T07:14:55Z
Antonin Houska <ah@cybertec.at> wrote: > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > The concurrency test failed once. I tried to reproduce the below scenario > > but no luck,i think the reason the assert failure happened because > > after speculative insert there might be no spec CONFIRM or ABORT, thoughts? > > Perhaps, I'll try. I'm not sure the REPACK decoding worker does anthing > special regarding decoding. If you happen to see the problem again, please try > to preserve the related WAL segments - if this is a bug in PG executor, > pg_waldump might reveal that. I could not reproduce the failure, and have no idea how speculative insert can stay w/o CONFIRM / ABORT record. The only problem I could imagine is that change_useless_for_repack() filters out the CONFIRM / ABORT record accidentally, but neither code review nor debugger proves that theory. (Actually if this was the problem, the test failure probably wouldn't be that rare.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-03-07T14:28:00Z
Hello! On Thu, Mar 5, 2026 at 8:06 PM Antonin Houska <ah@cybertec.at> wrote: > This is it. One more diff added, to engage BulkInsertState. Few more comments: > * In case functions in the index need the active snapshot and caller > * hasn't set one. Looks like a stale comment. ----------- > recheck = ExecInsertIndexTuples(iistate->rri, > iistate->estate, > 0, > index_slot, > NIL, NULL); Such code in apply_concurrent_update and apply_concurrent_insert. AFAIU we need to call ResetPerTupleExprContext(iistate->estate); after list_free(recheck); to avoid small per-row memory leak. ----------------- In find_target_tuple index_rescan is called before setting the sk_argument - it works, but feels to be incorrect for the common case. We should call it once entry->sk_argument is ready. ----------------- I put possible fixes into the attached patch. Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-09T10:43:44Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Thu, Mar 5, 2026 at 8:06 PM Antonin Houska <ah@cybertec.at> wrote: > > This is it. One more diff added, to engage BulkInsertState. > > Few more comments: > ... > I put possible fixes into the attached patch. All LGTM, included in the next version. Thanks! -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-10T19:24:25Z
I have just pushed 0001 with some additional changes. Here's a rebase of the next ones; no changes other than fixing the conflicts. I'm seeing this warning caused by 0004, which I think is also being reported in CI https://cirrus-ci.com/task/6606871575920640 [281/1134] Compiling C object src/backend/postgres_lib.a.p/commands_cluster.c.o In file included from ../../source/repack/src/include/access/htup_details.h:22, from ../../source/repack/src/include/access/relscan.h:17, from ../../source/repack/src/include/access/heapam.h:19, from ../../source/repack/src/backend/commands/cluster.c:37: In function ‘VARSIZE_ANY’, inlined from ‘restore_tuple’ at ../../source/repack/src/backend/commands/cluster.c:3129:18, inlined from ‘apply_concurrent_changes’ at ../../source/repack/src/backend/commands/cluster.c:2915:9, inlined from ‘process_concurrent_changes’ at ../../source/repack/src/backend/commands/cluster.c:3386:2: ../../source/repack/src/include/varatt.h:243:51: warning: array subscript ‘varattrib_4b[0]’ is partly outside array bounds of ‘varlena[1]’ [-Warray-bounds=] 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> 2) & 0x3FFFFFFF) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ ../../source/repack/src/include/varatt.h:467:24: note: in expansion of macro ‘VARSIZE_4B’ 467 | return VARSIZE_4B(PTR); | ^~~~~~~~~~ ../../source/repack/src/backend/commands/cluster.c: In function ‘process_concurrent_changes’: ../../source/repack/src/backend/commands/cluster.c:3121:33: note: object ‘varhdr’ of size 4 3121 | varlena varhdr; | ^~~~~~ -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-10T20:05:12Z
On 2026-Mar-10, Alvaro Herrera wrote: > I'm seeing this warning caused by 0004 (Caused by 0003, not 0004, sorry) -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
RE: Adding REPACK [concurrently]
Shinoda, Noriyoshi <noriyoshi.shinoda@hpe.com> — 2026-03-11T01:13:29Z
Hi, Hackers. Thanks for developing this great feature. The committed documentation for the pg_stat_progress_repack view was missing the explanation for the "command" column. I've attached a small patch for monitoring.sgml. Regards, Noriyoshi Shinoda -----Original Message----- From: Alvaro Herrera <alvherre@alvh.no-ip.org> Sent: Wednesday, March 11, 2026 5:05 AM To: Antonin Houska <ah@cybertec.at> Cc: Mihail Nikalayeu <mihailnikalayeu@gmail.com>; Pg Hackers <pgsql-hackers@lists.postgresql.org>; Robert Treat <rob@xzilla.net> Subject: Re: Adding REPACK [concurrently] On 2026-Mar-10, Alvaro Herrera wrote: > I'm seeing this warning caused by 0004 (Caused by 0003, not 0004, sorry) -- Álvaro Herrera Breisgau, Deutschland — https://urldefense.com/v3/__https://www.EnterpriseDB.com/__;!!NpxR!jyoaRRv_itWrU5kI6TI2ew-oOOvVeDRnHHL_P1C5WaJtZyMAlB9hy0vA0JU_cVsS9Tk_8JIzihdBfX2LeR20cd0wuw$
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-11T15:54:22Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > I have just pushed 0001 with some additional changes. Thanks! > Here's a rebase of the next ones; no changes other than fixing the > conflicts. > > I'm seeing this warning caused by 0004, which I think is also being > reported in CI > https://cirrus-ci.com/task/6606871575920640 > > [281/1134] Compiling C object src/backend/postgres_lib.a.p/commands_cluster.c.o > In file included from ../../source/repack/src/include/access/htup_details.h:22, > from ../../source/repack/src/include/access/relscan.h:17, > from ../../source/repack/src/include/access/heapam.h:19, > from ../../source/repack/src/backend/commands/cluster.c:37: > In function ‘VARSIZE_ANY’, > inlined from ‘restore_tuple’ at ../../source/repack/src/backend/commands/cluster.c:3129:18, > inlined from ‘apply_concurrent_changes’ at ../../source/repack/src/backend/commands/cluster.c:2915:9, > inlined from ‘process_concurrent_changes’ at ../../source/repack/src/backend/commands/cluster.c:3386:2: > ../../source/repack/src/include/varatt.h:243:51: warning: array subscript ‘varattrib_4b[0]’ is partly outside array bounds of ‘varlena[1]’ [-Warray-bounds=] > 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> 2) & 0x3FFFFFFF) > | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ > ../../source/repack/src/include/varatt.h:467:24: note: in expansion of macro ‘VARSIZE_4B’ > 467 | return VARSIZE_4B(PTR); > | ^~~~~~~~~~ > ../../source/repack/src/backend/commands/cluster.c: In function ‘process_concurrent_changes’: > ../../source/repack/src/backend/commands/cluster.c:3121:33: note: object ‘varhdr’ of size 4 > 3121 | varlena varhdr; > | ^~~~~~ I'm not sure it can be fixed nicely in the REPACK (CONCURRENTLY) patch. I think the problem is that, in the current tree, VARSIZE_ANY() is used in such a way that the compiler cannot check the "array bounds". The restore_tuple() function is special in that it uses VARSIZE_ANY() to check a variable allocated from the stack, so the compiler can check the size. I'm trying to fix that in a new diff 0002 - the point is that VARSIZE_ANY() should not need to dereference a pointer to varattrib_4b, since the size information is always located at the beginning of the structure. Maybe you have better idea. Besides that, I've done a related change in 0003 (now 0004, due to the new diff): diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 77e301b7c63..8b5571374d0 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -3118,7 +3118,7 @@ restore_tuple(BufFile *file, Relation relation, MemoryContext cxt) BufFileReadExact(file, &natt_ext, sizeof(natt_ext)); for (int i = 0; i < natt_ext; i++) { - varlena varhdr; + alignas(uint32) varlena varhdr; char *ext_val; Size ext_val_size; And also this one in the same file, to suppress another compiler warning (occuring when configured w/o --enable-cassert): diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c index 707940c1127..90f3a8975b9 100644 --- a/src/backend/replication/pgoutput_repack/pgoutput_repack.c +++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c @@ -93,12 +93,9 @@ static void plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation, ReorderBufferChange *change) { - RepackDecodingState *dstate; - - dstate = (RepackDecodingState *) ctx->output_writer_private; - /* Changes of other relation should not have been decoded. */ - Assert(RelationGetRelid(relation) == dstate->relid); + Assert(RelationGetRelid(relation) == + ((RepackDecodingState *) ctx->output_writer_private)->relid); /* Decode entry depending on its type */ switch (change->action) -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-11T16:06:08Z
Shinoda, Noriyoshi (PSD Japan FSI) <noriyoshi.shinoda@hpe.com> wrote: > The committed documentation for the pg_stat_progress_repack view was missing the explanation for the "command" column. > I've attached a small patch for monitoring.sgml. LGTM, thanks! (I haven't added it to the next version, which now only deals with adding the CONCURRENTLY option.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-11T17:07:42Z
Antonin Houska <ah@cybertec.at> wrote: > I'm trying to fix that in a new diff 0002 - the point is that VARSIZE_ANY() > should not need to dereference a pointer to varattrib_4b, since the size > information is always located at the beginning of the structure. Maybe you > have better idea. I realize now that I ended up with VARSIZE_4B() that does not really need the new field in varattrib_4b. Simply thinko. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-12T18:25:54Z
On 2026-Mar-11, Shinoda, Noriyoshi (PSD Japan FSI) wrote: > Hi, Hackers. > Thanks for developing this great feature. > The committed documentation for the pg_stat_progress_repack view was missing the explanation for the "command" column. > I've attached a small patch for monitoring.sgml. Thanks, pushed. I also made some edits to the description of the pg_stat_progress_cluster view while there. I've been wondering for some time whether we should drop that one for pg19. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "People get annoyed when you try to debug them." (Larry Wall ...)
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2026-03-12T19:15:13Z
On Thu, Mar 12, 2026 at 2:25 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-11, Shinoda, Noriyoshi (PSD Japan FSI) wrote: > > > Hi, Hackers. > > Thanks for developing this great feature. > > The committed documentation for the pg_stat_progress_repack view was missing the explanation for the "command" column. > > I've attached a small patch for monitoring.sgml. > > Thanks, pushed. I also made some edits to the description of the > pg_stat_progress_cluster view while there. I've been wondering for some > time whether we should drop that one for pg19. > ISTM the user facing docs refer to cluster as an "obsolete" variant / spelling, rather than something marked as deprecated. This feels like it is meant to imply that the old functionality is not planned for removal in some future release (ie. deprecated), but that you may find that certain bits of support for it are already removed/broken. If that was the intention, I guess it gives justification to remove it now; that said it does seem rather unfriendly to not give any kind of bridge release to get from one side to the other, so I think the ideal would be to keep it for v19 and remove it in v20. Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-12T19:31:40Z
On 2026-Mar-11, Antonin Houska wrote: > I'm not sure it can be fixed nicely in the REPACK (CONCURRENTLY) patch. I > think the problem is that, in the current tree, VARSIZE_ANY() is used in such > a way that the compiler cannot check the "array bounds". The restore_tuple() > function is special in that it uses VARSIZE_ANY() to check a variable > allocated from the stack, so the compiler can check the size. > > I'm trying to fix that in a new diff 0002 - the point is that VARSIZE_ANY() > should not need to dereference a pointer to varattrib_4b, since the size > information is always located at the beginning of the structure. Maybe you > have better idea. I have no immediate ideas on this. I offer the following rather trivial fixup diffs, which I think should be mostly be for 0002. Other trivial things I'd like to change, if you don't mind, - the name pgoutput_repack sounds wrong to me. I would rather say rpck_output, repack_output, repack_plugin, ... or something. I don't understand where the suffix "output" comes from in the name "pgoutput", but it sounds like arbitrary nonsense to me. - I would rename the routines in that file to also have the name "repack", probably as prefixes. One thing we need and is rather not trivial, is to go through the table AM interface rather than calling heapam.c routines directly. I'm working on this now and will present a patch later. Another thing I noticed while going over the code was that we seem to spill whole tuples even for things like the old tuple of an UPDATE and for DELETE, but unless I misunderstand how this works, these shouldn't be necessary: we just need the replica identity so that we can locate the tuple to operate on. Especially for tuples that contain large toasted attributes, this is likely important. It may make sense to use the TupleTableSlot interface rather than HeapTuple for everything. I'm not really sure about this though. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Most hackers will be perfectly comfortable conceptualizing users as entropy sources, so let's move on." (Nathaniel Smith) https://mail.gnu.org/archive/html/monotone-devel/2007-01/msg00080.html -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-12T19:45:24Z
On 2026-Mar-12, Robert Treat wrote: > ISTM the user facing docs refer to cluster as an "obsolete" variant / > spelling, rather than something marked as deprecated. This feels like > it is meant to imply that the old functionality is not planned for > removal in some future release (ie. deprecated), but that you may find > that certain bits of support for it are already removed/broken. If > that was the intention, I guess it gives justification to remove it > now; that said it does seem rather unfriendly to not give any kind of > bridge release to get from one side to the other, so I think the ideal > would be to keep it for v19 and remove it in v20. Yeah, I think "obsolete" rather than "deprecated" is correct, because I don't see us removing either CLUSTER or VACUUM FULL. On the other hand, if you use pg_stat_progress_cluster while running REPACK CONCURRENTLY, then the resulting report might not fully make sense. But I agree it makes sense to keep the view in place until ... some later release. (Maybe v20, but also maybe a little later wouldn't hurt.) -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "On the other flipper, one wrong move and we're Fatal Exceptions" (T.U.X.: Term Unit X - http://www.thelinuxreview.com/TUX/)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-13T08:11:38Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > I offer the following rather trivial fixup diffs, which I think should > be mostly be for 0002. Thanks, just a few comments: * 0002 - I didn't add the 'repack_' prefix because the function is static, but realize now that it might be useful from the code browsing perspective. * 0008 - It's possible during query execution, when you know exactly which attributes you need to fetch from the tuple. However in store_change(), we don't know which attributes are external (indirect) without deforming them all. > Other trivial things I'd like to change, if you don't mind, > - the name pgoutput_repack sounds wrong to me. I would rather say > rpck_output, repack_output, repack_plugin, ... or something. I don't > understand where the suffix "output" comes from in the name > "pgoutput", but it sounds like arbitrary nonsense to me. No strong preference here, except that I don't like "rpck_...". How about pgoutput/repack.c? I think I tried to put the plugin into the existing "pgoutput" directory at some point, but don't remember why I eventually rejected that approach. > - I would rename the routines in that file to also have the name > "repack", probably as prefixes. ok > One thing we need and is rather not trivial, is to go through the table > AM interface rather than calling heapam.c routines directly. I'm > working on this now and will present a patch later. It occurred to me too, at least because copy_table_data() calls table_relation_copy_for_cluster() rather than heapam_relation_copy_for_cluster(). Thanks for working on it. > Another thing I noticed while going over the code was that we seem to > spill whole tuples even for things like the old tuple of an UPDATE and > for DELETE, but unless I misunderstand how this works, these shouldn't > be necessary: we just need the replica identity so that we can locate > the tuple to operate on. Especially for tuples that contain large > toasted attributes, this is likely important. I think we don't need to care, as both heap_update() and heap_delete() call ExtractReplicaIdentity(). That returns a real tuple, but it only contains the attributes constituting the replica identity. The other ones are set to NULL. > It may make sense to use the TupleTableSlot interface rather than > HeapTuple for everything. I'm not really sure about this though. Isn't this part of the adoption of table AM? For example, table_tuple_insert() accepts tuple slot rather than heap tuple. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-16T09:13:00Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > - the name pgoutput_repack sounds wrong to me. I would rather say > > rpck_output, repack_output, repack_plugin, ... or something. I don't > > understand where the suffix "output" comes from in the name > > "pgoutput", but it sounds like arbitrary nonsense to me. > > No strong preference here ... One more problem related to the replication slot is that, due to the call of CheckSlotPermissions() in setup_logical_decoding(), REPLICATION privilege is required for REPACK (CONCURRENTLY) to run. That's not too user-friendly. I think the reason to require the REPLICATION privilege is that, in generic case, the output plugin can access data of any table in the database. However REPACK uses one particular plugin and that plugin only decodes changes of one particular table. Thus I think we don't really need to call CheckSlotPermissions(). Do I seem to miss something? -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T12:21:08Z
On 2026-Mar-16, Antonin Houska wrote: > One more problem related to the replication slot is that, due to the call of > CheckSlotPermissions() in setup_logical_decoding(), REPLICATION privilege is > required for REPACK (CONCURRENTLY) to run. That's not too user-friendly. > > I think the reason to require the REPLICATION privilege is that, in generic > case, the output plugin can access data of any table in the database. However > REPACK uses one particular plugin and that plugin only decodes changes of one > particular table. Thus I think we don't really need to call > CheckSlotPermissions(). Do I seem to miss something? Yeah, I don't think it makes sense to require REPLICATION privilege to run REPACK. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Oh, great altar of passive entertainment, bestow upon me thy discordant images at such speed as to render linear thought impossible" (Calvin a la TV)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-16T13:46:02Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > I offer the following rather trivial fixup diffs, which I think should > be mostly be for 0002. This is a proposal to fix one more problem I had on my list: index build should not report its progress if invoked by REPACK. More info in the commit message and in the patch itself. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Matthias van de Meent <boekewurm+postgres@gmail.com> — 2026-03-16T14:21:18Z
On Mon, 16 Mar 2026 at 13:21, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Mar-16, Antonin Houska wrote: > > > One more problem related to the replication slot is that, due to the call of > > CheckSlotPermissions() in setup_logical_decoding(), REPLICATION privilege is > > required for REPACK (CONCURRENTLY) to run. That's not too user-friendly. > > > > I think the reason to require the REPLICATION privilege is that, in generic > > case, the output plugin can access data of any table in the database. However > > REPACK uses one particular plugin and that plugin only decodes changes of one > > particular table. Thus I think we don't really need to call > > CheckSlotPermissions(). Do I seem to miss something? > > Yeah, I don't think it makes sense to require REPLICATION privilege to > run REPACK. I don't think it makes sense to allow just any table owner to modify the effective_wal_level GUC; which is what the effect would be of removing the REPLICATION requirement from roles that want to REPACK CONCURRENTLY -- and in doing so create logical slots, which increase effective_wal_level to logical. Creating a replication slot requires REPLICATION privilege, because it consumes a (possibly very) limited server resource that can't be increased without restart and because it impacts other backends' WAL performance through effective_wal_level. Allowing users to consume said resource without first having the appropriate permissions makes this flag practically meaningless. Note that most of my argument hinges on the impact on other, unrelated databases/tables/sessions. Replication slots have a hard cap defined at startup, and effective_wal_level increases the WAL generated by practically all backends. If replication slot counts were SIGHUP, and RelationIsLogicallyLogged() returned true only in databases with LR slots, and only for the tables actually present in publications, I'd consider this much less problematic. However, we don't live in that world, so I am opposed to allowing table owners without REPLICATION to take any/all replication slots. Matthias van de Meent Databricks (https://www.databricks.com)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T15:10:46Z
On 2026-Mar-16, Matthias van de Meent wrote: > Note that most of my argument hinges on the impact on other, unrelated > databases/tables/sessions. Replication slots have a hard cap defined > at startup, and effective_wal_level increases the WAL generated by > practically all backends. I'd rather have a new GUC to declare a bunch of additional slots that are reserved exclusively for repack, set its default to something like 3, and call it a day. If all repack slots are in use, you don't get to run repack, period. A slot costs nothing if unused, and we really don't want to make the interaction with regular replication more complicated than it is today. > However, we don't live in that world, so I am opposed to allowing > table owners without REPLICATION to take any/all replication slots. I think requiring REPACK users to have the REPLICATION priv is rather user unfriendly. Some potential REPACK users might not have any other use for replication at all. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "World domination is proceeding according to plan" (Andrew Morton)
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2026-03-16T15:49:36Z
On Mon, Mar 16, 2026 at 11:11 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-16, Matthias van de Meent wrote: > > > However, we don't live in that world, so I am opposed to allowing > > table owners without REPLICATION to take any/all replication slots. > > I think requiring REPACK users to have the REPLICATION priv is rather > user unfriendly. Some potential REPACK users might not have any other > use for replication at all. > For many users, I feel like repack concurrently making use of replication machinery is an implementation detail that some will find quite surprising (pg_repack doesn't use it), so I'd agree requiring REPLICATION priv is both unfriendly and a bit counter intuitive, especially if you need to run repack concurrently on a stand alone server. That said, I think Matthias is right that we can't allow "repackers" to block "replicators"... > > Note that most of my argument hinges on the impact on other, unrelated > > databases/tables/sessions. Replication slots have a hard cap defined > > at startup, and effective_wal_level increases the WAL generated by > > practically all backends. > > I'd rather have a new GUC to declare a bunch of additional slots that > are reserved exclusively for repack, set its default to something like > 3, and call it a day. If all repack slots are in use, you don't get to > run repack, period. > > A slot costs nothing if unused, and we really don't want to make the > interaction with regular replication more complicated than it is today. > I'm never excited about adding GUCs, but at first thought this seems like a decent work-around; most people are unlikely to run multiple repack concurrently's, but they can if needed. (I think the most likely use case is on clusters using the "database per customer" pattern, but if we have the guc, people will have a means to deal with it). Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T16:03:47Z
On 2026-Mar-16, Robert Treat wrote: > I'm never excited about adding GUCs, but at first thought this seems > like a decent work-around; most people are unlikely to run multiple > repack concurrently's, but they can if needed. (I think the most > likely use case is on clusters using the "database per customer" > pattern, but if we have the guc, people will have a means to deal with > it). I wonder if, longer term, it would make sense to do away with the max_replication_slots GUC (and this new one) altogether, and use dynamic shared memory for slots instead. There's of course always the danger that people would accumulate arbitrary numbers of slots since they would never be forced to check. But that may be a lesser problem than having to gauge these GUCs with any care. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "You're _really_ hosed if the person doing the hiring doesn't understand relational systems: you end up with a whole raft of programmers, none of whom has had a Date with the clue stick." (Andrew Sullivan) https://postgr.es/m/20050809113420.GD2768@phlogiston.dyndns.org
-
Re: Adding REPACK [concurrently]
Mahendra Singh Thalor <mahi6run@gmail.com> — 2026-03-16T17:55:22Z
On Mon, 16 Mar 2026 at 21:33, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Mar-16, Robert Treat wrote: > > > I'm never excited about adding GUCs, but at first thought this seems > > like a decent work-around; most people are unlikely to run multiple > > repack concurrently's, but they can if needed. (I think the most > > likely use case is on clusters using the "database per customer" > > pattern, but if we have the guc, people will have a means to deal with > > it). > > I wonder if, longer term, it would make sense to do away with the > max_replication_slots GUC (and this new one) altogether, and use dynamic > shared memory for slots instead. There's of course always the danger > that people would accumulate arbitrary numbers of slots since they would > never be forced to check. But that may be a lesser problem than having > to gauge these GUCs with any care. > > -- > Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ > "You're _really_ hosed if the person doing the hiring doesn't understand > relational systems: you end up with a whole raft of programmers, none of > whom has had a Date with the clue stick." (Andrew Sullivan) > https://postgr.es/m/20050809113420.GD2768@phlogiston.dyndns.org > > Hi all, I was reading this thread and was doing some tests. postgres=# create table test1(a int); CREATE TABLE postgres=# create table test2(a int); CREATE TABLE postgres=# *vacuum full test1 , test2;* VACUUM postgres=# repack test1; REPACK postgres=# repack test2; REPACK postgres=#* repack test1, test2;* ERROR: syntax error at or near "," LINE 1: repack test1, test2; ^ I was not expecting any error but maybe I am missing something (some patch needs to be applied to test this query?). Thanks and Regards Mahendra -
Re: Adding REPACK [concurrently]
Matthias van de Meent <boekewurm+postgres@gmail.com> — 2026-03-16T19:24:45Z
On Mon, 16 Mar 2026 at 16:10, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Mar-16, Matthias van de Meent wrote: > > > Note that most of my argument hinges on the impact on other, unrelated > > databases/tables/sessions. Replication slots have a hard cap defined > > at startup, and effective_wal_level increases the WAL generated by > > practically all backends. > > I'd rather have a new GUC to declare a bunch of additional slots that > are reserved exclusively for repack, set its default to something like > 3, and call it a day. If all repack slots are in use, you don't get to > run repack, period. > > A slot costs nothing if unused, and we really don't want to make the > interaction with regular replication more complicated than it is today. There are a few overheads even for unused slots: each slot uses some shared memory, and many (most) functions that operate on the shared slot state have O(n_slots) overhead through e.g. ReplicationSlotsComputeRequiredXmin(); so having a 10k slot limit whilst using only 1 will be slower for that one active slot than if the limit was just 10. Granted, that's not a lot of overhead, but it's not free. > > However, we don't live in that world, so I am opposed to allowing > > table owners without REPLICATION to take any/all replication slots. > > I think requiring REPACK users to have the REPLICATION priv is rather > user unfriendly. Some potential REPACK users might not have any other > use for replication at all. I agree it's not user-friendly, but that's the point of limiting permissions. Users can't install c-functions without SUPERUSER, because it can cause cluster instability and crashes. Users can't create slots without REPLICATION, because they'll be able to negatively impact the whole cluster's performance, and possibly, stability, when taking up replication slots that otherwise would be used for critical HA purposes. Kind regards, Matthias van de Meent Databricks (https://www.databricks.com)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T19:26:49Z
On 2026-Mar-16, Mahendra Singh Thalor wrote: > postgres=#* repack test1, test2;* > ERROR: syntax error at or near "," > LINE 1: repack test1, test2; > ^ > > I was not expecting any error but maybe I am missing something (some patch > needs to be applied to test this query?). Yeah, there's no support for repacking multiple tables in one command. This is intentional. I think it'd be not very hard to add. But why? People isn't likely to be running emergency VACUUM FULL on multiple large tables in this way, listing multiple of them in a single command, IMO anyway. So I think we don't need it. (I think this is not similar to the case where normal vacuum is being run in an emergency for XID wraparound purposes, which the ability to run on multiple tables is more valuable. But at the same time, vacuumdb does a much better job at that, since it can process multiple tables in parallel.) -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T19:34:12Z
On 2026-Mar-16, Matthias van de Meent wrote: > On Mon, 16 Mar 2026 at 16:10, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > A slot costs nothing if unused, and we really don't want to make the > > interaction with regular replication more complicated than it is today. > > There are a few overheads even for unused slots: each slot uses some > shared memory, and many (most) functions that operate on the shared > slot state have O(n_slots) overhead through e.g. > ReplicationSlotsComputeRequiredXmin(); so having a 10k slot limit > whilst using only 1 will be slower for that one active slot than if > the limit was just 10. Granted, that's not a lot of overhead, but it's > not free. True, it's not really free in that sense. But let's put this aside, as it's not something I'm considering for pg19 anyway. > I agree it's not user-friendly, but that's the point of limiting > permissions. Users can't install c-functions without SUPERUSER, > because it can cause cluster instability and crashes. Users can't > create slots without REPLICATION, because they'll be able to > negatively impact the whole cluster's performance, and possibly, > stability, when taking up replication slots that otherwise would be > used for critical HA purposes. Well, as I said, these repack slots would be separate from the regular ones for replication and available only for repack, so they would not interfere with the count of slots used for replication, so the second point is moot, isn't it? -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-16T20:15:14Z
Matthias van de Meent <boekewurm+postgres@gmail.com> wrote: > I agree it's not user-friendly, but that's the point of limiting > permissions. Users can't install c-functions without SUPERUSER, > because it can cause cluster instability and crashes. Users can't > create slots without REPLICATION, because they'll be able to > negatively impact the whole cluster's performance, and possibly, > stability, when taking up replication slots that otherwise would be > used for critical HA purposes. I thought these attributes exist primarily for security purposes. If non-SUPERUSER user could install C-functions, it'd be easy to install code that leaks data. REPLICATION is currently the only way to limit access to the the publisher's data as there is no ACL for publications. And regarding resources, the REPLICATION attribute alone does not pose a limit on resource consumption unless you limit the total number of sessions of all the REPLICATION users at the same time. Anyway (fortunately?), the concurrent use of slots by REPACK is limited because, during the initialization of logical decoding, the backend needs to wait for all the transactions having XID assigned to finish, and these include the already running REPACK commands. See SnapBuildWaitSnapshot() and callers if you're interested in details. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Matthias van de Meent <boekewurm+postgres@gmail.com> — 2026-03-16T21:45:01Z
On Mon, 16 Mar 2026 at 20:34, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-16, Matthias van de Meent wrote: > > I agree it's not user-friendly, but that's the point of limiting > > permissions. Users can't install c-functions without SUPERUSER, > > because it can cause cluster instability and crashes. Users can't > > create slots without REPLICATION, because they'll be able to > > negatively impact the whole cluster's performance, and possibly, > > stability, when taking up replication slots that otherwise would be > > used for critical HA purposes. > > Well, as I said, these repack slots would be separate from the regular > ones for replication and available only for repack, so they would not > interfere with the count of slots used for replication, so the second > point is moot, isn't it? Sorry, I misread your response as "if at all, then at least like this", instead of "let's do this alternative in this patch". But, yes, a pool of slots used exclusively by REPACK CONCURRENTLY would also solve the slot availability issue. Kind regards, Matthias van de Meent Databricks (https://www.databricks.com)
-
Re: Adding REPACK [concurrently]
Matthias van de Meent <boekewurm+postgres@gmail.com> — 2026-03-16T21:50:04Z
On Mon, 16 Mar 2026 at 21:15, Antonin Houska <ah@cybertec.at> wrote: > > Matthias van de Meent <boekewurm+postgres@gmail.com> wrote: > > > I agree it's not user-friendly, but that's the point of limiting > > permissions. Users can't install c-functions without SUPERUSER, > > because it can cause cluster instability and crashes. Users can't > > create slots without REPLICATION, because they'll be able to > > negatively impact the whole cluster's performance, and possibly, > > stability, when taking up replication slots that otherwise would be > > used for critical HA purposes. > > I thought these attributes exist primarily for security purposes. Well, yes, but operational security is also security, right? > If > non-SUPERUSER user could install C-functions, it'd be easy to install code > that leaks data. Yes, as long as they're able to find the right primitives in the available binaries. That's certainly possible, but a bit more work than just none. > REPLICATION is currently the only way to limit access to the > the publisher's data as there is no ACL for publications. > And regarding resources, the REPLICATION attribute alone does not pose a limit > on resource consumption unless you limit the total number of sessions of all > the REPLICATION users at the same time. True. It's not great. And we also don't really have a (good) distinction for logical/physical replication permissions either... > Anyway (fortunately?), the concurrent use of slots by REPACK is limited > because, during the initialization of logical decoding, the backend needs to > wait for all the transactions having XID assigned to finish, and these include > the already running REPACK commands. See SnapBuildWaitSnapshot() and callers > if you're interested in details. Huh, so would you be able to run more than one Repack Concurrently in the same database? ISTM that would not be possible, apart from possibly a mechanism comparable to the SAFE_IN_IC flag (to not wait on those backends). Kind regards, Matthias van de Meent Databricks (https://www.databricks.com)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-16T22:03:04Z
On 2026-Mar-16, Matthias van de Meent wrote: > On Mon, 16 Mar 2026 at 21:15, Antonin Houska <ah@cybertec.at> wrote: > > Anyway (fortunately?), the concurrent use of slots by REPACK is limited > > because, during the initialization of logical decoding, the backend needs to > > wait for all the transactions having XID assigned to finish, and these include > > the already running REPACK commands. See SnapBuildWaitSnapshot() and callers > > if you're interested in details. > > Huh, so would you be able to run more than one Repack Concurrently in > the same database? ISTM that would not be possible, apart from > possibly a mechanism comparable to the SAFE_IN_IC flag (to not wait on > those backends). Yeah, this sounds kind of bad news ... -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Los dioses no protegen a los insensatos. Éstos reciben protección de otros insensatos mejor dotados" (Luis Wu, Mundo Anillo)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-17T10:53:40Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-16, Matthias van de Meent wrote: > > > On Mon, 16 Mar 2026 at 21:15, Antonin Houska <ah@cybertec.at> wrote: > > > > Anyway (fortunately?), the concurrent use of slots by REPACK is limited > > > because, during the initialization of logical decoding, the backend needs to > > > wait for all the transactions having XID assigned to finish, and these include > > > the already running REPACK commands. See SnapBuildWaitSnapshot() and callers > > > if you're interested in details. > > > > Huh, so would you be able to run more than one Repack Concurrently in > > the same database? ISTM that would not be possible, apart from > > possibly a mechanism comparable to the SAFE_IN_IC flag (to not wait on > > those backends). > > Yeah, this sounds kind of bad news ... Admittedly, it is a problem. I tried to address this in pg_squeeze by pre-allocating slots when it's clear (due to scheduling) that more than one table needs to be processed. This was an effort to achieve the best possible performance rather than a response to complaints of users about low throughput. Nevertheless, I'm glad I happened to mention it before it's too late. Regarding solution, a flag like SAFE_IN_IC alone does not help. The information that particular transaction is used by REPACK (and therefore it does not have to be decoded) would need to be propagated to the xl_running_xacts WAL record too. The enhancements I wrote for PG 20 (not all of them posted yet) that aim at eliminating the impact of REPACK on VACUUM xmin horizon should fix this problem: due to the MVCC-safety (i.e. preserving xmin/xmax of the tuples), REPACK will not need XID assigned (except for catalog changes, which will happen in separate transactions), so it won't block the logical decoding setup of other backends. So the question is whether we should implement a workaround for PG 19, that won't be needed in v20. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-17T19:57:06Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > On 2026-Mar-16, Matthias van de Meent wrote: > > > > > On Mon, 16 Mar 2026 at 21:15, Antonin Houska <ah@cybertec.at> wrote: > > > > > > Anyway (fortunately?), the concurrent use of slots by REPACK is limited > > > > because, during the initialization of logical decoding, the backend needs to > > > > wait for all the transactions having XID assigned to finish, and these include > > > > the already running REPACK commands. See SnapBuildWaitSnapshot() and callers > > > > if you're interested in details. > > > > > > Huh, so would you be able to run more than one Repack Concurrently in > > > the same database? ISTM that would not be possible, apart from > > > possibly a mechanism comparable to the SAFE_IN_IC flag (to not wait on > > > those backends). > > > > Yeah, this sounds kind of bad news ... > > Admittedly, it is a problem. I tried to address this in pg_squeeze by > pre-allocating slots when it's clear (due to scheduling) that more than one > table needs to be processed. This was an effort to achieve the best possible > performance rather than a response to complaints of users about low > throughput. Nevertheless, I'm glad I happened to mention it before it's too > late. > > Regarding solution, a flag like SAFE_IN_IC alone does not help. The > information that particular transaction is used by REPACK (and therefore it > does not have to be decoded) would need to be propagated to the > xl_running_xacts WAL record too. 0007 in the next version tries to implement that. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-18T19:12:19Z
Hello, While i was doing concurrency test onn V41 patches ,i found this crash because of the assert failure, TRAP: failed Assert("RelationGetRelid(relation) == ((RepackDecodingState *) ctx->output_writer_private)->relid"), File: "pgoutput_repack.c", Line: 97, PID: 397007 postgres: REPACK decoding worker for relation "stress_victim" (ExceptionalCondition+0x98)[0xaaaad9361698] /home/srinath/Desktop/pgbuild/lib/postgresql/pgoutput_repack.so(+0xfe8)[0xffff90e00fe8] postgres: REPACK decoding worker for relation "stress_victim" (+0x679e14)[0xaaaad9049e14] postgres: REPACK decoding worker for relation "stress_victim" (+0x689cd0)[0xaaaad9059cd0] postgres: REPACK decoding worker for relation "stress_victim" (+0x68a65c)[0xaaaad905a65c] postgres: REPACK decoding worker for relation "stress_victim" (+0x68b2f0)[0xaaaad905b2f0] postgres: REPACK decoding worker for relation "stress_victim" (ReorderBufferCommit+0x74)[0xaaaad905b374] postgres: REPACK decoding worker for relation "stress_victim" (+0x671ec4)[0xaaaad9041ec4] postgres: REPACK decoding worker for relation "stress_victim" (xact_decode+0x1a0)[0xaaaad9040edc] postgres: REPACK decoding worker for relation "stress_victim" (LogicalDecodingProcessRecord+0xd4)[0xaaaad9040a80] postgres: REPACK decoding worker for relation "stress_victim" (+0x33f558)[0xaaaad8d0f558] postgres: REPACK decoding worker for relation "stress_victim" (+0x341ccc)[0xaaaad8d11ccc] postgres: REPACK decoding worker for relation "stress_victim" (RepackWorkerMain+0x1ac)[0xaaaad8d11bd4] postgres: REPACK decoding worker for relation "stress_victim" (BackgroundWorkerMain+0x2b0)[0xaaaad900d21c] postgres: REPACK decoding worker for relation "stress_victim" (postmaster_child_launch+0x1f0)[0xaaaad9012070] postgres: REPACK decoding worker for relation "stress_victim" (+0x64b974)[0xaaaad901b974] postgres: REPACK decoding worker for relation "stress_victim" (+0x64bc64)[0xaaaad901bc64] postgres: REPACK decoding worker for relation "stress_victim" (+0x64a3e4)[0xaaaad901a3e4] postgres: REPACK decoding worker for relation "stress_victim" (+0x647648)[0xaaaad9017648] postgres: REPACK decoding worker for relation "stress_victim" (PostmasterMain+0x160c)[0xaaaad9016d98] postgres: REPACK decoding worker for relation "stress_victim" (main+0x3dc)[0xaaaad8ea7a38] /lib/aarch64-linux-gnu/libc.so.6(+0x284c4)[0xffff9c5c84c4] /lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x98)[0xffff9c5c8598] postgres: REPACK decoding worker for relation "stress_victim" (_start+0x30)[0xaaaad8abc970] 2026-03-16 19:40:21.622 IST [393820] LOG: background worker "REPACK decoding worker" (PID 397007) was terminated by signal 6: Aborted 2026-03-16 19:40:21.622 IST [393820] LOG: terminating any other active server processes 2026-03-16 19:40:21.632 IST [397036] FATAL: the database system is in recovery mode This crash happens if we run REPACK (concurrently) on a table while a heavy pgbench workload is concurrently executing multi-table(setup.sql) transactions(dual_chaos.sql). It triggers after a few back to back REPACK (concurrently) runs. i think i found the cause for this crash , because there were some changes which slipped under the nose of the change_useless_for_repack filter , which led some changes which are not related to the relation which we are currently doing REPACK (concurrently) got decoded and added into the reorderbuffer queue, the reason for this is repacked_rel_locator.relNumber is by default set to InvalidOid, this is actually set to the target relation during setup_logical_decoding but this done after DecodingContextFindStartpoint, in DecodingContextFindStartpoint changes are not filtered even if its not related to the target relation , because rm_decode->change_useless_for_repack->am_decoding_for_repack where repacked_rel_locator.relNumber is still InvalidOid, which makes it skip the filtering even its not the target relation, this makes it to be added to reorder buffer queue, so during the processing of reorder buffer plugin_change is called where assert fails, i have attached a diff patch to solve this. thoughts? -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-18T20:07:09Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > TRAP: failed Assert("RelationGetRelid(relation) == ((RepackDecodingState *) ctx->output_writer_private)->relid"), File: "pgoutput_repack.c", > Line: 97, PID: 397007 > This crash happens if we run REPACK (concurrently) on a table while a heavy > pgbench workload is concurrently executing multi-table(setup.sql) transactions(dual_chaos.sql). > It triggers after a few back to back REPACK (concurrently) runs. > > i think i found the cause for this crash , because there were some changes which > slipped under the nose of the change_useless_for_repack filter , which led some > changes which are not related to the relation which we are currently doing REPACK (concurrently) > got decoded and added into the reorderbuffer queue, the reason for this is repacked_rel_locator.relNumber > is by default set to InvalidOid, this is actually set to the target relation during setup_logical_decoding > but this done after DecodingContextFindStartpoint, in DecodingContextFindStartpoint changes are not > filtered even if its not related to the target relation , because rm_decode->change_useless_for_repack->am_decoding_for_repack > where repacked_rel_locator.relNumber is still InvalidOid, which makes it skip the filtering even its not the target relation, > this makes it to be added to reorder buffer queue, so during the processing of reorder buffer plugin_change is called > where assert fails, i have attached a diff patch to solve this. Thanks a lot! Yes, your explanation makes sense. I'll include the fix in the next version. I think it might also explain the other crash [1] you reported earlier. I'll try to reproduce that. [1] https://www.postgresql.org/message-id/CAFC%2Bb6o2yzA80YmfEhmMO9puN8qvGRvr-15BBLn3UmJxPfpr2w%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-19T19:52:37Z
Hi Srinath, On 2026-Mar-19, Srinath Reddy Sadipiralla wrote: > i think i found the cause for this crash, because there were some changes > which slipped under the nose of the change_useless_for_repack filter, which > led some changes which are not related to the relation which we are > currently doing REPACK (concurrently) got decoded and added into the > reorderbuffer queue, the reason for this is repacked_rel_locator.relNumber > is by default set to InvalidOid, this is actually set to the target relation > during setup_logical_decoding but this done after > DecodingContextFindStartpoint, in DecodingContextFindStartpoint changes are > not filtered even if its not related to the target relation, because > rm_decode->change_useless_for_repack->am_decoding_for_repack where > repacked_rel_locator.relNumber is still InvalidOid, which makes it skip the > filtering even its not the target relation, this makes it to be added to > reorder buffer queue, so during the processing of reorder buffer > plugin_change is called where assert fails, i have attached a diff patch to > solve this. Ah, thanks for tracking this down -- I've been seeing this crash also. So here's v43. Here, I've changed the CONCURRENTLY implementation to go through table AM. This necessitated changing it to use tuples in slots instead of HeapTuple. This is good because we can avoid repeated tuple form/deform, which could get pretty expensive. Antonin's 0004 patch here looks suspicious here though, because it deforms the tuple and forms it again, which sounds unnecessary now. Not yet committable; the change to use slots and table AM is a rather significant modification to what was before. Barring cosmetics and code reorganization, it should be close enough. I included your (Srinath's) patch as 0005. The file you sent is corrupt though, so I applied the visible part of change manually, in addition to removing the rest of the block that the patch somehow doesn't remove. This also includes as part of 0003 the change to table AM options I sent in [1]. I will probably commit that change in the way that the other thread decides, and then rebase this on top of that. [1] https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-19T20:37:35Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > So here's v43. Here, I've changed the CONCURRENTLY implementation to go > through table AM. This necessitated changing it to use tuples in slots > instead of HeapTuple. This is good because we can avoid repeated tuple > form/deform, which could get pretty expensive. Antonin's 0004 patch > here looks suspicious here though, because it deforms the tuple and > forms it again, which sounds unnecessary now. I suppose you mean v42-0004-Serialize-decoded-tuples-without-flattening.patch. This deforms the tuple to get the external attributes and to write them to file. The tuple the logical worker received from reorderbuffer.c cannot be passed to the backend executing REPACK because it may contain "external indirect" attributes, i.e. pointers to the worker's memory. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-20T07:59:23Z
On 2026-Mar-19, Antonin Houska wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > So here's v43. Here, I've changed the CONCURRENTLY implementation to go > > through table AM. This necessitated changing it to use tuples in slots > > instead of HeapTuple. This is good because we can avoid repeated tuple > > form/deform, which could get pretty expensive. Antonin's 0004 patch > > here looks suspicious here though, because it deforms the tuple and > > forms it again, which sounds unnecessary now. > > I suppose you mean > v42-0004-Serialize-decoded-tuples-without-flattening.patch. This deforms the > tuple to get the external attributes and to write them to file. The tuple the > logical worker received from reorderbuffer.c cannot be passed to the backend > executing REPACK because it may contain "external indirect" attributes, > i.e. pointers to the worker's memory. No, that patch has been absorbed in what is now v43-0003. I meant v43-0004 "Use BulkInsertState when copying data to the new heap.", that's why I said "patch 0004 here". In this patch, we have reform_tuple which deforms the tuple, sets to NULL any attribute that's marked dropped, and then forms a new one. This is wasteful and should probably be done elsewhere, while the tuple is still in slot representation. In fact, I suspect it may not be necessary at all anymore. I haven't verified whether all the code is covered by existing tests; what I did was just run them. But to ensure that it is all trustworthy, I'll spend some time with the coverage report to ensure there aren't any nasty surprises anywhere. The slot/tupdesc interface is notoriously bad at differentiating 0-based indexes of the attribute array, and 1-based proper attribute numbers, so it's very easy to do the wrong thing. (It's worse when you do an even number of wrong things and they cancel each other out.) -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "This is what I like so much about PostgreSQL. Most of the surprises are of the "oh wow! That's cool" Not the "oh shit!" kind. :)" Scott Marlowe, http://archives.postgresql.org/pgsql-admin/2008-10/msg00152.php
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-03-20T12:35:00Z
Hello, everyone! Some comments for v43: --- src/backend/commands/cluster.c:3211 slot_attisnull(dest, i) uses a 0-based loop index but slot_attisnull starts with "Assert(attnum > 0);". Also, it proves it has not been tested in any way yet. ------------------------------ src/backend/storage/ipc/standby.c:1376 "if (xlrec.xcnt > 0)" looks like it should be "xlrec.xcnt + xlrec.xcnt_repack > 0" because of "CurrentRunningXacts->xcnt = count - subcount - count_repack;" ------------------------------ src/backend/storage/ipc/procarray.c:2673-2681 "nrepack = logical_decoding_enabled ? MAX_REPACK_XIDS : 0" not sure it is a real issue, but if EnableLogicalDecoding is called somehow after allocating the array - it makes it possible to overrun the array later by MAX_REPACK_XIDS. ------------------------------ src/backend/commands/cluster.c:3006 "apply_cxt = AllocSetContextCreate(TopTransactionContext, "REPACK - apply", ALLOCSET_DEFAULT_SIZES);" Should we do it before the "MakeSingleTupleTableSlot" calls? Because MakeSingleTupleTableSlot stored CurrentMemoryContext in tts_mcxt. ------------------------------ src/backend/commands/cluster.c:3187 if (natt_ext != 0) elog(WARNING, "have natt_ext %d, weird", natt_ext); Should we make that ERROR? ------------------------------ src/backend/access/rmgrdesc/standbydesc.c:24 appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u oldestRunningXid %u", "oldestRunningXidLogical" at the end? ------------------------------ src/backend/catalog/index.c:766,1464-1469 "bool progress = (flags & INDEX_CREATE_REPORT_PROGRESS) != 0;" AFAIU gin, hash and btree (at least) still just unconditionally write PROGRESS_CREATEIDX_* progress. ------------------------------ src/backend/catalog/toasting.c:334 "INDEX_CREATE_IS_PRIMARY | INDEX_CREATE_REPORT_PROGRESS, 0," Should we add the "progress" flag too here and move it from make_new_heap? ------------------------------- Also just, gently remind you about [1] and a simple way to avoid any unnoticed MVCC-related issues with REPACK proposed here [2]. Best regards, Mikhail. [1]: https://www.postgresql.org/message-id/flat/5k2dfckyp6zv2fiovosvtbya5onvplgviz5n4kdamxupff4vi2%40yytzfnwr2ox7#c52bec0a074779921b14c1e46da9ed21 [2]: https://www.postgresql.org/message-id/CADzfLwUEH5+LjCN+6kRfSsXwuou8rKXyVV42Wi-O_TG0360Kug@mail.gmail.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-20T15:56:24Z
On 2026-Mar-20, Mihail Nikalayeu wrote: > Also just, gently remind you about [1] and a simple way to avoid any > unnoticed MVCC-related issues with REPACK proposed here [2]. Ah, thanks for the reminder, I've sent a patch there. > [1]: https://www.postgresql.org/message-id/flat/5k2dfckyp6zv2fiovosvtbya5onvplgviz5n4kdamxupff4vi2%40yytzfnwr2ox7#c52bec0a074779921b14c1e46da9ed21 > [2]: https://www.postgresql.org/message-id/CADzfLwUEH5+LjCN+6kRfSsXwuou8rKXyVV42Wi-O_TG0360Kug@mail.gmail.com -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Learn about compilers. Then everything looks like either a compiler or a database, and now you have two problems but one of them is fun." https://twitter.com/thingskatedid/status/1456027786158776329 -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-20T18:06:10Z
Antonin Houska <ah@cybertec.at> wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > > > The concurrency test failed once. I tried to reproduce the below scenario > > > but no luck,i think the reason the assert failure happened because > > > after speculative insert there might be no spec CONFIRM or ABORT, thoughts? > > > > Perhaps, I'll try. I'm not sure the REPACK decoding worker does anthing > > special regarding decoding. If you happen to see the problem again, please try > > to preserve the related WAL segments - if this is a bug in PG executor, > > pg_waldump might reveal that. > > I could not reproduce the failure, and have no idea how speculative insert can > stay w/o CONFIRM / ABORT record. The only problem I could imagine is that > change_useless_for_repack() filters out the CONFIRM / ABORT record > accidentally, but neither code review nor debugger proves that > theory. (Actually if this was the problem, the test failure probably wouldn't > be that rare.) I confirm that I was able to reproduce the crash using debugger and your more recent diagnosis [1]. Indeed, filtering was the problem. Unfortunately, I wasn't able to make the crash easily reproducible using isolation tester. The problem is that the logical decoding is performed by a background worker, and when the backend executing REPACK waits for the background worker, which in turn waits on an injection point, the isolation tester does not recognize that it's effectively the backend who is waiting on the injection point. Therefore the isolation tester does not proceed to the next step. Anyway, thanks again for your testing! [1] https://www.postgresql.org/message-id/CAFC%2Bb6qk3-DQTi43QMqvVLP%2BsudPV4vsLQm5iHfcCeObrNaVyA%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-23T10:20:03Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-19, Antonin Houska wrote: > > > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > > > So here's v43. Here, I've changed the CONCURRENTLY implementation to go > > > through table AM. This necessitated changing it to use tuples in slots > > > instead of HeapTuple. This is good because we can avoid repeated tuple > > > form/deform, which could get pretty expensive. Antonin's 0004 patch > > > here looks suspicious here though, because it deforms the tuple and > > > forms it again, which sounds unnecessary now. > > > > I suppose you mean > > v42-0004-Serialize-decoded-tuples-without-flattening.patch. This deforms the > > tuple to get the external attributes and to write them to file. The tuple the > > logical worker received from reorderbuffer.c cannot be passed to the backend > > executing REPACK because it may contain "external indirect" attributes, > > i.e. pointers to the worker's memory. > > No, that patch has been absorbed in what is now v43-0003. I meant > v43-0004 "Use BulkInsertState when copying data to the new heap.", > that's why I said "patch 0004 here". In this patch, we have > reform_tuple which deforms the tuple, sets to NULL any attribute that's > marked dropped, and then forms a new one. This is wasteful and should > probably be done elsewhere, while the tuple is still in slot > representation. In fact, I suspect it may not be necessary at all > anymore. The idea to "reform" the tuple comes from VACUUM FULL / CLUSTER. I agree the "deform" and "form" steps are no longer needed when we use tuple slots. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-23T12:00:34Z
Antonin Houska <ah@cybertec.at> wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > > > > > The concurrency test failed once. I tried to reproduce the below scenario > > > > but no luck,i think the reason the assert failure happened because > > > > after speculative insert there might be no spec CONFIRM or ABORT, thoughts? > > > > > > Perhaps, I'll try. I'm not sure the REPACK decoding worker does anthing > > > special regarding decoding. If you happen to see the problem again, please try > > > to preserve the related WAL segments - if this is a bug in PG executor, > > > pg_waldump might reveal that. > > > > I could not reproduce the failure, and have no idea how speculative insert can > > stay w/o CONFIRM / ABORT record. The only problem I could imagine is that > > change_useless_for_repack() filters out the CONFIRM / ABORT record > > accidentally, but neither code review nor debugger proves that > > theory. (Actually if this was the problem, the test failure probably wouldn't > > be that rare.) > > I confirm that I was able to reproduce the crash using debugger and your more > recent diagnosis [1]. Indeed, filtering was the problem. > > Unfortunately, I wasn't able to make the crash easily reproducible using > isolation tester. The problem is that the logical decoding is performed by a > background worker, and when the backend executing REPACK waits for the > background worker, which in turn waits on an injection point, the isolation > tester does not recognize that it's effectively the backend who is waiting on > the injection point. Therefore the isolation tester does not proceed to the > next step. I could not resist digging in it deeper :-) Attached is a test that reproduces the crash - it includes the isolation tester enhancement that I posted separately [1]. It crashes reliably with v43 [2] if your fix v43-0005 is omitted. [1] https://www.postgresql.org/message-id/4703.1774250534%40localhost [2] https://www.postgresql.org/message-id/202603191855.fzsgsnyzfvpt%40alvherre.pgsql -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Jim Jones <jim.jones@uni-muenster.de> — 2026-03-23T16:07:24Z
Hi, while reviewing another patch I noticed that REPACK is trying to access temp tables from other sessions. == session 1 == $ psql postgres psql (19devel) Type "help" for help. postgres=# SELECT pg_backend_pid(); pg_backend_pid ---------------- 730392 (1 row) postgres=# CREATE TEMP TABLE tmp AS SELECT generate_series(1, 1000) AS id; SELECT 1000 postgres=# BEGIN; LOCK TABLE tmp IN SHARE MODE; BEGIN LOCK TABLE postgres=*# == session 2 == $ psql postgres psql (19devel) Type "help" for help. postgres=# REPACK; (waits for LOCK) == session 3 == $ psql postgres psql (19devel) Type "help" for help. postgres=# SELECT pid, relation::regclass, mode, granted FROM pg_locks WHERE relation::regclass::text ~~ '%.tmp%'; pid | relation | mode | granted --------+----------------+---------------------+--------- 730608 | pg_temp_12.tmp | AccessExclusiveLock | f 730392 | pg_temp_12.tmp | ShareLock | t (2 rows) The same applies for REPACK USING INDEX if indisclustered is true. I played a bit with the code and perhaps skipping temp relations in get_tables_to_repack() before they're added to the list can do the trick. I tried the draft attached and REPACK could run despite the LOCK in the other session... in case it helps. Best, Jim -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-24T08:48:57Z
Jim Jones <jim.jones@uni-muenster.de> wrote: > while reviewing another patch I noticed that REPACK is trying to access > temp tables from other sessions. Thanks for the report. I agree that there's no reason for REPACK to get blocked on a lock of a table that it will not process anyway. However, as both VACUUM FULL and CLUSTER in PG 18 appear to have the same problem, I'm not sure your patch needs to be incorporated in the new feature. Adding a bug fix entry to the CF seems the appropriate action to me. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Jim Jones <jim.jones@uni-muenster.de> — 2026-03-24T12:13:41Z
On 24/03/2026 09:48, Antonin Houska wrote: > Thanks for the report. I agree that there's no reason for REPACK to get > blocked on a lock of a table that it will not process anyway. However, as both > VACUUM FULL and CLUSTER in PG 18 appear to have the same problem, I'm not sure > your patch needs to be incorporated in the new feature. Adding a bug fix entry > to the CF seems the appropriate action to me. Indeed. VACUUM FULL seems to have the same problem. I'll include it in the patch and open a new thread. Best, Jim
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-24T22:32:17Z
Hello, On 2026-Mar-20, Mihail Nikalayeu wrote: > Hello, everyone! > > Some comments for v43: Many thanks for the review. I have applied fixes for these, so here's v44. Changes: - 0001 now simply renames the existing function and adds the "concurrently" flag, instead of adding a separate function. I don't think there's a reason to keep the previous function name. - 0002 is unchanged. - 0003 contains the fixes to the problems pointed out by Mihail; otherwise it's pretty much the same as in v43. - 0004 is Antonin's bugfix from the crash reported by Srinath. - 0005 to 0007 are my proposed changes on this round. It's mostly just cosmetics. I intend to squash these all into a single commit (0003 to at least 0007) when posting the next version. - 0008 to 0010 are as posted by Antonin; they are unchanged, except for fixes for the problems pointed out by Mihail. Antonin, I would appreciate it if you want to change the "reform" bit in 0007 as discussed. For the next version I'll probably also rename the file cluster.c to repack.c as well as assorted symbols therein, as well as studying the coverage more closely. Thanks, -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-25T02:57:04Z
Hallo Alvaro, On Wed, Mar 25, 2026 at 4:02 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Many thanks for the review. I have applied fixes for these, so here's > v44. > Thanks for the patches. - 0004 is Antonin's bugfix from the crash reported by Srinath. > I think it's "0004 is Srinath's bugfix from the crash reported by Srinath." ;-) after i provided the analysis and fix for the crash[1], Antonin tried to reproduce this crash using isolation tester , for this he even proposed changes to isolation tester (so cool ... btw i reviewed it) [2] . i have done another round of stress testing on V43 , this time with more tests, as i did previously [3] did concurrency test - went well, crash test: after crash i observed that repack worker files are cleaned during server restart using RemovePgTempFiles but the transient table relation files remains not cleaned up, maybe we can do cleanup for this as well during server restart, I will think about this more. physical replication test where I did REPACK (concurrently) on primary and checked if data is intact using the 4 verifications I did here [3] on replica - went well. Then as suggested by Alvaro off-list I checked the lock upgrade behavior during the table swap phase. I observed that if another transaction holds a conflicting lock on the table when the swap is attempted, it can lead to “transient table” data loss during a manual or timeout abort. when a REPACK (concurrent) waits for a conflicting lock to be released and eventually hits a lock_timeout (or is cancelled via ctrl+c), the transaction aborts. During this abort, the cleanup process triggers smgrDoPendingDeletes. This results in the removal of all transient table relfiles and decoder worker files created during the process. This effectively wipes out the work done by the transient table creation before the swap could successfully complete, this happens because during transient table creation we add the table to the PendingRelDelete list. rebuild_relation→make_new_heap->heap_create_with_catalog→heap_create→table_relation_set_new_filelocator→RelationCreateStorage /* * Add the relation to the list of stuff to delete at abort, if we are * asked to do so. */ if (register_delete) { PendingRelDelete *pending; pending = (PendingRelDelete *) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->rlocator = rlocator; pending->procNumber = procNumber; pending->atCommit = false; /* delete if abort */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; } and the base/5/pgsql_tmp/ files also gets unlinked during the decoding worker cleanup, I think this cleanup of transient table relfiles and decoder files makes sense because we don’t have any resume operation in which we can re-use the transient table’s files, please correct me if I am not getting your point here. test scenario: session 1: postgres=# repack (concurrently) stress_victim; had a breakpoint rebuild_relation_finish_concurrent-> LockRelationOid(old_table_oid, AccessExclusiveLock); just before getting the exclusive lock. with lock_timeout = 5s session 2: postgres=# BEGIN; SELECT * FROM stress_victim LIMIT 1; -- left it open BEGIN id | balance | payload -----+---------+--------------------------------- ------------------------------------------------- ------------------------------------------------- ------------------------------------------------- -------------- 170 | 65 | d12f400c4d0d3c49818f88597e16cf29 d12f400c4d0d3c49818f88597e16cf29d12f400c4d0d3c498 18f88597e16cf29d12f400c4d0d3c49818f88597e16cf29d1 2f400c4d0d3c49818f88597e16cf29d12f400c4d0d3c49818 f88597e16cf29 (1 row) -- this gets us a conflicting lock (AccessShareLock) on the same table, REPACK (concurrently) is running on. session 1: release the breakpoint and now the backend waits for the conflicting lock to be released. in between if lock_timeout occurs then transaction aborts. postgres=# repack (concurrently) stress_victim; ERROR: canceling statement due to lock timeout CONTEXT: waiting for AccessExclusiveLock on relation 16637 of database 5 Now we can see the transient table relfiles and decoder worker files getting cleaned up. [1] - https://www.postgresql.org/message-id/CAFC%2Bb6qk3-DQTi43QMqvVLP%2BsudPV4vsLQm5iHfcCeObrNaVyA%40mail.gmail.com [2] - https://www.postgresql.org/message-id/flat/4703.1774250534%40localhost [3] - https://www.postgresql.org/message-id/CAFC%2Bb6o2yzA80YmfEhmMO9puN8qvGRvr-15BBLn3UmJxPfpr2w%40mail.gmail.com -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-25T09:00:49Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > - 0008 to 0010 are as posted by Antonin; they are unchanged, except for > fixes for the problems pointed out by Mihail. Antonin, I would > appreciate it if you want to change the "reform" bit in 0007 as > discussed. I've taken a look, but not sure if the tuple slots help here. In heapam_relation_copy_for_cluster(), both table_scan_getnextslot() and index_getnext_slot() call ExecStoreBufferHeapTuple() -> tts_buffer_heap_store_tuple(), which AFAICS do not deform the tuple. Then ExecFetchSlotHeapTuple() is used to retrieve the tuple, but again, the underlying slot (TTSOpsBufferHeapTuple) handles it by copying rather than deforming / forming. Thus I think the explicit "reforming" currently does not add any performance overhead. Of course, we can still use the slots, and do the following: 1) enforce tuple deforming (by calling slot_getallattrs()), 2) set the dropped attributes to NULL, 3) use ExecStoreVirtualTuple() to store the tuple into another slot and 4) get the heap tuple from the other slot. Should I do that? I'm asking because I wasn't sure if you're concerned about performance or coding (or both). Whatever approach we take, I see two more opportunities for better performance: 1. Do the "reforming" only if there are some dropped columns. (AFAICS even the old CLUSTER / VACUUM FULL did not check this.) 2. Get rid of the values of dropped columns earlier, so that the dropped values are not put into the tuplestore (likewise, I think that CLUSTER / VACUUM FULL did not care.) Besides that, I think that heap_form_tuple() should set the values of dropped columns to NULL by default, or do I miss something? Anyway, this should be addressed by a separate patch. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-25T15:52:16Z
Hello, While reviewing/testing V44 patch set , i found that if we run REPACK (CONCURRENTLY) without a table name inside a transaction block throws the error "REPACK CONCURRENTLY requires explicit table name" instead of the expected transaction block error. This occurs because ExecRepack() validates the parsed options and missing relation before verifying the transaction state. I attached a patch below to maintain consistency with other commands like VACUUM, REINDEX , and more and also not to confuse the user , because if user runs REPACK (CONCURRENTLY) without a table name inside a transaction block, if user gets "REPACK CONCURRENTLY requires explicit table name" and then to correct the mistake the user gives table and again runs the in transaction block , just to find out a new error "cannot run inside a transaction block". psql (19devel) Type "help" for help. postgres=# BEGIN; SET TRANSACTION READ ONLY; BEGIN SET postgres=*# REPACK (concurrently); ERROR: REPACK CONCURRENTLY requires explicit table name psql (19devel) Type "help" for help. postgres=# BEGIN; SET TRANSACTION READ ONLY; BEGIN SET postgres=*# REPACK (concurrently) stress_victim; ERROR: REPACK (CONCURRENTLY) cannot run inside a transaction block After the fix: psql (19devel) Type "help" for help. postgres=# BEGIN; SET TRANSACTION READ ONLY; BEGIN SET postgres=*# REPACK (concurrently); ERROR: REPACK (CONCURRENTLY) cannot run inside a transaction block Thoughts? -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-25T15:59:11Z
Sorry my bad :( , I attached my patch with *.patch instead of *.something or "nocfbot" prefix. -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-25T16:52:56Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > - 0008 to 0010 are as posted by Antonin; they are unchanged, except for > > fixes for the problems pointed out by Mihail. Antonin, I would > > appreciate it if you want to change the "reform" bit in 0007 as > > discussed. > > I've taken a look, but not sure if the tuple slots help here. In > heapam_relation_copy_for_cluster(), both table_scan_getnextslot() and > index_getnext_slot() call ExecStoreBufferHeapTuple() -> > tts_buffer_heap_store_tuple(), which AFAICS do not deform the tuple. Then > ExecFetchSlotHeapTuple() is used to retrieve the tuple, but again, the > underlying slot (TTSOpsBufferHeapTuple) handles it by copying rather than > deforming / forming. Thus I think the explicit "reforming" currently does not > add any performance overhead. Well, the deform / form steps do add some overhead of course, but these are necessary to get rid of the values of the dropped columns. I wanted to say that it wouldn't be cheaper with slots, because then we'd have to enforce the deform / form steps too, although the coding would be different: > Of course, we can still use the slots, and do the following: 1) enforce tuple > deforming (by calling slot_getallattrs()), 2) set the dropped attributes to > NULL, 3) use ExecStoreVirtualTuple() to store the tuple into another slot and > 4) get the heap tuple from the other slot. Should I do that? I'm asking > because I wasn't sure if you're concerned about performance or coding (or > both). > Whatever approach we take, I see two more opportunities for better > performance: > > 1. Do the "reforming" only if there are some dropped columns. (AFAICS even the > old CLUSTER / VACUUM FULL did not check this.) I think this would need more work because CLUSTER / VACCUM FULL / REPACK do not remove the dropped attributes from the tuple descriptor. So the optimization would only work until the first column is dropped. All the following runs would then do the reforming even if no other colmns were droped since the previous run. Perhaps we can teach REPACK to remove dropped columns from the tuple descriptor in the future. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-25T20:12:48Z
On 2026-Mar-25, Srinath Reddy Sadipiralla wrote: > Then as suggested by Alvaro off-list I checked the lock upgrade > behavior during the table swap phase. I observed that if another > transaction holds a conflicting lock on the table when the swap is > attempted, it can lead to “transient table” data loss during a manual > or timeout abort. when a REPACK (concurrent) waits for a conflicting > lock to be released and eventually hits a lock_timeout (or is > cancelled via ctrl+c), the transaction aborts. During this abort, the > cleanup process triggers smgrDoPendingDeletes. This results in the > removal of all transient table relfiles and decoder worker files > created during the process. This effectively wipes out the work done > by the transient table creation before the swap could successfully > complete, this happens because during transient table creation we add > the table to the PendingRelDelete list. I think we certainly need to make the files be deleted in some reasonable fashion if repack fails partway through. As for lock upgrade, I wonder if the best way to handle this isn't to hack the deadlock detector so that it causes any *other* process to die, if they detect that they would block on REPACK. Arguably there's nothing that you can do to a table while its undergoing REPACK CONCURRENTLY; any alterations would have to wait until the repacking is compelted. We can implement that idea simply enough, as shown in this crude prototype. (I omitted the last three patches in the series, and squashed my proposed changes into 0003, as announced in my previous posting.) The isolation test file is also a bit crude; I just copied repack.spec to a new file and removed the uninteresting bits. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "I am amazed at [the pgsql-sql] mailing list for the wonderful support, and lack of hesitasion in answering a lost soul's question, I just wished the rest of the mailing list could be like this." (Fotis) https://postgr.es/m/200606261359.k5QDxE2p004593@auth-smtp.hol.gr -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-26T08:23:54Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > Hello, everyone! > > Some comments for v43: Thanks! > ------------------------------ > > src/backend/catalog/index.c:766,1464-1469 > > "bool progress = (flags & INDEX_CREATE_REPORT_PROGRESS) != 0;" > > AFAIU gin, hash and btree (at least) still just unconditionally write > PROGRESS_CREATEIDX_* progress. I think you're right, I'll check it. > src/backend/catalog/toasting.c:334 > > "INDEX_CREATE_IS_PRIMARY | INDEX_CREATE_REPORT_PROGRESS, 0," > > Should we add the "progress" flag too here and move it from make_new_heap? Good catch. I'd prefer removing this hunk from the patch because the index on the TOAST relation is created while it's still empty. (The other commens have already been addressed by Alvaro.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-26T09:51:50Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Mar-25, Srinath Reddy Sadipiralla wrote: > > > Then as suggested by Alvaro off-list I checked the lock upgrade > > behavior during the table swap phase. I observed that if another > > transaction holds a conflicting lock on the table when the swap is > > attempted, it can lead to “transient table” data loss during a manual > > or timeout abort. when a REPACK (concurrent) waits for a conflicting > > lock to be released and eventually hits a lock_timeout (or is > > cancelled via ctrl+c), the transaction aborts. During this abort, the > > cleanup process triggers smgrDoPendingDeletes. This results in the > > removal of all transient table relfiles and decoder worker files > > created during the process. This effectively wipes out the work done > > by the transient table creation before the swap could successfully > > complete, this happens because during transient table creation we add > > the table to the PendingRelDelete list. > > I think we certainly need to make the files be deleted in some > reasonable fashion if repack fails partway through. I think that Srinath tries to explain the cleanup in detail, but in fact that's a normal processing of transaction abort. Not sure we need to do anything special. > As for lock upgrade, I wonder if the best way to handle this isn't to > hack the deadlock detector so that it causes any *other* process to die, > if they detect that they would block on REPACK. Arguably there's > nothing that you can do to a table while its undergoing REPACK > CONCURRENTLY; any alterations would have to wait until the repacking is > compelted. We can implement that idea simply enough, as shown in this > crude prototype. (I omitted the last three patches in the series, and > squashed my proposed changes into 0003, as announced in my previous > posting.) I haven't thought of it because I'm not familiar with the deadlock detector, but what you do seems consistent with the way blocking by autovacuum is handled. The only problem I noticed is that PROC_IN_CONCURRENT_REPACK is not cleared at the end of transaction. Perhaps it should be added to PROC_VACUUM_STATE_MASK (name of which is already misleading due to the presence of PROC_IN_SAFE_IC, but that's another problem). > The isolation test file is also a bit crude; I just copied repack.spec > to a new file and removed the uninteresting bits. Maybe just add a new permutation to repack.spec? I don't remembery if I created repack_toast.spec as a separate file just for better readability or if there was some other issue, but the deadlock test might fit into repack.spec. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-26T11:23:49Z
Antonin Houska <ah@cybertec.at> wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > As for lock upgrade, I wonder if the best way to handle this isn't to > > hack the deadlock detector so that it causes any *other* process to die, > > if they detect that they would block on REPACK. Arguably there's > > nothing that you can do to a table while its undergoing REPACK > > CONCURRENTLY; any alterations would have to wait until the repacking is > > compelted. We can implement that idea simply enough, as shown in this > > crude prototype. (I omitted the last three patches in the series, and > > squashed my proposed changes into 0003, as announced in my previous > > posting.) If we take this approach, some comments on deadlock need to be adjusted - see my proposals in nocfbot_comments_deadlock.diff. Besides that, nocfbot_comment_cluster_rel.diff suggests one more comment change that does not depend on the deadlock detection - I forgot to change it when implementing the lock upgrade. Also the commit message of 0003 needs to be adjusted. (Does it need to mention the problem at all?) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-26T13:19:53Z
Hi Antonin, On Mon, Mar 23, 2026 at 5:30 PM Antonin Houska <ah@cybertec.at> wrote: > > I could not resist digging in it deeper :-) Attached is a test that > reproduces > the crash - it includes the isolation tester enhancement that I posted > separately [1]. It crashes reliably with v43 [2] if your fix v43-0005 is > omitted. > +1, i tested the same and yeah it reproduces the crash ,also reviewed the patch LGTM, except i think you need to add this to makefile, so that a simple make check would be enough to run the isolation test, i think this all makes sense if this [1] patch goes in. diff --git a/contrib/test_decoding/Makefile b/contrib/test_decoding/Makefile index acbcaed2feb..201e3c84cb4 100644 --- a/contrib/test_decoding/Makefile +++ b/contrib/test_decoding/Makefile @@ -9,7 +9,10 @@ REGRESS = ddl xact rewrite toast permissions decoding_in_xact \ ISOLATION = mxact delayed_startup ondisk_startup concurrent_ddl_dml \ oldest_xmin snapshot_transfer subxact_without_top concurrent_stream \ twophase_snapshot slot_creation_error catalog_change_snapshot \ - skip_snapshot_restore invalidation_distribution parallel_session_origin + skip_snapshot_restore invalidation_distribution parallel_session_origin \ + filtering + +EXTRA_INSTALL = src/test/modules/injection_points REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf ISOLATION_OPTS = --temp-config $(top_srcdir)/contrib/test_decoding/logical.conf [1] - https://www.postgresql.org/message-id/flat/4703.1774250534%40localhost -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-26T14:06:29Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > (I omitted the last three patches in the series, and > squashed my proposed changes into 0003, as announced in my previous > posting.) I've updated the comment about on-disk attributes in repack_store_change(), but when verifying it, I hit an error when more than one UPDATEs (in separate transactions) were executed during a single run of REPACK. The problem is that reorderbuffer.c sets up an internal (sub)transaction before replaying each decoded transaction. Therefore the tuple slot should not be allocated in TopTransactionContext. I chose TopMemoryContext instead. BTW, if you want to verify that the updated comment is correct, just add elog(ERROR) next to it and run repack_toast.spec. The statement UPDATE repack_test SET i=3 where i=1; will then reach it. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-26T17:28:12Z
On 2026-Mar-25, Antonin Houska wrote: > I've taken a look, but not sure if the tuple slots help here. In > heapam_relation_copy_for_cluster(), both table_scan_getnextslot() and > index_getnext_slot() call ExecStoreBufferHeapTuple() -> > tts_buffer_heap_store_tuple(), which AFAICS do not deform the tuple. > Then ExecFetchSlotHeapTuple() is used to retrieve the tuple, but > again, the underlying slot (TTSOpsBufferHeapTuple) handles it by > copying rather than deforming / forming. Thus I think the explicit > "reforming" currently does not add any performance overhead. Looking again, I think we should leave this alone for now. The existing code (looking at heapam_relation_copy_for_cluster) is somewhat silly, in that we do a bunch of operations with slots, then we heap-ify the tuple by doing ExecFetchSlotHeapTuple(), then we do the reform_and_rewrite dance, which must deform the tuple only to immediately form it back; and after we've done that, we make it go through the rewriteheap.c motions, which again deforms and forms back (in certain cases). This code structure seems mostly a relic from the time when heapam.c was all we had, and I think we should rewrite it from scratch to work using only slots. But not for this patch, and not for pg19, because I think that's going to take some nontrivial effort on its own. So for REPACK in pg19 I think we should just do the simplest thing possible, and do the minimum amount of deform/form dance necessary to make this whole thing work. I suppose that that's what the submitted code (v44-0008) does, so I'm going to leave it at that. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "In Europe they call me Niklaus Wirth; in the US they call me Nickel's worth. That's because in Europe they call me by name, and in the US by value!"
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-26T19:15:14Z
On 2026-Mar-25, Srinath Reddy Sadipiralla wrote: > Hello, > > While reviewing/testing V44 patch set , i found that if we run REPACK > (CONCURRENTLY) without a table name inside a transaction block throws > the error "REPACK CONCURRENTLY requires explicit table name" instead > of the expected transaction block error. This occurs because > ExecRepack() validates the parsed options and missing relation before > verifying the transaction state. > > I attached a patch below to maintain consistency with other commands > like VACUUM, REINDEX , and more and also not to confuse the user , > because if user runs REPACK (CONCURRENTLY) without a table name inside > a transaction block, if user gets "REPACK CONCURRENTLY requires > explicit table name" and then to correct the mistake the user gives > table and again runs the in transaction block , just to find out a new > error "cannot run inside a transaction block". I don't disagree with changing this, but AFAICS the patch as presented provokes multiple test failures. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-26T19:36:45Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > On Mon, Mar 23, 2026 at 5:30 PM Antonin Houska <ah@cybertec.at> wrote: > >> I could not resist digging in it deeper :-) Attached is a test that reproduces >> the crash - it includes the isolation tester enhancement that I posted >> separately [1]. It crashes reliably with v43 [2] if your fix v43-0005 is >> omitted. > > +1, i tested the same and yeah it reproduces the crash ,also reviewed > the patch LGTM, except i think you need to add this to makefile, so that a simple > make check would be enough to run the isolation test, i think this all makes sense > if this [1] patch goes in. The purpose of this test was only to reproduce the crash you encountered during the stress test, and to confirm that your fix works. I didn't really intend to add the test to the tree. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-26T23:14:50Z
On 2026-Mar-26, Antonin Houska wrote: > I haven't thought of it because I'm not familiar with the deadlock detector, > but what you do seems consistent with the way blocking by autovacuum is > handled. > > The only problem I noticed is that PROC_IN_CONCURRENT_REPACK is not cleared at > the end of transaction. Perhaps it should be added to PROC_VACUUM_STATE_MASK > (name of which is already misleading due to the presence of PROC_IN_SAFE_IC, > but that's another problem). Yeah, good observation -- we should absolutely clear it at transaction end, and adding it to PROC_VACUUM_STATE_MASK seems the cleanest way to make that happen. However, we should also clear it as soon as we've acquired the AccessExclusiveLock on all rels. At that point we no longer need it, and it makes more sense to have other transactions resume the normal waiting behavior instead of having them fail right away. > Maybe just add a new permutation to repack.spec? I don't remembery if I > created repack_toast.spec as a separate file just for better readability or if > there was some other issue, but the deadlock test might fit into repack.spec. Yeah, that was my first impulse too, but it's not clear to me that things are better one way or the other. I haven't decided yet. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "[PostgreSQL] is a great group; in my opinion it is THE best open source development communities in existence anywhere." (Lamar Owen)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-27T16:12:55Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > src/backend/catalog/index.c:766,1464-1469 > > > > "bool progress = (flags & INDEX_CREATE_REPORT_PROGRESS) != 0;" > > > > AFAIU gin, hash and btree (at least) still just unconditionally write > > PROGRESS_CREATEIDX_* progress. > > I think you're right, I'll check it. I concluded this is a problem that exists for quite a while and should be fixed separately. Currently I don't see conflicts of parameter indexes between PROGRESS_COMMAND_REPACK and PROGRESS_COMMAND_CREATE_INDEX. There would be some if the index was built with the CONCURRENTLY option, but REPACK uses normal index build. I tried to write a patch that allows progress tracking of two commands at the same time (a "main command" and a "subcommand"), but regression tests revealed that contrib/file_fdw is broken in a way that I could not fix easily: during execution of a join, two COPY FROM commands are executed at the same time and they overwrite the status of each other. Unlike the concept of a sub-command, we cannot assume here that the command that started the reporting as the second will stop as the first. Thus in pgstat_progress_end_command() we cannot figure out which node is trying to stop the reporting. It needs more work, I can get back to it after PG 19 feature freeze. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-27T17:01:09Z
On 2026-Mar-27, Antonin Houska wrote: > I tried to write a patch that allows progress tracking of two commands at the > same time (a "main command" and a "subcommand"), but regression tests revealed > that contrib/file_fdw is broken in a way that I could not fix easily: during > execution of a join, two COPY FROM commands are executed at the same time and > they overwrite the status of each other. Unlike the concept of a sub-command, > we cannot assume here that the command that started the reporting as the > second will stop as the first. Thus in pgstat_progress_end_command() we cannot > figure out which node is trying to stop the reporting. Yeah, I think we've discussed this kind of thing in the context of the index creation by CLUSTER before, but no ideas have emerged on what the best implementation is. > It needs more work, I can get back to it after PG 19 feature freeze. Yeah, clearly it's not getting in pg19. I'm not terribly upset about that though; it would be nice to have, but it's not a dealbreaker. Anyway, I've been changing how all the new code for REPACK is distributed; here's patch series v45, and the breakdown and my assessment is: 0001 is looking good. I don't commit it just yet because there's no point in doing so until 0003 is also committable. 0002 continues to baffle me. I would much rather do without it. 0003 is the addition of CONCURRENTLY and all it needs. Regarding structure, I think it's good to have the code that runs in a separate worker have its own file, which I named commands/repack_worker.c. Luckily it's all well contained. It shares data structures and whatnot with the other parts of REPACK via include/commands/repack_internal.h. Patch "Use BulkInsertState when copying data to the new heap." is now 0004 and I put it right after 0003, and I will probably be squashing them into a single one. 0005 is new -- I renamed cluster.c to repack.c, as previously mentioned. Then 0006 is "Fix a few problems in index build progress reporting", which I'm likely to also squash in 0003 for v46. 0007 is "Error out any process that would block at REPACK" which is the complete patch to change the deadlock detector to raise an error at any process that competes with REPACK. This one I think I would commit separately from 0003 -- if nothing else, because it introduces novel behavior that could be contested, so I want to be able to easily revert it without endangering the rest of CONCURRENTLY. Lastly, 0008 is "Teach snapshot builder to skip transactions running REPACK (CONCURRENTLY)" which I see as the least mature of the pack. I would really like to be able to squash it with 0003, but I'm not yet trusting it enough. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "No nos atrevemos a muchas cosas porque son difíciles, pero son difíciles porque no nos atrevemos a hacerlas" (Séneca)
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-27T18:42:18Z
Hi Alvaro, On Fri, Mar 27, 2026 at 12:45 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > I don't disagree with changing this, but AFAICS the patch as presented > provokes multiple test failures. > Fixed with the attached patch. -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-29T22:02:37Z
On 2026-Mar-27, Alvaro Herrera wrote: > 0001 is looking good. I don't commit it just yet because there's no > point in doing so until 0003 is also committable. > > 0002 continues to baffle me. I would much rather do without it. > > 0003 is the addition of CONCURRENTLY and all it needs. Regarding > structure, I think it's good to have the code that runs in a separate > worker have its own file, which I named commands/repack_worker.c. > Luckily it's all well contained. It shares data structures and whatnot > with the other parts of REPACK via include/commands/repack_internal.h. I figured out that the right way to handle SET_VARSIZE() problem was to use a union, giving sufficient space so that the compiler doesn't complain. So I got rid of 0002 here. There are no other interesting changes in this version compared to v45, and I didn't get around to squashing anything. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-03-31T10:47:18Z
On Fri, Mar 27, 2026 at 10:31 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Lastly, 0008 is "Teach snapshot builder to skip transactions running > REPACK (CONCURRENTLY)" which I see as the least mature of the pack. I > would really like to be able to squash it with 0003, but I'm not yet > trusting it enough. > Few comments/questions by looking 0008 alone: 1. + TransactionId *xids_repack = NULL; + bool logical_decoding_enabled = IsLogicalDecodingEnabled(); Assert(!RecoveryInProgress()); ... ... /* * Allocating space for maxProcs xids is usually overkill; numProcs would * be sufficient. But it seems better to do the malloc while not holding @@ -2663,11 +2672,14 @@ GetRunningTransactionData(void) */ if (CurrentRunningXacts->xids == NULL) { + /* FIXME probably fails if logical decoding is enable on-the-fly */ + int nrepack = logical_decoding_enabled ? MAX_REPACK_XIDS : 0; This FIXME is important to fix for this patch, otherwise, we can't rely on transactions remembered as repack_concurrently. 2. * + /* + * TODO Consider a GUC to reserve certain amount of replication slots for + * REPACK (CONCURRENTLY) and using it here. + */ +#define MAX_REPACK_XIDS 16 + This sounds a bit scary as reserving replication slots for REPACK (CONCURRENTLY) may not be what users expect. But it is not clear why replication slots need to be reserved for this. IIUC, two reasons why logical decoding can ignore REPACK (CONCURRENTLY) are (a) does not perform any catalog changes relevant to logical decoding, (b) neither walsenders nor backends performing logical decoding needs to care sending the WAL generated by REPACK (CONCURRENTLY). Is that understanding correct? If so, we may want to clarify why we want to ignore this command's WAL while sending changes downstream in the commit message or give some reference of the patch where the same is mentioned. This can help reviewing this patch independently. BTW, are we intending to commit this patch series for PG19? -- With Regards, Amit Kapila. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-31T14:36:22Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Fri, Mar 27, 2026 at 10:31 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > > Lastly, 0008 is "Teach snapshot builder to skip transactions running > > REPACK (CONCURRENTLY)" which I see as the least mature of the pack. I > > would really like to be able to squash it with 0003, but I'm not yet > > trusting it enough. > > > > Few comments/questions by looking 0008 alone: Thanks. > 1. > + TransactionId *xids_repack = NULL; > + bool logical_decoding_enabled = IsLogicalDecodingEnabled(); > > Assert(!RecoveryInProgress()); > > ... > ... > > /* > * Allocating space for maxProcs xids is usually overkill; numProcs would > * be sufficient. But it seems better to do the malloc while not holding > @@ -2663,11 +2672,14 @@ GetRunningTransactionData(void) > */ > if (CurrentRunningXacts->xids == NULL) > { > + /* FIXME probably fails if logical decoding is enable on-the-fly */ > + int nrepack = logical_decoding_enabled ? MAX_REPACK_XIDS : 0; > > This FIXME is important to fix for this patch, otherwise, we can't > rely on transactions remembered as repack_concurrently. Indeed. > 2. > * > + /* > + * TODO Consider a GUC to reserve certain amount of replication slots for > + * REPACK (CONCURRENTLY) and using it here. > + */ > +#define MAX_REPACK_XIDS 16 > + > > This sounds a bit scary as reserving replication slots for REPACK > (CONCURRENTLY) may not be what users expect. But it is not clear why > replication slots need to be reserved for this. The point is that REPACK should not block replication [1]. Thus reserving slots for non-REPACK users is probably more precise statement. > IIUC, two reasons why logical decoding can ignore REPACK > (CONCURRENTLY) are (a) does not perform any catalog changes relevant > to logical decoding, (b) neither walsenders nor backends performing > logical decoding needs to care sending the WAL generated by REPACK > (CONCURRENTLY). Is that understanding correct? If so, we may want to > clarify why we want to ignore this command's WAL while sending changes > downstream in the commit message or give some reference of the patch > where the same is mentioned. This can help reviewing this patch > independently. Correct, but in fact this diff only affects the setup of the logical decoding rather than the decoding itself. On the other hand, if REPACK (CONCURRENTLY) starts when the decoding backend's snapshot builder is already in the SNAPBUILD_FULL_SNAPSHOT state, reorderbuffer.c processes the transaction normally, and another part of the series (v46-0002) ensures that the table rewriting is not decoded: REPACK simply tells heap_insert(), heap_update(), heap_delete() not to put the extra (replication specific) information into the corresponding WAL records. I suppose this is what you mean in (b). Regarding (a), yes, the absence of catalog changes in the REPACK's transaction is the reason that even the logical decoding setup does not have to wait for the transaction to finish. AFAIU the reason the snapshot builder needs to wait for completion of (non-REPACK) transaction started before SNAPBUILD_FULL_SNAPSHOT was reached is exactly that the transaction might have performed catalog changes before its decoding started, so we do not know for sure if it contains catalog changes or not. > BTW, are we intending to commit this patch series for PG19? Yes, that's the current plan. [1] https://www.postgresql.org/message-id/CABV9wwMQkN6wOxMnd1h95eqpC7wEqivBzsBzCp3VnxGFk%3DvDUw%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-31T15:35:42Z
On 2026-Mar-31, Antonin Houska wrote: > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > On Fri, Mar 27, 2026 at 10:31 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > BTW, are we intending to commit this patch series for PG19? > > Yes, that's the current plan. Yes -- though as I said upthread, this particular patch in the series is the one I'm the least sure about. Also, the structure of the patches to commit is not like the ones posted here or previously. I'll post a committable one later on; see below for a breakdown. For now, I brought the addition of options to table AM methods from [1] into this series, in what I think is pretty much final form (0002); and I added a 0004 patch that's code review for the big patch, which I'll squash for the next version, and is posted here separately just so that it's easy to see. My intention as to patches for final commit is: - 0001 "Make index_concurrently_create_copy more general" same as here. - 0002 "give options bitmask to table_delete/table_update" same as here, with a real commit message. - 0003 Rename cluster.c/h to repack.c/h (similar to 0006 here); no essential change in contents. - 0004 "Add CONCURRENTLY option to REPACK command". Squash of 0003, 0004, 0005 and 0007. - 0005 "Error out any process that would block at REPACK", same as here. I'm unsure on whether 0009 would be pushed or not. [1] https://postgr.es/m/202603171606.kf6pmhscqbqz@alvherre.pgsql -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-03-31T17:37:17Z
Hi Alvaro, On Thu, Mar 26, 2026 at 1:42 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > As for lock upgrade, I wonder if the best way to handle this isn't to > hack the deadlock detector so that it causes any *other* process to die, > if they detect that they would block on REPACK. Arguably there's > nothing that you can do to a table while its undergoing REPACK > CONCURRENTLY; any alterations would have to wait until the repacking is > compelted. We can implement that idea simply enough, as shown in this > crude prototype. > After testing this, I observed that it solves the scenario where a query is waiting on REPACK. For example, if a DROP TABLE requests an AEL and queues behind REPACK's ShareUpdateExclusiveLock, the deadlock detector comes when REPACK tries to upgrade to AEL, killing the DROP to prevent the circular queue deadlock, But the case I originally mentioned [1] was the reverse: what happens if a transaction already holds a lock that conflicts with the upcoming AEL upgrade (e.g., an analytical SELECT or an idle-in-transaction holding an AccessShareLock), but isn't waiting on REPACK at all? In this case, there's no circular wait. The deadlock detector never fires. REPACK simply queues behind the SELECT, eventually hits its lock_timeout, aborts and cleans up.Initially, I thought this cleanup was expected behavior. But after seeing your solution to protect REPACK from losing its transient table work, I thought it's "not expected". If the goal is to prevent REPACK's work from being wasted, should we error out the backend that is making REPACK wait during the final swap phase? I am thinking of something conceptually similar to ResolveRecoveryConflictWithLock,actively cancelling the conflicting session to allow the AEL upgrade to proceed. Thoughts? test scenario: session 1: postgres=# repack (concurrently) stress_victim; had a breakpoint rebuild_relation_finish_concurrent-> LockRelationOid(old_table_oid, AccessExclusiveLock); just before getting the exclusive lock. with lock_timeout = 5s session 2: postgres=# BEGIN; SELECT * FROM stress_victim LIMIT 1; -- left it open BEGIN id | balance | payload -----+---------+--------------------------------- ------------------------------------------------- ------------------------------------------------- ------------------------------------------------- -------------- 170 | 65 | d12f400c4d0d3c49818f88597e16cf29 d12f400c4d0d3c49818f88597e16cf29d12f400c4d0d3c498 18f88597e16cf29d12f400c4d0d3c49818f88597e16cf29d1 2f400c4d0d3c49818f88597e16cf29d12f400c4d0d3c49818 f88597e16cf29 (1 row) -- this gets us a conflicting lock (AccessShareLock) on the same table, REPACK (concurrently) is running on. session 1: release the breakpoint and now the backend waits for the conflicting lock to be released. in between if lock_timeout occurs then transaction aborts. postgres=# repack (concurrently) stress_victim; ERROR: canceling statement due to lock timeout CONTEXT: waiting for AccessExclusiveLock on relation 16637 of database 5 [1] - https://www.postgresql.org/message-id/CAFC%2Bb6pK9ogeSpMA8hg18XhC1eNPcsKWBwoC5OySXi4iTxwtRw%40mail.gmail.com -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-03-31T18:22:33Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > On Thu, Mar 26, 2026 at 1:42 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > As for lock upgrade, I wonder if the best way to handle this isn't to > hack the deadlock detector so that it causes any *other* process to die, > if they detect that they would block on REPACK. Arguably there's > nothing that you can do to a table while its undergoing REPACK > CONCURRENTLY; any alterations would have to wait until the repacking is > compelted. We can implement that idea simply enough, as shown in this > crude prototype. > > After testing this, I observed that it solves the scenario where a query is waiting > on REPACK. For example, if a DROP TABLE requests an AEL and queues > behind REPACK's ShareUpdateExclusiveLock, the deadlock detector comes > when REPACK tries to upgrade to AEL, killing the DROP to prevent the circular > queue deadlock, But the case I originally mentioned [1] was the reverse: what > happens if a transaction already holds a lock that conflicts with the upcoming > AEL upgrade (e.g., an analytical SELECT or an idle-in-transaction holding an AccessShareLock), > but isn't waiting on REPACK at all? > > In this case, there's no circular wait. The deadlock detector never fires. REPACK > simply queues behind the SELECT, eventually hits its lock_timeout, aborts and > cleans up. Why should the user set non-zero lock_timeout before running REPACK? -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-31T18:23:57Z
On 2026-Mar-31, Srinath Reddy Sadipiralla wrote: > In this case, there's no circular wait. The deadlock detector never > fires. REPACK simply queues behind the SELECT, eventually hits its > lock_timeout, aborts and cleans up.Initially, I thought this cleanup > was expected behavior. But after seeing your solution to protect > REPACK from losing its transient table work, I thought it's "not > expected". Yeah. Keep in mind that REPACK could have been running for many hours or even days before it reaches the point of acquiring its AEL lock to do the final swap; and it may well be critical work. We do not want to lose it. So whatever is waiting to obtain a lock on the table, or already has a lock on the table, has to yield. > If the goal is to prevent REPACK's work from being wasted, should we > error out the backend that is making REPACK wait during the final swap > phase? I am thinking of something conceptually similar to > ResolveRecoveryConflictWithLock, actively cancelling the conflicting > session to allow the AEL upgrade to proceed. Something like that might be appropriate, yeah. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "El miedo atento y previsor es la madre de la seguridad" (E. Burke)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-03-31T19:41:53Z
On 2026-Mar-31, Alvaro Herrera wrote: > My intention as to patches for final commit is: > > - 0001 "Make index_concurrently_create_copy more general" same as here. > - 0002 "give options bitmask to table_delete/table_update" same as here, > with a real commit message. > - 0003 Rename cluster.c/h to repack.c/h (similar to 0006 here); no > essential change in contents. > - 0004 "Add CONCURRENTLY option to REPACK command". Squash of 0003, > 0004, 0005 and 0007. > - 0005 "Error out any process that would block at REPACK", same as here. Here it is with that structure. The first three should be pretty much in final form (0003 needs a commit message), but I still want to make some more cosmetic adjustments to 0004. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ Thou shalt check the array bounds of all strings (indeed, all arrays), for surely where thou typest "foo" someone someday shall type "supercalifragilisticexpialidocious" (5th Commandment for C programmers)
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-01T08:42:22Z
On Tue, Mar 31, 2026 at 8:06 PM Antonin Houska <ah@cybertec.at> wrote: > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > 2. > > * > > + /* > > + * TODO Consider a GUC to reserve certain amount of replication slots for > > + * REPACK (CONCURRENTLY) and using it here. > > + */ > > +#define MAX_REPACK_XIDS 16 > > + > > > > This sounds a bit scary as reserving replication slots for REPACK > > (CONCURRENTLY) may not be what users expect. But it is not clear why > > replication slots need to be reserved for this. > > The point is that REPACK should not block replication [1]. Thus reserving > slots for non-REPACK users is probably more precise statement. > oh, so shouldn't be a separate patch than this and an important for this functionality to get committed? I don't see why we need to make such a GUC or knob as part of this patch if we need the same. > > IIUC, two reasons why logical decoding can ignore REPACK > > (CONCURRENTLY) are (a) does not perform any catalog changes relevant > > to logical decoding, (b) neither walsenders nor backends performing > > logical decoding needs to care sending the WAL generated by REPACK > > (CONCURRENTLY). Is that understanding correct? If so, we may want to > > clarify why we want to ignore this command's WAL while sending changes > > downstream in the commit message or give some reference of the patch > > where the same is mentioned. This can help reviewing this patch > > independently. > > Correct, but in fact this diff only affects the setup of the logical decoding > rather than the decoding itself. On the other hand, if REPACK (CONCURRENTLY) > starts when the decoding backend's snapshot builder is already in the > SNAPBUILD_FULL_SNAPSHOT state, reorderbuffer.c processes the transaction > normally, and another part of the series (v46-0002) ensures that the table > rewriting is not decoded: REPACK simply tells heap_insert(), heap_update(), > heap_delete() not to put the extra (replication specific) information into the > corresponding WAL records. I suppose this is what you mean in (b). > > Regarding (a), yes, the absence of catalog changes in the REPACK's transaction > is the reason that even the logical decoding setup does not have to wait for > the transaction to finish. > Hmm, but we don't do any catalog changes for transactions that have DML say only INSERT but we don't have separate logic like REPACK in snapbuild machinery. Same is probably true for dml operations on an unlogged table which doesn't have WAL to send nor any catalog operations involved but we don't have any special path for that in snapbuild code path. So, why do we need special handling for this operation? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-01T09:39:31Z
On Tue, Mar 31, 2026 at 11:54 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Mar-31, Srinath Reddy Sadipiralla wrote: > > > In this case, there's no circular wait. The deadlock detector never > > fires. REPACK simply queues behind the SELECT, eventually hits its > > lock_timeout, aborts and cleans up.Initially, I thought this cleanup > > was expected behavior. But after seeing your solution to protect > > REPACK from losing its transient table work, I thought it's "not > > expected". > > Yeah. Keep in mind that REPACK could have been running for many hours > or even days before it reaches the point of acquiring its AEL lock to do > the final swap; and it may well be critical work. We do not want to > lose it. So whatever is waiting to obtain a lock on the table, or > already has a lock on the table, has to yield. > > > If the goal is to prevent REPACK's work from being wasted, should we > > error out the backend that is making REPACK wait during the final swap > > phase? I am thinking of something conceptually similar to > > ResolveRecoveryConflictWithLock, actively cancelling the conflicting > > session to allow the AEL upgrade to proceed. > > Something like that might be appropriate, yeah. > What about if the blocking process is an autovacumm that is working to prevent XID wraparound? I think we already avoid killing it in such cases. BTW, one can say that cancelling a long-running report query also wastes a lot of effort of the user generating such a report. Why can't REPACK wait for such a select to finish instead of cancelling it? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-01T09:43:26Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Tue, Mar 31, 2026 at 8:06 PM Antonin Houska <ah@cybertec.at> wrote: > > > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > 2. > > > * > > > + /* > > > + * TODO Consider a GUC to reserve certain amount of replication slots for > > > + * REPACK (CONCURRENTLY) and using it here. > > > + */ > > > +#define MAX_REPACK_XIDS 16 > > > + > > > > > > This sounds a bit scary as reserving replication slots for REPACK > > > (CONCURRENTLY) may not be what users expect. But it is not clear why > > > replication slots need to be reserved for this. > > > > The point is that REPACK should not block replication [1]. Thus reserving > > slots for non-REPACK users is probably more precise statement. > > > > oh, so shouldn't be a separate patch than this and an important for > this functionality to get committed? I don't see why we need to make > such a GUC or knob as part of this patch if we need the same. REPACK is a new user of replication slots. Without that, there is no other way to "steal" the slots from the replication users. > > > IIUC, two reasons why logical decoding can ignore REPACK > > > (CONCURRENTLY) are (a) does not perform any catalog changes relevant > > > to logical decoding, (b) neither walsenders nor backends performing > > > logical decoding needs to care sending the WAL generated by REPACK > > > (CONCURRENTLY). Is that understanding correct? If so, we may want to > > > clarify why we want to ignore this command's WAL while sending changes > > > downstream in the commit message or give some reference of the patch > > > where the same is mentioned. This can help reviewing this patch > > > independently. > > > > Correct, but in fact this diff only affects the setup of the logical decoding > > rather than the decoding itself. On the other hand, if REPACK (CONCURRENTLY) > > starts when the decoding backend's snapshot builder is already in the > > SNAPBUILD_FULL_SNAPSHOT state, reorderbuffer.c processes the transaction > > normally, and another part of the series (v46-0002) ensures that the table > > rewriting is not decoded: REPACK simply tells heap_insert(), heap_update(), > > heap_delete() not to put the extra (replication specific) information into the > > corresponding WAL records. I suppose this is what you mean in (b). > > > > Regarding (a), yes, the absence of catalog changes in the REPACK's transaction > > is the reason that even the logical decoding setup does not have to wait for > > the transaction to finish. > > > > Hmm, but we don't do any catalog changes for transactions that have > DML say only INSERT but we don't have separate logic like REPACK in > snapbuild machinery. Same is probably true for dml operations on an > unlogged table which doesn't have WAL to send nor any catalog > operations involved but we don't have any special path for that in > snapbuild code path. So, why do we need special handling for this > operation? If an "ordinary" transaction, which had started before the snapshot builder reached the SNAPBUILD_FULL_SNAPSHOT state, runs DML, the snapshot builder does not know if the same transaction changed something in the catalog earlier. So it needs to wait for this transaction to finish before it advances to SNAPBUILD_CONSISTENT. For REPACK, we know that it cannot happen because it cannot run in transaction block for other reasons. So the snapshot builder does not have to wait. Nevertheless, I'm not sure it's a good idea for snapbuild.c to handle special cases like REPACK. Instead, I'm thinking if snapbuild.c can safely ignore XIDs of backends connected to databases other than the one we're decoding. Thus the restriction would be one backend running REPACK per database rather than cluster. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-01T10:22:15Z
On Wed, Apr 1, 2026 at 3:13 PM Antonin Houska <ah@cybertec.at> wrote: > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > On Tue, Mar 31, 2026 at 8:06 PM Antonin Houska <ah@cybertec.at> wrote: > > > > > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > > 2. > > > > * > > > > + /* > > > > + * TODO Consider a GUC to reserve certain amount of replication slots for > > > > + * REPACK (CONCURRENTLY) and using it here. > > > > + */ > > > > +#define MAX_REPACK_XIDS 16 > > > > + > > > > > > > > This sounds a bit scary as reserving replication slots for REPACK > > > > (CONCURRENTLY) may not be what users expect. But it is not clear why > > > > replication slots need to be reserved for this. > > > > > > The point is that REPACK should not block replication [1]. Thus reserving > > > slots for non-REPACK users is probably more precise statement. > > > > > > > oh, so shouldn't be a separate patch than this and an important for > > this functionality to get committed? I don't see why we need to make > > such a GUC or knob as part of this patch if we need the same. > > REPACK is a new user of replication slots. Without that, there is no other way > to "steal" the slots from the replication users. > > > > > IIUC, two reasons why logical decoding can ignore REPACK > > > > (CONCURRENTLY) are (a) does not perform any catalog changes relevant > > > > to logical decoding, (b) neither walsenders nor backends performing > > > > logical decoding needs to care sending the WAL generated by REPACK > > > > (CONCURRENTLY). Is that understanding correct? If so, we may want to > > > > clarify why we want to ignore this command's WAL while sending changes > > > > downstream in the commit message or give some reference of the patch > > > > where the same is mentioned. This can help reviewing this patch > > > > independently. > > > > > > Correct, but in fact this diff only affects the setup of the logical decoding > > > rather than the decoding itself. On the other hand, if REPACK (CONCURRENTLY) > > > starts when the decoding backend's snapshot builder is already in the > > > SNAPBUILD_FULL_SNAPSHOT state, reorderbuffer.c processes the transaction > > > normally, and another part of the series (v46-0002) ensures that the table > > > rewriting is not decoded: REPACK simply tells heap_insert(), heap_update(), > > > heap_delete() not to put the extra (replication specific) information into the > > > corresponding WAL records. I suppose this is what you mean in (b). > > > > > > Regarding (a), yes, the absence of catalog changes in the REPACK's transaction > > > is the reason that even the logical decoding setup does not have to wait for > > > the transaction to finish. > > > > > > > Hmm, but we don't do any catalog changes for transactions that have > > DML say only INSERT but we don't have separate logic like REPACK in > > snapbuild machinery. Same is probably true for dml operations on an > > unlogged table which doesn't have WAL to send nor any catalog > > operations involved but we don't have any special path for that in > > snapbuild code path. So, why do we need special handling for this > > operation? > > If an "ordinary" transaction, which had started before the snapshot builder > reached the SNAPBUILD_FULL_SNAPSHOT state, runs DML, the snapshot builder does > not know if the same transaction changed something in the catalog earlier. So > it needs to wait for this transaction to finish before it advances to > SNAPBUILD_CONSISTENT. For REPACK, we know that it cannot happen because it > cannot run in transaction block for other reasons. So the snapshot builder > does not have to wait. > > Nevertheless, I'm not sure it's a good idea for snapbuild.c to handle special > cases like REPACK. Instead, I'm thinking if snapbuild.c can safely ignore XIDs > of backends connected to databases other than the one we're decoding. > What if such transactions have made changes in the global catalog? Even if that won't matter, I feel such a change would be quite fundamental to snapbuild machinery and changing at this point would be risky. BTW, is the reason to skip REPACK while building a snapshot is that it can take a long time to finish? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-01T11:38:16Z
On 2026-Apr-01, Amit Kapila wrote: > What about if the blocking process is an autovacumm that is working to > prevent XID wraparound? I think we already avoid killing it in such > cases. If we just let REPACK finish, it will also renew the table's XID, so autovacuum is not needed in that case. I mean, there's no reason to let autovacuum process the contents of a table that is going to be replaced completely. One potentially problematic case would be that an emergency autovacuum has been running for a long time and about to finish, and REPACK is started. But in that case, autovacuum already has ShareUpdateExclusive, so REPACK wouldn't be able to start at all, which means it won't kill autovacuum. And in the case where autovacuum is doing emergency vacuuming, then it won't commit suicide, so it will be able to complete before repack starts. > BTW, one can say that cancelling a long-running report query also > wastes a lot of effort of the user generating such a report. Why can't > REPACK wait for such a select to finish instead of cancelling it? I don't understand exactly which scenario you're concerned about. Is there a long-running query which, after spending a lot of time running a report, tries to upgrade its lock level on the table? Keep in mind that this check only runs when the affected session runs the deadlock checker, which means it's been waiting to acquire a lock for deadlock_timeout seconds. It's not repack that kills the query. [ ... reflects ...] Oh, actually what Srinath proposed does exactly that -- kill other queries. Hmm, yeah, I'm less sure about that particular bit. Here I'm only talking about my proposal to have the deadlock detector handle the case of somebody waiting to lock the table. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-01T11:42:17Z
On 2026-Apr-01, Amit Kapila wrote: > BTW, is the reason to skip REPACK while building a snapshot is that it > can take a long time to finish? As I understand the issue, yes, that's precisely the problem: if you have one REPACK running, then starting a second REPACK (which requires building a new snapshot) would have to wait until the first REPACK is over. In other words, you wouldn't be able to have two repacks running concurrently. This sounds like a problematic requirement. So having snapbuild ignore REPACK is there to allow the second REPACK to work at all. But more generally, *all* users of snapbuild would be prevented from starting until REPACK is done; so if you have a very very large table that takes a long time to repack, then everything would be blocked behind it until it completes, which sounds extremely unpleasant. So, if we're unable to get this particular patch in, we would have to have a big fat warning in the docs, telling people to be careful about other load if they choose to run concurrent repack -- it could have serious consequences. But on the other hand, it's better to *have* the tool, even if it has problems, than not have it. Keep in mind that pg_repack and pg_squeeze also have all these problems/limitations (and others), and still people use them. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-01T12:21:40Z
On Wed, Apr 1, 2026 at 5:08 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-01, Amit Kapila wrote: > > > What about if the blocking process is an autovacumm that is working to > > prevent XID wraparound? I think we already avoid killing it in such > > cases. > > If we just let REPACK finish, it will also renew the table's XID, so > autovacuum is not needed in that case. I mean, there's no reason to let > autovacuum process the contents of a table that is going to be replaced > completely. > > One potentially problematic case would be that an emergency autovacuum > has been running for a long time and about to finish, and REPACK is > started. But in that case, autovacuum already has ShareUpdateExclusive, > so REPACK wouldn't be able to start at all, which means it won't kill > autovacuum. And in the case where autovacuum is doing emergency > vacuuming, then it won't commit suicide, so it will be able to complete > before repack starts. > > > BTW, one can say that cancelling a long-running report query also > > wastes a lot of effort of the user generating such a report. Why can't > > REPACK wait for such a select to finish instead of cancelling it? > > I don't understand exactly which scenario you're concerned about. Is > there a long-running query which, after spending a lot of time running a > report, tries to upgrade its lock level on the table? Keep in mind that > this check only runs when the affected session runs the deadlock > checker, which means it's been waiting to acquire a lock for > deadlock_timeout seconds. It's not repack that kills the query. > > [ ... reflects ...] Oh, actually what Srinath proposed does exactly > that -- kill other queries. Hmm, yeah, I'm less sure about that > particular bit. > Yes, I was talking about Srinath's proposed solution. Do we need to do anything about it? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-01T12:31:21Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Wed, Apr 1, 2026 at 3:13 PM Antonin Houska <ah@cybertec.at> wrote: > > > > Nevertheless, I'm not sure it's a good idea for snapbuild.c to handle special > > cases like REPACK. Instead, I'm thinking if snapbuild.c can safely ignore XIDs > > of backends connected to databases other than the one we're decoding. > > > > What if such transactions have made changes in the global catalog? > Even if that won't matter, I feel such a change would be quite > fundamental to snapbuild machinery and changing at this point would be > risky. I had thought that catalog is usually needed only to retrieve the tuple descriptor, but regression tests with some Assert() statements prove now that shared catalogs can be accessed too. So that idea does not seem to be useful. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-01T12:35:01Z
On Wed, Apr 1, 2026 at 5:12 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-01, Amit Kapila wrote: > > > BTW, is the reason to skip REPACK while building a snapshot is that it > > can take a long time to finish? > > As I understand the issue, yes, that's precisely the problem: if you > have one REPACK running, then starting a second REPACK (which requires > building a new snapshot) would have to wait until the first REPACK is > over. In other words, you wouldn't be able to have two repacks running > concurrently. This sounds like a problematic requirement. So having > snapbuild ignore REPACK is there to allow the second REPACK to work at > all. But more generally, *all* users of snapbuild would be prevented > from starting until REPACK is done; so if you have a very very large > table that takes a long time to repack, then everything would be blocked > behind it until it completes, which sounds extremely unpleasant. > > So, if we're unable to get this particular patch in, we would have to > have a big fat warning in the docs, telling people to be careful about > other load if they choose to run concurrent repack -- it could have > serious consequences. > Right, I think during this time logical workers will keep timing out and restarting without any progress because during this wait, we won't be sending keep_alive messages. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-01T13:20:44Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > So, if we're unable to get this particular patch in, we would have to > have a big fat warning in the docs, telling people to be careful about > other load if they choose to run concurrent repack -- it could have > serious consequences. But on the other hand, it's better to *have* the > tool, even if it has problems, than not have it. Keep in mind that > pg_repack and pg_squeeze also have all these problems/limitations (and > others), and still people use them. 1. To be precise, pg_squeeze has this limitation. pg_repack does not use logical replication. 2. I expect the limitation of PG core to be relaxed in versions > 19, as long as we integrate the MVCC safety feature. REPACK will then run w/o XID most of the time (XID will only be needed for catalog changes), so other decoding backends won't need to wait for its completion. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-01T14:55:54Z
Hello, i was fuzz testing v48 , and found a crash when REPACK (concurrently) itself errors out, 1) while running create table test(id int); REPACK (concurrently) test; TBH i didn't knew this, sometimes it's better to not know "rules" ;) [NOTE: maybe we should add that we can't run REPACK (concurrently) on table without identity index or primary key into repack.sgml] ERROR: cannot process relation "test" 2026-04-01 19:06:31.211 IST [2495575] HINT: Relation "test" has no identity index. 2026-04-01 19:06:31.211 IST [2495575] STATEMENT: repack (concurrently) test; TRAP: failed Assert("proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]"), File: "procarray.c", Line: 719, PID: 2495575 postgres: srinath postgres [local] REPACK(ExceptionalCondition+0x98)[0xaaaaad938d84] postgres: srinath postgres [local] REPACK(ProcArrayEndTransaction+0x1f0)[0xaaaaad6c15fc] postgres: srinath postgres [local] REPACK(+0x210cf0)[0xaaaaad190cf0] postgres: srinath postgres [local] REPACK(+0x2117e4)[0xaaaaad1917e4] postgres: srinath postgres [local] REPACK(AbortCurrentTransaction+0x10)[0xaaaaad191740] postgres: srinath postgres [local] REPACK(PostgresMain+0x568)[0xaaaaad7116e4] postgres: srinath postgres [local] REPACK(+0x786ae0)[0xaaaaad706ae0] postgres: srinath postgres [local] REPACK(postmaster_child_launch+0x1f0)[0xaaaaad5d719c] postgres: srinath postgres [local] REPACK(+0x65ea98)[0xaaaaad5dea98] postgres: srinath postgres [local] REPACK(+0x65b650)[0xaaaaad5db650] postgres: srinath postgres [local] REPACK(PostmasterMain+0x1564)[0xaaaaad5dae1c] postgres: srinath postgres [local] REPACK(main+0x3dc)[0xaaaaad466348] /lib/aarch64-linux-gnu/libc.so.6(+0x284c4)[0xffffb40d84c4] /lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x98)[0xffffb40d8598] postgres: srinath postgres [local] REPACK(_start+0x30)[0xaaaaad06ddf0] 2026-04-01 19:06:31.800 IST [2494560] LOG: client backend (PID 2495575) was terminated by signal 6: Aborted 2) And when running REPACK (concurrently) on the same table while already a repack was running on the same table ,just to verify the deadlock occurs and gets errored out that "could not wait for concurrent REPACK" but instead got the same crash. ERROR: could not wait for concurrent REPACK 2026-04-01 12:55:39.481 IST [2397660] DETAIL: Process 2397660 waits for REPACK running on process 2397307 2026-04-01 12:55:39.481 IST [2397660] CONTEXT: waiting for ShareUpdateExclusiveLock on relation 16385 of database 5 2026-04-01 12:55:39.481 IST [2397660] STATEMENT: repack (concurrently) stress_victim ; 2026-04-01 12:55:39.497 IST [2397151] LOG: checkpoint complete: time: wrote 2056 buffers (12.5%), wrote 0 SLRU buffers; 0 WAL file(s) added, 0 removed, 0 recycled; write=206.804 s, sync=0.003 s, total=861.616 s; sync files=17, longest=0.002 s, average=0.001 s; distance=318978 kB, estimate=515341 kB; lsn=2/02810A60, redo lsn=2/02810910 TRAP: failed Assert("proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]"), File: "procarray.c", Line: 719, PID: 2397660 postgres: srinath postgres [local] REPACK(ExceptionalCondition+0x98)[0xaaaae7d58d84] postgres: srinath postgres [local] REPACK(ProcArrayEndTransaction+0x1f0)[0xaaaae7ae15fc] postgres: srinath postgres [local] REPACK(+0x210cf0)[0xaaaae75b0cf0] postgres: srinath postgres [local] REPACK(+0x2117e4)[0xaaaae75b17e4] postgres: srinath postgres [local] REPACK(AbortCurrentTransaction+0x10)[0xaaaae75b1740] postgres: srinath postgres [local] REPACK(PostgresMain+0x568)[0xaaaae7b316e4] postgres: srinath postgres [local] REPACK(+0x786ae0)[0xaaaae7b26ae0] postgres: srinath postgres [local] REPACK(postmaster_child_launch+0x1f0)[0xaaaae79f719c] postgres: srinath postgres [local] REPACK(+0x65ea98)[0xaaaae79fea98] postgres: srinath postgres [local] REPACK(+0x65b650)[0xaaaae79fb650] postgres: srinath postgres [local] REPACK(PostmasterMain+0x1564)[0xaaaae79fae1c] postgres: srinath postgres [local] REPACK(main+0x3dc)[0xaaaae7886348] /lib/aarch64-linux-gnu/libc.so.6(+0x284c4)[0xffff9ec984c4] /lib/aarch64-linux-gnu/libc.so.6(__libc_start_main+0x98)[0xffff9ec98598] postgres: srinath postgres [local] REPACK(_start+0x30)[0xaaaae748ddf0] 2026-04-01 12:58:18.198 IST [2397147] LOG: client backend (PID 2397660) was terminated by signal 6: Aborted the reason for this crash was ProcGlobal->statusFlags was not initialized during the start of ExecRepack , earlier Abort before reaching the proper initialization of ProcGlobal->statusFlags which was done in rebuild_relation caused this assert failure in ProcArrayEndTransaction. Here's the diff to solve this crash. diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 29ba49744eb..d44092a407a 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -284,7 +284,23 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * that others can conflict with. */ if ((params.options & CLUOPT_CONCURRENT) != 0) + { + /* + * Do not let other backends wait for our completion during their + * setup of logical replication. Unlike logical replication publisher, + * we will have XID assigned, so the other backends - whether + * walsenders involved in logical replication or regular backends + * executing also REPACK (CONCURRENTLY) - would have to wait for our + * completion before they can build their initial snapshot. It is o.k. + * for any decoding backend to ignore us because we do not change + * tuple descriptor of any table, and the data changes we write should + * not be decoded by other backends. + */ + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK; + ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; + LWLockRelease(ProcArrayLock); + } /* * If a single relation is specified, process it and we're done ... unless @@ -988,22 +1004,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose, if (concurrent) { - /* - * Do not let other backends wait for our completion during their - * setup of logical replication. Unlike logical replication publisher, - * we will have XID assigned, so the other backends - whether - * walsenders involved in logical replication or regular backends - * executing also REPACK (CONCURRENTLY) - would have to wait for our - * completion before they can build their initial snapshot. It is o.k. - * for any decoding backend to ignore us because we do not change - * tuple descriptor of any table, and the data changes we write should - * not be decoded by other backends. - */ - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); - MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK; - ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; - LWLockRelease(ProcArrayLock); - /* * The worker needs to be member of the locking group we're the leader * of. We ought to become the leader before the worker starts. The Thoughts? -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-01T17:06:53Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > i was fuzz testing v48 , and found a crash when REPACK (concurrently) itself errors out, > 1) while running > > create table test(id int); > REPACK (concurrently) test; > > TBH i didn't knew this, sometimes it's better to not know "rules" ;) > [NOTE: maybe we should add that we can't run > REPACK (concurrently) on table without identity index or primary key into repack.sgml] > > ERROR: cannot process relation "test" > 2026-04-01 19:06:31.211 IST [2495575] HINT: Relation "test" has no identity index. > 2026-04-01 19:06:31.211 IST [2495575] STATEMENT: repack (concurrently) test; > TRAP: failed Assert("proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]"), File: "procarray.c", Line: 719, PID: 2495575 > Here's the diff to solve this crash. Thanks. Attached here is v48-0006 fixed. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-01T21:52:12Z
On 2026-Apr-01, Antonin Houska wrote: > Thanks. Attached here is v48-0006 fixed. Ah thanks, I incorporated this in the series and rebased, and here's also a quick and simple additional patch that adds a GUC max_repack_replication_slots which are especially there to support REPACK. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-02T00:19:42Z
Hi Antonin, On Wed, Apr 1, 2026 at 10:36 PM Antonin Houska <ah@cybertec.at> wrote: > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > i was fuzz testing v48 , and found a crash when REPACK (concurrently) > itself errors out, > > 1) while running > > > > create table test(id int); > > REPACK (concurrently) test; > > > > TBH i didn't knew this, sometimes it's better to not know "rules" ;) > > [NOTE: maybe we should add that we can't run > > REPACK (concurrently) on table without identity index or primary key > into repack.sgml] > > > > ERROR: cannot process relation "test" > > 2026-04-01 19:06:31.211 IST [2495575] HINT: Relation "test" has no > identity index. > > 2026-04-01 19:06:31.211 IST [2495575] STATEMENT: repack (concurrently) > test; > > TRAP: failed Assert("proc->statusFlags == > ProcGlobal->statusFlags[proc->pgxactoff]"), File: "procarray.c", Line: 719, > PID: 2495575 > > Here's the diff to solve this crash. > > Thanks. Attached here is v48-0006 fixed. > On Wed, Apr 1, 2026 at 8:25 PM Srinath Reddy Sadipiralla < srinath2133@gmail.com> wrote: > Here's the diff to solve this crash. diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c > index 29ba49744eb..d44092a407a 100644 > --- a/src/backend/commands/repack.c > +++ b/src/backend/commands/repack.c > @@ -284,7 +284,23 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool > isTopLevel) > * that others can conflict with. > */ > if ((params.options & CLUOPT_CONCURRENT) != 0) > + { > + /* > + * Do not let other backends wait for our completion during their > + * setup of logical replication. Unlike logical replication publisher, > + * we will have XID assigned, so the other backends - whether > + * walsenders involved in logical replication or regular backends > + * executing also REPACK (CONCURRENTLY) - would have to wait for our > + * completion before they can build their initial snapshot. It is o.k. + * for any decoding backend to ignore us because we do not change > + * tuple descriptor of any table, and the data changes we write should > + * not be decoded by other backends. > + */ > + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); > MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK; > + ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; > + LWLockRelease(ProcArrayLock); > + } > > /* > * If a single relation is specified, process it and we're done ... unless > @@ -988,22 +1004,6 @@ rebuild_relation(Relation OldHeap, Relation index, > bool verbose, > > if (concurrent) > { > - /* > - * Do not let other backends wait for our completion during their > - * setup of logical replication. Unlike logical replication publisher, > - * we will have XID assigned, so the other backends - whether > - * walsenders involved in logical replication or regular backends > - * executing also REPACK (CONCURRENTLY) - would have to wait for our > - * completion before they can build their initial snapshot. It is o.k. > - * for any decoding backend to ignore us because we do not change > - * tuple descriptor of any table, and the data changes we write should > - * not be decoded by other backends. > - */ > - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); > - MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK; > - ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; > - LWLockRelease(ProcArrayLock); > - > /* > * The worker needs to be member of the locking group we're the leader > * of. We ought to become the leader before the worker starts. The i think as i did earlier in the diff, shouldn't we remove the duplicate code from rebuild_relation, am i missing something? -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/ -
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-02T00:35:45Z
On Sat, Mar 28, 2026 at 12:12 AM Srinath Reddy Sadipiralla < srinath2133@gmail.com> wrote: > Hi Alvaro, > > On Fri, Mar 27, 2026 at 12:45 AM Alvaro Herrera <alvherre@alvh.no-ip.org> > wrote: > >> >> I don't disagree with changing this, but AFAICS the patch as presented >> provokes multiple test failures. >> > > Fixed with the attached patch. > Just want to remind about this patch. -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-02T08:27:21Z
On 2026-Apr-02, Srinath Reddy Sadipiralla wrote: > On Sat, Mar 28, 2026 at 12:12 AM Srinath Reddy Sadipiralla < > srinath2133@gmail.com> wrote: > > > Hi Alvaro, > > > >> I don't disagree with changing this, but AFAICS the patch as presented > >> provokes multiple test failures. > > > > Fixed with the attached patch. > > Just want to remind about this patch. Yeah, it doesn't apply anymore. I rebased it some time ago, but it still failed a few tests -- I'm guessing you don't have either TAP tests or injection points enabled, which would explain why you don't see those failures. So please rebase it against v49, but also look into making it pass all tests (I suggest making it go through CI). Thanks, -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "We’ve narrowed the problem down to the customer’s pants being in a situation of vigorous combustion" (Robert Haas, Postgres expert extraordinaire)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-03T12:08:38Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-01, Antonin Houska wrote: > > > Thanks. Attached here is v48-0006 fixed. > > Ah thanks, I incorporated this in the series and rebased, and here's > also a quick and simple additional patch that adds a GUC > max_repack_replication_slots which are especially there to support > REPACK. This is an alternative implementation of 0006, allowing one backend running REPACK (CONCURRENTLY) in a database, instead of one backend in a cluster. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-03T14:59:09Z
On 2026-Apr-03, Antonin Houska wrote: > This is an alternative implementation of 0006, allowing one backend running > REPACK (CONCURRENTLY) in a database, instead of one backend in a cluster. Thanks! so I'm removing the previous one and taking this one. Here's a v50: - In testing, I noticed that we could sometimes request a Flush for a WAL position that hasn't been written yet. This is due to my replacing the original code that wrote a dummy xlog record that we could flush, with a call to XLogGetInsertRecPtr(). So we'd get an error like LOG: request to flush past end of generated WAL; request 0/15CF0018, current position 0/15CF000 Antonin promptly noticed that this is because XLogGetInsertRecPtr() gets the LSN past the segment header, which is 18 bytes wrong. So the fix here is to use XLogGetInsertEndRecPtr() instead. - My testing also uncovered a problem with exclusion constraints; tables with them would fail to repack like ERROR: exclusion constraint record missing for rel temporal_fk_mltrng2mltrng_pk_repacknew Antonin sent a patch to create copies of the constraints on the transient index, which seems like it fixes the problem. Those constraints are obviously discarded together with the transient index. - I polished the patch to reserve replication slots for REPACK. Given the new implementation of 0006 that was submitted implies that we can now run multiple repacks concurrently, I changed the default of 1 to 5. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Here's a general engineering tip: if the non-fun part is too complex for you to figure out, that might indicate the fun part is too ambitious." (John Naylor) https://postgr.es/m/CAFBsxsG4OWHBbSDM%3DsSeXrQGOtkPiOEOuME4yD7Ce41NtaAD9g%40mail.gmail.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-03T15:58:16Z
On Thu, Apr 2, 2026 at 1:57 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Yeah, it doesn't apply anymore. I rebased it some time ago, but it > still failed a few tests -- I'm guessing you don't have either TAP tests > or injection points enabled, which would explain why you don't see those > failures. So please rebase it against v49, but also look into making it > pass all tests (I suggest making it go through CI). > Rebased on V50 and check-world passed. -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-03T17:24:30Z
On 2026-Apr-03, Antonin Houska wrote: > diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c > index 00b21ede481..c25dbeadff3 100644 > --- a/src/backend/commands/repack_worker.c > +++ b/src/backend/commands/repack_worker.c > @@ -233,6 +234,13 @@ repack_setup_logical_decoding(Oid relid) > > EnsureLogicalDecodingEnabled(); > > + /* > + * By declaring that our output plugin does not need shared catalogs, we > + * avoid waiting for completion of transactions running in other databases > + * than the one we're connected to. > + */ > + accessSharedCatalogsInDecoding = false; > + > /* > * Neither prepare_write nor do_write callback nor update_progress is > * useful for us. I find this reliance on a global variable for this a bit icky. Would it work to instead change the CreateInitDecodingContext() signature, so that instead of "bool need_full_snapshot" it has a three-valued boolean to distinguish the two cases from the original plus this new one? I think the value could be stored in LogicalDecodingContext, from where standby_decode() could obtain it. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "The important things in the world are problems with society that we don't understand at all. The machines will become more complicated but they won't be more complicated than the societies that run them." (Freeman Dyson)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-03T19:31:48Z
On 2026-Apr-03, Alvaro Herrera wrote: > - I polished the patch to reserve replication slots for REPACK. Given > the new implementation of 0006 that was submitted implies that we can > now run multiple repacks concurrently, I changed the default of 1 to 5. Srinath let me know that this new part was causing CI failures on Windows. This version v51 should be okay (or, at least, it passes for me on CI). Additional changes worth mentioning: - I think it's nicer for the index_create() API to get a bit to indicate suppression of progress reporting; so existing callers don't need to do anything. I guess this is mostly a matter of taste. - I incorporated Srinath's fix for the PreventInTransactionBlock block. - When CheckSlotRequirements() is to complain about "max_replication_slots or max_repack_replication_slots", it seems actually nicer to say exactly which one of these is the cause of the problem. This is easy to change; patch 0010 does it; it requires passing down a "repack" flag all the way from CheckLogicalDecodingRequirements() and it needs to add an argument to CreateInitDecodingContext(), which is perhaps not so great. On the whole I'm inclined to do it anyway, but I'm about equally happy to leave it alone. This is what would change: original: ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("replication slots can only be used if \"%s\" > 0 or \"%s\" > 0", - "max_replication_slots", "max_repack_replication_slots"))); patched: ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("replication slots can only be used if \"%s\" > 0", + repack ? "max_repack_replication_slots" : "max_replication_slots")); -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "This is a foot just waiting to be shot" (Andrew Dunstan) -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-03T19:37:57Z
Hi, Sorry -- there's a doc build failure also in CI. Fixed here. Regards -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Use it up, wear it out, make it do, or do without"
-
RE: Adding REPACK [concurrently]
Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> — 2026-04-04T06:20:35Z
Dear Álvaro, While testing REPACK CONCURRENTLY command with xid_wraparound module, noticed that wraparound-autovac worker was terminated with an error like below. `` ERROR: could not wait for concurrent REPACK DETAIL: Process 41512 waits for REPACK running on process 41027 CONTEXT: waiting for ShareUpdateExclusiveLock on relation 16384 of database 5 automatic vacuum of table "postgres.public.test" ``` The behavior is different from other commands, like LOCK and maybe normal REPACK. In these cases the autovac worker would wait till the command fails. Is it an intentional behavior? If so, what is the advantage that we terminate the failsafe vacuum? Best regards, Hayato Kuroda FUJITSU LIMITED -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-04T08:50:14Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-03, Antonin Houska wrote: > > > diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c > > index 00b21ede481..c25dbeadff3 100644 > > --- a/src/backend/commands/repack_worker.c > > +++ b/src/backend/commands/repack_worker.c > > > @@ -233,6 +234,13 @@ repack_setup_logical_decoding(Oid relid) > > > > EnsureLogicalDecodingEnabled(); > > > > + /* > > + * By declaring that our output plugin does not need shared catalogs, we > > + * avoid waiting for completion of transactions running in other databases > > + * than the one we're connected to. > > + */ > > + accessSharedCatalogsInDecoding = false; > > + > > /* > > * Neither prepare_write nor do_write callback nor update_progress is > > * useful for us. > > I find this reliance on a global variable for this a bit icky. Would it > work to instead change the CreateInitDecodingContext() signature, so > that instead of "bool need_full_snapshot" it has a three-valued boolean > to distinguish the two cases from the original plus this new one? I > think the value could be stored in LogicalDecodingContext, from where > standby_decode() could obtain it. I agree that the global variable is not handy, but instead of modifying CreateInitDecodingContext(), how about adding a boolean returning callback to OutputPluginCallbacks? The point is that whether shared catalogs are needed during the decoding or not is actually property of the plugin. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-04T09:48:58Z
On 2026-Apr-04, Antonin Houska wrote: > I agree that the global variable is not handy, but instead of modifying > CreateInitDecodingContext(), how about adding a boolean returning callback to > OutputPluginCallbacks? The point is that whether shared catalogs are needed > during the decoding or not is actually property of the plugin. Oh, yeah, that sounds good to me. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "After a quick R of TFM, all I can say is HOLY CR** THAT IS COOL! PostgreSQL was amazing when I first started using it at 7.2, and I'm continually astounded by learning new features and techniques made available by the continuing work of the development team." Berend Tober, http://archives.postgresql.org/pgsql-hackers/2007-08/msg01009.php
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-04T10:19:10Z
Hello, On 2026-Apr-04, Hayato Kuroda (Fujitsu) wrote: > While testing REPACK CONCURRENTLY command with xid_wraparound module, noticed that > wraparound-autovac worker was terminated with an error like below. > > `` > ERROR: could not wait for concurrent REPACK > DETAIL: Process 41512 waits for REPACK running on process 41027 > CONTEXT: waiting for ShareUpdateExclusiveLock on relation 16384 of database 5 > automatic vacuum of table "postgres.public.test" > ``` > > The behavior is different from other commands, like LOCK and maybe normal REPACK. > In these cases the autovac worker would wait till the command fails. > > Is it an intentional behavior? If so, what is the advantage that we terminate the > failsafe vacuum? Hmm, this is intentional; see here: https://postgr.es/m/202604011050.7ya3k4ccd3hg@alvherre.pgsql Note that in order for this to happen, the autovacuum must be starting when the repack is already underway. The theory behind this behavior is that the autovacuum run is not useful anyway: its purpose is to advance the freeze xmin/multixact, but the repack is also going to do that. Once repack is done, autovacuum can assess again whether an emergency vacuum is needed, and launch a worker in that case. Do you think it would be better if we allowed autovacuum to continue? I'm not 100% myself of this new behavior, and it would be very good to give it some more thought. I suppose it's unfortunate that autovacuum launcher is going to try again and again to get workers to process that table, and they are going to be killed over and over. Maybe it would be better to have autovac ignore those tables. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Para tener más hay que desear menos"
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-04T13:16:58Z
Hi Alvaro, On Sat, Apr 4, 2026 at 1:01 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-03, Alvaro Herrera wrote: > > > - I polished the patch to reserve replication slots for REPACK. Given > > the new implementation of 0006 that was submitted implies that we can > > now run multiple repacks concurrently, I changed the default of 1 to 5. > > Srinath let me know that this new part was causing CI failures on > Windows. This version v51 should be okay (or, at least, it passes for > me on CI). yes indeed , TotalMaxReplicationSlots was the culprit , as the window after getting repack worked was forked , i think this was set back to 0. Thanks for fixing this with "repack" flag , initially i thought why can't we go with a macro #define TotalMaxReplicationSlots (max_replication_slots + max_repack_replication_slots) but i thought maybe in future we might need this "repack" flag to add more slot requirements. -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-04T13:55:48Z
On 2026-Apr-04, Srinath Reddy Sadipiralla wrote: > yes indeed , TotalMaxReplicationSlots was the culprit , as the window > after getting repack worked was forked , i think this was set back to 0. > Thanks for fixing this with "repack" flag , initially i thought why can't we > go with a macro > #define TotalMaxReplicationSlots (max_replication_slots + > max_repack_replication_slots) > but i thought maybe in future we might need this "repack" flag to add > more slot requirements. Yeah, the other option would have been to add the variable to restore_backend_variables() so that it's restored after the fork+exec, but that didn't seem better. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Niemand ist mehr Sklave, als der sich für frei hält, ohne es zu sein." Nadie está tan esclavizado como el que se cree libre no siéndolo (Johann Wolfgang von Goethe) -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-04T15:29:19Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-04, Antonin Houska wrote: > > > I agree that the global variable is not handy, but instead of modifying > > CreateInitDecodingContext(), how about adding a boolean returning callback to > > OutputPluginCallbacks? The point is that whether shared catalogs are needed > > during the decoding or not is actually property of the plugin. > > Oh, yeah, that sounds good to me. This is it. New callback was actually not needed, I just added a new flag to the OutputPluginOptions structure. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-04T23:53:09Z
On 2026-Apr-04, Antonin Houska wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > On 2026-Apr-04, Antonin Houska wrote: > > > > > I agree that the global variable is not handy, but instead of modifying > > > CreateInitDecodingContext(), how about adding a boolean returning callback to > > > OutputPluginCallbacks? The point is that whether shared catalogs are needed > > > during the decoding or not is actually property of the plugin. > > > > Oh, yeah, that sounds good to me. > > This is it. New callback was actually not needed, I just added a new flag to > the OutputPluginOptions structure. Thank you, I removed the previous one and picked up this one (it's 0001 here.) The only potentially troublesome thing I see with it is this change: /* * Update range of interesting xids based on the running xacts * information. We don't increase ->xmax using it, because once we are in * a consistent state we can do that ourselves and much more efficiently * so, because we only need to do it for catalog transactions since we * only ever look at those. * * NB: We only increase xmax when a catalog modifying transaction commits * (see SnapBuildCommitTxn). Because of this, xmax can be lower than * xmin, which looks odd but is correct and actually more efficient, since * we hit fast paths in heapam_visibility.c. + * + * If database specific transaction info was used during startup, the info + * for the whole cluster can make xmin go backwards. That would be bad + * because we might no longer have older XIDs in ->committed. */ - builder->xmin = running->oldestRunningXid; + if (NormalTransactionIdFollows(running->oldestRunningXid, builder->xmin)) + builder->xmin = running->oldestRunningXid; I can't see any problem with advancing the ->xmin only when it goes forward, but I wonder if it's possible to introduce any bugs this way. This bit looks funny though: /* * Advance the xmin limit for the current replication slot, to allow * vacuum to clean up the tuples this slot has been protecting. * * The reorderbuffer might have an xmin among the currently running * snapshots; use it if so. If not, we need only consider the snapshots * we'll produce later, which can't be less than the oldest running xid in * the record we're reading now. */ xmin = ReorderBufferGetOldestXmin(builder->reorder); - if (xmin == InvalidTransactionId) + /* + * Like above, do not let slot xmin go backwards. + */ + if (xmin == InvalidTransactionId && !db_specific) xmin = running->oldestRunningXid; I probably need some sleep, but this doesn't make sense to me. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "No me acuerdo, pero no es cierto. No es cierto, y si fuera cierto, no me acuerdo." (Augusto Pinochet a una corte de justicia) -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-05T07:54:49Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-04, Antonin Houska wrote: > > > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > > > On 2026-Apr-04, Antonin Houska wrote: > > > > > > > I agree that the global variable is not handy, but instead of modifying > > > > CreateInitDecodingContext(), how about adding a boolean returning callback to > > > > OutputPluginCallbacks? The point is that whether shared catalogs are needed > > > > during the decoding or not is actually property of the plugin. > > > > > > Oh, yeah, that sounds good to me. > > > > This is it. New callback was actually not needed, I just added a new flag to > > the OutputPluginOptions structure. > > Thank you, I removed the previous one and picked up this one (it's 0001 > here.) The only potentially troublesome thing I see with it is this change: > > /* > * Update range of interesting xids based on the running xacts > * information. We don't increase ->xmax using it, because once we are in > * a consistent state we can do that ourselves and much more efficiently > * so, because we only need to do it for catalog transactions since we > * only ever look at those. > * > * NB: We only increase xmax when a catalog modifying transaction commits > * (see SnapBuildCommitTxn). Because of this, xmax can be lower than > * xmin, which looks odd but is correct and actually more efficient, since > * we hit fast paths in heapam_visibility.c. > + * > + * If database specific transaction info was used during startup, the info > + * for the whole cluster can make xmin go backwards. That would be bad > + * because we might no longer have older XIDs in ->committed. > */ > - builder->xmin = running->oldestRunningXid; > + if (NormalTransactionIdFollows(running->oldestRunningXid, builder->xmin)) > + builder->xmin = running->oldestRunningXid; > > > I can't see any problem with advancing the ->xmin only when it goes > forward, but I wonder if it's possible to introduce any bugs this way. > > > This bit looks funny though: > > /* > * Advance the xmin limit for the current replication slot, to allow > * vacuum to clean up the tuples this slot has been protecting. > * > * The reorderbuffer might have an xmin among the currently running > * snapshots; use it if so. If not, we need only consider the snapshots > * we'll produce later, which can't be less than the oldest running xid in > * the record we're reading now. > */ > xmin = ReorderBufferGetOldestXmin(builder->reorder); > - if (xmin == InvalidTransactionId) > + /* > + * Like above, do not let slot xmin go backwards. > + */ > + if (xmin == InvalidTransactionId && !db_specific) > xmin = running->oldestRunningXid; > > I probably need some sleep, but this doesn't make sense to me. ok, maybe just skip the whole cleanup in that special case. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-05T11:50:12Z
On 2026-Apr-05, Antonin Houska wrote: > ok, maybe just skip the whole cleanup in that special case. Hmm, should we make this test only in the db_specific case? Doing it unconditionally makes me a bit nervous (maybe because I don't fully understand historic snapshot building). Anyway I just pushed the addition of a progress-suppression bit to index_create() interface. Here's the rest of the series. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Ninguna manada de bestias tiene una voz tan horrible como la humana" (Orual)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-05T16:30:41Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-05, Antonin Houska wrote: > > > ok, maybe just skip the whole cleanup in that special case. > > Hmm, should we make this test only in the db_specific case? Doing it > unconditionally makes me a bit nervous (maybe because I don't fully > understand historic snapshot building). I thought about adding Assert(db_specific) in front of the new return statement. So what you suggest makes sense to me. As far as I understand, the xl_running_xacts record is not directly involved in the snapshot build. Rather, the list of XIDs for snapshots is created and updated by processing COMMIT and ABORT records. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-05T18:56:16Z
Hi, So I've been trying to understand the "Introduce an option to make logical replication database specific." patch and I have to confess I just cannot. As far as I can read, the point is that if we reach SnapBuildProcessRunningXacts() when db_specific is true (which means standby_decode is called in an output plugin that has set need_shared_catalogs to false), _and_ we've not reached consistent state yet, then we'll call LogStandbySnapshot with our DB oid to emit a new xl_running_xacts message. So the WAL-decoding process emits WAL. I don't know if in normal conditions logical decoding processes emit WAL. If this is exceptional, I think we should add a comment. Now, this additional WAL message will be processed by all other processes decoding WAL. Perhaps it will ignored by most of them. But most importantly, it will also reach back to ourselves, at which point we can hopefully use it to see that we might have reached consistent state within our database. Then we know our snapshot is ready to be used. Is this correct? I think the reason it's safe to skip a lot of the processing caused by this additional process, is that xl_running_xacts messages are also emitted in other places in a non-database specific manner. So all the other placecs that are emitting that message continue to exist and cause logical-decoders operate in the same way as before. I think we should sprinkle lots of comments in several places about this. For example, I propose that standby_redo() should have something like * If 'dbid' is valid, only gather transactions running in that database. + * Such records should not be the only ones emitted, because this has + * potentially dangerous side-effects which makes some places ignore them: + * + * 1. SnapBuildProcessRunningXacts will skip computing the xmin and restart + * point from its input record if the record's xmin is older that the + * snapbuilder's current xmin; this should normally be fine because that + * information will be updated from other xl_running_xacts records. + * 2. standby_redo will likewise skip processing such a record * (are there other things that should be mentioned?) Also, LogStandbySnapshot() should have a comment explaining that passing a valid dboid is a weird corner case which is to be used with care, and that functions X Y and Z are going to ignore snapshots carrying a valid dbid. Why do we call SnapBuildFindSnapshot() to do this, instead of doing it directly in SnapBuildProcessRunningXacts? Seems like it would be more straightforward. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-05T20:41:50Z
Hi, So here's a v55 version of the base REPACK patches that I'm feeling comfortable calling very close to committable. I'm going to give an additional read tomorrow and maybe make cosmetic adjustments, but there should be nothing substantial. Of course, the subsequent additions in the other patches of v54 are still in the cards, and they are most likely essential. Changes compared to v54: - changed reform_tuple() to not deform the tuple if no attributes are going to be touched. We can simply make a copy instead, which I suspect is considerably cheaper (but I didn't measure). - cleaned up worker shmem shutdown callback. I think it's how it is because it copied parallel worker code, but that has a weird structure for --as far as I can see-- no good reason (we oughta change it too) - renamed the worker from pgoutput_repack to pgrepack. (Note that this is an internal name that users don't face.) - reverted some unnecessary changes to master Thanks -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-06T05:24:23Z
On Sat, Apr 4, 2026 at 3:49 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-04, Hayato Kuroda (Fujitsu) wrote: > > > While testing REPACK CONCURRENTLY command with xid_wraparound module, noticed that > > wraparound-autovac worker was terminated with an error like below. > > > > `` > > ERROR: could not wait for concurrent REPACK > > DETAIL: Process 41512 waits for REPACK running on process 41027 > > CONTEXT: waiting for ShareUpdateExclusiveLock on relation 16384 of database 5 > > automatic vacuum of table "postgres.public.test" > > ``` > > > > The behavior is different from other commands, like LOCK and maybe normal REPACK. > > In these cases the autovac worker would wait till the command fails. > > > > Is it an intentional behavior? If so, what is the advantage that we terminate the > > failsafe vacuum? > > Hmm, this is intentional; see here: > https://postgr.es/m/202604011050.7ya3k4ccd3hg@alvherre.pgsql > Note that in order for this to happen, the autovacuum must be starting > when the repack is already underway. The theory behind this behavior is > that the autovacuum run is not useful anyway: its purpose is to advance > the freeze xmin/multixact, but the repack is also going to do that. > Once repack is done, autovacuum can assess again whether an emergency > vacuum is needed, and launch a worker in that case. > But won't it delay in update of datfrozenxid/datminmxid? For example, say repack errored out due to some reason, won't it further delay the update to datfrozenxid/datminmxid which in turn can delay truncate of clog. Is it possible that after repack errored out the launcher delays in picking the same table again which further add to such a delay? > Do you think it would be better if we allowed autovacuum to continue? > IIUC, the disadvantage of letting it continue is that if repack is successful then we have unnecessarily occupied the worker which won't do anything useful. If we want to not let autovacuum continue then we should somehow deal with boundary cases which shouldn't lead to delay in making progress to update frozen xids. > I'm not 100% myself of this new behavior, and it would be very good to > give it some more thought. > > > I suppose it's unfortunate that autovacuum launcher is going to try > again and again to get workers to process that table, and they are going > to be killed over and over. Maybe it would be better to have autovac > ignore those tables. > I feel that would be better at least when we know that the repack concurrently command is already in progress. It can help avoid launching workers again and again, especially when repack concurrently command is going to take a long time. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-06T09:03:44Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > So I've been trying to understand the "Introduce an option to make > logical replication database specific." patch and I have to confess I > just cannot. > > As far as I can read, the point is that if we reach > SnapBuildProcessRunningXacts() when db_specific is true (which means > standby_decode is called in an output plugin that has set > need_shared_catalogs to false), _and_ we've not reached consistent state > yet, then we'll call LogStandbySnapshot with our DB oid to emit a new > xl_running_xacts message. Right. > So the WAL-decoding process emits WAL. I don't know if in normal > conditions logical decoding processes emit WAL. If this is exceptional, > I think we should add a comment. Emitting WAL in logical decoding is not exceptional: SnapBuildWaitSnapshot() already calls LogStandbySnapshot(), in order to get to the next phase. > Now, this additional WAL message will be processed by all other > processes decoding WAL. Perhaps it will ignored by most of them. Right. That's one thing that I realized late yesterday, after having posted the latest version of the patch. In SnapBuildProcessRunningXacts(), we need if (OidIsValid(running->dbid)) return; rather than if (db_specific) return; because other backends can also generate their database-specific records. > But > most importantly, it will also reach back to ourselves, at which point > we can hopefully use it to see that we might have reached consistent > state within our database. Then we know our snapshot is ready to be > used. > > Is this correct? Yes. > I think the reason it's safe to skip a lot of the processing caused by > this additional process, is that xl_running_xacts messages are also > emitted in other places in a non-database specific manner. So all the > other placecs that are emitting that message continue to exist and > cause logical-decoders operate in the same way as before. Yes. I'm still thinking though if, after having used the database-specific record to reach CONSITENT, we sould enforce one cluster-wide record, so that the cleanup (in "our backend") takes place sooner rather than later. Not sure about that. > I think we should sprinkle lots of comments in several places about > this. For example, I propose that standby_redo() should have something > like > > * If 'dbid' is valid, only gather transactions running in that database. > + * Such records should not be the only ones emitted, because this has > + * potentially dangerous side-effects which makes some places ignore them: > + * > + * 1. SnapBuildProcessRunningXacts will skip computing the xmin and restart > + * point from its input record if the record's xmin is older that the > + * snapbuilder's current xmin; this should normally be fine because that > + * information will be updated from other xl_running_xacts records. > + * 2. standby_redo will likewise skip processing such a record > * > (are there other things that should be mentioned?) I added something like that, but - due to the reference to SnapBuildProcessRunningXacts() - less verbose about snapbuild.c. > Also, LogStandbySnapshot() should have a comment explaining that passing > a valid dboid is a weird corner case which is to be used with care, and > that functions X Y and Z are going to ignore snapshots carrying a valid > dbid. One more check added in standby_decode() (and mentioned in that function in the comment). > Why do we call SnapBuildFindSnapshot() to do this, instead of doing it > directly in SnapBuildProcessRunningXacts? Seems like it would be more > straightforward. Yes, fixed. One more problem I found when testing w/o background worker (contrib/test_decoding) was that accessSharedCatalogsInDecoding was not set back to true. Both AllocateSnapshotBuilder() FreeSnapshotBuilder() do that now. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
vignesh C <vignesh21@gmail.com> — 2026-04-06T09:15:31Z
On Mon, 6 Apr 2026 at 02:12, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hi, > > So here's a v55 version of the base REPACK patches that I'm feeling > comfortable calling very close to committable. I'm going to give an > additional read tomorrow and maybe make cosmetic adjustments, but there > should be nothing substantial. Of course, the subsequent additions in > the other patches of v54 are still in the cards, and they are most > likely essential. Few comments: 1) Can we add a comment why we should error out here, as repack concurrently requires this whereas repack does not require this check. Even if it is required for decoding can't it be handled by replica identity full: + /* + * If the identity index is not set due to replica identity being, PK + * might exist. + */ + ident_idx = RelationGetReplicaIndex(rel); + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) + ident_idx = rel->rd_pkindex; + if (!OidIsValid(ident_idx)) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot process relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has no identity index.", + RelationGetRelationName(rel))); 2) Do you think it will be good to add a test to simulate a case where one of the swap_replation_files is successful and a failure after that. We can verify that the oid should still point to old oids: + /* + * Even ShareUpdateExclusiveLock should have prevented others from + * creating / dropping indexes (even using the CONCURRENTLY option), so we + * do not need to check whether the lists match. + */ + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + Oid mapped_tables[4] = {0}; + + swap_relation_files(ind_old, ind_new, + (old_table_oid == RelationRelationId), + false, /* swap_toast_by_content */ + true, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); 3) I'm not sure if this change should be part of this patch: diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl index b49007167b0..2e7f1054e62 100644 --- a/src/backend/storage/lmgr/generate-lwlocknames.pl +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl @@ -162,7 +162,7 @@ while (<$lwlocklist>) die "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but " - . " missing from lwlocklist.h" + . "missing from lwlocklist.h" if $lwlock_count < scalar @wait_event_lwlocks; 4) Can we add an example for concurrently in documentation 5) Typos 5.a) "jsut" should be "just": + * operation to avoid any lock-upgrade hazards. In the concurrent case, we + * grab ShareUpdateExclusiveLock (jsut like VACUUM) for most of the 5.b In commit message "intial" should be "initial" While the "concurrent data" changes are applied at specific stages (we cannot do that until the intial copy is finished and indexes are built), a background 6) This includes are not required in repack.c, for me it could compile without it: +#include "access/detoast.h" +#include "access/xloginsert.h" +#include "catalog/pg_control.h" 7) Can you check if the copyright year mentioned for the new files are correct, as different files mention different years like: /*------------------------------------------------------------------------- * * pgrepack.c * Logical Replication output plugin for REPACK command * * Copyright (c) 2012-2026, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/replication/pgrepack/pgrepack.c * *------------------------------------------------------------------------- */ /*------------------------------------------------------------------------- * * repack_worker.c * Implementation of the background worker for ad-hoc logical decoding * during REPACK (CONCURRENTLY). * * * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * * * IDENTIFICATION * src/backend/commands/repack_worker.c Meson.build file: # Copyright (c) 2022-2026, PostgreSQL Global Development Group Regards, Vignesh -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-06T09:38:20Z
On 2026-Apr-06, vignesh C wrote: > On Mon, 6 Apr 2026 at 02:12, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Few comments: Thanks for reviewing this patch! > 1) Can we add a comment why we should error out here, as repack > concurrently requires this whereas repack does not require this check. > Even if it is required for decoding can't it be handled by replica > identity full: > + /* > + * If the identity index is not set due to replica identity being, PK > + * might exist. > + */ > + ident_idx = RelationGetReplicaIndex(rel); > + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) > + ident_idx = rel->rd_pkindex; > + if (!OidIsValid(ident_idx)) > + ereport(ERROR, > + > errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), > + errmsg("cannot process relation \"%s\"", > + RelationGetRelationName(rel)), > + errhint("Relation \"%s\" has no > identity index.", > + RelationGetRelationName(rel))); Ah, I was just rewriting that comment moments ago. I think we could make it work with replica identity full in theory, but I'm guessing it would be useless. You really need an index that lets you locate the affected tuples; otherwise the replay would take forever. If the table is big, that would make the whole thing unworkable. If the table isn't big, then you can probably just use straight repack without too much disruption. /* * 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. */ Now, it's possible to have a non-unique index on some columns (not good enough to be replica identity) which gives you a list of candidate tuples, and then the implementation chooses one based on the other columns which are present in the FULL replica identity. (This seems a bit dangerous, because there might be multiple matching tuples; and how do you choose?) Now let's further suppose you can narrow down to a single tuple; in that case, using FULL would work. However, as I said, this requires more implementation effort. We could entertain a patch for this during the pg20 cycle, though I'm doubtful that it would really be worth your while. > 2) Do you think it will be good to add a test to simulate a case where > one of the swap_replation_files is successful and a failure after > that. We can verify that the oid should still point to old oids: Hmm, it's not clear to me in which cases this can happen. Are you thinking that the first swap_replation_files call dies because of out-of-memory? Note that the really weird cases, like pg_class or mapped relations, are directly rejected. So we don't get into the branch with !RelFileNumberIsValid, and so on. I mean -- I'm not opposed to adding a test case for it. But I suspect it's going to be somewhat annoying to write. > 3) I'm not sure if this change should be part of this patch: > diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl > b/src/backend/storage/lmgr/generate-lwlocknames.pl > index b49007167b0..2e7f1054e62 100644 > --- a/src/backend/storage/lmgr/generate-lwlocknames.pl > +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl > @@ -162,7 +162,7 @@ while (<$lwlocklist>) > > die > "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but " > - . " missing from lwlocklist.h" > + . "missing from lwlocklist.h" > if $lwlock_count < scalar @wait_event_lwlocks; Yeah, will remove. > 4) Can we add an example for concurrently in documentation > 5) Typos Sure. > 6) This includes are not required in repack.c, for me it could compile > without it: > +#include "access/detoast.h" > +#include "access/xloginsert.h" > +#include "catalog/pg_control.h" > 7) Can you check if the copyright year mentioned for the new files are > correct, as different files mention different years like: I'll look into these, thanks for pointing it out. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Linux transformó mi computadora, de una `máquina para hacer cosas', en un aparato realmente entretenido, sobre el cual cada día aprendo algo nuevo" (Jaime Salinas) -
Re: Adding REPACK [concurrently]
vignesh C <vignesh21@gmail.com> — 2026-04-06T10:01:27Z
On Mon, 6 Apr 2026 at 15:08, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-06, vignesh C wrote: > > > On Mon, 6 Apr 2026 at 02:12, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > Few comments: > > Thanks for reviewing this patch! > > > 1) Can we add a comment why we should error out here, as repack > > concurrently requires this whereas repack does not require this check. > > Even if it is required for decoding can't it be handled by replica > > identity full: > > + /* > > + * If the identity index is not set due to replica identity being, PK > > + * might exist. > > + */ > > + ident_idx = RelationGetReplicaIndex(rel); > > + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) > > + ident_idx = rel->rd_pkindex; > > + if (!OidIsValid(ident_idx)) > > + ereport(ERROR, > > + > > errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), > > + errmsg("cannot process relation \"%s\"", > > + RelationGetRelationName(rel)), > > + errhint("Relation \"%s\" has no > > identity index.", > > + RelationGetRelationName(rel))); > > Ah, I was just rewriting that comment moments ago. I think we could > make it work with replica identity full in theory, but I'm guessing it > would be useless. You really need an index that lets you locate the > affected tuples; otherwise the replay would take forever. If the table > is big, that would make the whole thing unworkable. If the table isn't > big, then you can probably just use straight repack without too much > disruption. > > /* > * 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. > */ Should this be mentioned in XXX comment: It might be possible to have repack work with a FULL replica identity; however that requires more work and is not implemented yet. > > 2) Do you think it will be good to add a test to simulate a case where > > one of the swap_replation_files is successful and a failure after > > that. We can verify that the oid should still point to old oids: > > Hmm, it's not clear to me in which cases this can happen. Are you > thinking that the first swap_replation_files call dies because of > out-of-memory? Yes, I was thinking of that. > Note that the really weird cases, like pg_class or mapped relations, are > directly rejected. So we don't get into the branch with > !RelFileNumberIsValid, and so on. > > I mean -- I'm not opposed to adding a test case for it. But I suspect > it's going to be somewhat annoying to write. I will verify this scenario through debugger. Regards, Vignesh -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-06T10:14:57Z
Hello! On Sun, Apr 5, 2026 at 10:41 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > So here's a v55 version of the base REPACK patches that I'm feeling > comfortable calling very close to committable. Some comments for v55. repack.c:2725 if (!VARATT_IS_EXTERNAL(varlen)) continue; I think it should be VARATT_IS_EXTERNAL_INDIRECT - the same as in pgrepack:244. Also, after natt_ext--; I think it worth to add Assert(natt_ext >= 0); or Assert(natt_ext == 0); but in another place. Or exit early with for (int i = 0; i < desc->natts && natt_ext > 0; i++) ---------------------------- repack.c:2587 table_tuple_insert(rel, slot, GetCurrentCommandId(true), HEAP_INSERT_NO_LOGICAL, NULL); More idiomatic to use TABLE_INSERT_NO_LOGICAL instead. ---------------------------- repack.c:2696 ExecForceStoreHeapTuple(tup, slot, false); AFAIU there is a memory leak here. Memory allocated above (for tuple) is not freed in any way, because shouldFree == false. Also, ExecClearTuple (tts_virtual_clear for virtual tuples) requires TTS_SHOULDFREE to be set to free anything. ------------------------- grab ShareUpdateExclusiveLock (jsut like VACUUM typo in "just" -------------------------- "If the identity index is not set due to replica identity being, PK" Missing "FULL" after "being"? ------------------------- Commit message: "intial copy" -> "initial copy" "backed performing REPACK" -> "backend performing REPACK" Best regards, Mikhail. -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-06T10:48:23Z
On 2026-Apr-06, Mihail Nikalayeu wrote: > repack.c:2725 > > if (!VARATT_IS_EXTERNAL(varlen)) > continue; > > I think it should be VARATT_IS_EXTERNAL_INDIRECT - the same as in pgrepack:244. Right. > Also, after > > natt_ext--; > > I think it worth to add > > Assert(natt_ext >= 0); > > or Assert(natt_ext == 0); but in another place. > > Or exit early with > for (int i = 0; i < desc->natts && natt_ext > 0; i++) Hmm, how about something like this? natt_ext--; if (natt_ext < 0) ereport(ERROR, errcode(ERRCODE_DATA_CORRUPTED), errmsg("insufficient number of attributes stored separately")); I'd like to give more details, such as the tuple's identity, but that seems hard ... > ---------------------------- > repack.c:2587 > > table_tuple_insert(rel, slot, GetCurrentCommandId(true), > HEAP_INSERT_NO_LOGICAL, NULL); > > More idiomatic to use TABLE_INSERT_NO_LOGICAL instead. Ah right. > ---------------------------- > repack.c:2696 > > ExecForceStoreHeapTuple(tup, slot, false); > > AFAIU there is a memory leak here. Memory allocated above (for tuple) > is not freed in any way, because shouldFree == false. > Also, ExecClearTuple (tts_virtual_clear for virtual tuples) requires > TTS_SHOULDFREE to be set to free anything. Yeah but I don't want the virtual tuple to be materialized (which would happen in tts_virtual_materialize if I set shouldFree=true). The memory should be freed in ResetPerTupleExprContext(chgcxt->cc_estate); anyway, right? Maybe deserves a comment. > ------------------------- > grab ShareUpdateExclusiveLock (jsut like VACUUM > > typo in "just" Right. > -------------------------- > > "If the identity index is not set due to replica identity being, PK" > > Missing "FULL" after "being"? Ah yeah, I rewrote this. > ------------------------- > > Commit message: > > "intial copy" -> "initial copy" > "backed performing REPACK" -> "backend performing REPACK" Thanks! -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Sallah, I said NO camels! That's FIVE camels; can't you count?" (Indiana Jones) -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-06T11:21:32Z
Hi! On Mon, Apr 6, 2026 at 12:48 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > Hmm, how about something like this? > > natt_ext--; > if (natt_ext < 0) > ereport(ERROR, > errcode(ERRCODE_DATA_CORRUPTED), > errmsg("insufficient number of attributes stored separately")); I think it is ok. > Yeah but I don't want the virtual tuple to be materialized (which would > happen in tts_virtual_materialize if I set shouldFree=true). The memory > should be freed in > ResetPerTupleExprContext(chgcxt->cc_estate); > anyway, right? Maybe deserves a comment. Not sure, ResetPerTupleExprContext resets just "ExecutorState". But slots are created in another memory context. Also, we can't reset slot->tts_mcxt itself - it will free the slot also. -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-06T11:23:57Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-06, vignesh C wrote: > > 2) Do you think it will be good to add a test to simulate a case where > > one of the swap_replation_files is successful and a failure after > > that. We can verify that the oid should still point to old oids: > > Hmm, it's not clear to me in which cases this can happen. Are you > thinking that the first swap_replation_files call dies because of > out-of-memory? > > Note that the really weird cases, like pg_class or mapped relations, are > directly rejected. So we don't get into the branch with > !RelFileNumberIsValid, and so on. > > I mean -- I'm not opposed to adding a test case for it. But I suspect > it's going to be somewhat annoying to write. After all, I think we'd end up testing whether transaction abort works correctly. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-06T11:56:33Z
On Mon, Apr 6, 2026 at 3:08 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-06, vignesh C wrote: > > > On Mon, 6 Apr 2026 at 02:12, Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > Few comments: > > Thanks for reviewing this patch! > > > 1) Can we add a comment why we should error out here, as repack > > concurrently requires this whereas repack does not require this check. > > Even if it is required for decoding can't it be handled by replica > > identity full: > > + /* > > + * If the identity index is not set due to replica identity being, PK > > + * might exist. > > + */ > > + ident_idx = RelationGetReplicaIndex(rel); > > + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) > > + ident_idx = rel->rd_pkindex; > > + if (!OidIsValid(ident_idx)) > > + ereport(ERROR, > > + > > errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), > > + errmsg("cannot process relation \"%s\"", > > + RelationGetRelationName(rel)), > > + errhint("Relation \"%s\" has no > > identity index.", > > + RelationGetRelationName(rel))); > > Ah, I was just rewriting that comment moments ago. I think we could > make it work with replica identity full in theory, but I'm guessing it > would be useless. You really need an index that lets you locate the > affected tuples; otherwise the replay would take forever. If the table > is big, that would make the whole thing unworkable. If the table isn't > big, then you can probably just use straight repack without too much > disruption. > > /* > * 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. > */ > > Now, it's possible to have a non-unique index on some columns (not good > enough to be replica identity) which gives you a list of candidate > tuples, and then the implementation chooses one based on the other > columns which are present in the FULL replica identity. (This seems a > bit dangerous, because there might be multiple matching tuples; and how > do you choose?) Now let's further suppose you can narrow down to a > single tuple; in that case, using FULL would work. > I think this is already working for apply worker where we compare tuples if the index is non_uniuqe, see FindReplTupleInLocalRel. > However, as I said, > this requires more implementation effort. We could entertain a patch > for this during the pg20 cycle, though I'm doubtful that it would really > be worth your while. > It is fine to support it later in pg20. In general, I wonder if we have evaluated whether some of the apply-worker infrastructure could have been reused here? -- With Regards, Amit Kapila. -
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-06T21:11:30Z
Hi, I just saw this got committed and wanted to briefly play with it. It works nicely! Except that at first I tried this in a debugging build, and was briefly rather dismayed by the performance. It was really slow. But it's not really related to repack / the patches here. Turns out that it is to a good part due to heap_insert() ->CacheInvalidateHeapTuple() ->CacheInvalidateHeapTupleCommon() ->AssertCouldGetRelation() not being cheap and running a *lot*. Admittedly it's way worse if you build with -O0, which I tend to do to make debugging easier. In that config, the assert single-handled increases the time for a repack by 35% or so. Noah, is there any reason we need to do the AssertCouldGetRelation() before the !IsCatalogRelation(relation)? Given that the goal is to make RelationGetRelid() safe, it doesn't seem there is? It's totally valid to not have done so initially, this is a quite complicated feature: I saw this is using individual heap_insert()s during the heapam_relation_copy_for_cluster(). Doing individual WAL logged inserts isn't exactly cheap or efficient from a WAL volume perspective... Is there anything other than round tuits preventing us from using multi_insert? That actually would also reduce the cost in the REPACK decoding worker, due to having to parse far fewer WAL records. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-06T21:59:59Z
On 2026-Apr-06, Andres Freund wrote: > I just saw this got committed and wanted to briefly play with it. It works > nicely! Yeah, I have to say that Antonin did a great job here. > Except that at first I tried this in a debugging build, and was briefly rather > dismayed by the performance. It was really slow. But it's not really related > to repack / the patches here. > > In that config, the assert single-handled increases the time for a repack by > 35% or so. Yeah, I saw it was kinda sluggish, but wow, I didn't see *that* much overhead. > It's totally valid to not have done so initially, this is a quite complicated > feature: > > I saw this is using individual heap_insert()s during the > heapam_relation_copy_for_cluster(). Doing individual WAL logged inserts isn't > exactly cheap or efficient from a WAL volume perspective... > > Is there anything other than round tuits preventing us from using > multi_insert? > > That actually would also reduce the cost in the REPACK decoding worker, due to > having to parse far fewer WAL records. Nope, not really ... but I don't have any :-( -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-06T22:22:32Z
Hello, On 2026-Apr-06, Mihail Nikalayeu wrote: > > Yeah but I don't want the virtual tuple to be materialized (which would > > happen in tts_virtual_materialize if I set shouldFree=true). The memory > > should be freed in > > ResetPerTupleExprContext(chgcxt->cc_estate); > > anyway, right? Maybe deserves a comment. > > Not sure, ResetPerTupleExprContext resets just "ExecutorState". > But slots are created in another memory context. > > Also, we can't reset slot->tts_mcxt itself - it will free the slot also. So what I ended up doing, is to just not change to the slot's context in restore_tuple. That was just dumb (mea culpa). So all the memory we allocate for the slot lives in the executor context, and goes away on the ResetPerTupleExprContext call at the bottom of the loop. BTW I don't understand why you say that function only resets ExecutorState -- as far as I can tell, it does this #define ResetPerTupleExprContext(estate) \ do { \ if ((estate)->es_per_tuple_exprcontext) \ ResetExprContext((estate)->es_per_tuple_exprcontext); \ } while (0) which in turn does #define ResetExprContext(econtext) \ MemoryContextReset((econtext)->ecxt_per_tuple_memory) which AFAICT is exactly what we want. Anyway, here's the three missing parts. I have not yet edited the deadlock-checker one to protect autovacuum from processing tables under repack. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ -
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-06T23:53:02Z
Hi! On Tue, Apr 7, 2026 at 12:22 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > So what I ended up doing, is to just not change to the slot's context in > restore_tuple. Yes, it should work. > BTW I > don't understand why you say that function only resets ExecutorState My bad, I confused "ExprContext" with "ExecutorState" (names passed to AllocSetContextCreate) while tracing. Mikhail.
-
Re: Adding REPACK [concurrently]
Noah Misch <noah@leadboat.com> — 2026-04-07T01:10:56Z
On Mon, Apr 06, 2026 at 05:11:30PM -0400, Andres Freund wrote: > heap_insert() > ->CacheInvalidateHeapTuple() > ->CacheInvalidateHeapTupleCommon() > ->AssertCouldGetRelation() > not being cheap and running a *lot*. > > Admittedly it's way worse if you build with -O0, which I tend to do to make > debugging easier. > > In that config, the assert single-handled increases the time for a repack by > 35% or so. > > > Noah, is there any reason we need to do the AssertCouldGetRelation() before > the !IsCatalogRelation(relation)? Given that the goal is to make > RelationGetRelid() safe, it doesn't seem there is? By running AssertCouldGetRelation() during every INSERT statement, this detects cases that would be unsafe when the target of the INSERT happens to be a system catalog. Little of our INSERT/UPDATE coverage targets a system catalog. Hence, the current position is better for detection. I wonder if this got slower in v19. In v14-v18, the assert's cost is proportional to the number of held lwlocks, often 0 or 1. In v19, it's proportional to PrivateRefCountHash cardinality.
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-07T01:58:19Z
Hi, On 2026-04-06 18:10:56 -0700, Noah Misch wrote: > On Mon, Apr 06, 2026 at 05:11:30PM -0400, Andres Freund wrote: > > heap_insert() > > ->CacheInvalidateHeapTuple() > > ->CacheInvalidateHeapTupleCommon() > > ->AssertCouldGetRelation() > > not being cheap and running a *lot*. > > > > Admittedly it's way worse if you build with -O0, which I tend to do to make > > debugging easier. > > > > In that config, the assert single-handled increases the time for a repack by > > 35% or so. > > > > > > Noah, is there any reason we need to do the AssertCouldGetRelation() before > > the !IsCatalogRelation(relation)? Given that the goal is to make > > RelationGetRelid() safe, it doesn't seem there is? > > By running AssertCouldGetRelation() during every INSERT statement, this > detects cases that would be unsafe when the target of the INSERT happens to be > a system catalog. I see. > Little of our INSERT/UPDATE coverage targets a system catalog. Sure. We do have plenty DML doing heap_insert/update however. > Hence, the current position is better for detection. What if we returned early in AssertBufferLocksPermitCatalogRead() if InterruptHoldoffCount == 0? That'd only fail if some code manually did a RESUME_INTERRUPTS() to balance the one acquired as part of the content lock? > I wonder if this got slower in v19. In v14-v18, the assert's cost is > proportional to the number of held lwlocks, often 0 or 1. In v19, it's > proportional to PrivateRefCountHash cardinality. Yea, plausible. It will only scan PrivateRefCountHash if PrivateRefCountOverflowed overflowed, but it did overflow in the case I was testing... Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Noah Misch <noah@leadboat.com> — 2026-04-07T02:42:33Z
On Mon, Apr 06, 2026 at 09:58:19PM -0400, Andres Freund wrote: > On 2026-04-06 18:10:56 -0700, Noah Misch wrote: > > Hence, the current position is better for detection. > > What if we returned early in AssertBufferLocksPermitCatalogRead() if > InterruptHoldoffCount == 0? That'd only fail if some code manually did a > RESUME_INTERRUPTS() to balance the one acquired as part of the content lock? Sounds good.
-
Re: Adding REPACK [concurrently]
Tom Lane <tgl@sss.pgh.pa.us> — 2026-04-07T04:44:47Z
Maybe you saw this already, but BF member skink is failing on src/test/modules/injection_points/specs/repack.spec: https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&dt=2026-04-06%2022%3A50%3A41 regards, tom lane
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T08:40:25Z
On 2026-Apr-07, Tom Lane wrote: > Maybe you saw this already, but BF member skink is failing on > src/test/modules/injection_points/specs/repack.spec: > > https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&dt=2026-04-06%2022%3A50%3A41 Thanks, I hadn't, and I cannot reproduce it locally. Anyway, the valgrind report is ==1617044== VALGRINDERROR-BEGIN ==1617044== Syscall param pwrite64(buf) points to uninitialised byte(s) ==1617044== at 0x6704003: pwrite (pwrite64.c:25) ==1617044== by 0x44AAC72: pg_pwritev (pg_iovec.h:101) ==1617044== by 0x44AC6B5: FileWriteV (fd.c:2280) ==1617044== by 0x44A8EC4: FileWrite (fd.h:245) ==1617044== by 0x44A8EC4: BufFileDumpBuffer (buffile.c:538) ==1617044== by 0x44A9034: BufFileFlush (buffile.c:724) ==1617044== by 0x44A9661: BufFileClose (buffile.c:418) ==1617044== by 0x426C4DE: export_initial_snapshot (repack_worker.c:346) ==1617044== by 0x426CA17: RepackWorkerMain (repack_worker.c:145) ==1617044== by 0x441D3AD: BackgroundWorkerMain (bgworker.c:865) ==1617044== by 0x44219D9: postmaster_child_launch (launch_backend.c:265) ==1617044== Address 0x12d745e2 is 106 bytes inside a block of size 8,272 client-defined ==1617044== at 0x4661CFE: palloc (mcxt.c:1411) ==1617044== by 0x44A8C54: makeBufFileCommon (buffile.c:121) ==1617044== by 0x44A933F: BufFileCreateFileSet (buffile.c:272) ==1617044== by 0x426C4A5: export_initial_snapshot (repack_worker.c:341) ==1617044== by 0x426CA17: RepackWorkerMain (repack_worker.c:145) ==1617044== by 0x441D3AD: BackgroundWorkerMain (bgworker.c:865) ==1617044== by 0x44219D9: postmaster_child_launch (launch_backend.c:265) ==1617044== by 0x4423B6D: StartBackgroundWorker (postmaster.c:4197) ==1617044== by 0x4423DB8: maybe_start_bgworkers (postmaster.c:4362) ==1617044== by 0x4425119: LaunchMissingBackgroundProcesses (postmaster.c:3437) ==1617044== by 0x4426A75: ServerLoop (postmaster.c:1737) ==1617044== by 0x44280DC: PostmasterMain (postmaster.c:1412) ==1617044== Uninitialised value was created by a stack allocation ==1617044== at 0x4674D39: SerializeSnapshot (snapmgr.c:1737) ==1617044== ==1617044== VALGRINDERROR-END and obviously BufFileCreateFileSet() is being called by existing code already and it doesn't fail, so I *think* the problem might be that SerializedSnapshotData has some padding bytes that are being written. Maybe using palloc0() is enough? I'll try that. If that doesn't silence skink, I guess my next step is to reproduce skink's environment more precisely ... -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "Uno puede defenderse de los ataques; contra los elogios se esta indefenso"
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-07T08:42:22Z
Hi Tom, On Tue, Apr 7, 2026 at 10:14 AM Tom Lane <tgl@sss.pgh.pa.us> wrote: > Maybe you saw this already, but BF member skink is failing on > src/test/modules/injection_points/specs/repack.spec: > > > https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&dt=2026-04-06%2022%3A50%3A41 > i looked into this , it seems like valgrind catches the uninitialised padding bytes, which repack worker is writing using BufFileWrite, it seems this fix solved the problem. diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 2e6197f5f35..f5682b87626 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -1739,6 +1739,8 @@ SerializeSnapshot(Snapshot snapshot, char *start_address) Assert(snapshot->subxcnt >= 0); + MemSet(&serialized_snapshot, 0, sizeof(SerializedSnapshotData)); + /* Copy all required fields */ serialized_snapshot.xmin = snapshot->xmin; serialized_snapshot.xmax = snapshot->xmax; thoughts? -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T08:57:46Z
Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > Hi Tom, > > On Tue, Apr 7, 2026 at 10:14 AM Tom Lane <tgl@sss.pgh.pa.us> wrote: > > Maybe you saw this already, but BF member skink is failing on > src/test/modules/injection_points/specs/repack.spec: > > https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=skink&dt=2026-04-06%2022%3A50%3A41 > > i looked into this , it seems like valgrind catches the uninitialised padding bytes, which > repack worker is writing using BufFileWrite, it seems this fix solved the problem. > > diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c > index 2e6197f5f35..f5682b87626 100644 > --- a/src/backend/utils/time/snapmgr.c > +++ b/src/backend/utils/time/snapmgr.c > @@ -1739,6 +1739,8 @@ SerializeSnapshot(Snapshot snapshot, char *start_address) > > Assert(snapshot->subxcnt >= 0); > > + MemSet(&serialized_snapshot, 0, sizeof(SerializedSnapshotData)); > + > /* Copy all required fields */ > serialized_snapshot.xmin = snapshot->xmin; > serialized_snapshot.xmax = snapshot->xmax; > > thoughts? Could you reproduce the failure in your environment? I haven't thought of this explanation because BufFileWrite() only copies the data to a buffer in the BufFile structure and BufFileDumpBuffer() writes the buffer. Maybe valgrind is able to track the copying? -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T09:22:18Z
On 2026-Apr-07, Antonin Houska wrote: > > @@ -1739,6 +1739,8 @@ SerializeSnapshot(Snapshot snapshot, char *start_address) > > > > Assert(snapshot->subxcnt >= 0); > > > > + MemSet(&serialized_snapshot, 0, sizeof(SerializedSnapshotData)); > > + > > /* Copy all required fields */ > > serialized_snapshot.xmin = snapshot->xmin; > > serialized_snapshot.xmax = snapshot->xmax; > > > > thoughts? > > Could you reproduce the failure in your environment? Yeah, he did. > I haven't thought of this explanation because BufFileWrite() only copies the > data to a buffer in the BufFile structure and BufFileDumpBuffer() writes the > buffer. Maybe valgrind is able to track the copying? Yeah, apparently it keeps track of tainted bytes somehow. Clever. The change to palloc0() that I was proposing did not fix the problem, because the stack allocated struct overwrote those zeroes with the uninitialized padding bytes. I ended up with an equivalent fix to Srinath's -- zero-initializing the stack-allocated struct, so that the bytes that end up copied by memcpy() are all defined. Srinath confirmed that in his environment the valgrind failure goes away, so I think we're good. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "No tengo por qué estar de acuerdo con lo que pienso" (Carlos Caszeli) -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T09:29:17Z
Antonin Houska <ah@cybertec.at> wrote: > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > i looked into this , it seems like valgrind catches the uninitialised padding bytes, which > > repack worker is writing using BufFileWrite, it seems this fix solved the problem. > > > > diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c > > index 2e6197f5f35..f5682b87626 100644 > > --- a/src/backend/utils/time/snapmgr.c > > +++ b/src/backend/utils/time/snapmgr.c > > @@ -1739,6 +1739,8 @@ SerializeSnapshot(Snapshot snapshot, char *start_address) > > > > Assert(snapshot->subxcnt >= 0); > > > > + MemSet(&serialized_snapshot, 0, sizeof(SerializedSnapshotData)); > > + > > /* Copy all required fields */ > > serialized_snapshot.xmin = snapshot->xmin; > > serialized_snapshot.xmax = snapshot->xmax; > > > > thoughts? > > Could you reproduce the failure in your environment? > > I haven't thought of this explanation because BufFileWrite() only copies the > data to a buffer in the BufFile structure and BufFileDumpBuffer() writes the > buffer. Maybe valgrind is able to track the copying? Given this message, you may be right: ==1617044== Address 0x12d745e2 is 106 bytes inside a block of size 8,272 client-defined In my environment, the 'buffer' field starts at offset 80 into the BufFile structure. We first write 8 bytes into it BufFileWrite(file, &snap_size, sizeof(snap_size)); followed by the snapshot. Since sizeof(SerializedSnapshotData) is 24, the offset 106 should be the padding following the 'takenDuringRecovery' field. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T09:35:50Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-07, Antonin Houska wrote: > > > I haven't thought of this explanation because BufFileWrite() only copies the > > data to a buffer in the BufFile structure and BufFileDumpBuffer() writes the > > buffer. Maybe valgrind is able to track the copying? > > Yeah, apparently it keeps track of tainted bytes somehow. Clever. > > The change to palloc0() that I was proposing did not fix the problem, > because the stack allocated struct overwrote those zeroes with the > uninitialized padding bytes. > > I ended up with an equivalent fix to Srinath's -- zero-initializing > the stack-allocated struct, so that the bytes that end up copied by > memcpy() are all defined. Srinath confirmed that in his environment the > valgrind failure goes away, so I think we're good. Thanks! -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T09:39:12Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-06, Andres Freund wrote: > > > I just saw this got committed and wanted to briefly play with it. It works > > nicely! > > Yeah, I have to say that Antonin did a great job here. Likewise, I appreciate all your improvements! And of course, all the reviews and testing done by other developers. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Srinath Reddy Sadipiralla <srinath2133@gmail.com> — 2026-04-07T09:41:17Z
On Tue, Apr 7, 2026 at 2:59 PM Antonin Houska <ah@cybertec.at> wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > Srinath Reddy Sadipiralla <srinath2133@gmail.com> wrote: > > > > > i looked into this , it seems like valgrind catches the uninitialised > padding bytes, which > > > repack worker is writing using BufFileWrite, it seems this fix solved > the problem. > > > > > > diff --git a/src/backend/utils/time/snapmgr.c > b/src/backend/utils/time/snapmgr.c > > > index 2e6197f5f35..f5682b87626 100644 > > > --- a/src/backend/utils/time/snapmgr.c > > > +++ b/src/backend/utils/time/snapmgr.c > > > @@ -1739,6 +1739,8 @@ SerializeSnapshot(Snapshot snapshot, char > *start_address) > > > > > > Assert(snapshot->subxcnt >= 0); > > > > > > + MemSet(&serialized_snapshot, 0, sizeof(SerializedSnapshotData)); > > > + > > > /* Copy all required fields */ > > > serialized_snapshot.xmin = snapshot->xmin; > > > serialized_snapshot.xmax = snapshot->xmax; > > > > > > thoughts? > > > > Could you reproduce the failure in your environment? > > > > I haven't thought of this explanation because BufFileWrite() only copies > the > > data to a buffer in the BufFile structure and BufFileDumpBuffer() writes > the > > buffer. Maybe valgrind is able to track the copying? > > Given this message, you may be right: > > ==1617044== Address 0x12d745e2 is 106 bytes inside a block of size 8,272 > client-defined > > In my environment, the 'buffer' field starts at offset 80 into the BufFile > structure. We first write 8 bytes into it > > BufFileWrite(file, &snap_size, sizeof(snap_size)); > > followed by the snapshot. Since sizeof(SerializedSnapshotData) is 24, the > offset 106 should be the padding following the 'takenDuringRecovery' field. > yeah , same in my environment aslo ==00:00:00:06.569 3479320== Syscall param pwrite64(buf) points to uninitialised byte(s) ==00:00:00:06.569 3479320== at 0x55D6D38: pwrite (pwrite64.c:25) ==00:00:00:06.569 3479320== by 0x842EE7: pg_pwritev (pg_iovec.h:101) ==00:00:00:06.569 3479320== by 0x845F67: FileWriteV (fd.c:2280) ==00:00:00:06.569 3479320== by 0x840877: FileWrite (fd.h:245) ==00:00:00:06.569 3479320== by 0x841407: BufFileDumpBuffer (buffile.c:538) ==00:00:00:06.569 3479320== by 0x841A67: BufFileFlush (buffile.c:724) ==00:00:00:06.569 3479320== by 0x8410B7: BufFileClose (buffile.c:418) ==00:00:00:06.569 3479320== by 0x4BFBBB: export_initial_snapshot (repack_worker.c:346) ==00:00:00:06.569 3479320== by 0x4BF703: RepackWorkerMain (repack_worker.c:145) ==00:00:00:06.569 3479320== by 0x765D3F: BackgroundWorkerMain (bgworker.c:865) ==00:00:00:06.569 3479320== by 0x76C703: postmaster_child_launch (launch_backend.c:265) ==00:00:00:06.569 3479320== by 0x774F87: StartBackgroundWorker (postmaster.c:4197) ==00:00:00:06.569 3479320== Address 0x7b055f2 is 106 bytes inside a block of size 8,272 client-defined ==00:00:00:06.569 3479320== at 0xB234F4: palloc (mcxt.c:1411) ==00:00:00:06.569 3479320== by 0x8408BF: makeBufFileCommon (buffile.c:121) ==00:00:00:06.569 3479320== by 0x840C2F: BufFileCreateFileSet (buffile.c:272) ==00:00:00:06.569 3479320== by 0x4BFB7F: export_initial_snapshot (repack_worker.c:341) ==00:00:00:06.569 3479320== by 0x4BF703: RepackWorkerMain (repack_worker.c:145) ==00:00:00:06.569 3479320== by 0x765D3F: BackgroundWorkerMain (bgworker.c:865) ==00:00:00:06.569 3479320== by 0x76C703: postmaster_child_launch (launch_backend.c:265) ==00:00:00:06.569 3479320== by 0x774F87: StartBackgroundWorker (postmaster.c:4197) ==00:00:00:06.569 3479320== by 0x775277: maybe_start_bgworkers (postmaster.c:4362) ==00:00:00:06.569 3479320== by 0x7739F7: LaunchMissingBackgroundProcesses (postmaster.c:3437) ==00:00:00:06.569 3479320== by 0x770C2B: ServerLoop (postmaster.c:1737) ==00:00:00:06.569 3479320== by 0x77037B: PostmasterMain (postmaster.c:1412) ==00:00:00:06.569 3479320== Uninitialised value was created by a stack allocation ==00:00:00:06.569 3479320== at 0xB43008: SerializeSnapshot (snapmgr.c:1737) ==00:00:00:06.569 3479320== and you are spot on here with the explanation with offsets Size snap_size; 8 bytes; TransactionId xmin; 4 bytes TransactionId xmax; 4 bytes uint32 xcnt; 4 bytes int32 subxcnt; 4 bytes bool suboverflowed; 1 byte bool takenDuringRecovery; 1 byte /* 2 bytes of padding */ CommandId curcid; 4 bytes -- Thanks, Srinath Reddy Sadipiralla EDB: https://www.enterprisedb.com/
-
Re: Adding REPACK [concurrently]
John Naylor <johncnaylorls@gmail.com> — 2026-04-07T10:08:25Z
Hi, mamba is failing with repack.c: In function 'initialize_change_context': repack.c:2946:7: error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast] 2946 | (Datum) NULL); | ^ cc1: all warnings being treated as errors -- John Naylor Amazon Web Services -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T10:30:07Z
Hi, On 2026-Apr-07, John Naylor wrote: > mamba is failing with > > repack.c: In function 'initialize_change_context': > repack.c:2946:7: error: cast from pointer to integer of different size > [-Werror=pointer-to-int-cast] > 2946 | (Datum) NULL); > | ^ > cc1: all warnings being treated as errors Hmm, yeah that should have been "(Datum) 0". Pushed. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "The Postgresql hackers have what I call a "NASA space shot" mentality. Quite refreshing in a world of "weekend drag racer" developers." (Scott Marlowe)
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T11:19:17Z
Hi, There's one more failure in thorntail https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=thorntail&dt=2026-04-07%2006%3A31%3A03 because it runs with wal_level=minimal. I think we can make it work by using a temp-config argument to run the tests, as in the attached. I didn't actually try to run the buildfarm client though ... -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-07T12:15:16Z
On Tue, Apr 7, 2026 at 3:52 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Anyway, here's the three missing parts. I have not yet edited the > deadlock-checker one to protect autovacuum from processing tables under > repack. > I have a question based on 0001's commit message: "This patch adds a new option to logical replication output plugin, to declare that it does not use shared catalogs (i.e. catalogs that can be changed by transactions running in other databases in the cluster).". In which cases, currently plugin needs to access multi-database transactions or transactions that need to access shared catalogs and on what basis a plugin can decide that the changes it requires won't need any such access. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T12:17:57Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > There's one more failure in thorntail > https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=thorntail&dt=2026-04-07%2006%3A31%3A03 > because it runs with wal_level=minimal. I think we can make it work by > using a temp-config argument to run the tests, as in the attached. LGTM. We had almost exactly this in earlier versions of the patch [1], before commit 67c20979ce. [1] https://www.postgresql.org/message-id/137668.1768235610%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
RE: Adding REPACK [concurrently]
Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> — 2026-04-07T12:32:32Z
Dear Álvaro, Thanks for updating the patch. I have two comments/questions. 01. ``` --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -394,6 +394,14 @@ systable_beginscan(Relation heapRelation, SysScanDesc sysscan; Relation irel; + /* + * If this backend promised that it won't access shared catalogs during + * logical decoding, this it the right place to verify. + */ + Assert(!HistoricSnapshotActive() || + accessSharedCatalogsInDecoding || + !heapRelation->rd_rel->relisshared); ``` Not sure it's OK to use Assert(). elog(ERROR) might be better if we want to really avoid the case. 02. SnapBuildProcessRunningXacts Per my understanding, the db_specic snapshot can be also serialized. Is it possibility tha normal logical decoding system restores the snapshot and obtain the wrong result? Best regards, Hayato Kuroda FUJITSU LIMITED
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T12:33:50Z
On 2026-Apr-07, Amit Kapila wrote: > I have a question based on 0001's commit message: "This patch adds a > new option to logical replication output plugin, to declare that it > does not use shared catalogs (i.e. catalogs that can be changed by > transactions running in other databases in the cluster).". In which > cases, currently plugin needs to access multi-database transactions or > transactions that need to access shared catalogs and on what basis a > plugin can decide that the changes it requires won't need any such > access. I don't think any plugin needs "multi-database" access as such, but needing access to shared catalogs is likely normal. Repack knows it won't access any shared catalogs, so it can set the flag at ease. There's a cross-check added in the commit that tests for access to shared catalogs if the flag is set to false. I guess you could set it to false and see what breaks :-) -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-07T14:10:34Z
Hi, On 2026-04-07 14:33:50 +0200, Alvaro Herrera wrote: > On 2026-Apr-07, Amit Kapila wrote: > > > I have a question based on 0001's commit message: "This patch adds a > > new option to logical replication output plugin, to declare that it > > does not use shared catalogs (i.e. catalogs that can be changed by > > transactions running in other databases in the cluster).". In which > > cases, currently plugin needs to access multi-database transactions or > > transactions that need to access shared catalogs and on what basis a > > plugin can decide that the changes it requires won't need any such > > access. > > I don't think any plugin needs "multi-database" access as such, but > needing access to shared catalogs is likely normal. Repack knows it > won't access any shared catalogs, so it can set the flag at ease. > > There's a cross-check added in the commit that tests for access to > shared catalogs if the flag is set to false. I guess you could set it > to false and see what breaks :-) I think this has a quite high chance of indirect breakages. You just need some cache invalidation processing / building accessing shared catalogs to violate the rule, and whether that happens very heavily depends on what cache entries are present and whether something registers relcache callbacks or such. This can be triggered by an output function during logical decoding or such, so you don't really have control over it. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T14:19:20Z
Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> wrote: > 02. SnapBuildProcessRunningXacts > > Per my understanding, the db_specic snapshot can be also serialized. Is it > possibility tha normal logical decoding system restores the snapshot and obtain > the wrong result? I don't think that the database-specific xl_running_xacts WAL record affects what SnapBuildSerialize() writes to disk: the contents of builder->committed, etc. is updated by decoding COMMIT and ABORT records. On the other hand, with that kind of record, there's probably no reason to call SnapBuildSerialize(). Good catch, thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T15:38:24Z
Andres Freund <andres@anarazel.de> wrote: > On 2026-04-07 14:33:50 +0200, Alvaro Herrera wrote: > > On 2026-Apr-07, Amit Kapila wrote: > > > > > I have a question based on 0001's commit message: "This patch adds a > > > new option to logical replication output plugin, to declare that it > > > does not use shared catalogs (i.e. catalogs that can be changed by > > > transactions running in other databases in the cluster).". In which > > > cases, currently plugin needs to access multi-database transactions or > > > transactions that need to access shared catalogs and on what basis a > > > plugin can decide that the changes it requires won't need any such > > > access. > > > > I don't think any plugin needs "multi-database" access as such, but > > needing access to shared catalogs is likely normal. Repack knows it > > won't access any shared catalogs, so it can set the flag at ease. > > > > There's a cross-check added in the commit that tests for access to > > shared catalogs if the flag is set to false. I guess you could set it > > to false and see what breaks :-) > > I think this has a quite high chance of indirect breakages. You just need some > cache invalidation processing / building accessing shared catalogs to violate > the rule, and whether that happens very heavily depends on what cache entries > are present and whether something registers relcache callbacks or such. > > This can be triggered by an output function during logical decoding or such, > so you don't really have control over it. The REPACK plugin only deforms tuples and writes them to a file, so I think that things like this should not happen. However, I admit that an option that allows the plugin developer to declare "I don't need shared catalogs" may be considered deceptive. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-07T15:48:42Z
Hi, On 2026-04-07 17:38:24 +0200, Antonin Houska wrote: > Andres Freund <andres@anarazel.de> wrote: > > > On 2026-04-07 14:33:50 +0200, Alvaro Herrera wrote: > > > On 2026-Apr-07, Amit Kapila wrote: > > > > > > > I have a question based on 0001's commit message: "This patch adds a > > > > new option to logical replication output plugin, to declare that it > > > > does not use shared catalogs (i.e. catalogs that can be changed by > > > > transactions running in other databases in the cluster).". In which > > > > cases, currently plugin needs to access multi-database transactions or > > > > transactions that need to access shared catalogs and on what basis a > > > > plugin can decide that the changes it requires won't need any such > > > > access. > > > > > > I don't think any plugin needs "multi-database" access as such, but > > > needing access to shared catalogs is likely normal. Repack knows it > > > won't access any shared catalogs, so it can set the flag at ease. > > > > > > There's a cross-check added in the commit that tests for access to > > > shared catalogs if the flag is set to false. I guess you could set it > > > to false and see what breaks :-) > > > > I think this has a quite high chance of indirect breakages. You just need some > > cache invalidation processing / building accessing shared catalogs to violate > > the rule, and whether that happens very heavily depends on what cache entries > > are present and whether something registers relcache callbacks or such. > > > > This can be triggered by an output function during logical decoding or such, > > so you don't really have control over it. > > The REPACK plugin only deforms tuples and writes them to a file, so I think > that things like this should not happen. You don't need to do it yourself. It just requires a shared_preload_library extension to register a relcache invalidation callback that accesses shared catalog. It's only kind of an accident that we don't have a case today that accesses shared catcaches during a relcache build in core (I'm not even sure there's nothing). You'd just need somebody to add e.g. relcache caching for publications for that to change. Or look up information about a reloption in pg_parameter_acl. Or lookup tablespace configuration. > However, I admit that an option that allows the plugin developer to declare > "I don't need shared catalogs" may be considered deceptive. At the very least it would need to be a runtime check rather than just an assert. This would much more likely to be hit in production because otherwise it's probably hard to hit the case where shared invalidations happen in the wrong moment. And the consequences are corrupted caches, which could cause all kinds of havoc. But I think this may need more infrastructure / deeper analysis than what we can do right now. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-07T16:01:16Z
Andres Freund <andres@anarazel.de> wrote: > On 2026-04-07 17:38:24 +0200, Antonin Houska wrote: > > The REPACK plugin only deforms tuples and writes them to a file, so I think > > that things like this should not happen. > > You don't need to do it yourself. It just requires a shared_preload_library > extension to register a relcache invalidation callback that accesses shared > catalog. > > It's only kind of an accident that we don't have a case today that accesses > shared catcaches during a relcache build in core (I'm not even sure there's > nothing). You'd just need somebody to add e.g. relcache caching for > publications for that to change. Or look up information about a reloption in > pg_parameter_acl. Or lookup tablespace configuration. > > > > However, I admit that an option that allows the plugin developer to declare > > "I don't need shared catalogs" may be considered deceptive. > > At the very least it would need to be a runtime check rather than just an > assert. This would much more likely to be hit in production because otherwise > it's probably hard to hit the case where shared invalidations happen in the > wrong moment. And the consequences are corrupted caches, which could cause > all kinds of havoc. > > > But I think this may need more infrastructure / deeper analysis than what we > can do right now. ok, thanks a lot for having looked at it. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2026-04-07T19:43:50Z
On Mon, Apr 6, 2026 at 6:22 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-06, Mihail Nikalayeu wrote: <snip> > > Anyway, here's the three missing parts. I have not yet edited the > deadlock-checker one to protect autovacuum from processing tables under > repack. > I have this lingering bit of paranoia that users could end up in a situation with a large / long running repack that goes past failsafe age which prevents the simpler fix of failsafe autovacuum from running. While the repack finishing would resolve this issue, we can't know ahead of time that the repack would finish in time, and statistically speaking, failsafe autovacuum should generally run much quicker than any repack could. I'm not sure if that means we should let failsafe vacuum cancel repacks (that seems a bit extreme), but maybe we want to help $operator to think about this decision, except if we don't allow autovacuum to wait and we don't allow it to respawn, I wonder if the end user will ever realize they are in this position. Granted, there doesn't seem like a clean fix for this... Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-07T20:24:04Z
Hi, On 2026-04-07 00:22:32 +0200, Alvaro Herrera wrote: > From 4303eea0a72408183f9f5afcf8d2801df20f8ffe Mon Sep 17 00:00:00 2001 > From: Antonin Houska <ah@cybertec.at> > Date: Wed, 1 Apr 2026 17:35:47 +0200 > Subject: [PATCH v56 3/3] Error out any process that would block at REPACK > > Any process waiting on REPACK to release its lock would actually cause > it to deadlock when it tries to upgrade its lock to AEL, losing all work > done to that point. We avoid this by teaching the deadlock detector to > raise an error when this condition is detected. I'm rather doubtful that that is ok. Won't this make it basically unsafe to use repack in way way too many situations? If sessions start erroring out, rather than wait, because they briefly lock a relation or such it'll imo make this rather unsafe in production. ISTM that the proper fix here might be to allow repack to do a lock upgrade that jumps to the front of the lock's wait queue. The lock upgrade is the reason for the deadlock, right? And the reason it will often cause a deadlock is that the repack will be at the tail, rather than the front of the wait queue. So let's fix that. Jumping the queue won't make the lock upgrade immediately, of course, but I think it would fix the issue of the repack, rather than some other process, getting cancelled? Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-07T21:38:11Z
On 2026-Apr-06, Amit Kapila wrote: > On Sat, Apr 4, 2026 at 3:49 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > I suppose it's unfortunate that autovacuum launcher is going to try > > again and again to get workers to process that table, and they are going > > to be killed over and over. Maybe it would be better to have autovac > > ignore those tables. > > I feel that would be better at least when we know that the repack > concurrently command is already in progress. It can help avoid > launching workers again and again, especially when repack concurrently > command is going to take a long time. Okay. I implemented that now, and here it is. 0001 is as before; 0002 creates shared memory for repack and has autovacuum recheck each table there before processing it. Having implemented it, I'm not sure the resulting behavior is all that different from before. I mean, the only difference is that the worker is going to see the table listed in repack shmem and skip it; as opposed to trying to lock it and be terminated by the deadlock detector one second later, at which point it continues with the next table on its list. So it's some wasted work to set up the autovac work, but nothing more. However, there's also the point Andres just made in [1] which basically kills this idea. So I'm withdrawing this proposal. Still, having written it, I thought it'd be better to get it archived. [1] https://postgr.es/m/4n4q3preb3lgyhpzstebhux7b2aojhsw7gik4ivaznyggiezrs@lrznutssxlh2 -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ “Cuando no hay humildad las personas se degradan” (A. Christie)
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-08T03:49:04Z
On Tue, Apr 7, 2026 at 7:49 PM Antonin Houska <ah@cybertec.at> wrote: > > Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> wrote: > > > 02. SnapBuildProcessRunningXacts > > > > Per my understanding, the db_specic snapshot can be also serialized. Is it > > possibility tha normal logical decoding system restores the snapshot and obtain > > the wrong result? > > I don't think that the database-specific xl_running_xacts WAL record affects > what SnapBuildSerialize() writes to disk: the contents of builder->committed, > etc. is updated by decoding COMMIT and ABORT records. > I think the point is that the other process say a walsender could restore such a snapshot making it take the wrong decision. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-08T07:31:55Z
Robert Treat <rob@xzilla.net> wrote: > On Mon, Apr 6, 2026 at 6:22 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-Apr-06, Mihail Nikalayeu wrote: > <snip> > > > > Anyway, here's the three missing parts. I have not yet edited the > > deadlock-checker one to protect autovacuum from processing tables under > > repack. > > > > I have this lingering bit of paranoia that users could end up in a > situation with a large / long running repack that goes past failsafe > age which prevents the simpler fix of failsafe autovacuum from > running. While the repack finishing would resolve this issue, we can't > know ahead of time that the repack would finish in time, and > statistically speaking, failsafe autovacuum should generally run much > quicker than any repack could. I'm not sure if that means we should > let failsafe vacuum cancel repacks (that seems a bit extreme), but > maybe we want to help $operator to think about this decision, except > if we don't allow autovacuum to wait and we don't allow it to respawn, > I wonder if the end user will ever realize they are in this position. > Granted, there doesn't seem like a clean fix for this... If REPACK is not going to finish in time, I think it makes little difference whether VACUUM is allowed to wait or not: even if it waits, it will start just too late. One reason to avoid waiting might be to allow autovacuum to work on other tables in between. I agree that the DBA should have some guidance to asses whether REPACK or (failsafe) VACUUM is the appropriate action. While failsafe VACUUM is clearly a means to avoid XID wraparound, I tend to consider REPACK primarily a command to remove table bloat. Or is there a situation where REPACK is better even to avoid the wraparound? Technically, the deadlock can be avoided by not running DDLs on the table while REPACK is running. I'm just thinking if, by mentioning this in the REPACK documentation, we'd admit that the REPACK (CONCURRENTLY) feature is actually incomplete. On the other hand, if we don't mention the risk of deadlock, it's a similar situation to not mentioning it for commands like ALTER TABLE: if ALTER TABLE performs table rewrite, deadlock can also result in a significant amount of wasted resources. (Of course, it's not the same if the purpose of REPACK is considered substitute for failsafe VACUUM, but I'm not sure about that.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-08T08:35:49Z
On Wed, Apr 8, 2026 at 1:54 AM Andres Freund <andres@anarazel.de> wrote: > > Hi, > > On 2026-04-07 00:22:32 +0200, Alvaro Herrera wrote: > > From 4303eea0a72408183f9f5afcf8d2801df20f8ffe Mon Sep 17 00:00:00 2001 > > From: Antonin Houska <ah@cybertec.at> > > Date: Wed, 1 Apr 2026 17:35:47 +0200 > > Subject: [PATCH v56 3/3] Error out any process that would block at REPACK > > > > Any process waiting on REPACK to release its lock would actually cause > > it to deadlock when it tries to upgrade its lock to AEL, losing all work > > done to that point. We avoid this by teaching the deadlock detector to > > raise an error when this condition is detected. > > I'm rather doubtful that that is ok. > Another possible idea is that after copying table_data to the new table, we mark the old table as in_use_by_repack and release the ShareUpdateExclusiveLock on the old table. Then the function CheckTableNotInUse() should be updated to give an ERROR if the table is marked as in_use_by_repack. Now, acquiring AEL by repack (concurrently) should be safe because all concurrent DDLs should be errored out due to flag in_use_by_repack. Can this address the problem we are worried about the lock upgrade? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Tomas Vondra <tomas@vondra.me> — 2026-04-08T10:20:53Z
Hi, while building on a rpi5 with a 32-bit system, I'm getting these warnings: config.status: linking src/makefiles/Makefile.linux to src/Makefile.port In file included from ../../../src/include/access/tupmacs.h:20, from ../../../src/include/access/htup_details.h:20, from ../../../src/include/access/relscan.h:17, from ../../../src/include/access/heapam.h:19, from repack.c:36: In function ‘VARSIZE_ANY’, inlined from ‘restore_tuple’ at repack.c:2731:15: ../../../src/include/varatt.h:243:51: warning: array subscript ‘varattrib_4b[0]’ is partly outside array bounds of ‘union <anonymous>[1]’ [-Warray-bounds=] 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> 2) & 0x3FFFFFFF) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ ../../../src/include/varatt.h:467:24: note: in expansion of macro ‘VARSIZE_4B’ 467 | return VARSIZE_4B(PTR); | ^~~~~~~~~~ repack.c: In function ‘restore_tuple’: repack.c:2717:49: note: object ‘chunk_header’ of size 4 2717 | } chunk_header; | ^~~~~~~~~~~~ In function ‘VARSIZE_ANY’, inlined from ‘restore_tuple’ at repack.c:2734:4: ../../../src/include/varatt.h:243:51: warning: array subscript ‘varattrib_4b[0]’ is partly outside array bounds of ‘union <anonymous>[1]’ [-Warray-bounds=] 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> 2) & 0x3FFFFFFF) | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ ../../../src/include/varatt.h:467:24: note: in expansion of macro ‘VARSIZE_4B’ 467 | return VARSIZE_4B(PTR); | ^~~~~~~~~~ repack.c: In function ‘restore_tuple’: repack.c:2717:49: note: object ‘chunk_header’ of size 4 2717 | } chunk_header; | ^~~~~~~~~~~~ I'm not sure if it's just the compiler (gcc 14.2) being pesky, or if it's an actual issue. The repack tests seem to pass fine. regards -- Tomas Vondra -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-08T10:46:46Z
Tomas Vondra <tomas@vondra.me> wrote: > while building on a rpi5 with a 32-bit system, I'm getting these warnings: > > config.status: linking src/makefiles/Makefile.linux to src/Makefile.port > In file included from ../../../src/include/access/tupmacs.h:20, > from ../../../src/include/access/htup_details.h:20, > from ../../../src/include/access/relscan.h:17, > from ../../../src/include/access/heapam.h:19, > from repack.c:36: > In function ‘VARSIZE_ANY’, > inlined from ‘restore_tuple’ at repack.c:2731:15: > ../../../src/include/varatt.h:243:51: warning: array subscript > ‘varattrib_4b[0]’ is partly outside array bounds of ‘union > <anonymous>[1]’ [-Warray-bounds=] > 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> > 2) & 0x3FFFFFFF) > | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ > ../../../src/include/varatt.h:467:24: note: in expansion of macro > ‘VARSIZE_4B’ > 467 | return VARSIZE_4B(PTR); > | ^~~~~~~~~~ > repack.c: In function ‘restore_tuple’: > repack.c:2717:49: note: object ‘chunk_header’ of size 4 > 2717 | } chunk_header; > | ^~~~~~~~~~~~ > In function ‘VARSIZE_ANY’, > inlined from ‘restore_tuple’ at repack.c:2734:4: > ../../../src/include/varatt.h:243:51: warning: array subscript > ‘varattrib_4b[0]’ is partly outside array bounds of ‘union > <anonymous>[1]’ [-Warray-bounds=] > 243 | ((((const varattrib_4b *) (PTR))->va_4byte.va_header >> > 2) & 0x3FFFFFFF) > | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~ > ../../../src/include/varatt.h:467:24: note: in expansion of macro > ‘VARSIZE_4B’ > 467 | return VARSIZE_4B(PTR); > | ^~~~~~~~~~ > repack.c: In function ‘restore_tuple’: > repack.c:2717:49: note: object ‘chunk_header’ of size 4 > 2717 | } chunk_header; > | ^~~~~~~~~~~~ > I'm not sure if it's just the compiler (gcc 14.2) being pesky, or if > it's an actual issue. The repack tests seem to pass fine. We already introduced this definition above in the function to suppress this kind of warning union { alignas(int32) varlena hdr; char data[sizeof(void *)]; } chunk_header; The problem on a 32-bit system probably is that sizeof(void *) is 4. We need some other constant. Maybe (sizeof(varlena) + 1) ... Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Tom Lane <tgl@sss.pgh.pa.us> — 2026-04-08T16:47:33Z
Antonin Houska <ah@cybertec.at> writes: > Tomas Vondra <tomas@vondra.me> wrote: >> while building on a rpi5 with a 32-bit system, I'm getting these warnings: >> ... >> I'm not sure if it's just the compiler (gcc 14.2) being pesky, or if >> it's an actual issue. The repack tests seem to pass fine. Several 32-bit BF animals are showing these too (so far: dodo, grison, and turaco). > We already introduced this definition above in the function to suppress this > kind of warning > union > { > alignas(int32) varlena hdr; > char data[sizeof(void *)]; > } chunk_header; > The problem on a 32-bit system probably is that sizeof(void *) is 4. We need > some other constant. Maybe (sizeof(varlena) + 1) ... This seems unnecessarily Rube Goldberg-ish already. Why not just uint64 chunk_header; It will not hurt anything if the variable has more-than-required alignment. And it'd be better if it were the same size everywhere. regards, tom lane -
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-08T17:22:41Z
Hi, On 2026-04-08 14:05:49 +0530, Amit Kapila wrote: > On Wed, Apr 8, 2026 at 1:54 AM Andres Freund <andres@anarazel.de> wrote: > > On 2026-04-07 00:22:32 +0200, Alvaro Herrera wrote: > > > From 4303eea0a72408183f9f5afcf8d2801df20f8ffe Mon Sep 17 00:00:00 2001 > > > From: Antonin Houska <ah@cybertec.at> > > > Date: Wed, 1 Apr 2026 17:35:47 +0200 > > > Subject: [PATCH v56 3/3] Error out any process that would block at REPACK > > > > > > Any process waiting on REPACK to release its lock would actually cause > > > it to deadlock when it tries to upgrade its lock to AEL, losing all work > > > done to that point. We avoid this by teaching the deadlock detector to > > > raise an error when this condition is detected. > > > > I'm rather doubtful that that is ok. > > > > Another possible idea is that after copying table_data to the new > table, we mark the old table as in_use_by_repack and release the > ShareUpdateExclusiveLock on the old table. Then the function > CheckTableNotInUse() should be updated to give an ERROR if the table > is marked as in_use_by_repack. Now, acquiring AEL by repack > (concurrently) should be safe because all concurrent DDLs should be > errored out due to flag in_use_by_repack. Can this address the problem > we are worried about the lock upgrade? I don't think this is a viable path. You need to prevent any further lock acquisitions on the relation to be able to swap it, not just conflicting DDL. And you need to wait for all pre-existing locks to have been released. That doesn't really get easier by what you propose. I don't think CheckTableNotInUse() would work anyway - don't we already hold locks by the point we call it? And even if that were not the case, there are several paths to locking relations that don't ever go anywhere near CheckTableNotInUse(). Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-08T23:36:00Z
Hello, Andres! On Wed, Apr 8, 2026 at 7:22 PM Andres Freund <andres@anarazel.de> wrote: > I don't think this is a viable path. You need to prevent any further lock > acquisitions on the relation to be able to swap it, not just conflicting DDL. AFAIU, Amit's main idea is that we currently upgrade the lock instead of **releasing and re-acquiring** it because we fear DDL between those actions. DML actions are ok for us; REPACK will wait for them while getting AEL. Later changes will be applied by REPACK backend while holding AEL. One more thing we may prevent from sneaking into that hole is a VACUUM. It will not break anything, but will be huge waste of time and resources. > And you need to wait for all pre-existing locks to have been released. That > doesn't really get easier by what you propose. Do you mean locks from other sessions accessing the table? Is it done automatically while waiting for AEL? > I don't think CheckTableNotInUse() would work anyway - don't we already hold > locks by the point we call it? Yes, the DDL session already holds locks by the time CheckTableNotInUse is called - but is that really the problem? They will be released on error. > And even if that were not the case, there are > several paths to locking relations that don't ever go anywhere near > CheckTableNotInUse(). But those aren't DDL, so they shouldn't be the problem (CREATE TRIGGER might be - it seems to ignore CheckTableNotInUse, but perhaps it's fine). So, in my undeerstanding Amit's idea has two parts: "set flag and release/re-acquire" + "use CheckTableNotInUse (or some place like that) to check the flag and fail for DDL commands." Reading your arguments again, they all seem valid under the assumption that we still hold SUEL while requesting AEL. I suspect you may have just skimmed past the 'release/re-acquire' part - but more probably I missed something. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-09T04:54:05Z
On Thu, Apr 9, 2026 at 5:08 AM Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > On Wed, Apr 8, 2026 at 7:22 PM Andres Freund <andres@anarazel.de> wrote: > > I don't think this is a viable path. You need to prevent any further lock > > acquisitions on the relation to be able to swap it, not just conflicting DDL. > > AFAIU, Amit's main idea is that we currently upgrade the lock instead > of **releasing and re-acquiring** it because we fear DDL between those > actions. > DML actions are ok for us; REPACK will wait for them while getting > AEL. Later changes will be applied by REPACK backend while holding > AEL. > Yes, this is exactly what I had in mind. > One more thing we may prevent from sneaking into that hole is a > VACUUM. It will not break anything, but will be huge waste of time and > resources. > We can prevent other commands (if required) by checking the rel_in_use_by_repack flag but I thought for the initial version it is better to do what is minimally required. > > And you need to wait for all pre-existing locks to have been released. That > > doesn't really get easier by what you propose. > > Do you mean locks from other sessions accessing the table? Is it done > automatically while waiting for AEL? > Right and this is already the case with the code where locks not-conflicting with ShareUpdateExclusiveLock could be present before we try to upgrade the lock. The point was we will not let DDL execute on the table after the new flag (rel_in_use_by_repack) is set and we released the ShareUpdateExclusiveLock. > > I don't think CheckTableNotInUse() would work anyway - don't we already hold > > locks by the point we call it? > > Yes, the DDL session already holds locks by the time > CheckTableNotInUse is called - but is that really the problem? They > will be released on error. > Yes, that is the key to solve this problem. Let me take an example to explain this a bit more. Right now, the problem can happen in the following kind of sequence. Session-1: REPACK (CONCURRENTLY) foo; -- now say after the above command has acquired ShareUpdateExclusiveLock and is doing the work of copying the table, Session-2 did following actions. Session-2: Select * from foo; -- this is allowed ALTER TABLE foo ADD COLUMN c2; -- this will blocked as Session-1 already has acquired ShareUpdateExclusiveLock Session-1: -- continues and tries to upgrade the lock to AEL. This leads to deadlock ERROR in the current session doing REPACK (CONCURRENTLY). Now, with the solution I proposed, because we will release ShareUpdateExclusiveLock after setting a flag like rel_in_use_by_repack, the ALTER TABLE in session-2 will succeed but will error_out by CheckTableNotInUse(or a similar function that checks rel_in_use_by_repack). Both with and without this solution, acquiring AEL by REPACK (CONCURRENTLY) needs to wait concurrent locks like the one for SELECT in above example that could have been acquired during the time REPACKing had ShareUpdateExclusiveLock. > > And even if that were not the case, there are > > several paths to locking relations that don't ever go anywhere near > > CheckTableNotInUse(). > > But those aren't DDL, so they shouldn't be the problem (CREATE TRIGGER > might be - it seems to ignore CheckTableNotInUse, but perhaps it's > fine). > > So, in my undeerstanding Amit's idea has two parts: "set flag and > release/re-acquire" + "use CheckTableNotInUse (or some place like > that) to check the flag and fail for DDL commands." > True, this is the core of the idea. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-09T06:59:00Z
Tom Lane <tgl@sss.pgh.pa.us> wrote: > Antonin Houska <ah@cybertec.at> writes: > > We already introduced this definition above in the function to suppress this > > kind of warning > > > union > > { > > alignas(int32) varlena hdr; > > char data[sizeof(void *)]; > > } chunk_header; > > > The problem on a 32-bit system probably is that sizeof(void *) is 4. We need > > some other constant. Maybe (sizeof(varlena) + 1) ... > > This seems unnecessarily Rube Goldberg-ish already. Indeed. > Why not just > > uint64 chunk_header; > > It will not hurt anything if the variable has more-than-required > alignment. And it'd be better if it were the same size everywhere. That's certainly better. Thanks. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-09T08:43:14Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Thu, Apr 9, 2026 at 5:08 AM Mihail Nikalayeu > <mihailnikalayeu@gmail.com> wrote: > > > > On Wed, Apr 8, 2026 at 7:22 PM Andres Freund <andres@anarazel.de> wrote: > > > I don't think this is a viable path. You need to prevent any further lock > > > acquisitions on the relation to be able to swap it, not just conflicting DDL. > > > > AFAIU, Amit's main idea is that we currently upgrade the lock instead > > of **releasing and re-acquiring** it because we fear DDL between those > > actions. > > DML actions are ok for us; REPACK will wait for them while getting > > AEL. Later changes will be applied by REPACK backend while holding > > AEL. > > > > Yes, this is exactly what I had in mind. > > > One more thing we may prevent from sneaking into that hole is a > > VACUUM. It will not break anything, but will be huge waste of time and > > resources. > > > > We can prevent other commands (if required) by checking the > rel_in_use_by_repack flag but I thought for the initial version it is > better to do what is minimally required. > > > > And you need to wait for all pre-existing locks to have been released. That > > > doesn't really get easier by what you propose. > > > > Do you mean locks from other sessions accessing the table? Is it done > > automatically while waiting for AEL? > > > > Right and this is already the case with the code where locks > not-conflicting with ShareUpdateExclusiveLock could be present before > we try to upgrade the lock. The point was we will not let DDL execute > on the table after the new flag (rel_in_use_by_repack) is set and we > released the ShareUpdateExclusiveLock. > > > > I don't think CheckTableNotInUse() would work anyway - don't we already hold > > > locks by the point we call it? > > > > Yes, the DDL session already holds locks by the time > > CheckTableNotInUse is called - but is that really the problem? They > > will be released on error. > > > > Yes, that is the key to solve this problem. Let me take an example to > explain this a bit more. Right now, the problem can happen in the > following kind of sequence. > > Session-1: > REPACK (CONCURRENTLY) foo; > > -- now say after the above command has acquired > ShareUpdateExclusiveLock and is doing the work of copying the table, > Session-2 did following actions. > > Session-2: > Select * from foo; -- this is allowed > ALTER TABLE foo ADD COLUMN c2; -- this will blocked as Session-1 > already has acquired ShareUpdateExclusiveLock > > Session-1: > -- continues and tries to upgrade the lock to AEL. This leads to > deadlock ERROR in the current session doing REPACK (CONCURRENTLY). > > Now, with the solution I proposed, because we will release > ShareUpdateExclusiveLock after setting a flag like > rel_in_use_by_repack, the ALTER TABLE in session-2 will succeed but > will error_out by CheckTableNotInUse(or a similar function that checks > rel_in_use_by_repack). Both with and without this solution, acquiring > AEL by REPACK (CONCURRENTLY) needs to wait concurrent locks like the > one for SELECT in above example that could have been acquired during > the time REPACKing had ShareUpdateExclusiveLock. > > > > And even if that were not the case, there are > > > several paths to locking relations that don't ever go anywhere near > > > CheckTableNotInUse(). > > > > But those aren't DDL, so they shouldn't be the problem (CREATE TRIGGER > > might be - it seems to ignore CheckTableNotInUse, but perhaps it's > > fine). > > > > So, in my undeerstanding Amit's idea has two parts: "set flag and > > release/re-acquire" + "use CheckTableNotInUse (or some place like > > that) to check the flag and fail for DDL commands." > > > > True, this is the core of the idea. This approach LGTM when it comes to concurrent DDLs. However, consider REPACK holding ShareUpdateExclusiveLock (SUEL) and VACUUM (w/o VACOPT_SKIP_LOCKED) waiting for the same lock. Once REPACK releases its SUEL, VACUUM gets it and processes the table, then REPACK finally gets AccessExclusiveLock (AEL) and finishes too. Nothing went wrong from the data consistency POV, however the VACUUM proably wasted a lot of resources, because REPACK does "more than VACUUM". Furthermore, while REPACK was waiting for the AEL (and the VACUUM was running), a *lot of* DMLs could have been executed on the table, and REPACK will have to replay those while holding AEL. The point is that REPACK should hold AEL for as short time as possible. What Andres proposed (AFAIU) should help to avoid this problem because REPACK's request for AEL would get in front of the VACUUM's request for SUEL in the queue. Of course, VACUUM would eventually run too, but - if REPACK succeeded - it'd be much cheaper because it would (supposedly) find very little work to do. Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should possibly have higher priority than REPACK), but I think this prioritization should be implemented in other way than just letting it get in the way of REPACK (at the time REPACK is nearly finished). [1] https://www.postgresql.org/message-id/CABV9wwMrrP4S54jPGn5D-a8AbJm%2Be5%2BWORs6ykUBgXdc-%2B%2BNtQ%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-09T09:11:16Z
Hi! On Thu, Apr 9, 2026 at 10:43 AM Antonin Houska <ah@cybertec.at> wrote: > This approach LGTM when it comes to concurrent DDLs. However, consider REPACK > holding ShareUpdateExclusiveLock (SUEL) and VACUUM (w/o VACOPT_SKIP_LOCKED) > waiting for the same lock. Once REPACK releases its SUEL, VACUUM gets it and > processes the table, then REPACK finally gets AccessExclusiveLock (AEL) and > finishes too. > One more thing we may prevent from sneaking into that hole is a > VACUUM. It will not break anything, but will be huge waste of time and > resources. I thought about that too, I think we may just add some kind of CheckTableNotInUse in VACUUM after getting the SUEL. Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-09T09:26:29Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Thu, Apr 9, 2026 at 10:43 AM Antonin Houska <ah@cybertec.at> wrote: > > This approach LGTM when it comes to concurrent DDLs. However, consider REPACK > > holding ShareUpdateExclusiveLock (SUEL) and VACUUM (w/o VACOPT_SKIP_LOCKED) > > waiting for the same lock. Once REPACK releases its SUEL, VACUUM gets it and > > processes the table, then REPACK finally gets AccessExclusiveLock (AEL) and > > finishes too. > > > One more thing we may prevent from sneaking into that hole is a > > VACUUM. It will not break anything, but will be huge waste of time and > > resources. > > > I thought about that too, I think we may just add some kind of > CheckTableNotInUse in VACUUM after getting the SUEL. Sure, it's possible, but IMO the principal question is whether REPACK should let VACUUM and DDLs error out, or just let them wait. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-09T14:06:17Z
Hello! On Thu, Apr 9, 2026 at 11:26 AM Antonin Houska <ah@cybertec.at> wrote: > Sure, it's possible, but IMO the principal question is whether REPACK should > let VACUUM and DDLs error out, or just let them wait. One more idea: instead of ERROR in CheckTableNotInUse in case of in_repack - just release the lock and retry a little later (by that time, the repack operation will likely have acquired the AEL).
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-09T14:13:26Z
Hi, On 2026-04-09 01:36:00 +0200, Mihail Nikalayeu wrote: > Hello, Andres! > > On Wed, Apr 8, 2026 at 7:22 PM Andres Freund <andres@anarazel.de> wrote: > > I don't think this is a viable path. You need to prevent any further lock > > acquisitions on the relation to be able to swap it, not just conflicting DDL. > > AFAIU, Amit's main idea is that we currently upgrade the lock instead > of **releasing and re-acquiring** it because we fear DDL between those > actions. I got that, I just don't think it's going to work in any sort of way reasonably well. The fix here should be to make the system understand how to make lock upgrades as safe as possible, not to hack around the corners. For that you need to teach the deadlock detector about all of this properly. > > I don't think CheckTableNotInUse() would work anyway - don't we already hold > > locks by the point we call it? > > Yes, the DDL session already holds locks by the time > CheckTableNotInUse is called - but is that really the problem? They > will be released on error. There's no guarantee they get there if it's done after the lock acquisition. It can be part of a more complicated lock cycle. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-09T14:20:18Z
Hi, On 2026-04-09 10:43:14 +0200, Antonin Houska wrote: > What Andres proposed (AFAIU) should help to avoid this problem because > REPACK's request for AEL would get in front of the VACUUM's request for SUEL > in the queue. Note that that already happens today. This works today (without the error triggering patch): S1: REPACK starts S2: LOCK TABLE / VACUUM / ... starts waiting S1: REPACK tries to get AEL S1: REPACK's lock requests get reordered in the wait queue to be before S2 and just gets the lock S1: REPACK finishes S2: lock acquisition completes. That's because we do already have this "jumping the wait queue" logic, which I had forgotten about. What does *not* work is this: S1: REPACK starts S2: BEGIN; SELECT 1 FROM table LIMIT 1; S2: LOCK TABLE / VACUUM / ... starts waiting S1: REPACK tries to get AEL S1: lock is not granted, can't be reordered to be before S2, because S2 holds conflicting lock, deadlock detector triggers S2: lock acquisition completes But with my proposal to properly teach the deadlock detector about assuming there's a wait edge for the eventual lock upgrade by S1, the first example would still work, because the lock upgrade would not be considered a hard cycle, and the second example would have S2 error out. > Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should > possibly have higher priority than REPACK), but I think this prioritization > should be implemented in other way than just letting it get in the way of > REPACK (at the time REPACK is nearly finished). Yea, it makes no sense to interrupt the long running repack, given that the new relation will have much less stuff for vacuum to do. Greetings, Andres Freund -
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-09T14:26:22Z
Hi, On 2026-04-09 16:06:17 +0200, Mihail Nikalayeu wrote: > On Thu, Apr 9, 2026 at 11:26 AM Antonin Houska <ah@cybertec.at> wrote: > > Sure, it's possible, but IMO the principal question is whether REPACK should > > let VACUUM and DDLs error out, or just let them wait. > > One more idea: instead of ERROR in CheckTableNotInUse in case of > in_repack - just release the lock and retry a little later (by that > time, the repack operation will likely have acquired the AEL). I continue to think this approach has no chance of working. Even if it theoretically could, there are lots of paths to locking relations that do not go through CheckTableNotInUse(), and we're not going to just route them all through CheckTableNotInUse(). What CheckTableNotInUse() is for is to prevent DDL from changing the structure of the table when it is still being referred to. You can't just call CheckTableNotInUse() from a LOCK TABLE - it would trigger wrong errors *ALL THE TIME* because a LOCK TABLE does not need to error out just because there's also a cursor on the table. And it'd trigger lots of bogus errors. See the first example in https://postgr.es/m/fpr4nsmyy3mpfrm2mijspr44dgol2cjeke5tyznb4btsznxsgx%40iifdbfe2wl63 S2 would get the lock and then error out due to the proposed check. Even though there's no need for it. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-09T14:34:52Z
Hi, On 2026-04-09 10:26:22 -0400, Andres Freund wrote: > On 2026-04-09 16:06:17 +0200, Mihail Nikalayeu wrote: > > On Thu, Apr 9, 2026 at 11:26 AM Antonin Houska <ah@cybertec.at> wrote: > > > Sure, it's possible, but IMO the principal question is whether REPACK should > > > let VACUUM and DDLs error out, or just let them wait. > > > > One more idea: instead of ERROR in CheckTableNotInUse in case of > > in_repack - just release the lock and retry a little later (by that > > time, the repack operation will likely have acquired the AEL). > > I continue to think this approach has no chance of working. Even if it > theoretically could, there are lots of paths to locking relations that do not > go through CheckTableNotInUse(), and we're not going to just route them all > through CheckTableNotInUse(). > > What CheckTableNotInUse() is for is to prevent DDL from changing the structure > of the table when it is still being referred to. You can't just call > CheckTableNotInUse() from a LOCK TABLE - it would trigger wrong errors *ALL > THE TIME* because a LOCK TABLE does not need to error out just because there's > also a cursor on the table. > > > And it'd trigger lots of bogus errors. See the first example in > https://postgr.es/m/fpr4nsmyy3mpfrm2mijspr44dgol2cjeke5tyznb4btsznxsgx%40iifdbfe2wl63 > > S2 would get the lock and then error out due to the proposed check. Even > though there's no need for it. Before you protest that you could just let the non-DDL operation have its lock, sure, but that's an utterly terrible idea, because it will lead to more and more work accumulating that then has to happen when the AEL is actually held. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Alexander Lakhin <exclusion@gmail.com> — 2026-04-10T07:00:00Z
Hello Alvaro, 08.04.2026 00:38, Alvaro Herrera wrote: > Okay. I implemented that now, and here it is. ... Could you please look at an assertion failure produced by the following script, starting from 0d3dba38c:? createdb db1 createdb db2 echo " CREATE TABLE t0 (a text); BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; INSERT INTO t0 VALUES ('a'); SELECT pg_sleep(1); " | psql db1 & echo " CREATE TABLE t1 (id int PRIMARY KEY); CREATE TABLE t2 (id int PRIMARY KEY, a TEXT, FOREIGN KEY (id) REFERENCES t1); SET min_parallel_table_scan_size = 1; REPACK (CONCURRENTLY) t2; " | psql db2 wait It triggers for me: TRAP: failed Assert("TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin)"), File: "procarray.c", Line: 2071, PID: 3529520 postgres: parallel worker for PID 3529517 (ExceptionalCondition+0x69)[0x62f724b19c4c] postgres: parallel worker for PID 3529517 (+0x522456)[0x62f72498e456] postgres: parallel worker for PID 3529517 (GetSnapshotData+0x6b)[0x62f72498f50c] postgres: parallel worker for PID 3529517 (GetNonHistoricCatalogSnapshot+0x4b)[0x62f724b5bd90] postgres: parallel worker for PID 3529517 (GetCatalogSnapshot+0x20)[0x62f724b5ce7b] postgres: parallel worker for PID 3529517 (systable_beginscan+0x10b)[0x62f7245c31e7] postgres: parallel worker for PID 3529517 (+0x69e1e7)[0x62f724b0a1e7] postgres: parallel worker for PID 3529517 (+0x69e5a6)[0x62f724b0a5a6] postgres: parallel worker for PID 3529517 (+0x6a4d86)[0x62f724b10d86] postgres: parallel worker for PID 3529517 (RelationIdGetRelation+0x83)[0x62f724b11208] postgres: parallel worker for PID 3529517 (relation_open+0x1e)[0x62f72456c235] ... 2026-04-10 05:59:51.471 UTC [3529495] LOG: background worker "parallel worker" (PID 3529520) was terminated by signal 6: Aborted Best regards, Alexander -
RE: Adding REPACK [concurrently]
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> — 2026-04-10T10:53:53Z
Hi, When testing REPACK concurrently, I noticed that all WALs are retained from the moment REPACK begins copying data to the new table until the command finishes replaying concurrent changes on the new table and stops the repack decoding worker. I understand the reason: the REPACK command itself starts a long-running transaction, and logical decoding does not advance restart_lsn beyond the oldest running transaction's start position. As a result, slot.restart_lsn remains unchanged, preventing the checkpointer from recycling WALs. However, since REPACK can run for a long time (hours or even days), I'd like to confirm whether this is expected behavior or if we plan to improve it in the future ? And additionally, IIUC, REPACK without using concurrent option does not have this issue. Given that we do not restart a REPACK, I think the repack decoding worker should be able to advance restart_lsn each time after writing changes (similar to how a physical slot behaves). To illustrate this, I've written a patch (attached) that implements this approach, and it works fine for me. BTW, catalog_xmin also won't advance, but that seems not a big issue as the REPACK transaction itself also holds a snapshot that retains catalog tuples, so advancing catalog_xmin wouldn't change the situation anyway. Thoughts ? Best Regards, Hou zj
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-10T10:57:47Z
Alexander Lakhin <exclusion@gmail.com> wrote: > Could you please look at an assertion failure produced by the following > script, starting from 0d3dba38c:? > createdb db1 > createdb db2 > > echo " > CREATE TABLE t0 (a text); > BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE; > INSERT INTO t0 VALUES ('a'); > SELECT pg_sleep(1); > " | psql db1 & > > echo " > CREATE TABLE t1 (id int PRIMARY KEY); > CREATE TABLE t2 (id int PRIMARY KEY, a TEXT, FOREIGN KEY (id) REFERENCES t1); > SET min_parallel_table_scan_size = 1; > REPACK (CONCURRENTLY) t2; > " | psql db2 > wait > > It triggers for me: > TRAP: failed Assert("TransactionIdPrecedesOrEquals(TransactionXmin, RecentXmin)"), File: "procarray.c", Line: 2071, PID: 3529520 Attached is a fix that works for me. Nevertheless, REPACK (CONCURRENTLY) in your test goes ahead only due to commit 0d3dba38c7, which will probably be reverted [1]. Then REPACK will wait for the transaction in the other database (db1) to complete before it can actually start. Thanks for the report! [1] https://www.postgresql.org/message-id/cdgw4sbbfcgk6du3iv54r2dgiy4tfywoklbotlmj4irxavdcr3@glxfw5jj277q -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-10T13:21:45Z
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> wrote: > When testing REPACK concurrently, I noticed that all WALs are retained from > the moment REPACK begins copying data to the new table until the command > finishes replaying concurrent changes on the new table and stops the repack > decoding worker. > > I understand the reason: the REPACK command itself starts a long-running > transaction, and logical decoding does not advance restart_lsn beyond the > oldest running transaction's start position. As a result, slot.restart_lsn > remains unchanged, preventing the checkpointer from recycling WALs. I think you're right, sorry for the omission. > However, since REPACK can run for a long time (hours or even days), I'd like > to confirm whether this is expected behavior or if we plan to improve it > in the future ? And additionally, Yes, it will be improved. I have a draft patch for it, will rebase and post it soon. The plan is to: 1) preserve the original xmin/xmax of the tuples when we insert them into the new heap. Thus, besides achieving MVCC safety, we won't need XID assigned for most of the time. 2) do catalog changes in separate transactions - XID needed here, but these transactions take very short time. 3) use a single snapshot only for limited number of tuples/pages. When more data needs to be copied, a new snapshot is built, supposedly with higher ->xmin than the prevous one. > IIUC, REPACK without using concurrent option does not have this issue. It does not have the WAL recycling issue because it does not need to read WAL. However it also runs in a long transaction. Even though it does not need XID for the actual heap rewriting, it gets one at the moment it locks the table using AccessExclusiveLock (which is at the very beginning). > Given that we do not restart a REPACK, I think the repack decoding worker > should be able to advance restart_lsn each time after writing changes > (similar to how a physical slot behaves). To illustrate this, I've written > a patch (attached) that implements this approach, and it works fine for me. LGTM, thanks! > BTW, catalog_xmin also won't advance, but that seems not a big issue as > the REPACK transaction itself also holds a snapshot that retains catalog tuples, > so advancing catalog_xmin wouldn't change the situation anyway. The snapshot "resetting" (mentioned above) should fix this problem too. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Robert Treat <rob@xzilla.net> — 2026-04-10T16:14:59Z
On Thu, Apr 9, 2026 at 10:20 AM Andres Freund <andres@anarazel.de> wrote: > On 2026-04-09 10:43:14 +0200, Antonin Houska wrote: > > What Andres proposed (AFAIU) should help to avoid this problem because > > REPACK's request for AEL would get in front of the VACUUM's request for SUEL > > in the queue. > > Note that that already happens today. > > This works today (without the error triggering patch): > > S1: REPACK starts > S2: LOCK TABLE / VACUUM / ... starts waiting > S1: REPACK tries to get AEL > S1: REPACK's lock requests get reordered in the wait queue to be before S2 and > just gets the lock > S1: REPACK finishes > S2: lock acquisition completes. > > That's because we do already have this "jumping the wait queue" logic, which I > had forgotten about. > You know, I was wondering how this wasn't already a problem for pg_repack/pg_squeeze, and I guess this explains it :-P > > What does *not* work is this: > > S1: REPACK starts > S2: BEGIN; SELECT 1 FROM table LIMIT 1; > S2: LOCK TABLE / VACUUM / ... starts waiting > S1: REPACK tries to get AEL > S1: lock is not granted, can't be reordered to be before S2, because S2 holds > conflicting lock, deadlock detector triggers > S2: lock acquisition completes > > But with my proposal to properly teach the deadlock detector about assuming > there's a wait edge for the eventual lock upgrade by S1, the first example > would still work, because the lock upgrade would not be considered a hard > cycle, and the second example would have S2 error out. > In the above S2 will error out if you try to run a VACUUM, but the point still stands that calling an explicit LOCK or similar could lead to this issue. In the current repack world, we document the need for lock escalation at the end of the repacking and caution that doing things like DDL or explicit LOCKing could cause trouble, so don't do that. What you're proposing above would be an improvement though, IMHO. > > > Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should > > possibly have higher priority than REPACK), but I think this prioritization > > should be implemented in other way than just letting it get in the way of > > REPACK (at the time REPACK is nearly finished). > > Yea, it makes no sense to interrupt the long running repack, given that the > new relation will have much less stuff for vacuum to do. > We might be talking about 2 different scenarios. In the case where we are at the point of lock escalation, you would probably want the repack to get priority over a waiting vacuum, even a failsafe vacuum. But outside of that scenario, we can't know that the repack is the better option (and statistically it probably isn't) since a repack that is actively copying rows might still need to rebuild some large number of indexes (or just some really expensive index) which could take significantly longer than a failsafe vacuum would need to ensure wraparound avoidance. I don't think we'd go as far as saying the failsafe vacuum should cancel the repack, but I think ideally we'd like it to not be canceled either, since that would increase likelihood for dba/monitoring to pick up on the situation, and in the case that REPACK fails for some reason, the failsafe vacuum could immediately start working without having to go through any additional hoops. Robert Treat https://xzilla.net
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-10T18:56:42Z
Robert Treat <rob@xzilla.net> wrote: > On Thu, Apr 9, 2026 at 10:20 AM Andres Freund <andres@anarazel.de> wrote: > > On 2026-04-09 10:43:14 +0200, Antonin Houska wrote: > > > Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should > > > possibly have higher priority than REPACK), but I think this prioritization > > > should be implemented in other way than just letting it get in the way of > > > REPACK (at the time REPACK is nearly finished). > > > > Yea, it makes no sense to interrupt the long running repack, given that the > > new relation will have much less stuff for vacuum to do. > > > > We might be talking about 2 different scenarios. In the case where we > are at the point of lock escalation, you would probably want the > repack to get priority over a waiting vacuum, even a failsafe vacuum. > But outside of that scenario, we can't know that the repack is the > better option (and statistically it probably isn't) since a repack > that is actively copying rows might still need to rebuild some large > number of indexes (or just some really expensive index) which could > take significantly longer than a failsafe vacuum would need to ensure > wraparound avoidance. I don't think we'd go as far as saying the > failsafe vacuum should cancel the repack, but I think ideally we'd > like it to not be canceled either, since that would increase > likelihood for dba/monitoring to pick up on the situation, and in the > case that REPACK fails for some reason, the failsafe vacuum could > immediately start working without having to go through any additional > hoops. What about just teaching the anti-wraparound VACUUM to print out a WARNING if it could not lock the table in "reasonable" time? The DBA would then have to consider if the blocking command should be cancelled, whether it's REPACK or something else. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-12T13:31:20Z
Hello! On Thu, Apr 9, 2026 at 4:20 PM Andres Freund <andres@anarazel.de> wrote: > But with my proposal to properly teach the deadlock detector about assuming > there's a wait edge for the eventual lock upgrade by S1, the first example > would still work, because the lock upgrade would not be considered a hard > cycle, and the second example would have S2 error out. Attached patch contains some (maybe naive) POC for similar approach. It adds a 'deadlock_protected' flag, which changes how the deadlock detector cancels backends. Instead of cancelling the backend entered the deadlock detector - it cancel some another (nearest hard edge) until it is possible to get the lock (either by reordering or directly). Also, I added test cases with the scenarios you mentioned into the repack spec - to ensure repack is still working while other backend are cancelled. > > Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should > > possibly have higher priority than REPACK), but I think this prioritization > > should be implemented in other way than just letting it get in the way of > > REPACK (at the time REPACK is nearly finished). > > Yea, it makes no sense to interrupt the long running repack, given that the > new relation will have much less stuff for vacuum to do. For now I decided to keep anti-wraparound unaffected because the feature is may be used not only for repack but also for other potential scenarios. But yes, maybe it's worth canceling antiwraparound in the repack case. Also, checking proc flags to detect antuwraparound requires LWLockConditionalAcquire(ProcArrayLock) - something I don't like in deadlock detector. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Alexander Lakhin <exclusion@gmail.com> — 2026-04-12T14:00:00Z
Hello Antonin and Alvaro, 10.04.2026 13:57, Antonin Houska wrote: > Attached is a fix that works for me. > > Nevertheless, REPACK (CONCURRENTLY) in your test goes ahead only due to commit > 0d3dba38c7, which will probably be reverted [1]. Then REPACK will wait for the > transaction in the other database (db1) to complete before it can actually > start. Thank you for the fix! I've stumbled upon one more issue with this feature: CREATE TABLE t (i int PRIMARY KEY); REPACK (CONCURRENTLY) t; fails for me with sanitizers enabled and min_dynamic_shared_memory = '1GB' in postgresql.conf as below: 2026-04-12 13:23:02.000 UTC [2733633] LOG: statement: REPACK (CONCURRENTLY) t; repack.c:3373:15: runtime error: load of value 240, which is not a valid value for type '_Bool' #0 0x6441f7eba454 in start_repack_decoding_worker .../src/backend/commands/repack.c:3373 #1 0x6441f7ebdaad in rebuild_relation .../src/backend/commands/repack.c:1010 #2 0x6441f7ebe9a2 in cluster_rel .../src/backend/commands/repack.c:656 #3 0x6441f7ebefea in process_single_relation .../src/backend/commands/repack.c:2359 #4 0x6441f7ebf870 in ExecRepack .../src/backend/commands/repack.c:296 #5 0x6441f886f20e in standard_ProcessUtility .../src/backend/tcop/utility.c:867 ... 2026-04-12 13:23:03.620 UTC [2733620] LOG: client backend (PID 2733633) was terminated by signal 6: Aborted 2026-04-12 13:23:03.620 UTC [2733620] DETAIL: Failed process was running: REPACK (CONCURRENTLY) t; Could you please have a look? Best regards, Alexander
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-12T14:02:59Z
Hi, On 2026-04-10 12:14:59 -0400, Robert Treat wrote: > On Thu, Apr 9, 2026 at 10:20 AM Andres Freund <andres@anarazel.de> wrote: > > > Anti-wraparound (failsafe) VACUUM is a bit different case [1] (i.e. it should > > > possibly have higher priority than REPACK), but I think this prioritization > > > should be implemented in other way than just letting it get in the way of > > > REPACK (at the time REPACK is nearly finished). > > > > Yea, it makes no sense to interrupt the long running repack, given that the > > new relation will have much less stuff for vacuum to do. > > > > We might be talking about 2 different scenarios. In the case where we > are at the point of lock escalation, you would probably want the > repack to get priority over a waiting vacuum, even a failsafe vacuum. > But outside of that scenario, we can't know that the repack is the > better option (and statistically it probably isn't) since a repack > that is actively copying rows might still need to rebuild some large > number of indexes (or just some really expensive index) which could > take significantly longer than a failsafe vacuum would need to ensure > wraparound avoidance. I don't really understand why you consider REPACK CONCURRENTLY to be so different from other existing long-running operations? > I don't think we'd go as far as saying the failsafe vacuum should cancel the > repack, but I think ideally we'd like it to not be canceled either I don't think what I suggested could lead to (auto-)vacuum getting cancelled? Antonin's earlier patch could, but as long as you only do cancellations if there's an actual deadlock cycle, VACUUM should not get cancelled, since it won't already hold a lower level lock? > , since that would increase likelihood for dba/monitoring to pick up on the > situation, and in the case that REPACK fails for some reason, the failsafe > vacuum could immediately start working without having to go through any > additional hoops. Not sure I buy this (leaving aside the premise doesn't hold, I think). If you have monitoring for either long running tasks or tables not getting autovacuumed, it'd pick up on that regardless of whether autovacuum is getting cancelled or not. What realistic monitoring would pick up on autovacuum just being stuck waiting for a lock for long, but would not pick up on repack running forever or age(datfrozenxid) getting a bit long in the tooth? Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-12T14:05:34Z
Hi, On 2026-04-12 15:31:20 +0200, Mihail Nikalayeu wrote: > On Thu, Apr 9, 2026 at 4:20 PM Andres Freund <andres@anarazel.de> wrote: > > But with my proposal to properly teach the deadlock detector about > assuming > > there's a wait edge for the eventual lock upgrade by S1, the first example > > would still work, because the lock upgrade would not be considered a hard > > cycle, and the second example would have S2 error out. > > Attached patch contains some (maybe naive) POC for similar approach. > It adds a 'deadlock_protected' flag, which changes how the deadlock > detector cancels backends. > > Instead of cancelling the backend entered the deadlock detector - it > cancel some another (nearest hard edge) until it is possible to get the > lock (either by > reordering or directly). I don't think that's as good. The problem is that that way you're only detecting the deadlocks once they have materialized (i.e. once repack actually does the lock upgrade), rather than cancelling when we know that the problem starts. Having sessions pointlessly blocked for many hours is bad. > Also, I added test cases with the scenarios you mentioned into the repack > spec - to ensure repack is still working while other backend are cancelled. I think we should perhaps commit spec tests for these (I've not yet reviewed them, but in principle), even before we fix the problem. It's good to document the current behavior and have a comment that the wrongly cancelled case should not trigger an error. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-12T14:58:31Z
Hi! On Sun, Apr 12, 2026 at 4:05 PM Andres Freund <andres@anarazel.de> wrote: > I don't think that's as good. The problem is that that way you're only > detecting the deadlocks once they have materialized (i.e. once repack actually > does the lock upgrade), rather than cancelling when we know that the problem > starts. Having sessions pointlessly blocked for many hours is bad. O, I think I understand you now. You propose to somehow mark SUEL as "going to become AEL, so, the deadlock detector should treat it as such". In that case LOCK TABLE should get some kind of "future deadlock detected". Yep, that feels much better. I'll try to check that approach tomorrow. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-13T09:42:03Z
Alexander Lakhin <exclusion@gmail.com> wrote: > I've stumbled upon one more issue with this feature: > CREATE TABLE t (i int PRIMARY KEY); > REPACK (CONCURRENTLY) t; > > fails for me with sanitizers enabled and > min_dynamic_shared_memory = '1GB' > in postgresql.conf as below: > 2026-04-12 13:23:02.000 UTC [2733633] LOG: statement: REPACK (CONCURRENTLY) t; > repack.c:3373:15: runtime error: load of value 240, which is not a valid value for type '_Bool' > #0 0x6441f7eba454 in start_repack_decoding_worker .../src/backend/commands/repack.c:3373 > #1 0x6441f7ebdaad in rebuild_relation .../src/backend/commands/repack.c:1010 > #2 0x6441f7ebe9a2 in cluster_rel .../src/backend/commands/repack.c:656 > #3 0x6441f7ebefea in process_single_relation .../src/backend/commands/repack.c:2359 > #4 0x6441f7ebf870 in ExecRepack .../src/backend/commands/repack.c:296 > #5 0x6441f886f20e in standard_ProcessUtility .../src/backend/tcop/utility.c:867 I could not reproduce the problem, but noticed that the field is not initialized correctly. Please confirm that 0001 should fix that. While working on it, I noticed that one field can be removed from DecodingWorkerShared - 0002 removes that. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-14T13:37:56Z
Andres Freund <andres@anarazel.de> wrote: > On 2026-04-12 15:31:20 +0200, Mihail Nikalayeu wrote: > > Instead of cancelling the backend entered the deadlock detector - it > > cancel some another (nearest hard edge) until it is possible to get the > > lock (either by > > reordering or directly). > > I don't think that's as good. The problem is that that way you're only > detecting the deadlocks once they have materialized (i.e. once repack actually > does the lock upgrade), rather than cancelling when we know that the problem > starts. This is my hack that tries to do that. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-14T13:58:55Z
Hi, On 2026-04-14 15:37:56 +0200, Antonin Houska wrote: > Andres Freund <andres@anarazel.de> wrote: > > > On 2026-04-12 15:31:20 +0200, Mihail Nikalayeu wrote: > > > Instead of cancelling the backend entered the deadlock detector - it > > > cancel some another (nearest hard edge) until it is possible to get the > > > lock (either by > > > reordering or directly). > > > > I don't think that's as good. The problem is that that way you're only > > detecting the deadlocks once they have materialized (i.e. once repack actually > > does the lock upgrade), rather than cancelling when we know that the problem > > starts. > > This is my hack that tries to do that. I still think this needs to be in the deadlock detector. The lock cycle just needs to be a bit more complicated for a hack in JoinWaitQueue not to work. There's no guarantee that the wait that triggers the deadlock is actually on the relation being repacked. Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-14T16:55:17Z
Hello! On Tue, Apr 14, 2026 at 3:58 PM Andres Freund <andres@anarazel.de> wrote: > I still think this needs to be in the deadlock detector. The lock cycle just > needs to be a bit more complicated for a hack in JoinWaitQueue not to work. > There's no guarantee that the wait that triggers the deadlock is actually on > the relation being repacked. I have started prototyping a way to declare a "future" lock which the deadlock detector treats as a hard edge. But I currently stuck on issues related to the fact that SUE doesn't force weak locks (fast-path) to go through FastPathTransferRelationLocks, so the deadlock detector can't handle the case when another backend tries to execute LOCK TABLE repack_test IN SHARE UPDATE EXCLUSIVE MODE; Also, VACUUM takes the same lock. I'm not sure how to deal with this in a non-hacky way. One option is to force SUE to transfer locks if relation it is trying to lock relation which marked with "future lock". But I am not sure it is good enough or covers all tricky cases (multiple backends in the loop). Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-15T14:50:11Z
Andres Freund <andres@anarazel.de> wrote: > On 2026-04-14 15:37:56 +0200, Antonin Houska wrote: > > Andres Freund <andres@anarazel.de> wrote: > > > > > On 2026-04-12 15:31:20 +0200, Mihail Nikalayeu wrote: > > > > Instead of cancelling the backend entered the deadlock detector - it > > > > cancel some another (nearest hard edge) until it is possible to get the > > > > lock (either by > > > > reordering or directly). > > > > > > I don't think that's as good. The problem is that that way you're only > > > detecting the deadlocks once they have materialized (i.e. once repack actually > > > does the lock upgrade), rather than cancelling when we know that the problem > > > starts. > > > > This is my hack that tries to do that. > > I still think this needs to be in the deadlock detector. The lock cycle just > needs to be a bit more complicated for a hack in JoinWaitQueue not to work. > There's no guarantee that the wait that triggers the deadlock is actually on > the relation being repacked. ok, I see. I thought of a "hypothetical graph", which would include the to-be-granted lock, but the major issue is that it will not work correctly without the locking the LMGR's LW locks we do in CheckDeadLock(): for (i = 0; i < NUM_LOCK_PARTITIONS; i++) LWLockAcquire(LockHashPartitionLockByIndex(i), LW_EXCLUSIVE); And obviously, doing this each time we want to insert a lock into the queue would be bad for performance. It's even mentioned in the storage/lmgr/README that the current approach is optimistic, so I think that major rework would be needed if we wanted to entirely avoid waiting that leads to deadlock. The approach proposed by Mihail [1] seems the least problematic to me, and something like that occurred to me when I thought about the problem the first time. However, when we wake up the other processes in order to run the deadlock detection, they should do that immediately. I've got no good idea about implementation at the moment, since latch can be set for unrelated reasons. (Besides that, I have some more questions about this patch, which I can post separately.) [1] https://www.postgresql.org/message-id/CADzfLwURKVNQ%2B%2BDpi7bjoGfj-8pchDQEVex3eWBx0NCYn6TbDQ%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Andres Freund <andres@anarazel.de> — 2026-04-15T14:59:51Z
Hi, On 2026-04-15 16:50:11 +0200, Antonin Houska wrote: > I thought of a "hypothetical graph", which would include the to-be-granted > lock, but the major issue is that it will not work correctly without the > locking the LMGR's LW locks we do in CheckDeadLock(): > > for (i = 0; i < NUM_LOCK_PARTITIONS; i++) > LWLockAcquire(LockHashPartitionLockByIndex(i), LW_EXCLUSIVE); > > And obviously, doing this each time we want to insert a lock into the queue > would be bad for performance. Hence my suggestion to do this as part of the deadlock check. Then we don't do this unnecessary work outside of the case where we actually need it. That does need to deal with the case of the deadlock check running first in the backend doing repack, but that's not that hard - I think it'd be good enough to set its deadlock timeout temporarily to a higher value. The backend *should* still run the deadlock detector, because it could probably still get into a deadlock (e.g. due to a pg_class access or something). Greetings, Andres Freund
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-15T15:54:12Z
Hello! On Wed, Apr 15, 2026 at 4:50 PM Antonin Houska <ah@cybertec.at> wrote: > The approach proposed by Mihail [1] seems the least problematic to me, and > something like that occurred to me when I thought about the problem the first > time. However, when we wake up the other processes in order to run the > deadlock detection, they should do that immediately. I've got no good idea > about implementation at the moment, since latch can be set for unrelated > reasons. (Besides that, I have some more questions about this patch, which I > can post separately.) It is already possible to "deadlock" ANOTHER backend while running the deadlock check [0]. Supported in current infra, just not used at the moment (my POC used that also). [0]: https://github.com/postgres/postgres/blob/master/src/backend/storage/lmgr/proc.c#L1870-L1873
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-15T18:28:24Z
Andres Freund <andres@anarazel.de> wrote: > On 2026-04-15 16:50:11 +0200, Antonin Houska wrote: > > I thought of a "hypothetical graph", which would include the to-be-granted > > lock, but the major issue is that it will not work correctly without the > > locking the LMGR's LW locks we do in CheckDeadLock(): > > > > for (i = 0; i < NUM_LOCK_PARTITIONS; i++) > > LWLockAcquire(LockHashPartitionLockByIndex(i), LW_EXCLUSIVE); > > > > And obviously, doing this each time we want to insert a lock into the queue > > would be bad for performance. > > Hence my suggestion to do this as part of the deadlock check. Then we don't do > this unnecessary work outside of the case where we actually need it. > That does need to deal with the case of the deadlock check running first in > the backend doing repack, but that's not that hard - I think it'd be good > enough to set its deadlock timeout temporarily to a higher value. The backend > *should* still run the deadlock detector, because it could probably still get > into a deadlock (e.g. due to a pg_class access or something). Yes, the question is when we should run that check. I thought that it should happen during each lock acquisition, and that made me worried about performance. AFAIU you suggest REPACK to do something like: 1. Acquire ShareUpdateExclusiveLock 2. Perform the "enhanced check" to see if a future request for AccessExclusiveLock can trigger a deadlock. 3. Do major part of the work (copy the table, build indexes, ...) 4. Request AccessExclusive lock. 5. Finish the work (process the remaining concurrent changes and swap the table files). This makes me concerned that if another session does BEGIN; TABLE t; between steps 2 and 4, and something like -- Get AccessExclusiveLock ALTER TABLE t ADD COLUMN j int; just after step 4, the two session will probably up in a deadlock anyway. In other words, even if REPACK does the check early, it does not prevent other sessions from getting in the way. Maybe I'm still missing something. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-16T11:18:55Z
Hello! On Wed, Apr 15, 2026 at 8:28 PM Antonin Houska <ah@cybertec.at> wrote: > just after step 4, the two session will probably up in a deadlock anyway. In > other words, even if REPACK does the check early, it does not prevent other > sessions from getting in the way. > > Maybe I'm still missing something. I was trying to solve that by using another approach: the ability to define a "future lock" [0]. It is declared even before taking the actual lock, so, no race is possible. That approach works correctly except one case - ShareUpdateExclusiveLock from another backend, I described it here [1] a little bit. For now, I don't know how to solve it without a performance downgrade. [0]: https://github.com/michail-nikolaev/postgres/commit/ba0f4247dad3d96b8282cd18056b7776cd69317c [1]: https://www.postgresql.org/message-id/flat/CADzfLwU8Qw6LXFHO7Tbjc-O7o%2BtM26jdnOJBWqYLu61rf7bO%2Bg%40mail.gmail.com#1e96f8882363afb2fc53c2f08346f527
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-17T02:01:00Z
Hello! I think I got working POC for deadlock-detector enhancements for REPACK (and potentially other). Each PGPROC gets one FutureWaitLock slot: a (locktag, mode) pair, which is empty when locktag_lockmethodid == 0. A backend calls LockDeclareFutureWait() to publish its intent. Only REPACK (CONCURRENTLY) uses it today, declaring its future AccessExclusiveLock. Only the owning backend writes its slot; it does so under the partition lock covering the declared tag. Remote readers (the deadlock detector) must hold that partition lock to observe it. The detector holds all partition locks during its graph walk, so it can read any slot safely. The slot clears in LockAcquireExtended() at the exact point the backend attaches its waitLink for the same tag and mode. It is also cleared on abort, backend exit, and end of transaction. FindLockCycleRecurseFuture() treats the declared future lock as a hard edge to (a) current holders whose mode conflicts and (b) already-queued waiters that JoinWaitQueue() would place ahead of the future request. Weak relation locks are kept in per-backend fast-path arrays and remain invisible to the detector's graph walk. Normally LockAcquireExtended() migrates them to the main lock table when a conflicting strong lock is requested. This action is gated by FastPathStrongRelationLocks->count to prevent new fast-path lock processing. A future-wait declaration does not trigger that migration due to performance considerations. Instead CheckDeadLock() now snapshots currently declared fast-path-incompatible future waits and increments FastPathStrongRelationLocks->count for those tags (blocking new fast-path acquisitions). It then calls FastPathTransferRelationLocks() for each such tag to move existing holders into the main table, making everything visible to the deadlock walker. Once the deadlock checker finishes, FastPathStrongRelationLocks->count is decremented enabling fast-path processing again. Performance degradation occurs only during deadlock processing and only if REPACK is active. If a deadlock contains a "future" edge it is reported as "future deadlock detected". Repack first declares future AE and only then proceeds to actually get SUE. Added test cases for different scenarios, including SUE from another backend and a 3-backend deadlock.
-
Re: Adding REPACK [concurrently]
Justin Pryzby <pryzby@telsasoft.com> — 2026-04-17T14:44:12Z
I had trouble testing this at first: postgres=# REPACK (CONCURRENTLY) users; ERROR: 42501: permission denied to use replication slots DETAIL: Only roles with the REPLICATION attribute may use replication slots. CONTEXT: REPACK decoding worker LOCATION: CheckSlotPermissions, slot.c:1697 That's surprising, since it was run as a superuser. It turns out that repack runs as the owner of the table, and the table *owner* needs to have REPLICATION -- regardless of who runs the command. I imagine people have been testing with one user, that both owns the table and invokes REPACK. Maybe this just needs to be clarified in the documentation/error message? -- Justin
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-18T19:23:08Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > I think I got working POC for deadlock-detector enhancements for > REPACK (and potentially other). That looks interesting, I'll check it. I've also thought about the problem quite a bit this week. I tried to add a pointer to PGPROC that, like ->waitLock, points to the lock being acquired, but it's initialized before the actual waiting starts. I adjusted the deadlock detector to use that pointer too, but it did not work. The problem was probably that the lock wasn't in the queue during the check. Finally it occurred to me that a new field can be added to the LOCK structure, indicating that the lock is being upgraded. It enforces some extra deadlock checks by other processes, so that the upgrading process does not have to care about deadlock detection at all. More info in the commit message. It should handle all the cases in your tests, however a new injection point would be needed. (Not added yet.) -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-18T22:46:00Z
Hello, Antonin! I've briefly looked at your patch. As I understand it, it cancels the other process only when REPACK actually tries to upgrade the lock. In that sense, its approach is close to my variant at [0]. AFAIU, Andres's concern is that the "victim" should be cancelled sooner, rather than waiting until REPACK actually attempts the upgrade. I was trying to solve it by [1]. ------------------------------- There are some comments on [1]: Some details related to POC from previous letter (once I re-read that in the morning I relized it is not so easy to understand, sorry). So, goal of patch to: * make sure REPACK will survive deadlock which may caused by lock upgrade * reject some other backend with error early than deadlock really occur - to avoid pointless waiting for other commands * implement it at deadlock detector level to correctly handle different 2+ backend-involved scenarious To achive it the first idea is to add some kind of "future lock" (FutureWaitLock). It may declare an intention to acquire a lock of a certain level as high-priority in the future. Once deadlock detector starts to walking graph it treat that intention like an actual "waiting". That way deadlock detector looks for one more step in the future - moment of actual acquiring the "future" lock - and if it ends with cycle - reject waiting backend ("future deadlock detected"). Looks like best place to put that FutureWaitLock is PGPROC itself. Few moments to consider: * it is not allowed to declare a future lock if backend already holds some kind of lock for the same tag (this may cause a race condition). * so, REPACK first declares future Access Excluvie and only after it Share Update Exclusive * declared future lock should be >= SUE So far everything looks good. In case of that scenario: S1: BEGIN; SELECT * FROM t; S2: REPACK (CONCURRENTLY) t; S1: LOCK TABLE t in ACCESS EXCLUSIVE MODE <--- "future deadlock detected" Deadlock detector sees: S1 ----> S2 (waiting for AE conflicting with SUE) S2 ----> S1 (future wait for AE conflict with current Access Share) But there is a tricky case related to SUE: S1: BEGIN; SELECT * FROM t; S2: REPACK (CONCURRENTLY) t; S1: LOCK TABLE t in SHARE UPDATE EXCLUSIVE MODE; In that case we have almost the same deadlock scenario - but that is not visible to deadlock detector. It happens because SUE does not force all locks taken on 't' to be transfered using FastPathTransferRelationLocks into the main table (SUE is does not ConflictsWithRelationFastPath). Because of it S2 -> S1 edge is not visible by deadlock detector (Access Share is held using fast-path). To deal with it we may force any relation with FutureWaitLock to through slow-path locking - but I don't think it is acceptable. Instead next approach is proposed: * deadlock detector checks if any "future" locks are present in the system (counter in shared memory) * if so - it iterates over all PROCs to collect relations which are "future locked" * for each such relations - FastPathTransferRelationLocks called and slow-path is forced (FastPathStrongRelationLocks->count) * deadlock detector start looking for cycles * once ready - FastPathStrongRelationLocks->count is decremented to allow fast-path That way performance degradation happens only during deadlock detector processing and only if some future locks present. Due tue LW ordering we need to use some tricks to avoid LW-level deadlocks (using some kind of retry logic, but that is more explained in the patch). [0]: https://www.postgresql.org/message-id/flat/CADzfLwURKVNQ%2B%2BDpi7bjoGfj-8pchDQEVex3eWBx0NCYn6TbDQ%40mail.gmail.com#bceb18354aa20c130a94b1deedd76fb7. [1}] postgresql.org/message-id/flat/CADzfLwU8Qw6LXFHO7Tbjc-O7o%2BtM26jdnOJBWqYLu61rf7bO%2Bg%40mail.gmail.com#1e96f8882363afb2fc53c2f08346f527 -
Re: Adding REPACK [concurrently]
Alexander Lakhin <exclusion@gmail.com> — 2026-04-19T11:00:01Z
Hello Antonin and Alvaro, I've noticed a wrong reference coined in commit 28d534e2a: A comment in repack_worker.c says: /* * Override the default bgworker_die() with die() so we can use * CHECK_FOR_INTERRUPTS(). */ pqsignal(SIGTERM, die); BackgroundWorkerUnblockSignals(); but bgworker_die() doesn't exist since d62dca3b2 (2026-02-18). Maybe this is not needed anymore? Best regards, Alexander
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-20T07:44:45Z
Justin Pryzby <pryzby@telsasoft.com> wrote: > I had trouble testing this at first: > > postgres=# REPACK (CONCURRENTLY) users; > ERROR: 42501: permission denied to use replication slots > DETAIL: Only roles with the REPLICATION attribute may use replication slots. > CONTEXT: REPACK decoding worker > LOCATION: CheckSlotPermissions, slot.c:1697 > > That's surprising, since it was run as a superuser. > > It turns out that repack runs as the owner of the table, and the table > *owner* needs to have REPLICATION -- regardless of who runs the command. > > I imagine people have been testing with one user, that both owns the > table and invokes REPACK. Maybe this just needs to be clarified in the > documentation/error message? It was discussed earlier [1] and the concerns about possibly excessive resource consumptions were addressed by [2]. So I think it the fix was just forgotten. Attached here. [1] https://www.postgresql.org/message-id/202603161220.7nv6whwu33hi@alvherre.pgsql [2] https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e76d8c749c3152657711ed733f0aea61c0e36a91 -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-20T07:47:36Z
Antonin Houska <ah@cybertec.at> wrote: > It was discussed earlier [1] and the concerns about possibly excessive > resource consumptions were addressed by [2]. So I think it the fix was just > forgotten. Attached here. Sorry, I attached wrong patch. This is what I meant. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Chao Li <li.evan.chao@gmail.com> — 2026-04-20T07:54:19Z
> On Apr 20, 2026, at 15:47, Antonin Houska <ah@cybertec.at> wrote: > > Antonin Houska <ah@cybertec.at> wrote: > >> It was discussed earlier [1] and the concerns about possibly excessive >> resource consumptions were addressed by [2]. So I think it the fix was just >> forgotten. Attached here. > > Sorry, I attached wrong patch. This is what I meant. > > -- > Antonin Houska > Web: https://www.cybertec-postgresql.com > > <0001-Do-not-check-the-REPLICATION-attribute-when-running-.patch> In my test, I found the exact same issue and I was about to post a fix that is the same as v1. So, v1 looks good to me. BTW, Antonin, can you please take a look at [1] that is the other issue I found with repack. [1] https://www.postgresql.org/message-id/10DD5E13-B45D-44F1-BE08-C63E00ABCAC0%40gmail.com Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-20T11:45:35Z
On 2026-Apr-20, Antonin Houska wrote: > Antonin Houska <ah@cybertec.at> wrote: > > > It was discussed earlier [1] and the concerns about possibly excessive > > resource consumptions were addressed by [2]. So I think it the fix was just > > forgotten. Attached here. > > Sorry, I attached wrong patch. This is what I meant. Yeah, I had also written the same patch a couple of days ago. BTW I ran into a small problem after adding some tests in cluster.sql that would exercise this -- that test would die more or less randomly but frequently in CI (which it never did in my laptop) because of the size of the snapshot, ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key; REPACK (CONCURRENTLY) ptnowner1; +ERROR: initial slot snapshot too large +CONTEXT: REPACK decoding worker RESET SESSION AUTHORIZATION; I think the solution for this is to move cluster to a separate parallel test. The one where it is now is a bit too crowded. Maybe the one for compression is okay? I'll test and push if I see it passing CI. Another obvious thing after adding tests is that the LOGIN privilege is required, which is also quite bogus IMO. 0002 here should solve that. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "If you want to have good ideas, you must have many ideas. Most of them will be wrong, and what you have to learn is which ones to throw away." (Linus Pauling) -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-20T13:46:13Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-Apr-20, Antonin Houska wrote: > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > It was discussed earlier [1] and the concerns about possibly excessive > > > resource consumptions were addressed by [2]. So I think it the fix was just > > > forgotten. Attached here. > > > > Sorry, I attached wrong patch. This is what I meant. > > Yeah, I had also written the same patch a couple of days ago. > > BTW I ran into a small problem after adding some tests in cluster.sql > that would exercise this -- that test would die more or less randomly > but frequently in CI (which it never did in my laptop) because of the > size of the snapshot, > > ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key; > REPACK (CONCURRENTLY) ptnowner1; > +ERROR: initial slot snapshot too large > +CONTEXT: REPACK decoding worker > RESET SESSION AUTHORIZATION; > > I think the solution for this is to move cluster to a separate parallel > test. The one where it is now is a bit too crowded. Maybe the one for > compression is okay? I'll test and push if I see it passing CI. That shouldn't break anything, but I have no idea why this problem was not triggered (as far as I remember) by the stress tests we ran during development. I thought that it might be due to less frequent calls of SnapBuildPurgeOlderTxn(), which might in turn be due to less frequent checkpoints (because xl_running_xacts is typically written during checkpoint), and that checkpoints may be deliberately less frequent to make regression tests run faster. However I don't immediately see related configuration in the regression test setup. > Another obvious thing after adding tests is that the LOGIN privilege is > required, which is also quite bogus IMO. 0002 here should solve that. ok > >From b3d4158356f4914d2b0cba86eef6994c0ee50ab9 Mon Sep 17 00:00:00 2001 > From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de> > Date: Mon, 20 Apr 2026 11:38:48 +0200 > Subject: [PATCH 1/2] REPACK: do not require the user to have REPLICATION > Because there are now successful concurrent repack runs in the > regression tests, we're forced to run test_plan_advice under > wal_level=replica. Is this an attempt to disable REPACK (CONCURRENTLY)? That would require wal_level=minimal (due to commit 67c20979ce). In which way does REPACK seem to break test_plan_advice? -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-20T13:54:52Z
On 2026-Apr-20, Antonin Houska wrote: > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > BTW I ran into a small problem after adding some tests in cluster.sql > > that would exercise this -- that test would die more or less randomly > > but frequently in CI (which it never did in my laptop) because of the > > size of the snapshot, > > > > ALTER TABLE ptnowner1 REPLICA IDENTITY USING INDEX ptnowner1_i_key; > > REPACK (CONCURRENTLY) ptnowner1; > > +ERROR: initial slot snapshot too large > > +CONTEXT: REPACK decoding worker > > RESET SESSION AUTHORIZATION; > > > > I think the solution for this is to move cluster to a separate parallel > > test. The one where it is now is a bit too crowded. Maybe the one for > > compression is okay? I'll test and push if I see it passing CI. > > That shouldn't break anything, but I have no idea why this problem was not > triggered (as far as I remember) by the stress tests we ran during > development. I took a guess that it's because some tests use minimally configured servers -- that is, max_connections=20 or so -- and then run 20 processes. If we then try to construct a snapshot that's limited to having only that many XIDs, we might not have enough room in the xip array. I didn't try to trace it super carefully though. > > >From b3d4158356f4914d2b0cba86eef6994c0ee50ab9 Mon Sep 17 00:00:00 2001 > > From: =?UTF-8?q?=C3=81lvaro=20Herrera?= <alvherre@kurilemu.de> > > Date: Mon, 20 Apr 2026 11:38:48 +0200 > > Subject: [PATCH 1/2] REPACK: do not require the user to have REPLICATION > > > Because there are now successful concurrent repack runs in the > > regression tests, we're forced to run test_plan_advice under > > wal_level=replica. > > Is this an attempt to disable REPACK (CONCURRENTLY)? That would require > wal_level=minimal (due to commit 67c20979ce). In which way does REPACK seem to > break test_plan_advice? No, quite the contrary. That test normally runs with wal_level=minimal, which causes REPACK to complain that it cannot start logical decoding. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Tom Lane <tgl@sss.pgh.pa.us> — 2026-04-20T14:42:06Z
Alvaro Herrera <alvherre@alvh.no-ip.org> writes: > On 2026-Apr-20, Antonin Houska wrote: >> Is this an attempt to disable REPACK (CONCURRENTLY)? That would require >> wal_level=minimal (due to commit 67c20979ce). In which way does REPACK seem to >> break test_plan_advice? > No, quite the contrary. That test normally runs with wal_level=minimal, > which causes REPACK to complain that it cannot start logical > decoding. So what you're saying is that the core regression tests will now fail with wal_level=minimal? I don't see how that can possibly be considered acceptable from a global standpoint; we might as well remove wal_level=minimal, because it will never again get tested. I think you need to move these tests out into some other test suite (or make a new one). regards, tom lane
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-20T15:11:56Z
On 2026-Apr-20, Tom Lane wrote: > So what you're saying is that the core regression tests will now fail > with wal_level=minimal? I don't see how that can possibly be > considered acceptable from a global standpoint; we might as well > remove wal_level=minimal, because it will never again get tested. Hmm, you're right, this was brain fade on my part. It surprising though that no buildfarm animal so far has complained. But I remember there's at least one that runs with that, per be142fa008ad. > I think you need to move these tests out into some other test suite > (or make a new one). I'll see what I can find. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Tom Lane <tgl@sss.pgh.pa.us> — 2026-04-20T16:01:42Z
Alvaro Herrera <alvherre@alvh.no-ip.org> writes: > It surprising though that no buildfarm animal so far has complained. > But I remember there's at least one that runs with that, per > be142fa008ad. Yeah, thorntail does, but it only runs once a day. regards, tom lane
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-20T16:30:25Z
On 2026-Apr-20, Alvaro Herrera wrote: > On 2026-Apr-20, Tom Lane wrote: > > I think you need to move these tests out into some other test suite > > (or make a new one). > > I'll see what I can find. I think the simplest would be to add them to src/test/modules/injection_points. We already have some repack tests there (because they needed injection points), and it has an SQL suite, so we would not be adding any extra overhead. However, the new tests are not related to injection points, so they would be out of place. Another possibility could be src/test/subscription, but it doesn't have sql tests; doesn't seem good to have them just for this. There's also contrib/test_decoding. It's somewhat vaguely adjacent, and the tests aren't _really_ about the decoding part, but of all these options, it seems the least bad one. I don't like the idea of adding another suite. Too much scaffolding for so little, I think. I don't have any other ideas ATM. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "We're here to devour each other alive" (Hobbes)
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-04-20T17:44:52Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > I've briefly looked at your patch. As I understand it, it cancels the other > process only when REPACK actually tries to upgrade the lock. ... > > AFAIU, Andres's concern is that the "victim" should be cancelled > sooner, rather than waiting until REPACK actually attempts the > upgrade. I thought the point is that the deadlock should be resolved in a controlled way, i.e. w/o relying on deadlock timeout. Once both processes sleep, the decision which one should be kicked off is effectively random. In other words, the actual deadlock IMO starts exactly at the moment both processes end up sleeping. But I may be wrong. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Chao Li <li.evan.chao@gmail.com> — 2026-04-21T07:24:25Z
> On Apr 10, 2026, at 18:53, Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> wrote: > > Hi, > > When testing REPACK concurrently, I noticed that all WALs are retained from > the moment REPACK begins copying data to the new table until the command > finishes replaying concurrent changes on the new table and stops the repack > decoding worker. > > I understand the reason: the REPACK command itself starts a long-running > transaction, and logical decoding does not advance restart_lsn beyond the > oldest running transaction's start position. As a result, slot.restart_lsn > remains unchanged, preventing the checkpointer from recycling WALs. > > However, since REPACK can run for a long time (hours or even days), I'd like > to confirm whether this is expected behavior or if we plan to improve it > in the future ? And additionally, IIUC, REPACK without using concurrent option > does not have this issue. > > Given that we do not restart a REPACK, I think the repack decoding worker > should be able to advance restart_lsn each time after writing changes > (similar to how a physical slot behaves). To illustrate this, I've written > a patch (attached) that implements this approach, and it works fine for me. > > BTW, catalog_xmin also won't advance, but that seems not a big issue as > the REPACK transaction itself also holds a snapshot that retains catalog tuples, > so advancing catalog_xmin wouldn't change the situation anyway. > > Thoughts ? > > Best Regards, > Hou zj > <v1-0001-Allow-old-WALs-to-be-removed-during-REPACK-CONCUR.patch> I found the same problem with LogicalConfirmReceivedLocation and posted a fix in a separate thread [1]. So I would withdraw my patch. Looking at this patch, the change is exactly the same as what I did in [1], but I think the code comment should be updated as well. For the comment change, please see my patch in [1]. [1] https://www.postgresql.org/message-id/D8D9F770-DAA2-482C-A7E0-F87E5104C13E%40gmail.com Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-22T09:30:26Z
On 2026-Apr-20, Alvaro Herrera wrote: > There's also contrib/test_decoding. It's somewhat vaguely adjacent, and > the tests aren't _really_ about the decoding part, but of all these > options, it seems the least bad one. Here's a patch that does this. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-04-23T11:01:57Z
On 2026-Apr-22, Alvaro Herrera wrote: > On 2026-Apr-20, Alvaro Herrera wrote: > > > There's also contrib/test_decoding. It's somewhat vaguely adjacent, and > > the tests aren't _really_ about the decoding part, but of all these > > options, it seems the least bad one. > > Here's a patch that does this. Pushed, thanks. Thorntail should turn green in its next run. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/ "Nunca se desea ardientemente lo que solo se desea por razón" (F. Alexandre)
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-23T11:43:18Z
Hello! On Mon, Apr 20, 2026 at 7:44 PM Antonin Houska <ah@cybertec.at> wrote: > When another process tries to get the lock after that, it checks this field, > and if it's set, it checks for deadlocks even if deadlock timeout hasn't > expired yet. If the process is already in the lock's queue and sleeping, the > lock upgrading process wakes it up so it checks the flag immediately. In any way there is no sense in waking up other backends just to force them to cancel themselves. Better to go in the [0] way - and just cancel another backend if we are repack and found cycle in the deadlock detector. We currently have all required infrastructure, even as the comment described the possibility [1]: > * Get this process out of wait state. (Note: we could do this more > * efficiently by relying on lockAwaited, but use this coding to > * preserve the flexibility to kill some other transaction than the > * one detecting the deadlock.) At the same time version [2] (with FutureWaitLock, explained in more detail in [3]) is correct and cancels other backends without pointless waiting, but it feels too complicated due to the complexity of supporting the FastLock path. Also, here's one simple idea inspired by your version. What about adding a new field "do not try to upgrade that lock" to the LOCK structure? If some backends try to (it has some lockmode and tries to upgrade it) and it does not has flag 'deadlock_protected' from [0] - just fail fast with "future deadlock detected". That way [0] ensures correctness and "do not try to upgrade that lock" just optimization to fail quickly for the most common scenarios. Some 2+ backend loops might still wait a long time to be cancelled (which is solved by [2], but complicated) - but I think this is a super rare case we can ignore (because repack is still protected from deadlock). Mikhail. [0]: https://www.postgresql.org/message-id/flat/CADzfLwURKVNQ%2B%2BDpi7bjoGfj-8pchDQEVex3eWBx0NCYn6TbDQ%40mail.gmail.com#bceb18354aa20c130a94b1deedd76fb7. [1]: https://github.com/postgres/postgres/blob/master/src/backend/storage/lmgr/proc.c#L1870-L1873 [2]: https://www.postgresql.org/message-id/flat/CADzfLwUSnGnkfLwCWHQ%3DVVuAY1YTo%2B0Lr7pb%2BOPWUZbcYKSRUw%40mail.gmail.com#e7ba203b7402de332dfb58ce5329a73a [3]: https://www.postgresql.org/message-id/flat/CADzfLwVf-3mjMwSTOcj9djNzGd-UjBOYbFjxgXRhtKuH_4rajA%40mail.gmail.com#b69f9c1d1d3d1cbf7f6d66be790894c4
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-26T13:34:00Z
Hello! I think I have a good enough approach now (at least balancing complexity and outcome). Patch (and commit message) is quite explanatory, but in a few words: - add 'upgradeIntent' to PROCLOCK (set by REPACK) - check that in the deadlock detector. If the backend finds the cycle and is part of it, but because it's upgrading an already announced lock, it cancels another backend instead of itself. - use that in the fast path of simple deadlock detection to avoid pointless waiting (for the easy case involving two backends) It doesn't cover all scenarios (explained in patch details) but for majority of realistic scenarios - yes. It may be extended to cover all of them, but I'm not sure it's worth the additional complexity. Best regards, Mikhail.
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-04-27T04:25:30Z
On Tue, Apr 7, 2026 at 9:18 PM Andres Freund <andres@anarazel.de> wrote: > > On 2026-04-07 17:38:24 +0200, Antonin Houska wrote: > > Andres Freund <andres@anarazel.de> wrote: > > > > > On 2026-04-07 14:33:50 +0200, Alvaro Herrera wrote: > > > > On 2026-Apr-07, Amit Kapila wrote: > > > > > > > > > I have a question based on 0001's commit message: "This patch adds a > > > > > new option to logical replication output plugin, to declare that it > > > > > does not use shared catalogs (i.e. catalogs that can be changed by > > > > > transactions running in other databases in the cluster).". In which > > > > > cases, currently plugin needs to access multi-database transactions or > > > > > transactions that need to access shared catalogs and on what basis a > > > > > plugin can decide that the changes it requires won't need any such > > > > > access. > > > > > > > > I don't think any plugin needs "multi-database" access as such, but > > > > needing access to shared catalogs is likely normal. Repack knows it > > > > won't access any shared catalogs, so it can set the flag at ease. > > > > > > > > There's a cross-check added in the commit that tests for access to > > > > shared catalogs if the flag is set to false. I guess you could set it > > > > to false and see what breaks :-) > > > > > > I think this has a quite high chance of indirect breakages. You just need some > > > cache invalidation processing / building accessing shared catalogs to violate > > > the rule, and whether that happens very heavily depends on what cache entries > > > are present and whether something registers relcache callbacks or such. > > > > > > This can be triggered by an output function during logical decoding or such, > > > so you don't really have control over it. > > > > The REPACK plugin only deforms tuples and writes them to a file, so I think > > that things like this should not happen. > > You don't need to do it yourself. It just requires a shared_preload_library > extension to register a relcache invalidation callback that accesses shared > catalog. > > It's only kind of an accident that we don't have a case today that accesses > shared catcaches during a relcache build in core (I'm not even sure there's > nothing). You'd just need somebody to add e.g. relcache caching for > publications for that to change. Or look up information about a reloption in > pg_parameter_acl. Or lookup tablespace configuration. > > > > However, I admit that an option that allows the plugin developer to declare > > "I don't need shared catalogs" may be considered deceptive. > > At the very least it would need to be a runtime check rather than just an > assert. This would much more likely to be hit in production because otherwise > it's probably hard to hit the case where shared invalidations happen in the > wrong moment. And the consequences are corrupted caches, which could cause > all kinds of havoc. > > > But I think this may need more infrastructure / deeper analysis than what we > can do right now. > We still have not decided on this point. The code corresponding to this part [1] has an open bug as well [2]. Based on the discussion here, I don't see we have a consensus with the current approach and even if we want to go with the current approach it seems that we need more work which needs further analysis. Alvaro, others, what is your take on this? [1]: commit 0d3dba38c777384a9dd7dffe924355c9683a6b71: Allow logical replication snapshots to be database-specific [2]: https://www.postgresql.org/message-id/CAHg%2BQDcQak4jx_6X2_Ws98rzG%3DxBARLjqm_%3D56wTRUtNsY4DZQ%40mail.gmail.com -- With Regards, Amit Kapila.
-
RE: Adding REPACK [concurrently]
Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> — 2026-04-27T04:46:37Z
Dear Hackers, > > We still have not decided on this point. The code corresponding to > this part [1] has an open bug as well [2]. Based on the discussion > here, I don't see we have a consensus with the current approach and > even if we want to go with the current approach it seems that we need > more work which needs further analysis. Alvaro, others, what is your > take on this? > FYI, the point I raised [1] [2] seemed not to be fixed yet. Not sure it's the last point we missed. [1]: https://www.postgresql.org/message-id/225003.1775571560%40localhost [2]: https://www.postgresql.org/message-id/CAA4eK1KWDbBk4FgbbWdivQLrPPzR4zgvfnHK4WjWC78rbuRVbg%40mail.gmail.com Best regards, Hayato Kuroda FUJITSU LIMITED
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-04-30T11:24:41Z
Hello! On Mon, Apr 27, 2026 at 6:25 AM Amit Kapila <amit.kapila16@gmail.com> wrote: > Alvaro, others, what is your take on this? I agree with you here - we should AT LEAST make that an ERROR instead of an assert and also check it during cache access (not only during the scan because of cache misses). But I think it will still be fragile in case of some extensions installed. Anyway... We also have an issue with correctness right now. I took the old stress test from [0] (the first two) and it fails now, even with the fix from [1] ("Possible premature SNAPBUILD_CONSISTENT with DB-specific running_xacts"). It looks like [1] fixes 008_repack_concurrently.pl, but 007_repack_concurrently.pl fails anyway, including pgbench: error: client 1 script 0 aborted in command 10 query 0: ERROR: could not create unique index "tbl_pkey_repacknew" # DETAIL: Key (i)=(383) is duplicated. and 'pgbench: error: pgbench:client 23 script 0 aborted in command 31 query 0: ERROR: division by zero Last one is not MVCC-related; you can see from the logs that it performs something like SELECT (509063) / 0 when the table sum changes. Setting need_shared_catalogs = true make them pass, so something is wrong with its correctness. P.S. I think it is good idea to add these stress tests to the source tree, perhaps with some kind PG_TEST_EXTRA=stress (as done in [1]). [0]: https://www.postgresql.org/message-id/flat/CADzfLwUitd5J17O9FUxNGrZBurOpL6n%2BtnS6dgArXi-i9DNxhg%40mail.gmail.com#c5945a539400676cdfd72eec6c101710 [1]: https://www.postgresql.org/message-id/flat/CAHg%2BQDcQak4jx_6X2_Ws98rzG%3DxBARLjqm_%3D56wTRUtNsY4DZQ%40mail.gmail.com [2]: https://www.postgresql.org/message-id/flat/CADzfLwWC%2BKxYWb-2QotWaz-q1LK8koLNVUR1Q8obAtt%2BR_sORA%40mail.gmail.com#c8435a284f8893bbc1a891c56be5e158 -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-01T07:30:27Z
Hello, On 2026-Apr-07, Hayato Kuroda (Fujitsu) wrote: > 01. > ``` > --- a/src/backend/access/index/genam.c > +++ b/src/backend/access/index/genam.c > @@ -394,6 +394,14 @@ systable_beginscan(Relation heapRelation, > SysScanDesc sysscan; > Relation irel; > > + /* > + * If this backend promised that it won't access shared catalogs during > + * logical decoding, this it the right place to verify. > + */ > + Assert(!HistoricSnapshotActive() || > + accessSharedCatalogsInDecoding || > + !heapRelation->rd_rel->relisshared); > ``` > > Not sure it's OK to use Assert(). elog(ERROR) might be better if we want to really > avoid the case. How about the attached? -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-04T13:24:49Z
Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > On Mon, Apr 27, 2026 at 6:25 AM Amit Kapila <amit.kapila16@gmail.com> wrote: > > Alvaro, others, what is your take on this? > > I agree with you here - we should AT LEAST make that an ERROR instead > of an assert and also check it during cache access (not only during > the scan because of cache misses). > But I think it will still be fragile in case of some extensions installed. > > Anyway... We also have an issue with correctness right now. > > I took the old stress test from [0] (the first two) and it fails now, > even with the fix from [1] ("Possible premature SNAPBUILD_CONSISTENT > with DB-specific running_xacts"). > > It looks like [1] fixes 008_repack_concurrently.pl, but > 007_repack_concurrently.pl fails anyway, including > > pgbench: error: client 1 script 0 aborted in command 10 query 0: > ERROR: could not create unique index "tbl_pkey_repacknew" > # DETAIL: Key (i)=(383) is duplicated. > and > 'pgbench: error: pgbench:client 23 script 0 aborted in command 31 > query 0: ERROR: division by zero > > Last one is not MVCC-related; you can see from the logs that it > performs something like SELECT (509063) / 0 when the table sum > changes. > > Setting need_shared_catalogs = true make them pass, so something is > wrong with its correctness. Thanks for testing again. Whether we keep the "database specific slots" or not, it'd be good to know what exactly the reason of these errors is. I wonder if the feature just exposes a problem that remains shadowed otherwise, due to the contention on replication slot. I'm going to investigate. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-05T12:47:46Z
Antonin Houska <ah@cybertec.at> wrote: > Mihail Nikalayeu <mihailnikalayeu@gmail.com> wrote: > > > On Mon, Apr 27, 2026 at 6:25 AM Amit Kapila <amit.kapila16@gmail.com> wrote: > > > Alvaro, others, what is your take on this? > > > > I agree with you here - we should AT LEAST make that an ERROR instead > > of an assert and also check it during cache access (not only during > > the scan because of cache misses). > > But I think it will still be fragile in case of some extensions installed. > > > > Anyway... We also have an issue with correctness right now. > > > > I took the old stress test from [0] (the first two) and it fails now, > > even with the fix from [1] ("Possible premature SNAPBUILD_CONSISTENT > > with DB-specific running_xacts"). > > > > It looks like [1] fixes 008_repack_concurrently.pl, but > > 007_repack_concurrently.pl fails anyway, including > > > > pgbench: error: client 1 script 0 aborted in command 10 query 0: > > ERROR: could not create unique index "tbl_pkey_repacknew" > > # DETAIL: Key (i)=(383) is duplicated. > > and > > 'pgbench: error: pgbench:client 23 script 0 aborted in command 31 > > query 0: ERROR: division by zero > > > > Last one is not MVCC-related; you can see from the logs that it > > performs something like SELECT (509063) / 0 when the table sum > > changes. > > > > Setting need_shared_catalogs = true make them pass, so something is > > wrong with its correctness. > > Thanks for testing again. Whether we keep the "database specific slots" or > not, it'd be good to know what exactly the reason of these errors is. I wonder > if the feature just exposes a problem that remains shadowed otherwise, due to > the contention on replication slot. I'm going to investigate. I think the problem is that with database-specific snapshot, SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin /* * Database specific transaction info may exist to reach CONSISTENT state * faster, however the code below makes no use of it. Moreover, such * record might cause problems because the following normal (cluster-wide) * record can have lower value of oldestRunningXid. In that case, let's * wait with the cleanup for the next regular cluster-wide record. */ if (OidIsValid(running->dbid)) return; and thus some transactions whose XID is below running->oldestRunningXid may continue to be incorrectly considered running. I originally thought that this should not happen because such transactions will be added to the builder's array of committed transactions by SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of a transaction listed in the xl_running_xacts WAL record is not guaranteed to follow the xl_running_xacts record in WAL. In other words, even if xl_running_xacts is created before a COMMIT record of the contained transaction, it may end up at higher LSN in WAL. So the cleanup I relied on might not take place. I've got no good idea how to fix that. Not sure I'm able to pursue the "database-specific snapshots" feature now. -- Antonin Houska Web: https://www.cybertec-postgresql.com -
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-05T15:02:39Z
On 2026-May-05, Antonin Houska wrote: > However, I failed to notice that COMMIT record of > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > follow the xl_running_xacts record in WAL. In other words, even if > xl_running_xacts is created before a COMMIT record of the contained > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > might not take place. That's pretty bad news. > I've got no good idea how to fix that. Not sure I'm able to pursue the > "database-specific snapshots" feature now. It appears that the only reasonable course of action at this point is to revert 0d3dba38c777. -- Álvaro Herrera Breisgau, Deutschland — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-05T15:04:42Z
On 2026-Apr-30, Mihail Nikalayeu wrote: > P.S. > I think it is good idea to add these stress tests to the source tree, > perhaps with some kind PG_TEST_EXTRA=stress (as done in [1]). Yeah, agreed. I'll see to that shortly. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "I am amazed at [the pgsql-sql] mailing list for the wonderful support, and lack of hesitasion in answering a lost soul's question, I just wished the rest of the mailing list could be like this." (Fotis) https://postgr.es/m/200606261359.k5QDxE2p004593@auth-smtp.hol.gr -
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-06T08:25:44Z
Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-May-05, Antonin Houska wrote: > > > However, I failed to notice that COMMIT record of > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > follow the xl_running_xacts record in WAL. In other words, even if > > xl_running_xacts is created before a COMMIT record of the contained > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > might not take place. > > That's pretty bad news. > > > I've got no good idea how to fix that. One idea occurred to me yet, effectively it's just a cleanup. Part of it was already proposed [1]. [1] https://www.postgresql.org/message-id/flat/CAHg%2BQDcQak4jx_6X2_Ws98rzG%3DxBARLjqm_%3D56wTRUtNsY4DZQ%40mail.gmail.com -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-08T12:25:12Z
On Wed, May 6, 2026 at 1:55 PM Antonin Houska <ah@cybertec.at> wrote: > > Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > > On 2026-May-05, Antonin Houska wrote: > > > > > However, I failed to notice that COMMIT record of > > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > > follow the xl_running_xacts record in WAL. In other words, even if > > > xl_running_xacts is created before a COMMIT record of the contained > > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > > might not take place. > > > > That's pretty bad news. > > > > > I've got no good idea how to fix that. > > One idea occurred to me yet, effectively it's just a cleanup. Part of it was > already proposed [1]. > Some issues/inefficiencies regarding this fix and base code related to db-specific snapshots built during decoding: * After fix, whenever a db-specific decoder sees a cluster-wide xl_running_xacts record, it unconditionally calls LogStandbySnapshot(MyDatabaseId) and returns. This triggers for every cluster-wide record the decoder encounters (including post snapbuild's CONSISTENT state) , for the entire duration of the decoding session. LogStandbySnapshot acquires ProcArrayLock + XidGenLock, calls GetRunningTransactionData, and writes WAL. With N active db-specific decoding sessions, each cluster-wide record now triggers N additional WAL writes. Additionally, LogStandbySnapshot also logs AccessExclusiveLocks before the running_xacts record. Physical standbys skip db-specific XLOG_RUNNING_XACTS records via standby_redo(), but they do process the preceding XLOG_STANDBY_LOCK records. The same locks may already have been logged in the most recent cluster-wide snapshot. Physical standbys could end up processing these lock records twice which may not be harmful because I think we avoid re-acquiring the lock but still it is a new overhead in the system. * When a cluster-wide running_xacts record arrives: SnapBuildProcessRunningXacts calls LogStandbySnapshot and returns early. ReorderBufferAbortOld is called, but with the cluster-wide oldestRunningXid, which could lag far behind the db-specific value (due to a long-running transaction in another database). When a db-specific record arrives: SnapBuildProcessRunningXacts processes it and advances builder->xmin with the db-specific (more current) oldestRunningXid. But ReorderBufferAbortOld is NOT called for db-specific records. This means the reorder buffer is cleaned up using a conservative, potentially very old, cluster-wide oldestRunningXid, even though builder->xmin has already advanced much further. The reorder buffer holds stale entries longer than necessary, increasing memory pressure. * I also see a design level problem with plugins that have need_shared_catalogs=false and use failover slots. IIUC, the db-specific optimization was designed around a live decoding session on the primary which can emit and immediately read its own db-specific records in the WAL stream to reach consistent state. The LogicalSlotAdvanceAndCheckSnapState path used by slotsync has a bounded WAL window and cannot exploit the feedback loop, making the two mechanisms fundamentally incompatible. I know the slot created by pgrepack doesn't enable failover option but we have not even added any guards or thought about db-specific snapbuilds with other parts of the system that rely on cluster-wide running_xact records, so there could be more problems which we don't see with the current set of tests. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-08T13:58:18Z
On 2026-May-08, Amit Kapila wrote: > On Wed, May 6, 2026 at 1:55 PM Antonin Houska <ah@cybertec.at> wrote: > > One idea occurred to me yet, effectively it's just a cleanup. Part of it was > > already proposed [1]. Hmm, I think this cleanup makes sense. If I apply the test patches (0001 and 0002 here), they fail almost immediately; but after applying 0003 all is again well. I think these tests are a good thing to have in the tree, even if we end up reverting db-specific snapshots later. After some back and forth, I modified the tests slightly so that the search PG_TEST_EXTRA for the string "stress_concurrently=N". The N can be changed so that the tests run for longer; if not given, it's taken as 1, and the tests run for around 6 seconds (so N=10 means runs for a minute). I think this is a convenient gadget for other tests of this kind on CONCURRENTLY commands, such as the one proposed for CIC elsewhere. As written, these tests would run nowhere until we add that string in some buildfarm animal. I debated with myself whether to assume N=1 when the string is not given. I still think that's a good idea but perhaps we should have something to prevent it from running by default when under Valgrind or other slow things like that. In normal conditions, the total runtime is not affected when they are run with N=1 as part of the whole test suite. > Some issues/inefficiencies regarding this fix and base code related to > db-specific snapshots built during decoding: [...] Thanks for spending time reviewing this code. I think none of these problems are fundamental in nature, but they are obviously worth addressing for 19. If we hit some roadblock, we can still revert only db-specific snapshots. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Puedes vivir sólo una vez, pero si lo haces bien, una vez es suficiente"
-
Re: Adding REPACK [concurrently]
Masahiko Sawada <sawada.mshk@gmail.com> — 2026-05-08T23:22:04Z
On Tue, Apr 7, 2026 at 8:49 PM Amit Kapila <amit.kapila16@gmail.com> wrote: > > On Tue, Apr 7, 2026 at 7:49 PM Antonin Houska <ah@cybertec.at> wrote: > > > > Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> wrote: > > > > > 02. SnapBuildProcessRunningXacts > > > > > > Per my understanding, the db_specic snapshot can be also serialized. Is it > > > possibility tha normal logical decoding system restores the snapshot and obtain > > > the wrong result? > > > > I don't think that the database-specific xl_running_xacts WAL record affects > > what SnapBuildSerialize() writes to disk: the contents of builder->committed, > > etc. is updated by decoding COMMIT and ABORT records. > > > > I think the point is that the other process say a walsender could > restore such a snapshot making it take the wrong decision. > Right. I think it affects even concurrent REPACK (CONCURRENTLY) running on other databases. They could end up restoring the snapshot serialized by another REPACK command running on another database and becoming the consistent state without waiting for transactions concurrently running on the same database, which is clearly wrong. Regards, -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com
-
Re: Adding REPACK [concurrently]
Masahiko Sawada <sawada.mshk@gmail.com> — 2026-05-08T23:22:15Z
On Fri, May 8, 2026 at 6:58 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-May-08, Amit Kapila wrote: > > > On Wed, May 6, 2026 at 1:55 PM Antonin Houska <ah@cybertec.at> wrote: > > > Some issues/inefficiencies regarding this fix and base code related to > > db-specific snapshots built during decoding: [...] > > Thanks for spending time reviewing this code. I think none of these > problems are fundamental in nature, but they are obviously worth > addressing for 19. If we hit some roadblock, we can still revert only > db-specific snapshots. > I'm still studying REPACK (CONCURRENTLY), and I have a question about db-specific decoder; as far as I read the codes, any decoding plugin can use db-specific decoder by setting need_shared_catalog=false but should we prevent such slots from being created on standbys? I'm a bit concerned that plugin developers inadvertently set need_shared_catalog=false in the startup callback and users would create such slots on standbys. For instance, if we create a logical slot with pgrepack plugin on the standby, we would get an assertion failure as the db-specific decoder tries to call LogStandbySnapshot() via SnapBuildProcessRunningXacts(). Even if we add !RecoveryInProgress() check there, the db-specific decoder on standbys requires for the primary server to run a REPACK (CONCURRENTLY). Regards, -- Masahiko Sawada Amazon Web Services: https://aws.amazon.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-10T11:24:29Z
On Fri, May 8, 2026 at 7:28 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-May-08, Amit Kapila wrote: > > > Some issues/inefficiencies regarding this fix and base code related to > > db-specific snapshots built during decoding: [...] > > Thanks for spending time reviewing this code. I think none of these > problems are fundamental in nature, but they are obviously worth > addressing for 19. If we hit some roadblock, we can still revert only > db-specific snapshots. > IIUC, the emails by Andres [1][2] on db-specific snapshots sound like concerns which are fundamental in nature. Apart from that as well, I think the first point mentioned in my email [3] should be at least addressed as that causes additional WAL even after reaching consistent_state for each runing_xact record for a db-specific decoder. [1] - https://www.postgresql.org/message-id/cdgw4sbbfcgk6du3iv54r2dgiy4tfywoklbotlmj4irxavdcr3%40glxfw5jj277q [2] - https://www.postgresql.org/message-id/pveffyxhnuurhb44uzqlwo3rkyzorkfh2rot7uwzlf2axhfvbp%407nrs2omysxkc [3] - https://www.postgresql.org/message-id/CAA4eK1LygCDP3FiFzXY9iVNFcHxhf7TT_DFf7tryTu2oipmfpA%40mail.gmail.com -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-10T11:31:04Z
On Tue, May 5, 2026 at 6:17 PM Antonin Houska <ah@cybertec.at> wrote: > > Antonin Houska <ah@cybertec.at> wrote: > > I think the problem is that with database-specific snapshot, > SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin > > /* > * Database specific transaction info may exist to reach CONSISTENT state > * faster, however the code below makes no use of it. Moreover, such > * record might cause problems because the following normal (cluster-wide) > * record can have lower value of oldestRunningXid. In that case, let's > * wait with the cleanup for the next regular cluster-wide record. > */ > if (OidIsValid(running->dbid)) > return; > > and thus some transactions whose XID is below running->oldestRunningXid may > continue to be incorrectly considered running. > > I originally thought that this should not happen because such transactions > will be added to the builder's array of committed transactions by > SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > follow the xl_running_xacts record in WAL. In other words, even if > xl_running_xacts is created before a COMMIT record of the contained > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > might not take place. > BTW, is it possible to write a test by using injection_points or via manual steps (by using debugger, etc) so that we can more clearly understand this problem and proposed fix? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-11T15:17:52Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Tue, May 5, 2026 at 6:17 PM Antonin Houska <ah@cybertec.at> wrote: > > > > Antonin Houska <ah@cybertec.at> wrote: > > > > I think the problem is that with database-specific snapshot, > > SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin > > > > /* > > * Database specific transaction info may exist to reach CONSISTENT state > > * faster, however the code below makes no use of it. Moreover, such > > * record might cause problems because the following normal (cluster-wide) > > * record can have lower value of oldestRunningXid. In that case, let's > > * wait with the cleanup for the next regular cluster-wide record. > > */ > > if (OidIsValid(running->dbid)) > > return; > > > > and thus some transactions whose XID is below running->oldestRunningXid may > > continue to be incorrectly considered running. > > > > I originally thought that this should not happen because such transactions > > will be added to the builder's array of committed transactions by > > SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > follow the xl_running_xacts record in WAL. In other words, even if > > xl_running_xacts is created before a COMMIT record of the contained > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > might not take place. > > > > BTW, is it possible to write a test by using injection_points or via > manual steps (by using debugger, etc) so that we can more clearly > understand this problem and proposed fix? So far I could observe the situation in WAL, but have no idea how it can happen. For example, transaction 49242 gets committed here rmgr: Transaction len (rec/tot): 46/ 46, tx: 49242, lsn: 0/18BC28C8, prev 0/18BC2890, desc: COMMIT 2026-05-11 16:38:16.603265 CEST and then it appears in the 'xids' list of RUNNING_XACTS: rmgr: Standby len (rec/tot): 106/ 106, tx: 0, lsn: 0/18BC3140, prev 0/18BC3100, desc: RUNNING_XACTS nextXid 49255 latestCompletedXid 49241 oldestRunningXid 49242; 13 xacts: 49248 49249 49246 49243 49252 49251 49244 49245 49242 49250 49253 49254 49247; dbid:5 I thought the situation is quite common (and therefore nothing of SnapBuildProcessRunningXacts() should be skipped), but when trying to reproduce the problem, I noticed that LogStandbySnapshot() shouldn't allow that ordering issue when logical decoding is enabled: /* * GetRunningTransactionData() acquired ProcArrayLock, we must release it. * For Hot Standby this can be done before inserting the WAL record * because ProcArrayApplyRecoveryInfo() rechecks the commit status using * the clog. For logical decoding, though, the lock can't be released * early because the clog might be "in the future" from the POV of the * historic snapshot. This would allow for situations where we're waiting * for the end of a transaction listed in the xl_running_xacts record * which, according to the WAL, has committed before the xl_running_xacts * record. Fortunately this routine isn't executed frequently, and it's * only a shared lock. */ if (!logical_decoding_enabled) LWLockRelease(ProcArrayLock); So I don't have the answer right now. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-11T19:30:01Z
Antonin Houska <ah@cybertec.at> wrote: > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > On Tue, May 5, 2026 at 6:17 PM Antonin Houska <ah@cybertec.at> wrote: > > > > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > > I think the problem is that with database-specific snapshot, > > > SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin > > > > > > /* > > > * Database specific transaction info may exist to reach CONSISTENT state > > > * faster, however the code below makes no use of it. Moreover, such > > > * record might cause problems because the following normal (cluster-wide) > > > * record can have lower value of oldestRunningXid. In that case, let's > > > * wait with the cleanup for the next regular cluster-wide record. > > > */ > > > if (OidIsValid(running->dbid)) > > > return; > > > > > > and thus some transactions whose XID is below running->oldestRunningXid may > > > continue to be incorrectly considered running. > > > > > > I originally thought that this should not happen because such transactions > > > will be added to the builder's array of committed transactions by > > > SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of > > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > > follow the xl_running_xacts record in WAL. In other words, even if > > > xl_running_xacts is created before a COMMIT record of the contained > > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > > might not take place. > > > > > > > BTW, is it possible to write a test by using injection_points or via > > manual steps (by using debugger, etc) so that we can more clearly > > understand this problem and proposed fix? > > So far I could observe the situation in WAL, but have no idea how it can > happen. For example, transaction 49242 gets committed here > > rmgr: Transaction len (rec/tot): 46/ 46, tx: 49242, lsn: 0/18BC28C8, prev > 0/18BC2890, desc: COMMIT 2026-05-11 16:38:16.603265 CEST > > and then it appears in the 'xids' list of RUNNING_XACTS: > > rmgr: Standby len (rec/tot): 106/ 106, tx: 0, lsn: > 0/18BC3140, prev 0/18BC3100, desc: RUNNING_XACTS nextXid 49255 > latestCompletedXid 49241 oldestRunningXid 49242; 13 xacts: 49248 49249 49246 > 49243 49252 49251 49244 49245 49242 49250 49253 49254 49247; dbid:5 > > > I thought the situation is quite common (and therefore nothing of > SnapBuildProcessRunningXacts() should be skipped), but when trying to > reproduce the problem, I noticed that LogStandbySnapshot() shouldn't allow > that ordering issue when logical decoding is enabled: > > /* > * GetRunningTransactionData() acquired ProcArrayLock, we must release it. > * For Hot Standby this can be done before inserting the WAL record > * because ProcArrayApplyRecoveryInfo() rechecks the commit status using > * the clog. For logical decoding, though, the lock can't be released > * early because the clog might be "in the future" from the POV of the > * historic snapshot. This would allow for situations where we're waiting > * for the end of a transaction listed in the xl_running_xacts record > * which, according to the WAL, has committed before the xl_running_xacts > * record. Fortunately this routine isn't executed frequently, and it's > * only a shared lock. > */ > if (!logical_decoding_enabled) > LWLockRelease(ProcArrayLock); > > So I don't have the answer right now. I think now that "waiting for the end of a transaction listed in the xl_running_xacts record" in the comment is about transaction removal from procarray. However, the COMMIT record can still be ahead of xl_running_xacts because RecordTransactionCommit() is called before ProcArrayEndTransaction(). I'll think again if the whole problem can be reproduced with injection points. -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
RE: Adding REPACK [concurrently]
Hayato Kuroda (Fujitsu) <kuroda.hayato@fujitsu.com> — 2026-05-12T02:25:47Z
Dear Antonin, FYI, I have also been spending time to reproduce the failure but I have not done yet. > So far I could observe the situation in WAL, but have no idea how it can > happen. Not sure it matches with your situation, but I could reproduce the situation by using gdb. 0. initialized an instance with wal_level=logical and defined a table. 1. established a connection 2. attached the backend via gdb 3. added a breakpoint in ProcArrayEndTransaction 4. committed a transaction, and it would stop at the breakpoint 5. established another connection 6. ran REPACK CONCURRENTLY 7. detached from the first backend. 8. all commands would finish. This could allow that COMMIT record exists ahead the RUNNING_XACTS record. When the backend reaches CommitTransaction()->ProcArrayEndTransaction(), the commit record has already been serialized, but the transaction is still marked as active on the proc array. Above workload allowed that repack worker could check in-between. [1]: ``` rmgr: Transaction len (rec/tot): 46/ 46, tx: 695, lsn: 0/018B89C0, prev 0/018B8980, desc: COMMIT 2026-05-12 11:11:31.588061 JST rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/018B89F0, prev 0/018B89C0, desc: RUNNING_XACTS nextXid 696 latestCompletedXid 694 oldestRunningXid 695; 1 xacts: 695; dbid: 0 rmgr: Standby len (rec/tot): 58/ 58, tx: 0, lsn: 0/018B8A30, prev 0/018B89F0, desc: RUNNING_XACTS nextXid 696 latestCompletedXid 694 oldestRunningXid 695; 1 xacts: 695; dbid: 5 ``` Best regards, Hayato Kuroda FUJITSU LIMITED
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-12T07:57:29Z
On Tue, May 12, 2026 at 1:00 AM Antonin Houska <ah@cybertec.at> wrote: > > Antonin Houska <ah@cybertec.at> wrote: > > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > > > On Tue, May 5, 2026 at 6:17 PM Antonin Houska <ah@cybertec.at> wrote: > > > > > > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > > > > I think the problem is that with database-specific snapshot, > > > > SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin > > > > > > > > /* > > > > * Database specific transaction info may exist to reach CONSISTENT state > > > > * faster, however the code below makes no use of it. Moreover, such > > > > * record might cause problems because the following normal (cluster-wide) > > > > * record can have lower value of oldestRunningXid. In that case, let's > > > > * wait with the cleanup for the next regular cluster-wide record. > > > > */ > > > > if (OidIsValid(running->dbid)) > > > > return; > > > > > > > > and thus some transactions whose XID is below running->oldestRunningXid may > > > > continue to be incorrectly considered running. > > > > > > > > I originally thought that this should not happen because such transactions > > > > will be added to the builder's array of committed transactions by > > > > SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of > > > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > > > follow the xl_running_xacts record in WAL. In other words, even if > > > > xl_running_xacts is created before a COMMIT record of the contained > > > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > > > might not take place. > > > > > > > > > > BTW, is it possible to write a test by using injection_points or via > > > manual steps (by using debugger, etc) so that we can more clearly > > > understand this problem and proposed fix? > > > > So far I could observe the situation in WAL, but have no idea how it can > > happen. For example, transaction 49242 gets committed here > > > > rmgr: Transaction len (rec/tot): 46/ 46, tx: 49242, lsn: 0/18BC28C8, prev > > 0/18BC2890, desc: COMMIT 2026-05-11 16:38:16.603265 CEST > > > > and then it appears in the 'xids' list of RUNNING_XACTS: > > > > rmgr: Standby len (rec/tot): 106/ 106, tx: 0, lsn: > > 0/18BC3140, prev 0/18BC3100, desc: RUNNING_XACTS nextXid 49255 > > latestCompletedXid 49241 oldestRunningXid 49242; 13 xacts: 49248 49249 49246 > > 49243 49252 49251 49244 49245 49242 49250 49253 49254 49247; dbid:5 > > > > > > I thought the situation is quite common (and therefore nothing of > > SnapBuildProcessRunningXacts() should be skipped), but when trying to > > reproduce the problem, I noticed that LogStandbySnapshot() shouldn't allow > > that ordering issue when logical decoding is enabled: > > > > /* > > * GetRunningTransactionData() acquired ProcArrayLock, we must release it. > > * For Hot Standby this can be done before inserting the WAL record > > * because ProcArrayApplyRecoveryInfo() rechecks the commit status using > > * the clog. For logical decoding, though, the lock can't be released > > * early because the clog might be "in the future" from the POV of the > > * historic snapshot. This would allow for situations where we're waiting > > * for the end of a transaction listed in the xl_running_xacts record > > * which, according to the WAL, has committed before the xl_running_xacts > > * record. Fortunately this routine isn't executed frequently, and it's > > * only a shared lock. > > */ > > if (!logical_decoding_enabled) > > LWLockRelease(ProcArrayLock); > > > > So I don't have the answer right now. > > I think now that "waiting for the end of a transaction listed in the > xl_running_xacts record" in the comment is about transaction removal from > procarray. However, the COMMIT record can still be ahead of xl_running_xacts > because RecordTransactionCommit() is called before > ProcArrayEndTransaction(). > I see your point. Due to this, once the xmin regresses based on cluster-wide running_xact, some transaction could appear to be running when it should have appeared as committed. However, still it is not clear how it could lead to one update in the transaction as successfully decoded and another one to be skipped. One theory could be that before the second update, somehow invalidation happens and when decoding tries to reload the catalog to decode second update, the relation is not visible because xmin has regressed and the update is somehow skipped. I can't see how it can happen in code but something like that is happening. Assuming, the problematic case is something like what I described, even than the fix of skipping cluster-wide running xacts and instead LOG db-specific running_xacts to help updating builder's xmin sounds inelegant and probably inefficient. For example, I think such a dependency means we can never enable db-specific snapshots on standby. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-05-12T11:08:20Z
Amit Kapila <amit.kapila16@gmail.com> wrote: > On Tue, May 12, 2026 at 1:00 AM Antonin Houska <ah@cybertec.at> wrote: > > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > > > > > On Tue, May 5, 2026 at 6:17 PM Antonin Houska <ah@cybertec.at> wrote: > > > > > > > > > > Antonin Houska <ah@cybertec.at> wrote: > > > > > > > > > > I think the problem is that with database-specific snapshot, > > > > > SnapBuildProcessRunningXacts() returns early, w/o adjusting builder->xmin > > > > > > > > > > /* > > > > > * Database specific transaction info may exist to reach CONSISTENT state > > > > > * faster, however the code below makes no use of it. Moreover, such > > > > > * record might cause problems because the following normal (cluster-wide) > > > > > * record can have lower value of oldestRunningXid. In that case, let's > > > > > * wait with the cleanup for the next regular cluster-wide record. > > > > > */ > > > > > if (OidIsValid(running->dbid)) > > > > > return; > > > > > > > > > > and thus some transactions whose XID is below running->oldestRunningXid may > > > > > continue to be incorrectly considered running. > > > > > > > > > > I originally thought that this should not happen because such transactions > > > > > will be added to the builder's array of committed transactions by > > > > > SnapBuildCommitTxn() anyway. However, I failed to notice that COMMIT record of > > > > > a transaction listed in the xl_running_xacts WAL record is not guaranteed to > > > > > follow the xl_running_xacts record in WAL. In other words, even if > > > > > xl_running_xacts is created before a COMMIT record of the contained > > > > > transaction, it may end up at higher LSN in WAL. So the cleanup I relied on > > > > > might not take place. > > > > > > > > > > > > > BTW, is it possible to write a test by using injection_points or via > > > > manual steps (by using debugger, etc) so that we can more clearly > > > > understand this problem and proposed fix? > > > > > > So far I could observe the situation in WAL, but have no idea how it can > > > happen. For example, transaction 49242 gets committed here > > > > > > rmgr: Transaction len (rec/tot): 46/ 46, tx: 49242, lsn: 0/18BC28C8, prev > > > 0/18BC2890, desc: COMMIT 2026-05-11 16:38:16.603265 CEST > > > > > > and then it appears in the 'xids' list of RUNNING_XACTS: > > > > > > rmgr: Standby len (rec/tot): 106/ 106, tx: 0, lsn: > > > 0/18BC3140, prev 0/18BC3100, desc: RUNNING_XACTS nextXid 49255 > > > latestCompletedXid 49241 oldestRunningXid 49242; 13 xacts: 49248 49249 49246 > > > 49243 49252 49251 49244 49245 49242 49250 49253 49254 49247; dbid:5 > > > > > > > > > I thought the situation is quite common (and therefore nothing of > > > SnapBuildProcessRunningXacts() should be skipped), but when trying to > > > reproduce the problem, I noticed that LogStandbySnapshot() shouldn't allow > > > that ordering issue when logical decoding is enabled: > > > > > > /* > > > * GetRunningTransactionData() acquired ProcArrayLock, we must release it. > > > * For Hot Standby this can be done before inserting the WAL record > > > * because ProcArrayApplyRecoveryInfo() rechecks the commit status using > > > * the clog. For logical decoding, though, the lock can't be released > > > * early because the clog might be "in the future" from the POV of the > > > * historic snapshot. This would allow for situations where we're waiting > > > * for the end of a transaction listed in the xl_running_xacts record > > > * which, according to the WAL, has committed before the xl_running_xacts > > > * record. Fortunately this routine isn't executed frequently, and it's > > > * only a shared lock. > > > */ > > > if (!logical_decoding_enabled) > > > LWLockRelease(ProcArrayLock); > > > > > > So I don't have the answer right now. > > > > I think now that "waiting for the end of a transaction listed in the > > xl_running_xacts record" in the comment is about transaction removal from > > procarray. However, the COMMIT record can still be ahead of xl_running_xacts > > because RecordTransactionCommit() is called before > > ProcArrayEndTransaction(). > > > > I see your point. Due to this, once the xmin regresses based on > cluster-wide running_xact, some transaction could appear to be running > when it should have appeared as committed. The problem is that xmin does not advance when it should. Attached is a test that reproduces the problem (it includes [1], to handle injection points in background worker), I hope the comments in the specification file are helpful. It's actually not exactly the problem reported in the stress test, but IMO the core issue is the same: effects of some transactions are lost. In the stress test, tuple deletion was lost, so the error was "could not create unique index". Here I only demonstrate lost INSERT. > Assuming, the problematic case is something > like what I described, even than the fix of skipping cluster-wide > running xacts and instead LOG db-specific running_xacts to help > updating builder's xmin sounds inelegant and probably inefficient. For > example, I think such a dependency means we can never enable > db-specific snapshots on standby. ok [1] https://www.postgresql.org/message-id/4703.1774250534%40localhost -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-13T12:34:17Z
On Tue, May 12, 2026 at 4:38 PM Antonin Houska <ah@cybertec.at> wrote: > > Amit Kapila <amit.kapila16@gmail.com> wrote: > > > > > I see your point. Due to this, once the xmin regresses based on > > cluster-wide running_xact, some transaction could appear to be running > > when it should have appeared as committed. > > The problem is that xmin does not advance when it should. Attached is a test > that reproduces the problem (it includes [1], to handle injection points in > background worker), I hope the comments in the specification file are helpful. > Yes, the comments were helpful. IIUC, the test skipped insert into repack_test because the transaction doing that insert happened before the snapbuild reached FULL_SNAPSHOT/CONSISTENT state, so its commit is not decoded. Then we also didn't update builder->xmin after reaching CONSISTENT state in the last running_xact record for MyDatabase. So, the insert is neither covered in initial copy, nor decoded, so after repack (concurrently) is finished the table is empty. I think the patch proposed will fix this specific issue but apart from other points raised for this patch few more are: (a) Post CONSISTENT state, for cleanup of db_specific snapshots, we need to separately again LOG db-specific running xacts whenever we encounter another running_xacts, (b) Other point is that because repack-concurrently always use full-snapshot, the serialization of snapshot is useless because it can't be used by others. > It's actually not exactly the problem reported in the stress test, but IMO the > core issue is the same: effects of some transactions are lost. In the stress > test, tuple deletion was lost, so the error was "could not create unique > index". Here I only demonstrate lost INSERT. > > > Assuming, the problematic case is something > > like what I described, even than the fix of skipping cluster-wide > > running xacts and instead LOG db-specific running_xacts to help > > updating builder's xmin sounds inelegant and probably inefficient. For > > example, I think such a dependency means we can never enable > > db-specific snapshots on standby. > > ok > So now the question is where do we go from here. I am not confident that the current code to achieve db-specific snapshots in logical decoding is the best possible solution both because of the drawbacks (like we won't be able to enable this on standby) and inefficiencies pointed out by me in this and previous emails in this work. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-13T16:58:14Z
Hello Amit, On 2026-May-13, Amit Kapila wrote: > So now the question is where do we go from here. I am not confident > that the current code to achieve db-specific snapshots in logical > decoding is the best possible solution both because of the drawbacks > (like we won't be able to enable this on standby) and inefficiencies > pointed out by me in this and previous emails in this work. This is a fair question. I don't think we have time to go much further on this aspect before beta 1, so we either accept this patch, fix the inefficiencies you pointed out and keep db-specific snapshots, or we revert db-specific snapshots and go back to the standard snapshot-taking technique for REPACK in 19 and see what we can improve for 20. Now, the worst consequence of reverting db-specific snapshots is that you will only be able to run REPACK in a single database at a time (because any subsequent REPACK will have to wait until the first one finishes before being able to get its snapshot). In most normal cases this is probably not a big deal. But if you have a multitenant system, and you want your users to be able to run REPACK on their tables, you may be a bit screwed. So I hesitate to just go and revert it without offering those people any alternative. (It's also possible that being unable to run more than one REPACK at a time is not so big a deal. After all, it's supposed to be an infrequent operation. And users probably don't or shouldn't have multi-terabyte tables in multitenant databases anyway.) I'm not sure I understand the point of the standby. I mean, you can't run REPACK on the standby anyway, so I don't see this as a very problematic restriction. Do you have other reasons for wanting a db-specific snapshot in a standby? -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-14T07:02:25Z
On Wed, May 13, 2026 at 10:28 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > Hello Amit, > > On 2026-May-13, Amit Kapila wrote: > > > So now the question is where do we go from here. I am not confident > > that the current code to achieve db-specific snapshots in logical > > decoding is the best possible solution both because of the drawbacks > > (like we won't be able to enable this on standby) and inefficiencies > > pointed out by me in this and previous emails in this work. > > This is a fair question. I don't think we have time to go much further > on this aspect before beta 1, so we either accept this patch, fix the > inefficiencies you pointed out and keep db-specific snapshots, > I don't think it would be easy to address these inefficiencies before beta 1. The root of those inefficiencies is that the patch reuses the cluster-wide running_xact WAL infrastructure to log db-specific running transactions, and then tries to feed that into the existing snapbuild machinery to reach a consistent state. As another example of this mismatch that occurred to me today: in SnapBuildCommitTxn, we are tracking the committed_xip array for all cluster-wide XIDs, even when using a db-specific snapshot. A db-specific snapshot shouldn't need to care about XIDs from other databases. We only try to take care of it in one part of the system where process running_xacts record. I admit that I don't know at this stage what exactly we should do about it but all such things deserve a discussion and careful thought. The broader issue is that the entire logical decoding mechanism is designed to process cluster-wide transactions. This patch tries to bypass that foundational assumption, but only during the initial snapshot construction while processing running_xacts record. To be clear, I am not against the idea of db-specific snapshots to enable concurrent repacks. My concern is simply the time required to get the architecture right. In its current state, we need more time to carefully consider how this db-specific concept interacts with the rest of the logical decoding machinery, which is built for cluster-wide records. > or we > revert db-specific snapshots and go back to the standard snapshot-taking > technique for REPACK in 19 and see what we can improve for 20. > > Now, the worst consequence of reverting db-specific snapshots is that > you will only be able to run REPACK in a single database at a time > (because any subsequent REPACK will have to wait until the first one > finishes before being able to get its snapshot). In most normal cases > this is probably not a big deal. But if you have a multitenant system, > and you want your users to be able to run REPACK on their tables, you > may be a bit screwed. So I hesitate to just go and revert it without > offering those people any alternative. > I understand your point but I feel we can extend the current feature in future versions to address such cases (allow REPACK CONCURRENTLY on tables in multiple-databases simultaneously). For now, they may need to rely on REPACK without CONCURRENTLY option, if they want to use it for multiple databases simultaneously. > (It's also possible that being unable to run more than one REPACK at a > time is not so big a deal. After all, it's supposed to be an infrequent > operation. And users probably don't or shouldn't have multi-terabyte > tables in multitenant databases anyway.) > > I'm not sure I understand the point of the standby. I mean, you can't > run REPACK on the standby anyway, so I don't see this as a very > problematic restriction. Do you have other reasons for wanting a > db-specific snapshot in a standby? > We are exposing need_shared_catalogs as a generic plugin option, defined as: 'it can be set to false if one is certain the plugin functions do not access shared system catalogs.' This implies it can be used for purposes other than REPACK. For example, one can imagine a single-database audit plugin that only cares about data modifications within a specific database. By setting need_shared_catalogs = false on a standby, it could reach a CONSISTENT state much faster, perfectly serving its needs. While such a plugin might not exist right now, my broader point is this: when we expose a generic facility, it can and will be used in ways beyond our initial core use cases. We should try to ensure the design doesn't permanently preclude such extensions. With the current design choice, we are painting ourselves into a corner where this feature cannot easily be extended to standbys even in the future. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-19T18:52:13Z
On 2026-May-14, Amit Kapila wrote: > The broader issue is that the entire logical decoding mechanism is > designed to process cluster-wide transactions. This patch tries to > bypass that foundational assumption, but only during the initial > snapshot construction while processing running_xacts record. > > To be clear, I am not against the idea of db-specific snapshots to > enable concurrent repacks. My concern is simply the time required to > get the architecture right. In its current state, we need more time to > carefully consider how this db-specific concept interacts with the > rest of the logical decoding machinery, which is built for > cluster-wide records. Hmm. So at this point I have to admit that the time I'll have before beta 1 is going to be very scarce. You're probably right that it's better to revert db-specific snapshots in pg19, and try again for 20. The revert should be a simple patch. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-23T15:29:35Z
On Tue, May 19, 2026 at 11:52 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-May-14, Amit Kapila wrote: > > > The broader issue is that the entire logical decoding mechanism is > > designed to process cluster-wide transactions. This patch tries to > > bypass that foundational assumption, but only during the initial > > snapshot construction while processing running_xacts record. > > > > To be clear, I am not against the idea of db-specific snapshots to > > enable concurrent repacks. My concern is simply the time required to > > get the architecture right. In its current state, we need more time to > > carefully consider how this db-specific concept interacts with the > > rest of the logical decoding machinery, which is built for > > cluster-wide records. > > Hmm. So at this point I have to admit that the time I'll have before > beta 1 is going to be very scarce. You're probably right that it's > better to revert db-specific snapshots in pg19, and try again for 20. > Sounds reasonable. > The revert should be a simple patch. > I also think so. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-24T11:29:28Z
On 2026-May-23, Amit Kapila wrote: > > The revert should be a simple patch. > > I also think so. Okay, pushed the revert after seeing it pass CI: https://cirrus-ci.com/build/5520722497372160 Thanks! -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
-
RE: Adding REPACK [concurrently]
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> — 2026-05-25T06:26:52Z
> -----Original Message----- > From: Antonin Houska <ah@cybertec.at> > Sent: Friday, April 10, 2026 9:22 PM > To: Hou, Zhijie/侯 志杰 <houzj.fnst@fujitsu.com> > Cc: Alvaro Herrera <alvherre@alvh.no-ip.org>; Amit Kapila > <amit.kapila16@gmail.com>; Kuroda, Hayato/黒田 隼人 > <kuroda.hayato@fujitsu.com>; Srinath Reddy Sadipiralla > <srinath2133@gmail.com>; Mihail Nikalayeu <mihailnikalayeu@gmail.com>; > Matthias van de Meent <boekewurm+postgres@gmail.com>; Pg Hackers > <pgsql-hackers@lists.postgresql.org>; Robert Treat <rob@xzilla.net> > Subject: Re: Adding REPACK [concurrently] > > Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> wrote: > > > When testing REPACK concurrently, I noticed that all WALs are retained > > from the moment REPACK begins copying data to the new table until the > > command finishes replaying concurrent changes on the new table and > > stops the repack decoding worker. > > > > I understand the reason: the REPACK command itself starts a > > long-running transaction, and logical decoding does not advance > > restart_lsn beyond the oldest running transaction's start position. As > > a result, slot.restart_lsn remains unchanged, preventing the checkpointer > from recycling WALs. > > I think you're right, sorry for the omission. > > > IIUC, REPACK without using concurrent option does not have this issue. > > It does not have the WAL recycling issue because it does not need to read > WAL. However it also runs in a long transaction. Even though it does not need > XID for the actual heap rewriting, it gets one at the moment it locks the table > using AccessExclusiveLock (which is at the very beginning). > > > Given that we do not restart a REPACK, I think the repack decoding > > worker should be able to advance restart_lsn each time after writing > > changes (similar to how a physical slot behaves). To illustrate this, > > I've written a patch (attached) that implements this approach, and it works > fine for me. > > LGTM, thanks! > Thanks for reviewing! After listening to the REPACK talk at pgconf.dev this year, I understand that WAL accumulation during REPACK CONCURRENTLY is not intended behavior. I think we can consider fixing this in the current release. Attached is the rebased patch, with comments adjusted based on Chao Li's comments. Best Regards, Hou zj
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-26T15:31:33Z
On 2026-May-25, Zhijie Hou (Fujitsu) wrote: > After listening to the REPACK talk at pgconf.dev this year, I understand that > WAL accumulation during REPACK CONCURRENTLY is not intended behavior. I think we > can consider fixing this in the current release. Attached is the rebased > patch, with comments adjusted based on Chao Li's comments. You're right, this is a thinko. I'll look at your patch hoping to get it pushed shortly. I wonder if we should add a TAP test to verify that WAL files are actually removed? Sounds a bit excessive TBH, but maybe it isn't really. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "I think my standards have lowered enough that now I think 'good design' is when the page doesn't irritate the living f*ck out of me." (JWZ)
-
RE: Adding REPACK [concurrently]
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> — 2026-05-27T08:08:41Z
On Tuesday, May 26, 2026 11:32 PM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > On 2026-May-25, Zhijie Hou (Fujitsu) wrote: > > > After listening to the REPACK talk at pgconf.dev this year, I > > understand that WAL accumulation during REPACK CONCURRENTLY is not > > intended behavior. I think we can consider fixing this in the current > > release. Attached is the rebased patch, with comments adjusted based on > Chao Li's comments. > > You're right, this is a thinko. I'll look at your patch hoping to get it pushed > shortly. I wonder if we should add a TAP test to verify that WAL files are > actually removed? Sounds a bit excessive TBH, but maybe it isn't really. I tried a bit, and the test complexity and speed (< 1s) appear to be within acceptable limits. I'm attaching 0002 as a reference test. 0001 remains unchanged. Best Regards, Hou zj
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-28T00:31:59Z
On Wed, May 27, 2026 at 1:08 AM Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> wrote: > > 0001 remains unchanged. > Few minor comments: ================= * + * To allow old WAL files to be recycled, we manually advance the + * slot each time a WAL segment boundary is crossed. This is safe only because REPACK creates a temporary slot that is dropped if REPACK fails — there's no scenario where this slot needs to restart decoding from an earlier position while still alive. I feel that is worth a mention. * @@ -1910,8 +1910,14 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) SpinLockRelease(&MyReplicationSlot->mutex); ReplicationSlotsComputeRequiredXmin(false); - ReplicationSlotsComputeRequiredLSN(); } + + /* + * Now the new restart_lsn is safely on disk, recompute the global WAL + * retention requirement. + */ + if (updated_restart) + ReplicationSlotsComputeRequiredLSN(); This change is not related to this patch, rather we need it even without this patch, is it worth mentioning in the commit message? -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-28T03:34:08Z
On Wed, May 27, 2026 at 5:31 PM Amit Kapila <amit.kapila16@gmail.com> wrote: > > On Wed, May 27, 2026 at 1:08 AM Zhijie Hou (Fujitsu) > <houzj.fnst@fujitsu.com> wrote: > > > > 0001 remains unchanged. > > > > Few minor comments: > ================= Commit message says: "This change does not advance catalog_xmin. REPACK already holds a snapshot that prevents catalog dead tuple removal, so catalog_xmin handling can be addressed independently.". Isn't it equally important to advance this, otherwise, for long running REPACKs dead tuples will be accumulated needlessly? If so, do we have any ideas to avoid this? -- With Regards, Amit Kapila.
-
RE: Adding REPACK [concurrently]
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> — 2026-05-28T05:18:34Z
On Thursday, May 28, 2026 11:34 AM Amit Kapila <amit.kapila16@gmail.com> wrote: > On Wed, May 27, 2026 at 5:31 PM Amit Kapila <amit.kapila16@gmail.com> > wrote: > > > > On Wed, May 27, 2026 at 1:08 AM Zhijie Hou (Fujitsu) > > <houzj.fnst@fujitsu.com> wrote: > > > > > > 0001 remains unchanged. > > > > > > > Few minor comments: > > ================= > > Commit message says: "This change does not advance catalog_xmin. > REPACK already holds a snapshot that prevents catalog dead tuple removal, > so catalog_xmin handling can be addressed independently.". > Isn't it equally important to advance this, otherwise, for long running REPACKs > dead tuples will be accumulated needlessly? If so, do we have any ideas to > avoid this? My understanding is that dead tuple accumulation is common to all long-running commands (including CLUSTER, VACUUM FULL, and REPACK without CONCURRENTLY). As long as a command holds a snapshot for a long time while scanning and copying data, the backend xmin will cause similar accumulation. So, this doesn't seem like a new issue to me, and given that catalog_xmin only affect tuples in system catalog which is less harmful, I thought it could be handled independently. There was a proposal to improve this case in [1]. Sorry if I've missed something. Attaching the v4 patch which improved the comments and commit message as suggested. [1] https://www.postgresql.org/message-id/125085.1775827305%40localhost Best Regards, Hou zj
-
Re: Adding REPACK [concurrently]
Amit Kapila <amit.kapila16@gmail.com> — 2026-05-29T15:39:18Z
On Wed, May 27, 2026 at 10:18 PM Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> wrote: > > On Thursday, May 28, 2026 11:34 AM Amit Kapila <amit.kapila16@gmail.com> wrote: > > On Wed, May 27, 2026 at 5:31 PM Amit Kapila <amit.kapila16@gmail.com> > > wrote: > > > > > > On Wed, May 27, 2026 at 1:08 AM Zhijie Hou (Fujitsu) > > > <houzj.fnst@fujitsu.com> wrote: > > > > > > > > 0001 remains unchanged. > > > > > > > > > > Few minor comments: > > > ================= > > > > Commit message says: "This change does not advance catalog_xmin. > > REPACK already holds a snapshot that prevents catalog dead tuple removal, > > so catalog_xmin handling can be addressed independently.". > > Isn't it equally important to advance this, otherwise, for long running REPACKs > > dead tuples will be accumulated needlessly? If so, do we have any ideas to > > avoid this? > > My understanding is that dead tuple accumulation is common to all long-running > commands (including CLUSTER, VACUUM FULL, and REPACK without CONCURRENTLY). As > long as a command holds a snapshot for a long time while scanning and copying > data, the backend xmin will cause similar accumulation. So, this doesn't seem > like a new issue to me, and given that catalog_xmin only affect tuples in system > catalog which is less harmful, I thought it could be handled independently. > There was a proposal to improve this case in [1]. > Fair enough. It makes sense to deal with catalog_xmin separately. > Attaching the v4 patch which improved the comments and commit message as > suggested. > I haven't tested it but otherwise the code changes looks good to me. -- With Regards, Amit Kapila.
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-05-29T22:25:12Z
On 2026-May-27, Zhijie Hou (Fujitsu) wrote: > I tried a bit, and the test complexity and speed (< 1s) appear to be within > acceptable limits. I'm attaching 0002 as a reference test. > > 0001 remains unchanged. Pushed 0001, in two parts. I rewrote the comment in decode_concurrent_changes() though, hope it ended up okay. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Los cuentos de hadas no dan al niño su primera idea sobre los monstruos. Lo que le dan es su primera idea de la posible derrota del monstruo." (G. K. Chesterton) -
RE: Adding REPACK [concurrently]
Zhijie Hou (Fujitsu) <houzj.fnst@fujitsu.com> — 2026-06-01T02:25:59Z
On Saturday, May 30, 2026 6:25 AM Alvaro Herrera <alvherre@alvh.no-ip.org> wrote: > > On 2026-May-27, Zhijie Hou (Fujitsu) wrote: > > > I tried a bit, and the test complexity and speed (< 1s) appear to be > > within acceptable limits. I'm attaching 0002 as a reference test. > > > > 0001 remains unchanged. > > Pushed 0001, in two parts. I rewrote the comment in > decode_concurrent_changes() though, hope it ended up okay. Thank you for pushing the patches ! The comments look good to me. In the original 0002 test patch, I realized that I missed to drop existing slots from previous tests (I did locally but missed to merge into the patch), which also prevented WAL removal, sorry for that. Attached is the fixed test patch. Best Regards, Hou zj
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-06-03T17:27:00Z
Hello, everyone! I think it is a good time to remind we still have a potential deadlock issue leading to loosing vast amount of REPACK work. Best soltuion I was able to craft so far is [1]. Best regards, Mikhail. P.S. I'll continue working on the repack stress test suite in the near future. [1]: https://www.postgresql.org/message-id/flat/CADzfLwXbUWuS6H4uJEFVL1jS1kzsVnuJ%2BzX1%2BtAEhQxBnEiGKw%40mail.gmail.com#99b8fbf05044d1fb82af01de69510c78
-
Re: Adding REPACK [concurrently]
Alvaro Herrera <alvherre@alvh.no-ip.org> — 2026-06-17T18:19:20Z
Hello Mihail, On 2026-Jun-03, Mihail Nikalayeu wrote: > Hello, everyone! > > I think it is a good time to remind we still have a potential deadlock > issue leading to loosing vast amount of REPACK work. > > Best soltuion I was able to craft so far is [1]. > [1]: https://www.postgresql.org/message-id/flat/CADzfLwXbUWuS6H4uJEFVL1jS1kzsVnuJ%2BzX1%2BtAEhQxBnEiGKw%40mail.gmail.com Right. I spent some time looking at this patch just before pgconf.dev, and I have to admit I was a bit scared -- that code is really non-obvious, and breakage here could mean big trouble in case something goes wrong in weird situations. So I think we should let this slip for pg19, and get this patch reviewed and pushed early in pg20 in the coming weeks, so that we have more of a chance to discover breakage in the time until pg20 release next year. -- Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/ "Por suerte hoy explotó el califont porque si no me habría muerto de aburrido" (Papelucho)
-
Re: Adding REPACK [concurrently]
Mihail Nikalayeu <mihailnikalayeu@gmail.com> — 2026-06-17T20:10:22Z
Hello, Álvaro! > Right. I spent some time looking at this patch just before pgconf.dev Thanks! > I have to admit I was a bit scared -- that code is really > non-obvious, and breakage here could mean big trouble in case something > goes wrong in weird situations. Another option we may consider for pg19 - is just a simple local-memory flag: "If I detect a deadlock and I am REPACK - cancel another, not me". It doesn't handle all tricky cases, but it deals with the most common ones and is much easier and less invasive to implement. It reuses mechanics that were already present (though unused, they were documented as "for future" in comments). POC of that approach is here - [0]. Best regards, Mikhail. [0]: https://www.postgresql.org/message-id/flat/CADzfLwURKVNQ++Dpi7bjoGfj-8pchDQEVex3eWBx0NCYn6TbDQ@mail.gmail.com#bceb18354aa20c130a94b1deedd76fb7
-
Re: Adding REPACK [concurrently]
Antonin Houska <ah@cybertec.at> — 2026-06-19T06:48:51Z
This is a diff to remove a comment that was only valid in earlier versions of the patch - I failed to notice that so far. Another comment in copy_table_data() explains why rd_toastoid is not set in the CONCURRENTLY mode: * This would not work with CONCURRENTLY because we may need to delete * TOASTed tuples from the new heap. With this hack, we'd delete them * from the old heap. */ NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid; -- Antonin Houska Web: https://www.cybertec-postgresql.com
-
Re: Adding REPACK [concurrently]
Heikki Linnakangas <hlinnaka@iki.fi> — 2026-07-01T14:43:50Z
On 17/06/2026 21:19, Alvaro Herrera wrote: > Hello Mihail, > > On 2026-Jun-03, Mihail Nikalayeu wrote: > >> Hello, everyone! >> >> I think it is a good time to remind we still have a potential deadlock >> issue leading to loosing vast amount of REPACK work. >> >> Best soltuion I was able to craft so far is [1]. > >> [1]: https://www.postgresql.org/message-id/flat/CADzfLwXbUWuS6H4uJEFVL1jS1kzsVnuJ%2BzX1%2BtAEhQxBnEiGKw%40mail.gmail.com > > Right. I spent some time looking at this patch just before pgconf.dev, > and I have to admit I was a bit scared -- that code is really > non-obvious, and breakage here could mean big trouble in case something > goes wrong in weird situations. > > So I think we should let this slip for pg19, and get this patch reviewed > and pushed early in pg20 in the coming weeks, so that we have more of a > chance to discover breakage in the time until pg20 release next year. There's still an open item for this on the wiki [1]. Is the decision to let this slip for pg19? If so, please update the open items list. Thanks! [1] https://wiki.postgresql.org/wiki/PostgreSQL_19_Open_Items - Heikki