Thread

Commits

Same data as JSON: GET /api/v1/messages/:b64id/commits the thread's linked commits as JSON, with link sources. API reference →
  1. Allow old WAL recycling during REPACK CONCURRENTLY

  2. Advance restart_lsn more eagerly in LogicalConfirmReceivedLocation

  3. Remove unnecessary signal handler change

  4. Improve REPACK (CONCURRENTLY) error messages some more

  5. Revert "Allow logical replication snapshots to be database-specific"

  6. Move REPACK (CONCURRENTLY) test out of stock regression tests

  7. REPACK: do not require REPLICATION or LOGIN

  8. Add missing initialization

  9. Simplify declaration of memcpy target

  10. Reserve replication slots specifically for REPACK

  11. doc: Add an example of REPACK (CONCURRENTLY)

  12. Allow logical replication snapshots to be database-specific

  13. Avoid different-size pointer-to-integer cast

  14. Fix valgrind failure

  15. Add CONCURRENTLY option to REPACK

  16. Rename cluster.c to repack.c (and corresponding .h)

  17. Allow index_create to suppress index_build progress reporting

  18. Make index_concurrently_create_copy more general

  19. Document the 'command' column of pg_stat_progress_repack

  20. Introduce the REPACK command

  21. Toggle logical decoding dynamically based on logical slot presence.

  22. Split vacuumdb to create vacuuming.c/h

  23. Remove ReorderBufferTupleBuf structure.

  24. Revert changes to CONCURRENTLY that "sped up" Xmin advance

  25. VACUUM: ignore indexing operations with CONCURRENTLY

  1. 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/
    
  2. 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
    
    
    
    
  3. 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
    
    
    
    
  4. 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)
    
  5. 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
    
    
    
    
  6. 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
    
    
    
    
  7. 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
    
    
    
    
  8. 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
    
    
    
    
  9. 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")
    
    
    
    
  10. 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
    
    
  11. 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
    
    
  12. 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/
    
    
    
    
  13. 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
    
    
    
    
  14. 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)
    
    
    
    
  15. 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"
    
    
    
    
  16. 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.
    
  17. 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
    
    
    
    
  18. 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
    
    
    
    
  19. 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/
    
    
    
    
  20. 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
    
    
    
    
  21. 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
    
    
    
    
  22. 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.
    
  23. 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
    
    
    
    
  24. 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
    
    
    
    
  25. 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
    
    
    
    
  26. 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
    
    
    
    
  27. 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/
    
    
    
    
  28. 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/
    
    
    
    
  29. 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
    
    
    
    
  30. 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
    
    
    
    
  31. 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
    
    
    
    
  32. 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
    
    
    
    
  33. 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.
    
    
    
    
  34. 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
    
    
    
    
  35. 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
    
    
    
    
  36. 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.
    
    
    
    
  37. 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
    
    
    
    
  38. 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
    
    
    
    
  39. 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
    
    
    
    
  40. 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.
    
    
    
    
  41. 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
    
    
    
    
  42. 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
    
    
    
    
  43. 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
    
    
    
    
  44. 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.
    
    
    
    
  45. 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
    
    
    
    
  46. 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.
    
    
    
    
  47. 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
    
    
    
    
  48. 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.
    
    
    
    
  49. 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)
    
    
    
    
  50. 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.
    
    
    
    
  51. 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
    
    
    
    
  52. 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)
    
  53. 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".
    
  54. 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.
    
  55. 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/
    
    
    
    
  56. 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
    
  57. 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
    
    
    
    
  58. 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.
    
  59. 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
    
    
    
    
  60. 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
    
    
    
    
  61. 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.
    
  62. 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
    
    
    
    
  63. 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/
    
    
    
    
  64. 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)
    
  65. 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
    
  66. 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
    
    
    
    
  67. 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
    
  68. 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.
    
    
    
    
  69. 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
    
    
    
    
  70. 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/
    
  71. 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
    
    
    
    
  72. 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)
    
    
    
    
  73. 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/
    
  74. 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
    
    
    
    
  75. 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")
    
  76. 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?
    
    
    
    
  77. 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
    
    
    
    
  78. 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.
    
    
    
    
  79. 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
    
    
    
    
  80. 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
    
    
    
    
  81. 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
    
    
    
    
  82. 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
    
    
    
    
  83. 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
    
    
    
    
  84. 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
    
    
    
    
  85. 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.
    
    
    
    
  86. 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
    
    
    
    
  87. 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.
    
    
    
    
  88. 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
    
    
    
    
  89. 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
    
    
    
    
  90. 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
    
    
  91. 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.
    
    
    
    
  92. 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.
    
    
    
    
  93. 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.
    
    
    
    
  94. 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
    
    
    
    
  95. 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
    
    
    
    
  96. 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
    
    
  97. 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)
    
    
    
    
  98. 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.
    
    
    
    
  99. 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.
    
  100. 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
    
    
  101. 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.
    
    
    
    
  102. 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.
    
    
    
    
  103. 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
    
    
    
    
  104. 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/
    
    
    
    
  105. 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.
    
    
    
    
  106. 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.
    
    
    
    
  107. 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
    
    
    
    
  108. 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
    
    
    
    
  109. 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
    
    
    
    
  110. 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)
    
    
    
    
  111. 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
    
    
    
    
  112. 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
    
    
  113. 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.
    
    
    
    
  114. 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
    
    
  115. 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.
    
    
    
    
  116. 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.
    
  117. 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
    
    
    
    
  118. 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.
    
  119. 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
    
    
    
    
  120. 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/
    
  121. 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
    
    
    
    
  122. 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
    
    
    
    
  123. 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
    
    
  124. 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.
    
  125. 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
    
  126. 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
    
    
    
    
  127. 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
    
    
  128. 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
    
  129. 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
    
    
    
    
  130. 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.
    
  131. 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/
    
    
    
    
  132. 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.
    
  133. 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
    
    
    
    
  134. 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.
    
  135. 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.
    
  136. 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
    
    
    
    
  137. 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.
    
  138. 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
    
    
    
    
  139. 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)
    
  140. 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
    
    
    
    
  141. 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.
    
  142. 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
    
    
    
    
  143. 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.
    
    
    
    
  144. 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
    
    
    
    
  145. 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).
    
    
    
    
  146. 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/
    
    
    
    
  147. 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
    
    
    
    
  148. 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
    
    
  149. 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
    
  150. 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/
    
    
    
    
  151. 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
    
    
    
    
  152. 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)
    
    
    
    
  153. 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/
    
  154. 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
    
    
    
    
  155. 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
    
    
    
    
  156. 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
    
    
    
    
  157. 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
    
    
    
    
  158. 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)
    
    
    
    
  159. 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
    
    
  160. 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.
    
    
    
    
  161. 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
    
    
  162. 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
    
    
    
    
  163. 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
    
    
  164. 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
    
    
    
    
  165. 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.
    
  166. 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
    
    
  167. 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/
    
  168. 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/
    
    
    
    
  169. 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$ 
    
    
    
  170. 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
    
    
  171. 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
    
    
    
    
  172. 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
    
    
    
    
  173. 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 ...)
    
    
    
    
  174. 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
    
    
    
    
  175. 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
    
  176. 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/)
    
    
    
    
  177. 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
    
    
    
    
  178. 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
    
    
    
    
  179. 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)
    
    
    
    
  180. 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
    
    
  181. 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)
    
    
    
    
  182. 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)
    
    
    
    
  183. 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
    
    
    
    
  184. 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
    
    
    
    
  185. 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
    
  186. 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)
    
    
    
    
  187. 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/
    
    
    
    
  188. 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/
    
    
    
    
  189. 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
    
    
    
    
  190. 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)
    
    
    
    
  191. 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)
    
    
    
    
  192. 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)
    
    
    
    
  193. 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
    
    
    
    
  194. 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
    
    
  195. 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/
    
  196. 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
    
    
    
    
  197. 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/
    
  198. 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
    
    
    
    
  199. 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
    
    
    
    
  200. 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
    
    
    
    
  201. 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
    
    
    
    
  202. 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
    
    
    
    
  203. 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
    
    
    
    
  204. 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
    
    
  205. 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
  206. 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
    
    
    
    
  207. 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
    
    
    
    
  208. 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/
    
  209. 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/
    
  210. 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
    
    
    
    
  211. 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/
    
  212. 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/
    
  213. 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
    
    
    
    
  214. 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
    
  215. 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
    
    
    
    
  216. 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
    
    
    
    
  217. 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
    
    
  218. 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/
    
  219. 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
    
    
  220. 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!"
    
    
    
    
  221. 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/
    
    
    
    
  222. 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
    
    
    
    
  223. 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)
    
    
    
    
  224. 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
    
    
    
    
  225. 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)
    
  226. 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/
    
  227. 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/
    
  228. 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.
    
    
    
    
  229. 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
    
    
    
    
  230. 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/
    
  231. 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/
    
  232. 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
    
    
    
    
  233. 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)
    
    
    
    
  234. 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)
    
  235. 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.
    
    
    
    
  236. 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.
    
    
    
    
  237. 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
    
    
    
    
  238. 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.
    
    
    
    
  239. 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/
    
    
    
    
  240. 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/
    
    
    
    
  241. 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.
    
    
    
    
  242. 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
    
    
    
    
  243. 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.
    
    
    
    
  244. 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
    
    
    
    
  245. 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/
    
  246. 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
    
    
  247. 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/
    
  248. 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/
    
  249. 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/
    
  250. 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)
    
    
    
    
  251. 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
    
    
  252. 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
    
  253. 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/
    
  254. 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)
    
    
    
    
  255. 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)
    
  256. 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"
    
  257. 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
    
    
  258. 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
    
    
    
    
  259. 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
    
    
    
    
  260. 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"
    
    
    
    
  261. 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/
    
  262. 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)
    
    
    
    
  263. 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
    
    
  264. 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)
    
  265. 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
    
    
  266. 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)
    
  267. 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
    
    
    
    
  268. 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/
    
    
    
    
  269. 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/
    
  270. 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.
    
    
    
    
  271. 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
    
    
  272. 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
    
    
    
    
  273. 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)
    
    
    
    
  274. 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
    
    
    
    
  275. 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.
    
    
    
    
  276. 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)
    
    
    
    
  277. 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.
    
    
    
    
  278. 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
    
    
    
    
  279. 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.
    
    
    
    
  280. 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
    
    
    
    
  281. 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/
    
    
    
    
  282. 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/
    
  283. 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.
    
    
    
    
  284. 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.
    
    
    
    
  285. 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
    
    
    
    
  286. 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.
    
    
    
    
  287. 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
    
    
    
    
  288. 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"
    
    
    
    
  289. 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/
    
  290. 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
    
    
    
    
  291. 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)
    
    
    
    
  292. 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
    
    
    
    
  293. 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
    
    
    
    
  294. 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
    
    
    
    
  295. 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/
    
  296. 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
    
    
    
    
  297. 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)
    
    
    
    
  298. 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/
    
  299. 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.
    
    
    
    
  300. 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
    
    
    
    
  301. 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
    
    
  302. 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/
    
    
    
    
  303. 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
    
    
    
    
  304. 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
    
    
    
    
  305. 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
    
    
    
    
  306. 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
    
    
    
    
  307. 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
    
    
    
    
  308. 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
    
    
    
    
  309. 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
    
    
    
    
  310. 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)
    
  311. 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.
    
    
    
    
  312. 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
    
    
    
    
  313. 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.
    
    
    
    
  314. 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
    
    
    
    
    
  315. 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
    
    
    
    
  316. 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
    
    
    
    
  317. 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
    
    
    
    
  318. 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.
    
    
    
    
  319. 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.
    
    
    
    
  320. 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
    
    
    
    
  321. 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
    
    
    
    
  322. 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.
    
    
    
    
  323. 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
    
    
    
    
  324. 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).
    
    
    
    
  325. 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
    
    
    
    
  326. 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
    
    
    
    
  327. 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
    
    
    
    
  328. 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
    
    
    
    
  329. 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
  330. 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
    
  331. 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
    
    
  332. 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
    
    
    
    
  333. 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
    
    
    
    
  334. 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
    
    
    
    
  335. 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.
    
  336. 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
  337. 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
    
    
    
    
  338. 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
    
    
    
    
  339. 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.
    
    
    
    
  340. 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
    
    
  341. 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
    
    
  342. 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
    
    
    
    
  343. 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.
    
    
    
    
  344. 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
    
    
    
    
  345. 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
    
    
    
    
  346. 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
    
    
    
    
  347. 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
    
    
    
    
  348. 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
    
    
    
    
  349. 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.
    
  350. 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
    
    
    
    
  351. 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
    
    
  352. 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
    
    
    
    
  353. 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
    
    
    
    
  354. 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
    
    
  355. 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
    
    
  356. 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/
    
    
    
    
    
    
    
    
  357. 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)
    
  358. 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
    
    
    
    
  359. 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/
    
    
    
    
  360. 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
    
    
    
    
  361. 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/
    
    
    
    
  362. 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
    
    
    
    
  363. 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)
    
    
    
    
  364. 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
    
    
    
    
  365. 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/
    
    
    
    
    
    
    
    
  366. 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/
    
  367. 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)
    
    
    
    
  368. 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
    
    
    
    
  369. 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.
    
  370. 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.
    
    
    
    
  371. 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
    
    
  372. 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
    
    
    
    
  373. 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/
    
  374. 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
    
    
    
    
  375. 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
    
    
    
    
  376. 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/
    
    
    
    
  377. 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
    
    
    
    
  378. 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
    
    
  379. 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.
    
    
    
    
  380. 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"
    
  381. 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
    
    
    
    
  382. 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
    
    
    
    
  383. 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.
    
    
    
    
  384. 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.
    
    
    
    
  385. 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
    
    
    
    
  386. 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
    
    
    
    
  387. 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
    
    
  388. 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.
    
    
    
    
  389. 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
    
    
  390. 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.
    
    
    
    
  391. 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/
    
    
    
    
  392. 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.
    
    
    
    
  393. 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/
    
    
    
    
  394. 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.
    
    
    
    
  395. 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/
    
    
    
    
  396. 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
    
  397. 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)
    
    
    
    
  398. 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
    
  399. 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.
    
    
    
    
  400. 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.
    
    
    
    
  401. 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
    
  402. 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.
    
    
    
    
  403. 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)
    
    
    
    
  404. 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
    
  405. 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
    
    
    
    
  406. 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)
    
    
    
    
  407. 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
    
    
    
    
  408. 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
    
    
  409. 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