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 autovacuum to use parallel vacuum workers.

  2. Add parallel vacuum worker usage to VACUUM (VERBOSE) and autovacuum logs.

  3. doc: Put new options in consistent order on man pages

  1. POC: Parallel processing of indexes in autovacuum

    Maxim Orlov <orlovmg@gmail.com> — 2025-04-16T11:04:53Z

    Hi!
    
    The VACUUM command can be executed with the parallel option. As
    documentation states, it will perform index vacuum and index cleanup phases
    of VACUUM in parallel using *integer* background workers. But such an
    interesting feature is not used for an autovacuum. After a quick look at
    the source codes, it became clear to me that when the parallel option was
    added, the corresponding option for autovacuum wasn't implemented, although
    there are no clear obstacles to this.
    
    Actually, one of our customers step into a problem with autovacuum on a
    table with many indexes and relatively long transactions. Of course, long
    transactions are an ultimate evil and the problem can be solved by calling
    running vacuum and a cron task, but, I think, we can do better.
    
    Anyhow, what about adding parallel option for an autovacuum? Here is a POC
    patch for proposed functionality. For the sake of simplicity's, several
    GUC's have been added. It would be good to think through the parallel
    launch condition without them.
    
    As always, any thoughts and opinions are very welcome!
    
    -- 
    Best regards,
    Maxim Orlov.
    
  2. Re: POC: Parallel processing of indexes in autovacuum

    wenhui qiu <qiuwenhuifx@gmail.com> — 2025-04-17T03:16:29Z

    HI *Maxim Orlov*
         Thank you for your working on this ,I like your idea ,but I have a
    suggestion ,autovacuum_max_workers is not need change requires restart , I
    think those guc are  can like
    autovacuum_max_workers
    +#max_parallel_index_autovac_workers = 0 # this feature disabled by default
    + # (change requires restart)
    +#autovac_idx_parallel_min_rows = 0
    + # (change requires restart)
    +#autovac_idx_parallel_min_indexes = 2
    + # (change requires restart)
    
    Thanks
    
    On Wed, Apr 16, 2025 at 7:05 PM Maxim Orlov <orlovmg@gmail.com> wrote:
    
    > Hi!
    >
    > The VACUUM command can be executed with the parallel option. As
    > documentation states, it will perform index vacuum and index cleanup
    > phases of VACUUM in parallel using *integer* background workers. But such
    > an interesting feature is not used for an autovacuum. After a quick look
    > at the source codes, it became clear to me that when the parallel option
    > was added, the corresponding option for autovacuum wasn't implemented, although
    > there are no clear obstacles to this.
    >
    > Actually, one of our customers step into a problem with autovacuum on a
    > table with many indexes and relatively long transactions. Of course, long
    > transactions are an ultimate evil and the problem can be solved by calling
    > running vacuum and a cron task, but, I think, we can do better.
    >
    > Anyhow, what about adding parallel option for an autovacuum? Here is a
    > POC patch for proposed functionality. For the sake of simplicity's, several
    > GUC's have been added. It would be good to think through the parallel
    > launch condition without them.
    >
    > As always, any thoughts and opinions are very welcome!
    >
    > --
    > Best regards,
    > Maxim Orlov.
    >
    
  3. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-01T01:02:21Z

    Hi,
    
    On Wed, Apr 16, 2025 at 4:05 AM Maxim Orlov <orlovmg@gmail.com> wrote:
    >
    > Hi!
    >
    > The VACUUM command can be executed with the parallel option. As documentation states, it will perform index vacuum and index cleanup phases of VACUUM in parallel using integer background workers. But such an interesting feature is not used for an autovacuum. After a quick look at the source codes, it became clear to me that when the parallel option was added, the corresponding option for autovacuum wasn't implemented, although there are no clear obstacles to this.
    >
    > Actually, one of our customers step into a problem with autovacuum on a table with many indexes and relatively long transactions. Of course, long transactions are an ultimate evil and the problem can be solved by calling running vacuum and a cron task, but, I think, we can do better.
    >
    > Anyhow, what about adding parallel option for an autovacuum? Here is a POC patch for proposed functionality. For the sake of simplicity's, several GUC's have been added. It would be good to think through the parallel launch condition without them.
    >
    > As always, any thoughts and opinions are very welcome!
    
    As I understand it, we initially disabled parallel vacuum for
    autovacuum because their objectives are somewhat contradictory.
    Parallel vacuum aims to accelerate the process by utilizing additional
    resources, while autovacuum is designed to perform cleaning operations
    with minimal impact on foreground transaction processing (e.g.,
    through vacuum delay).
    
    Nevertheless, I see your point about the potential benefits of using
    parallel vacuum within autovacuum in specific scenarios. The crucial
    consideration is determining appropriate criteria for triggering
    parallel vacuum in autovacuum. Given that we currently support only
    parallel index processing, suitable candidates might be autovacuum
    operations on large tables that have a substantial number of
    sufficiently large indexes and a high volume of garbage tuples.
    
    Once we have parallel heap vacuum, as discussed in thread[1], it would
    also likely be beneficial to incorporate it into autovacuum during
    aggressive vacuum or failsafe mode.
    
    Although the actual number of parallel workers ultimately depends on
    the number of eligible indexes, it might be beneficial to introduce a
    storage parameter, say parallel_vacuum_workers, that allows control
    over the number of parallel vacuum workers on a per-table basis.
    
    Regarding implementation: I notice the WIP patch implements its own
    parallel vacuum mechanism for autovacuum. Have you considered simply
    setting at_params.nworkers to a value greater than zero?
    
    Regards,
    
    [1] https://www.postgresql.org/message-id/CAD21AoAEfCNv-GgaDheDJ%2Bs-p_Lv1H24AiJeNoPGCmZNSwL1YA%40mail.gmail.com
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  4. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-02T16:58:30Z

    Thanks for raising this idea!
    
    I am generally -1 on the idea of autovacuum performing parallel
    index vacuum, because I always felt that the parallel option should
    be employed in a targeted manner for a specific table. if you have a bunch
    of large tables, some more important than others, a/c may end
    up using parallel resources on the least important tables and you
    will have to adjust a/v settings per table, etc to get the right table
    to be parallel index vacuumed by a/v.
    
    Also, with the TIDStore improvements for index cleanup, and the practical
    elimination of multi-pass index vacuums, I see this being even less
    convincing as something to add to a/v.
    
    Now, If I am going to allocate extra workers to run vacuum in parallel, why
    not just provide more autovacuum workers instead so I can get more tables
    vacuumed within a span of time?
    
    > Once we have parallel heap vacuum, as discussed in thread[1], it would
    > also likely be beneficial to incorporate it into autovacuum during
    > aggressive vacuum or failsafe mode.
    
    IIRC, index cleanup is disabled by failsafe.
    
    
    --
    Sami Imseih
    Amazon Web Services (AWS)
    
    
    
    
  5. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-02T18:12:49Z

    On Thu, May 1, 2025 at 8:03 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > As I understand it, we initially disabled parallel vacuum for
    > autovacuum because their objectives are somewhat contradictory.
    > Parallel vacuum aims to accelerate the process by utilizing additional
    > resources, while autovacuum is designed to perform cleaning operations
    > with minimal impact on foreground transaction processing (e.g.,
    > through vacuum delay).
    >
    Yep, we also decided that we must not create more a/v workers for
    index processing.
    In current implementation, the leader process sends a signal to the
    a/v launcher, and the launcher tries to launch all requested workers.
    But the number of workers never exceeds `autovacuum_max_workers`.
    Thus, we will never have more a/v workers than in the standard case
    (without this feature).
    
    > Nevertheless, I see your point about the potential benefits of using
    > parallel vacuum within autovacuum in specific scenarios. The crucial
    > consideration is determining appropriate criteria for triggering
    > parallel vacuum in autovacuum. Given that we currently support only
    > parallel index processing, suitable candidates might be autovacuum
    > operations on large tables that have a substantial number of
    > sufficiently large indexes and a high volume of garbage tuples.
    >
    > Although the actual number of parallel workers ultimately depends on
    > the number of eligible indexes, it might be beneficial to introduce a
    > storage parameter, say parallel_vacuum_workers, that allows control
    > over the number of parallel vacuum workers on a per-table basis.
    >
    For now, we have three GUC variables for this purpose:
    max_parallel_index_autovac_workers, autovac_idx_parallel_min_rows,
    autovac_idx_parallel_min_indexes.
    That is, everything is as you said. But we are still conducting
    research on this issue. I would like to get rid of some of these
    parameters.
    
    > Regarding implementation: I notice the WIP patch implements its own
    > parallel vacuum mechanism for autovacuum. Have you considered simply
    > setting at_params.nworkers to a value greater than zero?
    >
    About `at_params.nworkers = N` - that's exactly what we're doing (you
    can see it in the `vacuum_rel` function). But we cannot fully reuse
    code of VACUUM PARALLEL, because it creates its own processes via
    dynamic bgworkers machinery.
    As I said above - we don't want to consume additional resources. Also
    we don't want to complicate communication between processes (the idea
    is that a/v workers can only send signals to the a/v launcher).
    As a result, we created our own implementation of parallel index
    processing control - see changes in vacuumparallel.c and autovacuum.c.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  6. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-02T18:49:58Z

    On Fri, May 2, 2025 at 11:58 PM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I am generally -1 on the idea of autovacuum performing parallel
    > index vacuum, because I always felt that the parallel option should
    > be employed in a targeted manner for a specific table. if you have a bunch
    > of large tables, some more important than others, a/c may end
    > up using parallel resources on the least important tables and you
    > will have to adjust a/v settings per table, etc to get the right table
    > to be parallel index vacuumed by a/v.
    
    Hm, this is a good point. I think I should clarify one moment - in
    practice, there is a common situation when users have one huge table
    among all databases (with 80+ indexes created on it). But, of course,
    in general there may be few such tables.
    But we can still adjust the autovac_idx_parallel_min_rows parameter.
    If a table has a lot of dead tuples => it is actively used => table is
    important (?).
    Also, if the user can really determine the "importance" of each of the
    tables - we can provide an appropriate table option. Tables with this
    option set will be processed in parallel in priority order. What do
    you think about such an idea?
    
    >
    > Also, with the TIDStore improvements for index cleanup, and the practical
    > elimination of multi-pass index vacuums, I see this being even less
    > convincing as something to add to a/v.
    
    If I understood correctly, then we are talking about the fact that
    TIDStore can store so many tuples that in fact a second pass is never
    needed.
    But the number of passes does not affect the presented optimization in
    any way. We must think about a large number of indexes that must be
    processed. Even within a single pass we can have a 40% increase in
    speed.
    
    >
    > Now, If I am going to allocate extra workers to run vacuum in parallel, why
    > not just provide more autovacuum workers instead so I can get more tables
    > vacuumed within a span of time?
    
    For now, only one process can clean up indexes, so I don't see how
    increasing the number of a/v workers will help in the situation that I
    mentioned above.
    Also, we don't consume additional resources during autovacuum in this
    patch - total number of a/v workers always <= autovacuum_max_workers.
    
    BTW, see v2 patch, attached to this letter (bug fixes) :-)
    
    --
    Best regards,
    Daniil Davydov
    
  7. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-02T20:17:34Z

    > On Fri, May 2, 2025 at 11:58 PM Sami Imseih <samimseih@gmail.com> wrote:
    > >
    > > I am generally -1 on the idea of autovacuum performing parallel
    > > index vacuum, because I always felt that the parallel option should
    > > be employed in a targeted manner for a specific table. if you have a bunch
    > > of large tables, some more important than others, a/c may end
    > > up using parallel resources on the least important tables and you
    > > will have to adjust a/v settings per table, etc to get the right table
    > > to be parallel index vacuumed by a/v.
    >
    > Hm, this is a good point. I think I should clarify one moment - in
    > practice, there is a common situation when users have one huge table
    > among all databases (with 80+ indexes created on it). But, of course,
    > in general there may be few such tables.
    > But we can still adjust the autovac_idx_parallel_min_rows parameter.
    > If a table has a lot of dead tuples => it is actively used => table is
    > important (?).
    > Also, if the user can really determine the "importance" of each of the
    > tables - we can provide an appropriate table option. Tables with this
    > option set will be processed in parallel in priority order. What do
    > you think about such an idea?
    
    I think in most cases, the user will want to determine the priority of
    a table getting parallel vacuum cycles rather than having the autovacuum
    determine the priority. I also see users wanting to stagger
    vacuums of large tables with many indexes through some time period,
    and give the
    tables the full amount of parallel workers they can afford at these
    specific periods
    of time. A/V currently does not really allow for this type of
    scheduling, and if we
    give some kind of GUC to prioritize tables, I think users will constantly have
    to be modifying this priority.
    
    I am basing my comments on the scenarios I have seen on the field, and others
    may have a different opinion.
    
    > > Also, with the TIDStore improvements for index cleanup, and the practical
    > > elimination of multi-pass index vacuums, I see this being even less
    > > convincing as something to add to a/v.
    >
    > If I understood correctly, then we are talking about the fact that
    > TIDStore can store so many tuples that in fact a second pass is never
    > needed.
    > But the number of passes does not affect the presented optimization in
    > any way. We must think about a large number of indexes that must be
    > processed. Even within a single pass we can have a 40% increase in
    > speed.
    
    I am not discounting that a single table vacuum with many indexes will
    maybe perform better with parallel index scan, I am merely saying that
    the TIDStore optimization now makes index vacuums better and perhaps
    there is less of an incentive to use parallel.
    
    > > Now, If I am going to allocate extra workers to run vacuum in parallel, why
    > > not just provide more autovacuum workers instead so I can get more tables
    > > vacuumed within a span of time?
    >
    > For now, only one process can clean up indexes, so I don't see how
    > increasing the number of a/v workers will help in the situation that I
    > mentioned above.
    > Also, we don't consume additional resources during autovacuum in this
    > patch - total number of a/v workers always <= autovacuum_max_workers.
    
    Increasing a/v workers will not help speed up a specific table, what I
    am suggesting is that instead of speeding up one table, let's just allow
    other tables to not be starved of a/v cycles due to lack of a/v workers.
    
    --
    Sami
    
    
    
    
  8. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-02T22:06:41Z

    On Fri, May 2, 2025 at 9:58 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > > Once we have parallel heap vacuum, as discussed in thread[1], it would
    > > also likely be beneficial to incorporate it into autovacuum during
    > > aggressive vacuum or failsafe mode.
    >
    > IIRC, index cleanup is disabled by failsafe.
    
    Yes. My idea is to use parallel *heap* vacuum in autovacuum during
    failsafe mode. I think it would make sense as users want to complete
    freezing tables as soon as possible in this situation.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  9. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-02T22:27:40Z

    On Fri, May 2, 2025 at 11:13 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > On Thu, May 1, 2025 at 8:03 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > As I understand it, we initially disabled parallel vacuum for
    > > autovacuum because their objectives are somewhat contradictory.
    > > Parallel vacuum aims to accelerate the process by utilizing additional
    > > resources, while autovacuum is designed to perform cleaning operations
    > > with minimal impact on foreground transaction processing (e.g.,
    > > through vacuum delay).
    > >
    > Yep, we also decided that we must not create more a/v workers for
    > index processing.
    > In current implementation, the leader process sends a signal to the
    > a/v launcher, and the launcher tries to launch all requested workers.
    > But the number of workers never exceeds `autovacuum_max_workers`.
    > Thus, we will never have more a/v workers than in the standard case
    > (without this feature).
    
    I have concerns about this design. When autovacuuming on a single
    table consumes all available autovacuum_max_workers slots with
    parallel vacuum workers, the system becomes incapable of processing
    other tables. This means that when determining the appropriate
    autovacuum_max_workers value, users must consider not only the number
    of tables to be processed concurrently but also the potential number
    of parallel workers that might be launched. I think it would more make
    sense to maintain the existing autovacuum_max_workers parameter while
    introducing a new parameter that would either control the maximum
    number of parallel vacuum workers per autovacuum worker or set a
    system-wide cap on the total number of parallel vacuum workers.
    
    >
    > > Regarding implementation: I notice the WIP patch implements its own
    > > parallel vacuum mechanism for autovacuum. Have you considered simply
    > > setting at_params.nworkers to a value greater than zero?
    > >
    > About `at_params.nworkers = N` - that's exactly what we're doing (you
    > can see it in the `vacuum_rel` function). But we cannot fully reuse
    > code of VACUUM PARALLEL, because it creates its own processes via
    > dynamic bgworkers machinery.
    > As I said above - we don't want to consume additional resources. Also
    > we don't want to complicate communication between processes (the idea
    > is that a/v workers can only send signals to the a/v launcher).
    
    Could you elaborate on the reasons why you don't want to use
    background workers and avoid complicated communication between
    processes? I'm not sure whether these concerns provide sufficient
    justification for implementing its own parallel index processing.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  10. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-02T22:59:46Z

    > I think it would more make
    > sense to maintain the existing autovacuum_max_workers parameter while
    > introducing a new parameter that would either control the maximum
    > number of parallel vacuum workers per autovacuum worker or set a
    > system-wide cap on the total number of parallel vacuum workers.
    
    +1, and would it make sense for parallel workers to come from
    max_parallel_maintenance_workers? This is capped by
    max_parallel_workers and max_worker_processes, so increasing
    the defaults for all 3 will be needed as well.
    
    
    --
    Sami
    
    
    
    
  11. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-03T07:32:20Z

    On Sat, May 3, 2025 at 3:17 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I think in most cases, the user will want to determine the priority of
    > a table getting parallel vacuum cycles rather than having the autovacuum
    > determine the priority. I also see users wanting to stagger
    > vacuums of large tables with many indexes through some time period,
    > and give the
    > tables the full amount of parallel workers they can afford at these
    > specific periods
    > of time. A/V currently does not really allow for this type of
    > scheduling, and if we
    > give some kind of GUC to prioritize tables, I think users will constantly have
    > to be modifying this priority.
    
    If the user wants to determine priority himself, we anyway need to
    introduce some parameter (GUC or table option) that will give us a
    hint how we should schedule a/v work.
    You think that we should think about a more comprehensive behavior for
    such a parameter (so that the user doesn't have to change it often)? I
    will be glad to know your thoughts.
    
    > > If I understood correctly, then we are talking about the fact that
    > > TIDStore can store so many tuples that in fact a second pass is never
    > > needed.
    > > But the number of passes does not affect the presented optimization in
    > > any way. We must think about a large number of indexes that must be
    > > processed. Even within a single pass we can have a 40% increase in
    > > speed.
    >
    > I am not discounting that a single table vacuum with many indexes will
    > maybe perform better with parallel index scan, I am merely saying that
    > the TIDStore optimization now makes index vacuums better and perhaps
    > there is less of an incentive to use parallel.
    
    I still insist that this does not affect the parallel index vacuum,
    because we don't get an advantage in repeated passes. We get the same
    speed increase whether we have this optimization or not.
    Although it's even possible that the opposite is true - the situation
    will be better with the new TIDStore, but I can't say for sure.
    
    > > > Now, If I am going to allocate extra workers to run vacuum in parallel, why
    > > > not just provide more autovacuum workers instead so I can get more tables
    > > > vacuumed within a span of time?
    > >
    > > For now, only one process can clean up indexes, so I don't see how
    > > increasing the number of a/v workers will help in the situation that I
    > > mentioned above.
    > > Also, we don't consume additional resources during autovacuum in this
    > > patch - total number of a/v workers always <= autovacuum_max_workers.
    >
    > Increasing a/v workers will not help speed up a specific table, what I
    > am suggesting is that instead of speeding up one table, let's just allow
    > other tables to not be starved of a/v cycles due to lack of a/v workers.
    
    OK, I got it. But what if vacuuming of a single table will take (for
    example) 60% of all time? This is still a possible situation, and the
    fast vacuum of all other tables will not help us.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  12. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-03T08:10:27Z

    On Sat, May 3, 2025 at 5:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > > In current implementation, the leader process sends a signal to the
    > > a/v launcher, and the launcher tries to launch all requested workers.
    > > But the number of workers never exceeds `autovacuum_max_workers`.
    > > Thus, we will never have more a/v workers than in the standard case
    > > (without this feature).
    >
    > I have concerns about this design. When autovacuuming on a single
    > table consumes all available autovacuum_max_workers slots with
    > parallel vacuum workers, the system becomes incapable of processing
    > other tables. This means that when determining the appropriate
    > autovacuum_max_workers value, users must consider not only the number
    > of tables to be processed concurrently but also the potential number
    > of parallel workers that might be launched. I think it would more make
    > sense to maintain the existing autovacuum_max_workers parameter while
    > introducing a new parameter that would either control the maximum
    > number of parallel vacuum workers per autovacuum worker or set a
    > system-wide cap on the total number of parallel vacuum workers.
    >
    
    For now we have max_parallel_index_autovac_workers - this GUC limits
    the number of parallel a/v workers that can process a single table. I
    agree that the scenario you provided is problematic.
    The proposal to limit the total number of supportive a/v workers seems
    attractive to me (I'll implement it as an experiment).
    
    It seems to me that this question is becoming a key one. First we need
    to determine the role of the user in the whole scheduling mechanism.
    Should we allow users to determine priority? Will this priority affect
    only within a single vacuuming cycle, or it will be more 'global'?
    I guess I don't have enough expertise to determine this alone. I will
    be glad to receive any suggestions.
    
    > > About `at_params.nworkers = N` - that's exactly what we're doing (you
    > > can see it in the `vacuum_rel` function). But we cannot fully reuse
    > > code of VACUUM PARALLEL, because it creates its own processes via
    > > dynamic bgworkers machinery.
    > > As I said above - we don't want to consume additional resources. Also
    > > we don't want to complicate communication between processes (the idea
    > > is that a/v workers can only send signals to the a/v launcher).
    >
    > Could you elaborate on the reasons why you don't want to use
    > background workers and avoid complicated communication between
    > processes? I'm not sure whether these concerns provide sufficient
    > justification for implementing its own parallel index processing.
    >
    
    Here are my thoughts on this. A/v worker has a very simple role - it
    is born after the launcher's request and must do exactly one 'task' -
    vacuum table or participate in parallel index vacuum.
    We also have a dedicated 'launcher' role, meaning the whole design
    implies that only the launcher is able to launch processes.
    If we allow a/v worker to use bgworkers, then :
    1) A/v worker will go far beyond his responsibility.
    2) Its functionality will overlap with the functionality of the launcher.
    3) Resource consumption can jump dramatically, which is unexpected for
    the user. Autovacuum will also be dependent on other resources
    (bgworkers pool). The current design does not imply this.
    
    I wanted to create a patch that would fit into the existing mechanism
    without drastic innovations. But if you think that the above is not so
    important, then we can reuse VACUUM PARALLEL code and it would
    simplify the final implementation)
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  13. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-03T08:17:49Z

    On Sat, May 3, 2025 at 5:59 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > > I think it would more make
    > > sense to maintain the existing autovacuum_max_workers parameter while
    > > introducing a new parameter that would either control the maximum
    > > number of parallel vacuum workers per autovacuum worker or set a
    > > system-wide cap on the total number of parallel vacuum workers.
    >
    > +1, and would it make sense for parallel workers to come from
    > max_parallel_maintenance_workers? This is capped by
    > max_parallel_workers and max_worker_processes, so increasing
    > the defaults for all 3 will be needed as well.
    
    I may be wrong, but the `max_parallel_maintenance_workers` parameter
    is only used for commands that are explicitly run by the user. We
    already have `autovacuum_max_workers` and I think that code will be
    more consistent, if we adapt this particular parameter (perhaps with
    the addition of a new one, as I wrote in the previous letter).
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  14. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-05T23:56:27Z

    On Sat, May 3, 2025 at 1:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > On Sat, May 3, 2025 at 5:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > > In current implementation, the leader process sends a signal to the
    > > > a/v launcher, and the launcher tries to launch all requested workers.
    > > > But the number of workers never exceeds `autovacuum_max_workers`.
    > > > Thus, we will never have more a/v workers than in the standard case
    > > > (without this feature).
    > >
    > > I have concerns about this design. When autovacuuming on a single
    > > table consumes all available autovacuum_max_workers slots with
    > > parallel vacuum workers, the system becomes incapable of processing
    > > other tables. This means that when determining the appropriate
    > > autovacuum_max_workers value, users must consider not only the number
    > > of tables to be processed concurrently but also the potential number
    > > of parallel workers that might be launched. I think it would more make
    > > sense to maintain the existing autovacuum_max_workers parameter while
    > > introducing a new parameter that would either control the maximum
    > > number of parallel vacuum workers per autovacuum worker or set a
    > > system-wide cap on the total number of parallel vacuum workers.
    > >
    >
    > For now we have max_parallel_index_autovac_workers - this GUC limits
    > the number of parallel a/v workers that can process a single table. I
    > agree that the scenario you provided is problematic.
    > The proposal to limit the total number of supportive a/v workers seems
    > attractive to me (I'll implement it as an experiment).
    >
    > It seems to me that this question is becoming a key one. First we need
    > to determine the role of the user in the whole scheduling mechanism.
    > Should we allow users to determine priority? Will this priority affect
    > only within a single vacuuming cycle, or it will be more 'global'?
    > I guess I don't have enough expertise to determine this alone. I will
    > be glad to receive any suggestions.
    
    What I roughly imagined is that we don't need to change the entire
    autovacuum scheduling, but would like autovacuum workers to decides
    whether or not to use parallel vacuum during its vacuum operation
    based on GUC parameters (having a global effect) or storage parameters
    (having an effect on the particular table). The criteria of triggering
    parallel vacuum in autovacuum might need to be somewhat pessimistic so
    that we don't unnecessarily use parallel vacuum on many tables.
    
    >
    > > > About `at_params.nworkers = N` - that's exactly what we're doing (you
    > > > can see it in the `vacuum_rel` function). But we cannot fully reuse
    > > > code of VACUUM PARALLEL, because it creates its own processes via
    > > > dynamic bgworkers machinery.
    > > > As I said above - we don't want to consume additional resources. Also
    > > > we don't want to complicate communication between processes (the idea
    > > > is that a/v workers can only send signals to the a/v launcher).
    > >
    > > Could you elaborate on the reasons why you don't want to use
    > > background workers and avoid complicated communication between
    > > processes? I'm not sure whether these concerns provide sufficient
    > > justification for implementing its own parallel index processing.
    > >
    >
    > Here are my thoughts on this. A/v worker has a very simple role - it
    > is born after the launcher's request and must do exactly one 'task' -
    > vacuum table or participate in parallel index vacuum.
    > We also have a dedicated 'launcher' role, meaning the whole design
    > implies that only the launcher is able to launch processes.
    >
    > If we allow a/v worker to use bgworkers, then :
    > 1) A/v worker will go far beyond his responsibility.
    > 2) Its functionality will overlap with the functionality of the launcher.
    
    While I agree that the launcher process is responsible for launching
    autovacuum worker processes but I'm not sure it should be for
    launching everything related autovacuums. It's quite possible that we
    have parallel heap vacuum and processing the particular index with
    parallel workers in the future. The code could get more complex if we
    have the autovacuum launcher process launch such parallel workers too.
    I believe it's more straightforward to divide the responsibility like
    in a way that the autovacuum launcher is responsible for launching
    autovacuum workers and autovacuum workers are responsible for
    vacuuming tables no matter how to do that.
    
    > 3) Resource consumption can jump dramatically, which is unexpected for
    > the user.
    
    What extra resources could be used if we use background workers
    instead of autovacuum workers?
    
    > Autovacuum will also be dependent on other resources
    > (bgworkers pool). The current design does not imply this.
    
    I see your point but I think it doesn't necessarily need to reflect it
    at the infrastructure layer. For example, we can internally allocate
    extra background worker slots for parallel vacuum workers based on
    max_parallel_index_autovac_workers in addition to
    max_worker_processes. Anyway we might need something to check or
    validate max_worker_processes value to make sure that every autovacuum
    worker can use the specified number of parallel workers for parallel
    vacuum.
    
    > I wanted to create a patch that would fit into the existing mechanism
    > without drastic innovations. But if you think that the above is not so
    > important, then we can reuse VACUUM PARALLEL code and it would
    > simplify the final implementation)
    
    I'd suggest using the existing infrastructure if we can achieve the
    goal with it. If we find out there are some technical difficulties to
    implement it without new infrastructure, we can revisit this approach.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  15. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-06T00:21:07Z

    > On Sat, May 3, 2025 at 1:10 AM Daniil Davydov <3danissimo@gmail.com>
    > wrote:
    > >
    > > On Sat, May 3, 2025 at 5:28 AM Masahiko Sawada <sawada.mshk@gmail.com>
    > wrote:
    > > >
    > > > > In current implementation, the leader process sends a signal to the
    > > > > a/v launcher, and the launcher tries to launch all requested workers.
    > > > > But the number of workers never exceeds `autovacuum_max_workers`.
    > > > > Thus, we will never have more a/v workers than in the standard case
    > > > > (without this feature).
    > > >
    > > > I have concerns about this design. When autovacuuming on a single
    > > > table consumes all available autovacuum_max_workers slots with
    > > > parallel vacuum workers, the system becomes incapable of processing
    > > > other tables. This means that when determining the appropriate
    > > > autovacuum_max_workers value, users must consider not only the number
    > > > of tables to be processed concurrently but also the potential number
    > > > of parallel workers that might be launched. I think it would more make
    > > > sense to maintain the existing autovacuum_max_workers parameter while
    > > > introducing a new parameter that would either control the maximum
    > > > number of parallel vacuum workers per autovacuum worker or set a
    > > > system-wide cap on the total number of parallel vacuum workers.
    > > >
    > >
    > > For now we have max_parallel_index_autovac_workers - this GUC limits
    > > the number of parallel a/v workers that can process a single table. I
    > > agree that the scenario you provided is problematic.
    > > The proposal to limit the total number of supportive a/v workers seems
    > > attractive to me (I'll implement it as an experiment).
    > >
    > > It seems to me that this question is becoming a key one. First we need
    > > to determine the role of the user in the whole scheduling mechanism.
    > > Should we allow users to determine priority? Will this priority affect
    > > only within a single vacuuming cycle, or it will be more 'global'?
    > > I guess I don't have enough expertise to determine this alone. I will
    > > be glad to receive any suggestions.
    >
    > What I roughly imagined is that we don't need to change the entire
    > autovacuum scheduling, but would like autovacuum workers to decides
    > whether or not to use parallel vacuum during its vacuum operation
    > based on GUC parameters (having a global effect) or storage parameters
    > (having an effect on the particular table). The criteria of triggering
    > parallel vacuum in autovacuum might need to be somewhat pessimistic so
    > that we don't unnecessarily use parallel vacuum on many tables.
    
    
    Perhaps we should only provide a reloption, therefore only tables specified
    by the user via the reloption can be autovacuumed  in parallel?
    
    This gives a targeted approach. Of course if multiple of these allowed
    tables
    are to be autovacuumed at the same time, some may not get all the workers,
    But that’s not different from if you are to manually vacuum in parallel the
    tables
    at the same time.
    
    What do you think ?
    
    —
    Sami
    
    >
    
  16. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-06T04:54:49Z

    On Tue, May 6, 2025 at 6:57 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > What I roughly imagined is that we don't need to change the entire
    > autovacuum scheduling, but would like autovacuum workers to decides
    > whether or not to use parallel vacuum during its vacuum operation
    > based on GUC parameters (having a global effect) or storage parameters
    > (having an effect on the particular table). The criteria of triggering
    > parallel vacuum in autovacuum might need to be somewhat pessimistic so
    > that we don't unnecessarily use parallel vacuum on many tables.
    >
    
    +1, I think about it in the same way. I will expand on this topic in
    more detail in response to Sami's letter [1], so as not to repeat
    myself.
    
    > > Here are my thoughts on this. A/v worker has a very simple role - it
    > > is born after the launcher's request and must do exactly one 'task' -
    > > vacuum table or participate in parallel index vacuum.
    > > We also have a dedicated 'launcher' role, meaning the whole design
    > > implies that only the launcher is able to launch processes.
    > >
    > > If we allow a/v worker to use bgworkers, then :
    > > 1) A/v worker will go far beyond his responsibility.
    > > 2) Its functionality will overlap with the functionality of the launcher.
    >
    > While I agree that the launcher process is responsible for launching
    > autovacuum worker processes but I'm not sure it should be for
    > launching everything related autovacuums. It's quite possible that we
    > have parallel heap vacuum and processing the particular index with
    > parallel workers in the future. The code could get more complex if we
    > have the autovacuum launcher process launch such parallel workers too.
    > I believe it's more straightforward to divide the responsibility like
    > in a way that the autovacuum launcher is responsible for launching
    > autovacuum workers and autovacuum workers are responsible for
    > vacuuming tables no matter how to do that.
    
    It sounds very tempting. At the very beginning I did exactly that (to
    make sure that nothing would break in a parallel autovacuum). Only
    later it was decided to abandon the use of bgworkers.
    For now both approaches look fair for me. What do you think - will
    others agree that we can provide more responsibility to a/v workers?
    
    > > 3) Resource consumption can jump dramatically, which is unexpected for
    > > the user.
    >
    > What extra resources could be used if we use background workers
    > instead of autovacuum workers?
    
    I meant that more processes are starting to participate in the
    autovacuum than indicated in autovacuum_max_workers. And if a/v worker
    will use additional bgworkers => other operations cannot get these
    resources.
    
    > > Autovacuum will also be dependent on other resources
    > > (bgworkers pool). The current design does not imply this.
    >
    > I see your point but I think it doesn't necessarily need to reflect it
    > at the infrastructure layer. For example, we can internally allocate
    > extra background worker slots for parallel vacuum workers based on
    > max_parallel_index_autovac_workers in addition to
    > max_worker_processes. Anyway we might need something to check or
    > validate max_worker_processes value to make sure that every autovacuum
    > worker can use the specified number of parallel workers for parallel
    > vacuum.
    
    I don't think that we can provide all supportive workers for each
    parallel index vacuuming request. But I got your point - always keep
    several bgworkers that only a/v workers can use if needed and the size
    of this additional pool (depending on max_worker_processes) must be
    user-configurable.
    
    > > I wanted to create a patch that would fit into the existing mechanism
    > > without drastic innovations. But if you think that the above is not so
    > > important, then we can reuse VACUUM PARALLEL code and it would
    > > simplify the final implementation)
    >
    > I'd suggest using the existing infrastructure if we can achieve the
    > goal with it. If we find out there are some technical difficulties to
    > implement it without new infrastructure, we can revisit this approach.
    
    OK, in the near future I'll implement it and send a new patch to this
    thread. I'll be glad if you will take a look on it)
    
    [1] https://www.postgresql.org/message-id/CAA5RZ0vfBg%3Dc_0Sa1Tpxv8tueeBk8C5qTf9TrxKBbXUqPc99Ag%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  17. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-06T05:15:43Z

    On Mon, May 5, 2025 at 5:21 PM Sami Imseih <samimseih@gmail.com> wrote:
    >
    >
    >> On Sat, May 3, 2025 at 1:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >> >
    >> > On Sat, May 3, 2025 at 5:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >> > >
    >> > > > In current implementation, the leader process sends a signal to the
    >> > > > a/v launcher, and the launcher tries to launch all requested workers.
    >> > > > But the number of workers never exceeds `autovacuum_max_workers`.
    >> > > > Thus, we will never have more a/v workers than in the standard case
    >> > > > (without this feature).
    >> > >
    >> > > I have concerns about this design. When autovacuuming on a single
    >> > > table consumes all available autovacuum_max_workers slots with
    >> > > parallel vacuum workers, the system becomes incapable of processing
    >> > > other tables. This means that when determining the appropriate
    >> > > autovacuum_max_workers value, users must consider not only the number
    >> > > of tables to be processed concurrently but also the potential number
    >> > > of parallel workers that might be launched. I think it would more make
    >> > > sense to maintain the existing autovacuum_max_workers parameter while
    >> > > introducing a new parameter that would either control the maximum
    >> > > number of parallel vacuum workers per autovacuum worker or set a
    >> > > system-wide cap on the total number of parallel vacuum workers.
    >> > >
    >> >
    >> > For now we have max_parallel_index_autovac_workers - this GUC limits
    >> > the number of parallel a/v workers that can process a single table. I
    >> > agree that the scenario you provided is problematic.
    >> > The proposal to limit the total number of supportive a/v workers seems
    >> > attractive to me (I'll implement it as an experiment).
    >> >
    >> > It seems to me that this question is becoming a key one. First we need
    >> > to determine the role of the user in the whole scheduling mechanism.
    >> > Should we allow users to determine priority? Will this priority affect
    >> > only within a single vacuuming cycle, or it will be more 'global'?
    >> > I guess I don't have enough expertise to determine this alone. I will
    >> > be glad to receive any suggestions.
    >>
    >> What I roughly imagined is that we don't need to change the entire
    >> autovacuum scheduling, but would like autovacuum workers to decides
    >> whether or not to use parallel vacuum during its vacuum operation
    >> based on GUC parameters (having a global effect) or storage parameters
    >> (having an effect on the particular table). The criteria of triggering
    >> parallel vacuum in autovacuum might need to be somewhat pessimistic so
    >> that we don't unnecessarily use parallel vacuum on many tables.
    >
    >
    > Perhaps we should only provide a reloption, therefore only tables specified
    > by the user via the reloption can be autovacuumed  in parallel?
    >
    > This gives a targeted approach. Of course if multiple of these allowed tables
    > are to be autovacuumed at the same time, some may not get all the workers,
    > But that’s not different from if you are to manually vacuum in parallel the tables
    > at the same time.
    >
    > What do you think ?
    
    +1. I think that's a good starting point. We can later introduce a new
    GUC parameter that globally controls the maximum number of parallel
    vacuum workers used in autovacuum, if necessary.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  18. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-06T05:16:06Z

    On Tue, May 6, 2025 at 7:21 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > Perhaps we should only provide a reloption, therefore only tables specified
    > by the user via the reloption can be autovacuumed  in parallel?
    
    Аfter your comments (earlier in this thread) I decided to do just
    that. For now we have reloption, so the user can decide which tables
    are "important" for parallel index vacuuming.
    We also set lower bounds (hardcoded) on the number of indexes and the
    number of dead tuples. For example, there is no need to use a parallel
    vacuum if the table has only one index.
    The situation is more complicated with the number of dead tuples - we
    need tests that would show the optimal minimum value. This issue is
    still being worked out.
    
    > This gives a targeted approach. Of course if multiple of these allowed tables
    > are to be autovacuumed at the same time, some may not get all the workers,
    > But that’s not different from if you are to manually vacuum in parallel the tables
    > at the same time.
    
    I fully agree. Recently v2 patch has been supplemented with a new
    feature [1] - multiple tables in a cluster can be processed in
    parallel during autovacuum. And of course, not every a/v worker can
    get enough supportive processes, but this is considered normal
    behavior.
    Maximum number of supportive workers is limited by the GUC variable.
    
    [1] I guess that I'll send it within the v3 patch, that will also
    contain logic that was discussed in the letter above - using bgworkers
    instead of additional a/v workers. BTW, what do you think about this
    idea?
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  19. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-06T20:11:38Z

    > On Mon, May 5, 2025 at 5:21 PM Sami Imseih <samimseih@gmail.com> wrote:
    > >
    > >
    > >> On Sat, May 3, 2025 at 1:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >> >
    > >> > On Sat, May 3, 2025 at 5:28 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >> > >
    > >> > > > In current implementation, the leader process sends a signal to the
    > >> > > > a/v launcher, and the launcher tries to launch all requested workers.
    > >> > > > But the number of workers never exceeds `autovacuum_max_workers`.
    > >> > > > Thus, we will never have more a/v workers than in the standard case
    > >> > > > (without this feature).
    > >> > >
    > >> > > I have concerns about this design. When autovacuuming on a single
    > >> > > table consumes all available autovacuum_max_workers slots with
    > >> > > parallel vacuum workers, the system becomes incapable of processing
    > >> > > other tables. This means that when determining the appropriate
    > >> > > autovacuum_max_workers value, users must consider not only the number
    > >> > > of tables to be processed concurrently but also the potential number
    > >> > > of parallel workers that might be launched. I think it would more make
    > >> > > sense to maintain the existing autovacuum_max_workers parameter while
    > >> > > introducing a new parameter that would either control the maximum
    > >> > > number of parallel vacuum workers per autovacuum worker or set a
    > >> > > system-wide cap on the total number of parallel vacuum workers.
    > >> > >
    > >> >
    > >> > For now we have max_parallel_index_autovac_workers - this GUC limits
    > >> > the number of parallel a/v workers that can process a single table. I
    > >> > agree that the scenario you provided is problematic.
    > >> > The proposal to limit the total number of supportive a/v workers seems
    > >> > attractive to me (I'll implement it as an experiment).
    > >> >
    > >> > It seems to me that this question is becoming a key one. First we need
    > >> > to determine the role of the user in the whole scheduling mechanism.
    > >> > Should we allow users to determine priority? Will this priority affect
    > >> > only within a single vacuuming cycle, or it will be more 'global'?
    > >> > I guess I don't have enough expertise to determine this alone. I will
    > >> > be glad to receive any suggestions.
    > >>
    > >> What I roughly imagined is that we don't need to change the entire
    > >> autovacuum scheduling, but would like autovacuum workers to decides
    > >> whether or not to use parallel vacuum during its vacuum operation
    > >> based on GUC parameters (having a global effect) or storage parameters
    > >> (having an effect on the particular table). The criteria of triggering
    > >> parallel vacuum in autovacuum might need to be somewhat pessimistic so
    > >> that we don't unnecessarily use parallel vacuum on many tables.
    > >
    > >
    > > Perhaps we should only provide a reloption, therefore only tables specified
    > > by the user via the reloption can be autovacuumed  in parallel?
    > >
    > > This gives a targeted approach. Of course if multiple of these allowed tables
    > > are to be autovacuumed at the same time, some may not get all the workers,
    > > But that’s not different from if you are to manually vacuum in parallel the tables
    > > at the same time.
    > >
    > > What do you think ?
    >
    > +1. I think that's a good starting point. We can later introduce a new
    > GUC parameter that globally controls the maximum number of parallel
    > vacuum workers used in autovacuum, if necessary.
    
    and I this reloption should also apply to parallel heap vacuum in
    non-failsafe scenarios.
    In the failsafe case however, all tables will be eligible for parallel
    vacuum. Anyhow, that
    discussion could be taken in that thread, but wanted to point that out.
    
    --
    Sami Imseih
    Amazon Web Services (AWS)
    
    
    
    
  20. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-09T18:33:45Z

    Hi,
    As I promised - meet parallel index autovacuum with bgworkers
    (Parallel-index-autovacuum-with-bgworkers.patch). This is pretty
    simple implementation :
    1) Added new table option `parallel_idx_autovac_enabled` that must be
    set to `true` if user wants autovacuum to process table in parallel.
    2) Added new GUC variable `autovacuum_reserved_workers_num`. This is
    number of parallel workers from bgworkers pool that can be used only
    by autovacuum workers. The `autovacuum_reserved_workers_num` parameter
    actually reserves a requested part of the processes, the total number
    of which is equal to `max_worker_processes`.
    3) When an autovacuum worker decides to process some table in
    parallel, it just sets `VacuumParams->nworkers` to appropriate value
    (> 0) and then the code is executed as if it were a regular VACUUM
    PARALLEL.
    4) I kept test/modules/autovacuum as sandbox where you can play with
    parallel index autovacuum a bit.
    
    What do you think about this implementation?
    
    P.S.
    I also improved "self-managed" parallel autovacuum implementation
    (Self-managed-parallel-index-autovacuum.patch). For now it needs a lot
    of refactoring, but all features are working good.
    Both patches are targeting on master branch
    (bc35adee8d7ad38e7bef40052f196be55decddec)
    
    --
    Best regards,
    Daniil Davydov
    
  21. Re: POC: Parallel processing of indexes in autovacuum

    Matheus Alcantara <matheusssilv97@gmail.com> — 2025-05-15T21:06:19Z

    Hi,
    
    On 09/05/25 15:33, Daniil Davydov wrote:
    > Hi,
    > As I promised - meet parallel index autovacuum with bgworkers
    > (Parallel-index-autovacuum-with-bgworkers.patch). This is pretty
    > simple implementation :
    > 1) Added new table option `parallel_idx_autovac_enabled` that must be
    > set to `true` if user wants autovacuum to process table in parallel.
    > 2) Added new GUC variable `autovacuum_reserved_workers_num`. This is
    > number of parallel workers from bgworkers pool that can be used only
    > by autovacuum workers. The `autovacuum_reserved_workers_num` parameter
    > actually reserves a requested part of the processes, the total number
    > of which is equal to `max_worker_processes`.
    > 3) When an autovacuum worker decides to process some table in
    > parallel, it just sets `VacuumParams->nworkers` to appropriate value
    > (> 0) and then the code is executed as if it were a regular VACUUM
    > PARALLEL.
    > 4) I kept test/modules/autovacuum as sandbox where you can play with
    > parallel index autovacuum a bit.
    >
    > What do you think about this implementation?
    >
    I've reviewed the v1-0001 patch, the build on MacOS using meson+ninja is
    failing:
    ❯❯❯ ninja -C build install
    ninja: Entering directory `build'
    [1/126] Compiling C object
    src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    FAILED: src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    ../src/backend/utils/misc/guc_tables.c:3613:4: error: incompatible
    pointer to integer conversion initializing 'int' with an expression of
    type 'void *' [-Wint-conversion]
     3613 |                         NULL,
          |                         ^~~~
    
    It seems that the "autovacuum_reserved_workers_num" declaration on
    guc_tables.c has an extra gettext_noop() call?
    
    One other point is that as you've added TAP tests for the autovacuum I
    think you also need to create a meson.build file as you already create
    the Makefile.
    
    You also need to update the src/test/modules/meson.build and
    src/test/modules/Makefile to include the new test/modules/autovacuum
    path.
    
    -- 
    Matheus Alcantara
    
    
    
    
  22. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-16T05:10:10Z

    Hi,
    
    On Fri, May 16, 2025 at 4:06 AM Matheus Alcantara
    <matheusssilv97@gmail.com> wrote:
    > I've reviewed the v1-0001 patch, the build on MacOS using meson+ninja is
    > failing:
    > ❯❯❯ ninja -C build install
    > ninja: Entering directory `build'
    > [1/126] Compiling C object
    > src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    > FAILED: src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    > ../src/backend/utils/misc/guc_tables.c:3613:4: error: incompatible
    > pointer to integer conversion initializing 'int' with an expression of
    > type 'void *' [-Wint-conversion]
    >  3613 |                         NULL,
    >       |                         ^~~~
    >
    
    Thank you for reviewing this patch!
    
    > It seems that the "autovacuum_reserved_workers_num" declaration on
    > guc_tables.c has an extra gettext_noop() call?
    
    Good catch, I fixed this warning in the v2 version.
    
    >
    > One other point is that as you've added TAP tests for the autovacuum I
    > think you also need to create a meson.build file as you already create
    > the Makefile.
    >
    > You also need to update the src/test/modules/meson.build and
    > src/test/modules/Makefile to include the new test/modules/autovacuum
    > path.
    >
    
    OK, I should clarify this moment : modules/autovacuum is not a normal
    test but a sandbox - just an example of how we can trigger parallel
    index autovacuum. Also it may be used for debugging purposes.
    In fact, 001_autovac_parallel.pl is not verifying anything.
    I'll do as you asked (add all meson and Make stuff), but please don't
    focus on it. The creation of the real test is still in progress. (I'll
    try to complete it as soon as possible).
    
    In this letter I will divide the patch into 2 parts : implementation
    and sandbox. What do you think about implementation?
    
    
    --
    Best regards,
    Daniil Davydov
    
  23. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-20T22:30:01Z

    On Thu, May 15, 2025 at 10:10 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Fri, May 16, 2025 at 4:06 AM Matheus Alcantara
    > <matheusssilv97@gmail.com> wrote:
    > > I've reviewed the v1-0001 patch, the build on MacOS using meson+ninja is
    > > failing:
    > > ❯❯❯ ninja -C build install
    > > ninja: Entering directory `build'
    > > [1/126] Compiling C object
    > > src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    > > FAILED: src/backend/postgres_lib.a.p/utils_misc_guc_tables.c.o
    > > ../src/backend/utils/misc/guc_tables.c:3613:4: error: incompatible
    > > pointer to integer conversion initializing 'int' with an expression of
    > > type 'void *' [-Wint-conversion]
    > >  3613 |                         NULL,
    > >       |                         ^~~~
    > >
    >
    > Thank you for reviewing this patch!
    >
    > > It seems that the "autovacuum_reserved_workers_num" declaration on
    > > guc_tables.c has an extra gettext_noop() call?
    >
    > Good catch, I fixed this warning in the v2 version.
    >
    > >
    > > One other point is that as you've added TAP tests for the autovacuum I
    > > think you also need to create a meson.build file as you already create
    > > the Makefile.
    > >
    > > You also need to update the src/test/modules/meson.build and
    > > src/test/modules/Makefile to include the new test/modules/autovacuum
    > > path.
    > >
    >
    > OK, I should clarify this moment : modules/autovacuum is not a normal
    > test but a sandbox - just an example of how we can trigger parallel
    > index autovacuum. Also it may be used for debugging purposes.
    > In fact, 001_autovac_parallel.pl is not verifying anything.
    > I'll do as you asked (add all meson and Make stuff), but please don't
    > focus on it. The creation of the real test is still in progress. (I'll
    > try to complete it as soon as possible).
    >
    > In this letter I will divide the patch into 2 parts : implementation
    > and sandbox. What do you think about implementation?
    
    Thank you for updating the patches. I have some comments on v2-0001 patch:
    
    +   {
    +       {"autovacuum_reserved_workers_num", PGC_USERSET,
    RESOURCES_WORKER_PROCESSES,
    +           gettext_noop("Number of worker processes, reserved for
    participation in parallel index processing during autovacuum."),
    +           gettext_noop("This parameter is depending on
    \"max_worker_processes\" (not on \"autovacuum_max_workers\"). "
    +                        "*Only* autovacuum workers can use these
    additional processes. "
    +                        "Also, these processes are taken into account
    in \"max_parallel_workers\"."),
    +       },
    +       &av_reserved_workers_num,
    +       0, 0, MAX_BACKENDS,
    +       check_autovacuum_reserved_workers_num, NULL, NULL
    +   },
    
    I find that the name "autovacuum_reserved_workers_num" is generic. It
    would be better to have a more specific name for parallel vacuum such
    as autovacuum_max_parallel_workers. This parameter is related to
    neither autovacuum_worker_slots nor autovacuum_max_workers, which
    seems fine to me. Also, max_parallel_maintenance_workers doesn't
    affect this parameter.
    
    Which number does this parameter mean to specify: the maximum number
    of parallel vacuum workers that can be used during autovacuum or the
    maximum number of parallel vacuum workers that each autovacuum can
    use?
    
    ---
    The patch includes the changes to bgworker.c so that we can reserve
    some slots for autovacuums. I guess that this change is not
    necessarily necessary because if the user sets the related GUC
    parameters correctly the autovacuum workers can use parallel vacuum as
    expected.  Even if we need this change, I would suggest implementing
    it as a separate patch.
    
    ---
    +   {
    +       {
    +           "parallel_idx_autovac_enabled",
    +           "Allows autovacuum to process indexes of this table in
    parallel mode",
    +           RELOPT_KIND_HEAP,
    +           ShareUpdateExclusiveLock
    +       },
    +       false
    +   },
    
    The proposed reloption name doesn't align with our naming conventions.
    Looking at our existing reloptions, we typically write out full words
    rather than using abbreviations like 'autovac' or 'idx'.
    
    The new reloption name seems not to follow the conventional naming
    style for existing reloption. For instance, we don't use abbreviations
    such as 'autovac' and 'idx'.
    
    I guess we can implement this parameter as an integer parameter so
    that the user can specify the number of parallel vacuum workers for
    the table. For example, we can have a reloption
    autovacuum_parallel_workers. Setting 0 (by default) means to disable
    parallel vacuum during autovacuum, and setting special value -1 means
    to let PostgreSQL calculate the parallel degree for the table (same as
    the default VACUUM command behavior).
    
    I've also considered some alternative names. If we were to use
    parallel_maintenance_workers, it sounds like it controls the parallel
    degree for all operations using max_parallel_maintenance_workers,
    including CREATE INDEX. Similarly, vacuum_parallel_workers could be
    interpreted as affecting both autovacuum and manual VACUUM commands,
    suggesting that when users run "VACUUM (PARALLEL) t", the system would
    use their specified value for the parallel degree. I prefer
    autovacuum_parallel_workers or vacuum_parallel_workers.
    
    ---
    +   /*
    +    * If we are running autovacuum - decide whether we need to process indexes
    +    * of table with given oid in parallel.
    +    */
    +   if (AmAutoVacuumWorkerProcess() &&
    +       params->index_cleanup != VACOPTVALUE_DISABLED &&
    +       RelationAllowsParallelIdxAutovac(rel))
    
    I think that this should be done in autovacuum code.
    
    ---
    +/*
    + * Minimum number of dead tuples required for the table's indexes to be
    + * processed in parallel during autovacuum.
    + */
    +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    +
    +/*
    + * How many indexes should process each parallel worker during autovacuum.
    + */
    +#define NUM_INDEXES_PER_PARALLEL_WORKER 30
    
    These fixed values really useful in common cases? I think we already
    have an optimization where we skip vacuum indexes if the table has
    fewer dead tuples (see BYPASS_THRESHOLD_PAGES). Given that we rely on
    users' heuristics which table needs to use parallel vacuum during
    autovacuum, I think we don't need to apply these conditions.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  24. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-22T07:43:57Z

    Hi,
    
    On Wed, May 21, 2025 at 5:30 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > I have some comments on v2-0001 patch
    
    Thank you for reviewing this patch!
    
    > +   {
    > +       {"autovacuum_reserved_workers_num", PGC_USERSET,
    > RESOURCES_WORKER_PROCESSES,
    > +           gettext_noop("Number of worker processes, reserved for
    > participation in parallel index processing during autovacuum."),
    > +           gettext_noop("This parameter is depending on
    > \"max_worker_processes\" (not on \"autovacuum_max_workers\"). "
    > +                        "*Only* autovacuum workers can use these
    > additional processes. "
    > +                        "Also, these processes are taken into account
    > in \"max_parallel_workers\"."),
    > +       },
    > +       &av_reserved_workers_num,
    > +       0, 0, MAX_BACKENDS,
    > +       check_autovacuum_reserved_workers_num, NULL, NULL
    > +   },
    >
    > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > would be better to have a more specific name for parallel vacuum such
    > as autovacuum_max_parallel_workers. This parameter is related to
    > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > affect this parameter.
    > .......
    > I've also considered some alternative names. If we were to use
    > parallel_maintenance_workers, it sounds like it controls the parallel
    > degree for all operations using max_parallel_maintenance_workers,
    > including CREATE INDEX. Similarly, vacuum_parallel_workers could be
    > interpreted as affecting both autovacuum and manual VACUUM commands,
    > suggesting that when users run "VACUUM (PARALLEL) t", the system would
    > use their specified value for the parallel degree. I prefer
    > autovacuum_parallel_workers or vacuum_parallel_workers.
    >
    
    This was my headache when I created names for variables. Autovacuum
    initially implies parallelism, because we have several parallel a/v
    workers. So I think that parameter like
    `autovacuum_max_parallel_workers` will confuse somebody.
    If we want to have a more specific name, I would prefer
    `max_parallel_index_autovacuum_workers`.
    
    > Which number does this parameter mean to specify: the maximum number
    > of parallel vacuum workers that can be used during autovacuum or the
    > maximum number of parallel vacuum workers that each autovacuum can
    > use?
    
    First variant. I will concrete this in the variable's description.
    
    > +   {
    > +       {
    > +           "parallel_idx_autovac_enabled",
    > +           "Allows autovacuum to process indexes of this table in
    > parallel mode",
    > +           RELOPT_KIND_HEAP,
    > +           ShareUpdateExclusiveLock
    > +       },
    > +       false
    > +   },
    >
    > The proposed reloption name doesn't align with our naming conventions.
    > Looking at our existing reloptions, we typically write out full words
    > rather than using abbreviations like 'autovac' or 'idx'.
    >
    > The new reloption name seems not to follow the conventional naming
    > style for existing reloption. For instance, we don't use abbreviations
    > such as 'autovac' and 'idx'.
    
    OK, I'll fix it.
    
    > +   /*
    > +    * If we are running autovacuum - decide whether we need to process indexes
    > +    * of table with given oid in parallel.
    > +    */
    > +   if (AmAutoVacuumWorkerProcess() &&
    > +       params->index_cleanup != VACOPTVALUE_DISABLED &&
    > +       RelationAllowsParallelIdxAutovac(rel))
    >
    > I think that this should be done in autovacuum code.
    
    We need params->index cleanup variable to decide whether we need to
    use parallel index a/v. In autovacuum.c we have this code :
    ***
    /*
     * index_cleanup and truncate are unspecified at first in autovacuum.
     * They will be filled in with usable values using their reloptions
     * (or reloption defaults) later.
     */
    tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
    tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
    ***
    This variable is filled in inside the `vacuum_rel` function, so I
    think we should keep the above logic in vacuum.c.
    
    > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    >
    > These fixed values really useful in common cases? I think we already
    > have an optimization where we skip vacuum indexes if the table has
    > fewer dead tuples (see BYPASS_THRESHOLD_PAGES).
    
    When we allocate dead items (and optionally init parallel autocuum) we
    don't have sane value for `vacrel->lpdead_item_pages` (which should be
    compared with BYPASS_THRESHOLD_PAGES).
    The only criterion that we can focus on is the number of dead tuples
    indicated in the PgStat_StatTabEntry.
    
    ----
    
    > I guess we can implement this parameter as an integer parameter so
    > that the user can specify the number of parallel vacuum workers for
    > the table. For example, we can have a reloption
    > autovacuum_parallel_workers. Setting 0 (by default) means to disable
    > parallel vacuum during autovacuum, and setting special value -1 means
    > to let PostgreSQL calculate the parallel degree for the table (same as
    > the default VACUUM command behavior).
    > ...........
    > The patch includes the changes to bgworker.c so that we can reserve
    > some slots for autovacuums. I guess that this change is not
    > necessarily necessary because if the user sets the related GUC
    > parameters correctly the autovacuum workers can use parallel vacuum as
    > expected.  Even if we need this change, I would suggest implementing
    > it as a separate patch.
    > ..........
    > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    > +#define NUM_INDEXES_PER_PARALLEL_WORKER 30
    >
    > These fixed values really useful in common cases? Given that we rely on
    > users' heuristics which table needs to use parallel vacuum during
    > autovacuum, I think we don't need to apply these conditions.
    > ..........
    
    I grouped these comments together, because they all relate to a single
    question : how much freedom will we give to the user?
    Your opinion (as far as I understand) is that we allow users to
    specify any number of parallel workers for tables, and it is the
    user's responsibility to configure appropriate GUC variables, so that
    autovacuum can always process indexes in parallel.
    And we don't need to think about thresholds. Even if the table has a
    small number of indexes and dead rows - if the user specified table
    option, we must do a parallel index a/v with requested number of
    parallel workers.
    Please correct me if I messed something up.
    
    I think that this logic is well suited for the `VACUUM (PARALLEL)` sql
    command, which is manually called by the user.
    
    But autovacuum (as I think) should work as stable as possible and
    `unnoticed` by other processes. Thus, we must :
    1) Compute resources (such as the number of parallel workers for a
    single table's indexes vacuuming) as efficiently as possible.
    2) Provide a guarantee that as many tables as possible (among
    requested) will be processed in parallel.
    
    (1) can be achieved by calculating the parameters on the fly.
    NUM_INDEXES_PER_PARALLEL_WORKER is a rough mock. I can provide more
    accurate value in the near future.
    (2) can be achieved by workers reserving - we know that N workers
    (from bgworkers pool) are *always* at our disposal. And when we use
    such workers we are not dependent on other operations in the cluster
    and we don't interfere with other operations by taking resources away
    from them.
    
    If we give the user too much freedom in parallel index a/v tuning, all
    these requirements may be violated.
    
    This is only my opinion, and I can agree with yours. Maybe we need
    another person to judge us?
    Please see v3 patches that contain changes related to GUC parameter
    and table option (no changes in global logic by now).
    
    --
    Best regards,
    Daniil Davydov
    
  25. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-05-22T17:48:07Z

    I started looking at the patch but I have some high level thoughts I would
    like to share before looking further.
    
    > > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > > would be better to have a more specific name for parallel vacuum such
    > > as autovacuum_max_parallel_workers. This parameter is related to
    > > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > > affect this parameter.
    > > .......
    > > I've also considered some alternative names. If we were to use
    > > parallel_maintenance_workers, it sounds like it controls the parallel
    > > degree for all operations using max_parallel_maintenance_workers,
    > > including CREATE INDEX. Similarly, vacuum_parallel_workers could be
    > > interpreted as affecting both autovacuum and manual VACUUM commands,
    > > suggesting that when users run "VACUUM (PARALLEL) t", the system would
    > > use their specified value for the parallel degree. I prefer
    > > autovacuum_parallel_workers or vacuum_parallel_workers.
    > >
    >
    > This was my headache when I created names for variables. Autovacuum
    > initially implies parallelism, because we have several parallel a/v
    > workers. So I think that parameter like
    > `autovacuum_max_parallel_workers` will confuse somebody.
    > If we want to have a more specific name, I would prefer
    > `max_parallel_index_autovacuum_workers`.
    
    I don't think we should have a separate pool of parallel workers for those
    that are used to support parallel autovacuum. At the end of the day, these
    are parallel workers and they should be capped by max_parallel_workers. I think
    it will be confusing if we claim these are parallel workers, but they
    are coming from
    a different pool.
    
    I envision we have another GUC such as "max_parallel_autovacuum_workers"
    (which I think is a better name) that matches the behavior of
    "max_parallel_maintenance_worker". Meaning that the autovacuum workers
    still maintain their existing behavior ( launching a worker per table
    ), and if they do need
    to vacuum in parallel, they can draw from a pool of parallel workers.
    
    With the above said, I therefore think the reloption should actually be a number
    of parallel workers rather than a boolean. Let's take an example of a
    user that has 3 tables
    they wish to (auto)vacuum can process in parallel, and if available
    they wish each of these tables
    could be autovacuumed with 4 parallel workers. However, as to not
    overload the system, they
    cap the 'max_parallel_maintenance_worker' to something like 8. If it
    so happens that all
    3 tables are auto-vacuumed at the same time, there may not be enough
    parallel workers,
    so one table will be a loser and be vacuumed in serial. That is
    acceptable, and a/v logging
    ( and perhaps other stat views ) should display this behavior: workers
    planned vs workers launched.
    
    thoughts?
    
    
    --
    Sami Imseih
    Amazon Web Services (AWS)
    
    
    
    
  26. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-22T23:12:06Z

    On Thu, May 22, 2025 at 12:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Wed, May 21, 2025 at 5:30 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > I have some comments on v2-0001 patch
    >
    > Thank you for reviewing this patch!
    >
    > > +   {
    > > +       {"autovacuum_reserved_workers_num", PGC_USERSET,
    > > RESOURCES_WORKER_PROCESSES,
    > > +           gettext_noop("Number of worker processes, reserved for
    > > participation in parallel index processing during autovacuum."),
    > > +           gettext_noop("This parameter is depending on
    > > \"max_worker_processes\" (not on \"autovacuum_max_workers\"). "
    > > +                        "*Only* autovacuum workers can use these
    > > additional processes. "
    > > +                        "Also, these processes are taken into account
    > > in \"max_parallel_workers\"."),
    > > +       },
    > > +       &av_reserved_workers_num,
    > > +       0, 0, MAX_BACKENDS,
    > > +       check_autovacuum_reserved_workers_num, NULL, NULL
    > > +   },
    > >
    > > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > > would be better to have a more specific name for parallel vacuum such
    > > as autovacuum_max_parallel_workers. This parameter is related to
    > > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > > affect this parameter.
    > > .......
    > > I've also considered some alternative names. If we were to use
    > > parallel_maintenance_workers, it sounds like it controls the parallel
    > > degree for all operations using max_parallel_maintenance_workers,
    > > including CREATE INDEX. Similarly, vacuum_parallel_workers could be
    > > interpreted as affecting both autovacuum and manual VACUUM commands,
    > > suggesting that when users run "VACUUM (PARALLEL) t", the system would
    > > use their specified value for the parallel degree. I prefer
    > > autovacuum_parallel_workers or vacuum_parallel_workers.
    > >
    >
    > This was my headache when I created names for variables. Autovacuum
    > initially implies parallelism, because we have several parallel a/v
    > workers.
    
    I'm not sure if it's parallelism. We can have multiple autovacuum
    workers simultaneously working on different tables, which seems not
    parallelism to me.
    
    > So I think that parameter like
    > `autovacuum_max_parallel_workers` will confuse somebody.
    > If we want to have a more specific name, I would prefer
    > `max_parallel_index_autovacuum_workers`.
    
    It's better not to use 'index' as we're trying to extend parallel
    vacuum to heap scanning/vacuuming as well[1].
    
    >
    > > +   /*
    > > +    * If we are running autovacuum - decide whether we need to process indexes
    > > +    * of table with given oid in parallel.
    > > +    */
    > > +   if (AmAutoVacuumWorkerProcess() &&
    > > +       params->index_cleanup != VACOPTVALUE_DISABLED &&
    > > +       RelationAllowsParallelIdxAutovac(rel))
    > >
    > > I think that this should be done in autovacuum code.
    >
    > We need params->index cleanup variable to decide whether we need to
    > use parallel index a/v. In autovacuum.c we have this code :
    > ***
    > /*
    >  * index_cleanup and truncate are unspecified at first in autovacuum.
    >  * They will be filled in with usable values using their reloptions
    >  * (or reloption defaults) later.
    >  */
    > tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
    > tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
    > ***
    > This variable is filled in inside the `vacuum_rel` function, so I
    > think we should keep the above logic in vacuum.c.
    
    I guess that we can specify the parallel degree even if index_cleanup
    is still UNSPECIFIED. vacuum_rel() would then decide whether to use
    index vacuuming and vacuumlazy.c would decide whether to use parallel
    vacuum based on the specified parallel degree and index_cleanup value.
    
    >
    > > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    > >
    > > These fixed values really useful in common cases? I think we already
    > > have an optimization where we skip vacuum indexes if the table has
    > > fewer dead tuples (see BYPASS_THRESHOLD_PAGES).
    >
    > When we allocate dead items (and optionally init parallel autocuum) we
    > don't have sane value for `vacrel->lpdead_item_pages` (which should be
    > compared with BYPASS_THRESHOLD_PAGES).
    > The only criterion that we can focus on is the number of dead tuples
    > indicated in the PgStat_StatTabEntry.
    
    My point is that this criterion might not be useful. We have the
    bypass optimization for index vacuuming and having many dead tuples
    doesn't necessarily mean index vacuuming taking a long time. For
    example, even if the table has a few dead tuples, index vacuuming
    could take a very long time and parallel index vacuuming would help
    the situation, if the table is very large and has many indexes.
    
    >
    > ----
    >
    > > I guess we can implement this parameter as an integer parameter so
    > > that the user can specify the number of parallel vacuum workers for
    > > the table. For example, we can have a reloption
    > > autovacuum_parallel_workers. Setting 0 (by default) means to disable
    > > parallel vacuum during autovacuum, and setting special value -1 means
    > > to let PostgreSQL calculate the parallel degree for the table (same as
    > > the default VACUUM command behavior).
    > > ...........
    > > The patch includes the changes to bgworker.c so that we can reserve
    > > some slots for autovacuums. I guess that this change is not
    > > necessarily necessary because if the user sets the related GUC
    > > parameters correctly the autovacuum workers can use parallel vacuum as
    > > expected.  Even if we need this change, I would suggest implementing
    > > it as a separate patch.
    > > ..........
    > > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    > > +#define NUM_INDEXES_PER_PARALLEL_WORKER 30
    > >
    > > These fixed values really useful in common cases? Given that we rely on
    > > users' heuristics which table needs to use parallel vacuum during
    > > autovacuum, I think we don't need to apply these conditions.
    > > ..........
    >
    > I grouped these comments together, because they all relate to a single
    > question : how much freedom will we give to the user?
    > Your opinion (as far as I understand) is that we allow users to
    > specify any number of parallel workers for tables, and it is the
    > user's responsibility to configure appropriate GUC variables, so that
    > autovacuum can always process indexes in parallel.
    > And we don't need to think about thresholds. Even if the table has a
    > small number of indexes and dead rows - if the user specified table
    > option, we must do a parallel index a/v with requested number of
    > parallel workers.
    > Please correct me if I messed something up.
    >
    > I think that this logic is well suited for the `VACUUM (PARALLEL)` sql
    > command, which is manually called by the user.
    
    The current idea that users can use parallel vacuum on particular
    tables based on their heuristic makes sense to me as the first
    implementation.
    
    > But autovacuum (as I think) should work as stable as possible and
    > `unnoticed` by other processes. Thus, we must :
    > 1) Compute resources (such as the number of parallel workers for a
    > single table's indexes vacuuming) as efficiently as possible.
    > 2) Provide a guarantee that as many tables as possible (among
    > requested) will be processed in parallel.
    
    I think these ideas could be implemented on top of the current idea.
    
    > (1) can be achieved by calculating the parameters on the fly.
    > NUM_INDEXES_PER_PARALLEL_WORKER is a rough mock. I can provide more
    > accurate value in the near future.
    
    I think it requires more things than the number of indexes on the
    table to achieve (1). Suppose that there is a very large table that
    gets updates heavily and has a few indexes. If users want to avoid the
    table from being bloated, it would be a reasonable idea to use
    parallel vacuum during autovacuum and it would not be a good idea to
    disallow using parallel vacuum solely because it doesn't have more
    than 30 indexes. On the other hand, if the table had got many updates
    but not so now, users might want to use resources for autovacuums on
    other tables. We might need to consider autovacuum frequencies per
    table, the statistics of the previous autovacuum, or system loads etc.
    So I think that in order to achieve (1) we might need more statistics
    and using only NUM_INDEXES_PER_PARALLEL_WORKER would not work fine.
    
    > (2) can be achieved by workers reserving - we know that N workers
    > (from bgworkers pool) are *always* at our disposal. And when we use
    > such workers we are not dependent on other operations in the cluster
    > and we don't interfere with other operations by taking resources away
    > from them.
    
    Reserving some bgworkers for autovacuum could make sense. But I think
    it's better to implement it in a general way as it could be useful in
    other use cases too. That is, it might be a good to implement
    infrastructure so that any PostgreSQL code (possibly including
    extensions) can request allocating a pool of bgworkers for specific
    usage and use bgworkers from them.
    
    Regards,
    
    [1] https://www.postgresql.org/message-id/CAD21AoAEfCNv-GgaDheDJ%2Bs-p_Lv1H24AiJeNoPGCmZNSwL1YA%40mail.gmail.com
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  27. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-05-22T23:20:30Z

    On Thu, May 22, 2025 at 10:48 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I started looking at the patch but I have some high level thoughts I would
    > like to share before looking further.
    >
    > > > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > > > would be better to have a more specific name for parallel vacuum such
    > > > as autovacuum_max_parallel_workers. This parameter is related to
    > > > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > > > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > > > affect this parameter.
    > > > .......
    > > > I've also considered some alternative names. If we were to use
    > > > parallel_maintenance_workers, it sounds like it controls the parallel
    > > > degree for all operations using max_parallel_maintenance_workers,
    > > > including CREATE INDEX. Similarly, vacuum_parallel_workers could be
    > > > interpreted as affecting both autovacuum and manual VACUUM commands,
    > > > suggesting that when users run "VACUUM (PARALLEL) t", the system would
    > > > use their specified value for the parallel degree. I prefer
    > > > autovacuum_parallel_workers or vacuum_parallel_workers.
    > > >
    > >
    > > This was my headache when I created names for variables. Autovacuum
    > > initially implies parallelism, because we have several parallel a/v
    > > workers. So I think that parameter like
    > > `autovacuum_max_parallel_workers` will confuse somebody.
    > > If we want to have a more specific name, I would prefer
    > > `max_parallel_index_autovacuum_workers`.
    >
    > I don't think we should have a separate pool of parallel workers for those
    > that are used to support parallel autovacuum. At the end of the day, these
    > are parallel workers and they should be capped by max_parallel_workers. I think
    > it will be confusing if we claim these are parallel workers, but they
    > are coming from
    > a different pool.
    
    I agree that parallel vacuum workers used during autovacuum should be
    capped by the max_parallel_workers.
    
    >
    > I envision we have another GUC such as "max_parallel_autovacuum_workers"
    > (which I think is a better name) that matches the behavior of
    > "max_parallel_maintenance_worker". Meaning that the autovacuum workers
    > still maintain their existing behavior ( launching a worker per table
    > ), and if they do need
    > to vacuum in parallel, they can draw from a pool of parallel workers.
    >
    > With the above said, I therefore think the reloption should actually be a number
    > of parallel workers rather than a boolean. Let's take an example of a
    > user that has 3 tables
    > they wish to (auto)vacuum can process in parallel, and if available
    > they wish each of these tables
    > could be autovacuumed with 4 parallel workers. However, as to not
    > overload the system, they
    > cap the 'max_parallel_maintenance_worker' to something like 8. If it
    > so happens that all
    > 3 tables are auto-vacuumed at the same time, there may not be enough
    > parallel workers,
    > so one table will be a loser and be vacuumed in serial.
    
    +1 for the reloption having a number of parallel workers, leaving
    aside the name competition.
    
    > That is
    > acceptable, and a/v logging
    > ( and perhaps other stat views ) should display this behavior: workers
    > planned vs workers launched.
    
    Agreed. The workers planned vs. launched is reported only with VERBOSE
    option so we need to change it so that autovacuum can log it at least.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  28. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-05-25T17:22:42Z

    Hi,
    
    On Fri, May 23, 2025 at 6:12 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Thu, May 22, 2025 at 12:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > On Wed, May 21, 2025 at 5:30 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > > >
    > > > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > > > would be better to have a more specific name for parallel vacuum such
    > > > as autovacuum_max_parallel_workers. This parameter is related to
    > > > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > > > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > > > affect this parameter.
    > >
    > > This was my headache when I created names for variables. Autovacuum
    > > initially implies parallelism, because we have several parallel a/v
    > > workers.
    >
    > I'm not sure if it's parallelism. We can have multiple autovacuum
    > workers simultaneously working on different tables, which seems not
    > parallelism to me.
    
    Hm, I didn't thought about the 'parallelism' definition in this way.
    But I see your point - the next v4 patch will contain the naming that
    you suggest.
    
    >
    > > So I think that parameter like
    > > `autovacuum_max_parallel_workers` will confuse somebody.
    > > If we want to have a more specific name, I would prefer
    > > `max_parallel_index_autovacuum_workers`.
    >
    > It's better not to use 'index' as we're trying to extend parallel
    > vacuum to heap scanning/vacuuming as well[1].
    
    OK, I'll fix it.
    
    > > > +   /*
    > > > +    * If we are running autovacuum - decide whether we need to process indexes
    > > > +    * of table with given oid in parallel.
    > > > +    */
    > > > +   if (AmAutoVacuumWorkerProcess() &&
    > > > +       params->index_cleanup != VACOPTVALUE_DISABLED &&
    > > > +       RelationAllowsParallelIdxAutovac(rel))
    > > >
    > > > I think that this should be done in autovacuum code.
    > >
    > > We need params->index cleanup variable to decide whether we need to
    > > use parallel index a/v. In autovacuum.c we have this code :
    > > ***
    > > /*
    > >  * index_cleanup and truncate are unspecified at first in autovacuum.
    > >  * They will be filled in with usable values using their reloptions
    > >  * (or reloption defaults) later.
    > >  */
    > > tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
    > > tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
    > > ***
    > > This variable is filled in inside the `vacuum_rel` function, so I
    > > think we should keep the above logic in vacuum.c.
    >
    > I guess that we can specify the parallel degree even if index_cleanup
    > is still UNSPECIFIED. vacuum_rel() would then decide whether to use
    > index vacuuming and vacuumlazy.c would decide whether to use parallel
    > vacuum based on the specified parallel degree and index_cleanup value.
    >
    > >
    > > > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    > > >
    > > > These fixed values really useful in common cases? I think we already
    > > > have an optimization where we skip vacuum indexes if the table has
    > > > fewer dead tuples (see BYPASS_THRESHOLD_PAGES).
    > >
    > > When we allocate dead items (and optionally init parallel autocuum) we
    > > don't have sane value for `vacrel->lpdead_item_pages` (which should be
    > > compared with BYPASS_THRESHOLD_PAGES).
    > > The only criterion that we can focus on is the number of dead tuples
    > > indicated in the PgStat_StatTabEntry.
    >
    > My point is that this criterion might not be useful. We have the
    > bypass optimization for index vacuuming and having many dead tuples
    > doesn't necessarily mean index vacuuming taking a long time. For
    > example, even if the table has a few dead tuples, index vacuuming
    > could take a very long time and parallel index vacuuming would help
    > the situation, if the table is very large and has many indexes.
    
    That sounds reasonable. I'll fix it.
    
    > > But autovacuum (as I think) should work as stable as possible and
    > > `unnoticed` by other processes. Thus, we must :
    > > 1) Compute resources (such as the number of parallel workers for a
    > > single table's indexes vacuuming) as efficiently as possible.
    > > 2) Provide a guarantee that as many tables as possible (among
    > > requested) will be processed in parallel.
    > >
    > > (1) can be achieved by calculating the parameters on the fly.
    > > NUM_INDEXES_PER_PARALLEL_WORKER is a rough mock. I can provide more
    > > accurate value in the near future.
    >
    > I think it requires more things than the number of indexes on the
    > table to achieve (1). Suppose that there is a very large table that
    > gets updates heavily and has a few indexes. If users want to avoid the
    > table from being bloated, it would be a reasonable idea to use
    > parallel vacuum during autovacuum and it would not be a good idea to
    > disallow using parallel vacuum solely because it doesn't have more
    > than 30 indexes. On the other hand, if the table had got many updates
    > but not so now, users might want to use resources for autovacuums on
    > other tables. We might need to consider autovacuum frequencies per
    > table, the statistics of the previous autovacuum, or system loads etc.
    > So I think that in order to achieve (1) we might need more statistics
    > and using only NUM_INDEXES_PER_PARALLEL_WORKER would not work fine.
    >
    
    It's hard for me to imagine exactly how extended statistics will help
    us track such situations.
    It seems that for any of our heuristics, it will be possible to come
    up with a counter example.
    Maybe we can give advices (via logs) to the user? But for such an
    idea, tests should be conducted so that we can understand when
    resource consumption becomes ineffective.
    I guess that we need to agree on an implementation before conducting such tests.
    
    > > (2) can be achieved by workers reserving - we know that N workers
    > > (from bgworkers pool) are *always* at our disposal. And when we use
    > > such workers we are not dependent on other operations in the cluster
    > > and we don't interfere with other operations by taking resources away
    > > from them.
    >
    > Reserving some bgworkers for autovacuum could make sense. But I think
    > it's better to implement it in a general way as it could be useful in
    > other use cases too. That is, it might be a good to implement
    > infrastructure so that any PostgreSQL code (possibly including
    > extensions) can request allocating a pool of bgworkers for specific
    > usage and use bgworkers from them.
    
    Reserving infrastructure is an ambitious idea. I am not sure that we
    should implement it within this thread and feature.
    Maybe we should create a separate thread for it and as a
    justification, refer to parallel autovacuum?
    
    -----
    Thanks everybody for feedback! I attach a v4 patch to this letter.
    Main features :
    1) 'parallel_autovacuum_workers' reloption - integer value, that sets
    the maximum number of parallel a/v workers that can be taken from
    bgworkers pool in order to process this table.
    2) 'max_parallel_autovacuum_workers' - GUC variable, that sets the
    maximum total number of parallel a/v workers, that can be taken from
    bgworkers pool.
    3) Parallel autovacuum does not try to use thresholds like
    NUM_INDEXES_PER_PARALLEL_WORKER and AV_PARALLEL_DEADTUP_THRESHOLD.
    4) Parallel autovacuum now can report statistics like "planned vs. launched".
    5) For now I got rid of the 'reserving' idea, so now autovacuum
    leaders are competing with everyone for parallel workers from the
    bgworkers pool.
    
    What do you think about this implementation?
    
    --
    Best regards,
    Daniil Davydov
    
  29. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-06-17T22:36:41Z

    On Sun, May 25, 2025 at 10:22 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Fri, May 23, 2025 at 6:12 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Thu, May 22, 2025 at 12:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >
    > > > On Wed, May 21, 2025 at 5:30 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > > > >
    > > > > I find that the name "autovacuum_reserved_workers_num" is generic. It
    > > > > would be better to have a more specific name for parallel vacuum such
    > > > > as autovacuum_max_parallel_workers. This parameter is related to
    > > > > neither autovacuum_worker_slots nor autovacuum_max_workers, which
    > > > > seems fine to me. Also, max_parallel_maintenance_workers doesn't
    > > > > affect this parameter.
    > > >
    > > > This was my headache when I created names for variables. Autovacuum
    > > > initially implies parallelism, because we have several parallel a/v
    > > > workers.
    > >
    > > I'm not sure if it's parallelism. We can have multiple autovacuum
    > > workers simultaneously working on different tables, which seems not
    > > parallelism to me.
    >
    > Hm, I didn't thought about the 'parallelism' definition in this way.
    > But I see your point - the next v4 patch will contain the naming that
    > you suggest.
    >
    > >
    > > > So I think that parameter like
    > > > `autovacuum_max_parallel_workers` will confuse somebody.
    > > > If we want to have a more specific name, I would prefer
    > > > `max_parallel_index_autovacuum_workers`.
    > >
    > > It's better not to use 'index' as we're trying to extend parallel
    > > vacuum to heap scanning/vacuuming as well[1].
    >
    > OK, I'll fix it.
    >
    > > > > +   /*
    > > > > +    * If we are running autovacuum - decide whether we need to process indexes
    > > > > +    * of table with given oid in parallel.
    > > > > +    */
    > > > > +   if (AmAutoVacuumWorkerProcess() &&
    > > > > +       params->index_cleanup != VACOPTVALUE_DISABLED &&
    > > > > +       RelationAllowsParallelIdxAutovac(rel))
    > > > >
    > > > > I think that this should be done in autovacuum code.
    > > >
    > > > We need params->index cleanup variable to decide whether we need to
    > > > use parallel index a/v. In autovacuum.c we have this code :
    > > > ***
    > > > /*
    > > >  * index_cleanup and truncate are unspecified at first in autovacuum.
    > > >  * They will be filled in with usable values using their reloptions
    > > >  * (or reloption defaults) later.
    > > >  */
    > > > tab->at_params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
    > > > tab->at_params.truncate = VACOPTVALUE_UNSPECIFIED;
    > > > ***
    > > > This variable is filled in inside the `vacuum_rel` function, so I
    > > > think we should keep the above logic in vacuum.c.
    > >
    > > I guess that we can specify the parallel degree even if index_cleanup
    > > is still UNSPECIFIED. vacuum_rel() would then decide whether to use
    > > index vacuuming and vacuumlazy.c would decide whether to use parallel
    > > vacuum based on the specified parallel degree and index_cleanup value.
    > >
    > > >
    > > > > +#define AV_PARALLEL_DEADTUP_THRESHOLD  1024
    > > > >
    > > > > These fixed values really useful in common cases? I think we already
    > > > > have an optimization where we skip vacuum indexes if the table has
    > > > > fewer dead tuples (see BYPASS_THRESHOLD_PAGES).
    > > >
    > > > When we allocate dead items (and optionally init parallel autocuum) we
    > > > don't have sane value for `vacrel->lpdead_item_pages` (which should be
    > > > compared with BYPASS_THRESHOLD_PAGES).
    > > > The only criterion that we can focus on is the number of dead tuples
    > > > indicated in the PgStat_StatTabEntry.
    > >
    > > My point is that this criterion might not be useful. We have the
    > > bypass optimization for index vacuuming and having many dead tuples
    > > doesn't necessarily mean index vacuuming taking a long time. For
    > > example, even if the table has a few dead tuples, index vacuuming
    > > could take a very long time and parallel index vacuuming would help
    > > the situation, if the table is very large and has many indexes.
    >
    > That sounds reasonable. I'll fix it.
    >
    > > > But autovacuum (as I think) should work as stable as possible and
    > > > `unnoticed` by other processes. Thus, we must :
    > > > 1) Compute resources (such as the number of parallel workers for a
    > > > single table's indexes vacuuming) as efficiently as possible.
    > > > 2) Provide a guarantee that as many tables as possible (among
    > > > requested) will be processed in parallel.
    > > >
    > > > (1) can be achieved by calculating the parameters on the fly.
    > > > NUM_INDEXES_PER_PARALLEL_WORKER is a rough mock. I can provide more
    > > > accurate value in the near future.
    > >
    > > I think it requires more things than the number of indexes on the
    > > table to achieve (1). Suppose that there is a very large table that
    > > gets updates heavily and has a few indexes. If users want to avoid the
    > > table from being bloated, it would be a reasonable idea to use
    > > parallel vacuum during autovacuum and it would not be a good idea to
    > > disallow using parallel vacuum solely because it doesn't have more
    > > than 30 indexes. On the other hand, if the table had got many updates
    > > but not so now, users might want to use resources for autovacuums on
    > > other tables. We might need to consider autovacuum frequencies per
    > > table, the statistics of the previous autovacuum, or system loads etc.
    > > So I think that in order to achieve (1) we might need more statistics
    > > and using only NUM_INDEXES_PER_PARALLEL_WORKER would not work fine.
    > >
    >
    > It's hard for me to imagine exactly how extended statistics will help
    > us track such situations.
    > It seems that for any of our heuristics, it will be possible to come
    > up with a counter example.
    > Maybe we can give advices (via logs) to the user? But for such an
    > idea, tests should be conducted so that we can understand when
    > resource consumption becomes ineffective.
    > I guess that we need to agree on an implementation before conducting such tests.
    >
    > > > (2) can be achieved by workers reserving - we know that N workers
    > > > (from bgworkers pool) are *always* at our disposal. And when we use
    > > > such workers we are not dependent on other operations in the cluster
    > > > and we don't interfere with other operations by taking resources away
    > > > from them.
    > >
    > > Reserving some bgworkers for autovacuum could make sense. But I think
    > > it's better to implement it in a general way as it could be useful in
    > > other use cases too. That is, it might be a good to implement
    > > infrastructure so that any PostgreSQL code (possibly including
    > > extensions) can request allocating a pool of bgworkers for specific
    > > usage and use bgworkers from them.
    >
    > Reserving infrastructure is an ambitious idea. I am not sure that we
    > should implement it within this thread and feature.
    > Maybe we should create a separate thread for it and as a
    > justification, refer to parallel autovacuum?
    >
    > -----
    > Thanks everybody for feedback! I attach a v4 patch to this letter.
    > Main features :
    > 1) 'parallel_autovacuum_workers' reloption - integer value, that sets
    > the maximum number of parallel a/v workers that can be taken from
    > bgworkers pool in order to process this table.
    > 2) 'max_parallel_autovacuum_workers' - GUC variable, that sets the
    > maximum total number of parallel a/v workers, that can be taken from
    > bgworkers pool.
    > 3) Parallel autovacuum does not try to use thresholds like
     > NUM_INDEXES_PER_PARALLEL_WORKER and AV_PARALLEL_DEADTUP_THRESHOLD.
    > 4) Parallel autovacuum now can report statistics like "planned vs. launched".
    > 5) For now I got rid of the 'reserving' idea, so now autovacuum
    > leaders are competing with everyone for parallel workers from the
    > bgworkers pool.
    >
    > What do you think about this implementation?
    >
    
    I think it basically makes sense to me. A few comments:
    
    ---
    The patch implements max_parallel_autovacuum_workers as a
    PGC_POSTMASTER parameter but can we make it PGC_SIGHUP? I think we
    don't necessarily need to make it a PGC_POSTMATER since it actually
    doesn't affect how much shared memory we need to allocate.
    
    ---
    I think it's better to have the prefix "autovacuum" for the new GUC
    parameter for better consistency with other autovacuum-related GUC
    parameters.
    
    ---
     #include "storage/spin.h"
    @@ -514,6 +515,11 @@ ReinitializeParallelDSM(ParallelContext *pcxt)
        {
            WaitForParallelWorkersToFinish(pcxt);
            WaitForParallelWorkersToExit(pcxt);
    +
    +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    +       if (AmAutoVacuumWorkerProcess())
    +           ParallelAutoVacuumReleaseWorkers(pcxt->nworkers_launched);
    +
            pcxt->nworkers_launched = 0;
            if (pcxt->known_attached_workers)
            {
    @@ -1002,6 +1008,11 @@ DestroyParallelContext(ParallelContext *pcxt)
         */
        HOLD_INTERRUPTS();
        WaitForParallelWorkersToExit(pcxt);
    +
    +   /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    +   if (AmAutoVacuumWorkerProcess())
    +       ParallelAutoVacuumReleaseWorkers(pcxt->nworkers_launched);
    +
        RESUME_INTERRUPTS();
    
    I think that it's better to release workers in vacuumparallel.c rather
    than parallel.c.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  30. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-06-18T08:03:10Z

    Hi,
    
    On Wed, Jun 18, 2025 at 5:37 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Sun, May 25, 2025 at 10:22 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > Thanks everybody for feedback! I attach a v4 patch to this letter.
    > > Main features :
    > > 1) 'parallel_autovacuum_workers' reloption - integer value, that sets
    > > the maximum number of parallel a/v workers that can be taken from
    > > bgworkers pool in order to process this table.
    > > 2) 'max_parallel_autovacuum_workers' - GUC variable, that sets the
    > > maximum total number of parallel a/v workers, that can be taken from
    > > bgworkers pool.
    > > 3) Parallel autovacuum does not try to use thresholds like
    >  > NUM_INDEXES_PER_PARALLEL_WORKER and AV_PARALLEL_DEADTUP_THRESHOLD.
    > > 4) Parallel autovacuum now can report statistics like "planned vs. launched".
    > > 5) For now I got rid of the 'reserving' idea, so now autovacuum
    > > leaders are competing with everyone for parallel workers from the
    > > bgworkers pool.
    > >
    > > What do you think about this implementation?
    > >
    >
    > I think it basically makes sense to me. A few comments:
    >
    > ---
    > The patch implements max_parallel_autovacuum_workers as a
    > PGC_POSTMASTER parameter but can we make it PGC_SIGHUP? I think we
    > don't necessarily need to make it a PGC_POSTMATER since it actually
    > doesn't affect how much shared memory we need to allocate.
    >
    
    Yep, there's nothing stopping us from doing that. This is a usable
    feature, I'll implement it in the v5 patch.
    
    > ---
    > I think it's better to have the prefix "autovacuum" for the new GUC
    > parameter for better consistency with other autovacuum-related GUC
    > parameters.
    >
    > ---
    >  #include "storage/spin.h"
    > @@ -514,6 +515,11 @@ ReinitializeParallelDSM(ParallelContext *pcxt)
    >     {
    >         WaitForParallelWorkersToFinish(pcxt);
    >         WaitForParallelWorkersToExit(pcxt);
    > +
    > +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +       if (AmAutoVacuumWorkerProcess())
    > +           ParallelAutoVacuumReleaseWorkers(pcxt->nworkers_launched);
    > +
    >         pcxt->nworkers_launched = 0;
    >         if (pcxt->known_attached_workers)
    >         {
    > @@ -1002,6 +1008,11 @@ DestroyParallelContext(ParallelContext *pcxt)
    >      */
    >     HOLD_INTERRUPTS();
    >     WaitForParallelWorkersToExit(pcxt);
    > +
    > +   /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +   if (AmAutoVacuumWorkerProcess())
    > +       ParallelAutoVacuumReleaseWorkers(pcxt->nworkers_launched);
    > +
    >     RESUME_INTERRUPTS();
    >
    > I think that it's better to release workers in vacuumparallel.c rather
    > than parallel.c.
    >
    
    Agree with both comments.
    
    Thanks for the review! Please, see v5 patch :
    1) GUC variable and field in autovacuum shmem are renamed
    2) ParallelAutoVacuumReleaseWorkers call moved from parallel.c to
    vacuumparallel.c
    3) max_parallel_autovacuum_workers is now PGC_SIGHUP parameter
    4) Fix little bug (ParallelAutoVacuumReleaseWorkers in autovacuum.c:735)
    
    --
    Best regards,
    Daniil Davydov
    
  31. Re: POC: Parallel processing of indexes in autovacuum

    Matheus Alcantara <matheusssilv97@gmail.com> — 2025-07-04T14:21:52Z

    On Wed Jun 18, 2025 at 5:03 AM -03, Daniil Davydov wrote:
    >
    > Thanks for the review! Please, see v5 patch :
    > 1) GUC variable and field in autovacuum shmem are renamed
    > 2) ParallelAutoVacuumReleaseWorkers call moved from parallel.c to
    > vacuumparallel.c
    > 3) max_parallel_autovacuum_workers is now PGC_SIGHUP parameter
    > 4) Fix little bug (ParallelAutoVacuumReleaseWorkers in autovacuum.c:735)
    >
    
    Thanks for the new version!
    
    The "autovacuum_max_parallel_workers" declared on guc_tables.c mention
    that is capped by "max_worker_process":
    +	{
    +		{"autovacuum_max_parallel_workers", PGC_SIGHUP, VACUUM_AUTOVACUUM,
    +			gettext_noop("Maximum number of parallel autovacuum workers, that can be taken from bgworkers pool."),
    +			gettext_noop("This parameter is capped by \"max_worker_processes\" (not by \"autovacuum_max_workers\"!)."),
    +		},
    +		&autovacuum_max_parallel_workers,
    +		0, 0, MAX_BACKENDS,
    +		check_autovacuum_max_parallel_workers, NULL, NULL
    +	},
    
    
    But the postgresql.conf.sample say that it is limited by
    max_parallel_workers:
    +#autovacuum_max_parallel_workers = 0	# disabled by default and limited by max_parallel_workers
    
    IIUC the code, it cap by "max_worker_process", but Masahiko has mention
    on [1] that it should be capped by max_parallel_workers.
    
    ---
    
    We actually capping the autovacuum_max_parallel_workers by
    max_worker_process-1, so we can't have 10 max_worker_process and 10
    autovacuum_max_parallel_workers. Is that correct?
    +bool
    +check_autovacuum_max_parallel_workers(int *newval, void **extra,
    +									  GucSource source)
    +{
    +	if (*newval >= max_worker_processes)
    +		return false;
    +	return true;
    +}
    
    ---
    
    Locking unnecessary the AutovacuumLock if none if the if's is true can
    cause some performance issue here? I don't think that this would be a
    serious problem because this code will only be called if the
    configuration file is changed during the autovacuum execution right? But
    I could be wrong, so just sharing my thoughts on this (still learning
    about [auto]vacuum code).
    
    +
    +/*
    + * Make sure that number of available parallel workers corresponds to the
    + * autovacuum_max_parallel_workers parameter (after it was changed).
    + */
    +static void
    +check_parallel_av_gucs(int prev_max_parallel_workers)
    +{
    +	LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    +
    +	if (AutoVacuumShmem->av_available_parallel_workers >
    +		autovacuum_max_parallel_workers)
    +	{
    +		Assert(prev_max_parallel_workers > autovacuum_max_parallel_workers);
    +
    
    Typo on "exeed"
    
    +		/*
    +		 * Number of available workers must not exeed limit.
    +		 *
    +		 * Note, that if some parallel autovacuum workers are running at this
    +		 * moment, available workers number will not exeed limit after releasing
    +		 * them (see ParallelAutoVacuumReleaseWorkers).
    +		*/
    
    ---
    
    I'm not seeing an usage of this macro?
    +/*
    + * RelationGetParallelAutovacuumWorkers
    + *		Returns the relation's parallel_autovacuum_workers reloption setting.
    + *		Note multiple eval of argument!
    + */
    +#define RelationGetParallelAutovacuumWorkers(relation, defaultpw) \
    +	((relation)->rd_options ? \
    +	 ((StdRdOptions *) (relation)->rd_options)->autovacuum.parallel_autovacuum_workers : \
    +	 (defaultpw))
    +
    
    ---
    
    Also pgindent is needed on some files.
    
    ---
    
    I've made some tests and I can confirm that is working correctly for
    what I can see. I think that would be to start include the documentation
    changes, what do you think?
    
    [1] https://www.postgresql.org/message-id/CAD21AoAxTkpkLtJDgrH9dXg_h%2ByzOZpOZj3B-4FjW1Mr4qEdbQ%40mail.gmail.com
    
    --
    Matheus Alcantara
    
    
    
    
    
  32. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-07-06T08:00:32Z

    Hi,
    
    On Fri, Jul 4, 2025 at 9:21 PM Matheus Alcantara
    <matheusssilv97@gmail.com> wrote:
    >
    > The "autovacuum_max_parallel_workers" declared on guc_tables.c mention
    > that is capped by "max_worker_process":
    > +       {
    > +               {"autovacuum_max_parallel_workers", PGC_SIGHUP, VACUUM_AUTOVACUUM,
    > +                       gettext_noop("Maximum number of parallel autovacuum workers, that can be taken from bgworkers pool."),
    > +                       gettext_noop("This parameter is capped by \"max_worker_processes\" (not by \"autovacuum_max_workers\"!)."),
    > +               },
    > +               &autovacuum_max_parallel_workers,
    > +               0, 0, MAX_BACKENDS,
    > +               check_autovacuum_max_parallel_workers, NULL, NULL
    > +       },
    >
    > IIUC the code, it cap by "max_worker_process", but Masahiko has mention
    > on [1] that it should be capped by max_parallel_workers.
    >
    
    Thanks for looking into it!
    
    To be honest, I don't think that this parameter should be explicitly
    capped at all.
    Other parallel operations (for example parallel index build or VACUUM
    PARALLEL) just request as many workers as they want without looking at
    'max_parallel_workers'.
    And they will not complain, if not all requested workers were launched.
    
    Thus, even if 'autovacuum_max_parallel_workers' is higher than
    'max_parallel_workers' the worst that can happen is that not all
    requested workers will be running (which is a common situation).
    Users can handle it by looking for logs like "planned vs. launched"
    and increasing 'max_parallel_workers' if needed.
    
    On the other hand, obviously it doesn't make sense to request more
    workers than 'max_worker_processes' (moreover, this parameter cannot
    be changed as easily as 'max_parallel_workers').
    
    I will keep the 'max_worker_processes' limit, so autovacuum will not
    waste time initializing a parallel context if there is no chance that
    the request will succeed.
    But it's worth remembering that actually the
    'autovacuum_max_parallel_workers' parameter will always be implicitly
    capped by 'max_parallel_workers'.
    
    What do you think about it?
    
    > But the postgresql.conf.sample say that it is limited by
    > max_parallel_workers:
    > +#autovacuum_max_parallel_workers = 0   # disabled by default and limited by max_parallel_workers
    
    Good catch, I'll fix it.
    
    > ---
    >
    > We actually capping the autovacuum_max_parallel_workers by
    > max_worker_process-1, so we can't have 10 max_worker_process and 10
    > autovacuum_max_parallel_workers. Is that correct?
    
    Yep. The explanation can be found just above in this letter.
    
    > ---
    >
    > Locking unnecessary the AutovacuumLock if none if the if's is true can
    > cause some performance issue here? I don't think that this would be a
    > serious problem because this code will only be called if the
    > configuration file is changed during the autovacuum execution right? But
    > I could be wrong, so just sharing my thoughts on this (still learning
    > about [auto]vacuum code).
    >
    > +
    > +/*
    > + * Make sure that number of available parallel workers corresponds to the
    > + * autovacuum_max_parallel_workers parameter (after it was changed).
    > + */
    > +static void
    > +check_parallel_av_gucs(int prev_max_parallel_workers)
    > +{
    > +       LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    > +
    > +       if (AutoVacuumShmem->av_available_parallel_workers >
    > +               autovacuum_max_parallel_workers)
    > +       {
    > +               Assert(prev_max_parallel_workers > autovacuum_max_parallel_workers);
    > +
    >
    
    This function may be called by a/v launcher when we already have some
    a/v workers running.
    A/v workers can change the
    AutoVacuumShmem->av_available_parallel_workers value, so I think we
    should acquire appropriate lock before reading it.
    
    > Typo on "exeed"
    >
    > +               /*
    > +                * Number of available workers must not exeed limit.
    > +                *
    > +                * Note, that if some parallel autovacuum workers are running at this
    > +                * moment, available workers number will not exeed limit after releasing
    > +                * them (see ParallelAutoVacuumReleaseWorkers).
    > +               */
    
    Oops. I'll fix it.
    
    > ---
    >
    > I'm not seeing an usage of this macro?
    > +/*
    > + * RelationGetParallelAutovacuumWorkers
    > + *             Returns the relation's parallel_autovacuum_workers reloption setting.
    > + *             Note multiple eval of argument!
    > + */
    > +#define RelationGetParallelAutovacuumWorkers(relation, defaultpw) \
    > +       ((relation)->rd_options ? \
    > +        ((StdRdOptions *) (relation)->rd_options)->autovacuum.parallel_autovacuum_workers : \
    > +        (defaultpw))
    > +
    >
    
    Yes, this is the relic of a past implementation. I'll delete this macro.
    
    >
    > I've made some tests and I can confirm that is working correctly for
    > what I can see. I think that would be to start include the documentation
    > changes, what do you think?
    >
    
    It sounds tempting :)
    But perhaps first we should agree on the limitation of the
    'autovacuum_max_parallel_workers' parameter.
    
    Please, see v6 patches :
    1) Fixed typos in autovacuum.c and postgresql.conf.sample
    2) Removed unused macro 'RelationGetParallelAutovacuumWorkers'
    
    --
    Best regards,
    Daniil Davydov
    
  33. Re: POC: Parallel processing of indexes in autovacuum

    Matheus Alcantara <matheusssilv97@gmail.com> — 2025-07-08T15:20:54Z

    On Sun Jul 6, 2025 at 5:00 AM -03, Daniil Davydov wrote:
    >> The "autovacuum_max_parallel_workers" declared on guc_tables.c mention
    >> that is capped by "max_worker_process":
    >> +       {
    >> +               {"autovacuum_max_parallel_workers", PGC_SIGHUP, VACUUM_AUTOVACUUM,
    >> +                       gettext_noop("Maximum number of parallel autovacuum workers, that can be taken from bgworkers pool."),
    >> +                       gettext_noop("This parameter is capped by \"max_worker_processes\" (not by \"autovacuum_max_workers\"!)."),
    >> +               },
    >> +               &autovacuum_max_parallel_workers,
    >> +               0, 0, MAX_BACKENDS,
    >> +               check_autovacuum_max_parallel_workers, NULL, NULL
    >> +       },
    >>
    >> IIUC the code, it cap by "max_worker_process", but Masahiko has mention
    >> on [1] that it should be capped by max_parallel_workers.
    > To be honest, I don't think that this parameter should be explicitly
    > capped at all.
    > Other parallel operations (for example parallel index build or VACUUM
    > PARALLEL) just request as many workers as they want without looking at
    > 'max_parallel_workers'.
    > And they will not complain, if not all requested workers were launched.
    >
    > Thus, even if 'autovacuum_max_parallel_workers' is higher than
    > 'max_parallel_workers' the worst that can happen is that not all
    > requested workers will be running (which is a common situation).
    > Users can handle it by looking for logs like "planned vs. launched"
    > and increasing 'max_parallel_workers' if needed.
    >
    > On the other hand, obviously it doesn't make sense to request more
    > workers than 'max_worker_processes' (moreover, this parameter cannot
    > be changed as easily as 'max_parallel_workers').
    >
    > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > waste time initializing a parallel context if there is no chance that
    > the request will succeed.
    > But it's worth remembering that actually the
    > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > capped by 'max_parallel_workers'.
    >
    > What do you think about it?
    > 
    
    It make sense to me. The main benefit that I see on capping
    autovacuum_max_parallel_workers parameter is that users will see
    "invalid value for parameter "autovacuum_max_parallel_workers"" error on
    logs instead of need to search for "planned vs. launched", which can be
    trick if log_min_messages is not set to at least the info level (the
    default warning level will not show this log message). If we decide to
    not cap this on code I think that at least would be good to mention this
    on documentation.
    
    >>
    >> I've made some tests and I can confirm that is working correctly for
    >> what I can see. I think that would be to start include the documentation
    >> changes, what do you think?
    >>
    >
    > It sounds tempting :)
    > But perhaps first we should agree on the limitation of the
    > 'autovacuum_max_parallel_workers' parameter.
    >
    
    Agree
    
    > Please, see v6 patches :
    > 1) Fixed typos in autovacuum.c and postgresql.conf.sample
    > 2) Removed unused macro 'RelationGetParallelAutovacuumWorkers'
    >
    
    Thanks!
    
    --
    Matheus Alcantara
    
    
    
    
    
  34. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-07-09T05:26:24Z

    Hi,
    
    On Tue, Jul 8, 2025 at 10:20 PM Matheus Alcantara
    <matheusssilv97@gmail.com> wrote:
    >
    > On Sun Jul 6, 2025 at 5:00 AM -03, Daniil Davydov wrote:
    > > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > > waste time initializing a parallel context if there is no chance that
    > > the request will succeed.
    > > But it's worth remembering that actually the
    > > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > > capped by 'max_parallel_workers'.
    > >
    > > What do you think about it?
    > >
    >
    > It make sense to me. The main benefit that I see on capping
    > autovacuum_max_parallel_workers parameter is that users will see
    > "invalid value for parameter "autovacuum_max_parallel_workers"" error on
    > logs instead of need to search for "planned vs. launched", which can be
    > trick if log_min_messages is not set to at least the info level (the
    > default warning level will not show this log message).
    >
    
    I think I can refer to (for example) 'max_parallel_workers_per_gather'
    parameter, which allows
    setting values higher than 'max_parallel_workers' without throwing an
    error or warning.
    
    'autovacuum_max_parallel_workers' will behave the same way.
    
    > If we decide to not cap this on code I think that at least would be good to mention this
    > on documentation.
    
    Sure, it is worth noticing in documentation.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  35. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-07-14T07:09:39Z

    On Sun, Jul 6, 2025 at 5:00 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Fri, Jul 4, 2025 at 9:21 PM Matheus Alcantara
    > <matheusssilv97@gmail.com> wrote:
    > >
    > > The "autovacuum_max_parallel_workers" declared on guc_tables.c mention
    > > that is capped by "max_worker_process":
    > > +       {
    > > +               {"autovacuum_max_parallel_workers", PGC_SIGHUP, VACUUM_AUTOVACUUM,
    > > +                       gettext_noop("Maximum number of parallel autovacuum workers, that can be taken from bgworkers pool."),
    > > +                       gettext_noop("This parameter is capped by \"max_worker_processes\" (not by \"autovacuum_max_workers\"!)."),
    > > +               },
    > > +               &autovacuum_max_parallel_workers,
    > > +               0, 0, MAX_BACKENDS,
    > > +               check_autovacuum_max_parallel_workers, NULL, NULL
    > > +       },
    > >
    > > IIUC the code, it cap by "max_worker_process", but Masahiko has mention
    > > on [1] that it should be capped by max_parallel_workers.
    > >
    >
    > Thanks for looking into it!
    >
    > To be honest, I don't think that this parameter should be explicitly
    > capped at all.
    > Other parallel operations (for example parallel index build or VACUUM
    > PARALLEL) just request as many workers as they want without looking at
    > 'max_parallel_workers'.
    > And they will not complain, if not all requested workers were launched.
    >
    > Thus, even if 'autovacuum_max_parallel_workers' is higher than
    > 'max_parallel_workers' the worst that can happen is that not all
    > requested workers will be running (which is a common situation).
    > Users can handle it by looking for logs like "planned vs. launched"
    > and increasing 'max_parallel_workers' if needed.
    >
    > On the other hand, obviously it doesn't make sense to request more
    > workers than 'max_worker_processes' (moreover, this parameter cannot
    > be changed as easily as 'max_parallel_workers').
    >
    > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > waste time initializing a parallel context if there is no chance that
    > the request will succeed.
    > But it's worth remembering that actually the
    > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > capped by 'max_parallel_workers'.
    >
    > What do you think about it?
    >
    > > But the postgresql.conf.sample say that it is limited by
    > > max_parallel_workers:
    > > +#autovacuum_max_parallel_workers = 0   # disabled by default and limited by max_parallel_workers
    >
    > Good catch, I'll fix it.
    >
    > > ---
    > >
    > > We actually capping the autovacuum_max_parallel_workers by
    > > max_worker_process-1, so we can't have 10 max_worker_process and 10
    > > autovacuum_max_parallel_workers. Is that correct?
    >
    > Yep. The explanation can be found just above in this letter.
    >
    > > ---
    > >
    > > Locking unnecessary the AutovacuumLock if none if the if's is true can
    > > cause some performance issue here? I don't think that this would be a
    > > serious problem because this code will only be called if the
    > > configuration file is changed during the autovacuum execution right? But
    > > I could be wrong, so just sharing my thoughts on this (still learning
    > > about [auto]vacuum code).
    > >
    > > +
    > > +/*
    > > + * Make sure that number of available parallel workers corresponds to the
    > > + * autovacuum_max_parallel_workers parameter (after it was changed).
    > > + */
    > > +static void
    > > +check_parallel_av_gucs(int prev_max_parallel_workers)
    > > +{
    > > +       LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    > > +
    > > +       if (AutoVacuumShmem->av_available_parallel_workers >
    > > +               autovacuum_max_parallel_workers)
    > > +       {
    > > +               Assert(prev_max_parallel_workers > autovacuum_max_parallel_workers);
    > > +
    > >
    >
    > This function may be called by a/v launcher when we already have some
    > a/v workers running.
    > A/v workers can change the
    > AutoVacuumShmem->av_available_parallel_workers value, so I think we
    > should acquire appropriate lock before reading it.
    >
    > > Typo on "exeed"
    > >
    > > +               /*
    > > +                * Number of available workers must not exeed limit.
    > > +                *
    > > +                * Note, that if some parallel autovacuum workers are running at this
    > > +                * moment, available workers number will not exeed limit after releasing
    > > +                * them (see ParallelAutoVacuumReleaseWorkers).
    > > +               */
    >
    > Oops. I'll fix it.
    >
    > > ---
    > >
    > > I'm not seeing an usage of this macro?
    > > +/*
    > > + * RelationGetParallelAutovacuumWorkers
    > > + *             Returns the relation's parallel_autovacuum_workers reloption setting.
    > > + *             Note multiple eval of argument!
    > > + */
    > > +#define RelationGetParallelAutovacuumWorkers(relation, defaultpw) \
    > > +       ((relation)->rd_options ? \
    > > +        ((StdRdOptions *) (relation)->rd_options)->autovacuum.parallel_autovacuum_workers : \
    > > +        (defaultpw))
    > > +
    > >
    >
    > Yes, this is the relic of a past implementation. I'll delete this macro.
    >
    > >
    > > I've made some tests and I can confirm that is working correctly for
    > > what I can see. I think that would be to start include the documentation
    > > changes, what do you think?
    > >
    >
    > It sounds tempting :)
    > But perhaps first we should agree on the limitation of the
    > 'autovacuum_max_parallel_workers' parameter.
    >
    > Please, see v6 patches :
    > 1) Fixed typos in autovacuum.c and postgresql.conf.sample
    > 2) Removed unused macro 'RelationGetParallelAutovacuumWorkers'
    >
    
    Thank you for updating the patch! Here are some review comments:
    
    ---
    -   shared->maintenance_work_mem_worker =
    -       (nindexes_mwm > 0) ?
    -       maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
    -       maintenance_work_mem;
    +
    +   if (AmAutoVacuumWorkerProcess())
    +       shared->maintenance_work_mem_worker =
    +           (nindexes_mwm > 0) ?
    +           autovacuum_work_mem / Min(parallel_workers, nindexes_mwm) :
    +           autovacuum_work_mem;
    +   else
    +       shared->maintenance_work_mem_worker =
    +           (nindexes_mwm > 0) ?
    +           maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
    +           maintenance_work_mem;
    
    Since we have a similar code in dead_items_alloc() I think it's better
    to follow it:
    
        int         vac_work_mem = AmAutoVacuumWorkerProcess() &&
            autovacuum_work_mem != -1 ?
            autovacuum_work_mem : maintenance_work_mem;
    
    That is, we calculate vac_work_mem first and then calculate
    shared->maintenance_work_mem_worker. I think it's more straightforward
    as the formula of maintenance_work_mem_worker is the same whereas the
    amount of memory used for vacuum and autovacuum varies.
    
    ---
    +   nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember this value */
        DestroyParallelContext(pvs->pcxt);
    +
    +   /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    +   if (AmAutoVacuumWorkerProcess())
    +       ParallelAutoVacuumReleaseWorkers(nlaunched_workers);
    +
    
    Why don't we release workers before destroying the parallel context?
    
    ---
    @@ -558,7 +576,9 @@ parallel_vacuum_compute_workers(Relation *indrels,
    int nindexes, int nrequested,
         * We don't allow performing parallel operation in standalone backend or
         * when parallelism is disabled.
         */
    -   if (!IsUnderPostmaster || max_parallel_maintenance_workers == 0)
    +   if (!IsUnderPostmaster ||
    +       (autovacuum_max_parallel_workers == 0 && AmAutoVacuumWorkerProcess()) ||
    +       (max_parallel_maintenance_workers == 0 && !AmAutoVacuumWorkerProcess()))
            return 0;
    
        /*
    @@ -597,15 +617,17 @@ parallel_vacuum_compute_workers(Relation
    *indrels, int nindexes, int nrequested,
        parallel_workers = (nrequested > 0) ?
            Min(nrequested, nindexes_parallel) : nindexes_parallel;
    
    -   /* Cap by max_parallel_maintenance_workers */
    -   parallel_workers = Min(parallel_workers, max_parallel_maintenance_workers);
    +   /* Cap by GUC variable */
    +   parallel_workers = AmAutoVacuumWorkerProcess() ?
    +       Min(parallel_workers, autovacuum_max_parallel_workers) :
    +       Min(parallel_workers, max_parallel_maintenance_workers);
    
        return parallel_workers;
    
    How about calculating the maximum number of workers once and using it
    in the above both places?
    
    ---
    +   /* Check how many workers can provide autovacuum. */
    +   if (AmAutoVacuumWorkerProcess() && nworkers > 0)
    +       nworkers = ParallelAutoVacuumReserveWorkers(nworkers);
    +
    
    I think it's better to move this code to right after setting "nworkers
    = Min(nworkers, pvs->pcxt->nworkers);" as it's a more related code.
    
    The comment needs to be updated as it doesn't match what the function
    actually does (i.e. reserving the workers).
    
    ---
            /* Reinitialize parallel context to relaunch parallel workers */
            if (num_index_scans > 0)
    +       {
                ReinitializeParallelDSM(pvs->pcxt);
    
    +           /*
    +            * Release all launched (i.e. reserved) parallel autovacuum
    +            * workers.
    +            */
    +           if (AmAutoVacuumWorkerProcess())
    +               ParallelAutoVacuumReleaseWorkers(pvs->pcxt->nworkers_launched);
    +       }
    
    Why do we need to release all workers here? If there is a reason, we
    should mention it as a comment.
    
    ---
    @@ -706,16 +751,16 @@
    parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int
    num_index_scan
    
            if (vacuum)
                ereport(pvs->shared->elevel,
    -                   (errmsg(ngettext("launched %d parallel vacuum
    worker for index vacuuming (planned: %d)",
    -                                    "launched %d parallel vacuum
    workers for index vacuuming (planned: %d)",
    +                   (errmsg(ngettext("launched %d parallel %svacuum
    worker for index vacuuming (planned: %d)",
    +                                    "launched %d parallel %svacuum
    workers for index vacuuming (planned: %d)",
                                         pvs->pcxt->nworkers_launched),
    -                           pvs->pcxt->nworkers_launched, nworkers)));
    +                           pvs->pcxt->nworkers_launched,
    AmAutoVacuumWorkerProcess() ? "auto" : "", nworkers)));
    
    The "%svacuum" part doesn't work in terms of translation. We need to
    construct the whole sentence instead. But do we need this log message
    change in the first place? IIUC autovacuums write logs only when the
    execution time exceed the log_autovacuum_min_duration (or its
    reloption). The patch unconditionally sets LOG level for autovacuums
    but I'm not sure it's consistent with other autovacuum logging
    behavior:
    
    +   int         elevel = AmAutoVacuumWorkerProcess() ||
    +       vacrel->verbose ?
    +       INFO : DEBUG2;
    
    ---
    - *   Support routines for parallel vacuum execution.
    + *   Support routines for parallel [auto]vacuum execution.
    
    The patch includes the change of "vacuum" -> "[auto]vacuum" in many
    places. While I think we need to mention that vacuumparallel.c
    supports autovacuums I'm not sure we really need all of them. If we
    accept this style, we would require for all subsequent changes to
    follow it, which could increase maintenance costs.
    
    ---
    @@ -299,6 +301,7 @@ typedef struct
        WorkerInfo  av_startingWorker;
        AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
        pg_atomic_uint32 av_nworkersForBalance;
    +   uint32 av_available_parallel_workers;
    
    Other field names seem to have consistent naming rules; 'av_' prefix
    followed by name in camel case. So how about renaming it to
    av_freeParallelWorkers or something along those lines?
    
    ---
    +int
    +ParallelAutoVacuumReserveWorkers(int nworkers)
    +{
    
    Other exposed functions have "AutoVacuum" prefix, so how about
    renaming it to AutoVacuumReserveParallelWorkers() or something along
    those lines?
    
    ---
    +   if (AutoVacuumShmem->av_available_parallel_workers < nworkers)
    +   {
    +       /* Provide as many workers as we can. */
    +       can_launch = AutoVacuumShmem->av_available_parallel_workers;
    +       AutoVacuumShmem->av_available_parallel_workers = 0;
    +   }
    +   else
    +   {
    +       /* OK, we can provide all requested workers. */
    +       can_launch = nworkers;
    +       AutoVacuumShmem->av_available_parallel_workers -= nworkers;
    +   }
    
    Can we simplify this logic as follows?
    
     can_launch = Min(AutoVacuumShmem->av_available_parallel_workers, nworkers);
     AutoVacuumShmem->av_available_parallel_workers -= can_launch;
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  36. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-07-14T10:49:10Z

    Hi,
    
    On Mon, Jul 14, 2025 at 2:10 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > ---
    > -   shared->maintenance_work_mem_worker =
    > -       (nindexes_mwm > 0) ?
    > -       maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
    > -       maintenance_work_mem;
    > +
    > +   if (AmAutoVacuumWorkerProcess())
    > +       shared->maintenance_work_mem_worker =
    > +           (nindexes_mwm > 0) ?
    > +           autovacuum_work_mem / Min(parallel_workers, nindexes_mwm) :
    > +           autovacuum_work_mem;
    > +   else
    > +       shared->maintenance_work_mem_worker =
    > +           (nindexes_mwm > 0) ?
    > +           maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
    > +           maintenance_work_mem;
    >
    > Since we have a similar code in dead_items_alloc() I think it's better
    > to follow it:
    >
    >     int         vac_work_mem = AmAutoVacuumWorkerProcess() &&
    >         autovacuum_work_mem != -1 ?
    >         autovacuum_work_mem : maintenance_work_mem;
    >
    > That is, we calculate vac_work_mem first and then calculate
    > shared->maintenance_work_mem_worker. I think it's more straightforward
    > as the formula of maintenance_work_mem_worker is the same whereas the
    > amount of memory used for vacuum and autovacuum varies.
    >
    
    I was confused by the fact that initially maintenance_work_mem was used
    for calculations, not vac_work_mem. I agree that we should better use
    already calculated vac_work_mem value.
    
    > ---
    > +   nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember this value */
    >     DestroyParallelContext(pvs->pcxt);
    > +
    > +   /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +   if (AmAutoVacuumWorkerProcess())
    > +       ParallelAutoVacuumReleaseWorkers(nlaunched_workers);
    > +
    >
    > Why don't we release workers before destroying the parallel context?
    >
    
    Destroying parallel context includes waiting for all workers to exit (after
    which, other operations can use them).
    If we first call ParallelAutoVacuumReleaseWorkers, some operation can
    reasonably request all released workers. But this request can fail,
    because there is no guarantee that workers managed to finish.
    
    Actually, there's nothing wrong with that, but I think releasing workers
    only after finishing work is a more logical approach.
    
    > ---
    > @@ -558,7 +576,9 @@ parallel_vacuum_compute_workers(Relation *indrels,
    > int nindexes, int nrequested,
    >      * We don't allow performing parallel operation in standalone backend or
    >      * when parallelism is disabled.
    >      */
    > -   if (!IsUnderPostmaster || max_parallel_maintenance_workers == 0)
    > +   if (!IsUnderPostmaster ||
    > +       (autovacuum_max_parallel_workers == 0 && AmAutoVacuumWorkerProcess()) ||
    > +       (max_parallel_maintenance_workers == 0 && !AmAutoVacuumWorkerProcess()))
    >         return 0;
    >
    >     /*
    > @@ -597,15 +617,17 @@ parallel_vacuum_compute_workers(Relation
    > *indrels, int nindexes, int nrequested,
    >     parallel_workers = (nrequested > 0) ?
    >         Min(nrequested, nindexes_parallel) : nindexes_parallel;
    >
    > -   /* Cap by max_parallel_maintenance_workers */
    > -   parallel_workers = Min(parallel_workers, max_parallel_maintenance_workers);
    > +   /* Cap by GUC variable */
    > +   parallel_workers = AmAutoVacuumWorkerProcess() ?
    > +       Min(parallel_workers, autovacuum_max_parallel_workers) :
    > +       Min(parallel_workers, max_parallel_maintenance_workers);
    >
    >     return parallel_workers;
    >
    > How about calculating the maximum number of workers once and using it
    > in the above both places?
    >
    
    Agree. Good idea.
    
    > ---
    > +   /* Check how many workers can provide autovacuum. */
    > +   if (AmAutoVacuumWorkerProcess() && nworkers > 0)
    > +       nworkers = ParallelAutoVacuumReserveWorkers(nworkers);
    > +
    >
    > I think it's better to move this code to right after setting "nworkers
    > = Min(nworkers, pvs->pcxt->nworkers);" as it's a more related code.
    >
    > The comment needs to be updated as it doesn't match what the function
    > actually does (i.e. reserving the workers).
    >
    
    You are right, I'll fix it.
    
    > ---
    >         /* Reinitialize parallel context to relaunch parallel workers */
    >         if (num_index_scans > 0)
    > +       {
    >             ReinitializeParallelDSM(pvs->pcxt);
    >
    > +           /*
    > +            * Release all launched (i.e. reserved) parallel autovacuum
    > +            * workers.
    > +            */
    > +           if (AmAutoVacuumWorkerProcess())
    > +               ParallelAutoVacuumReleaseWorkers(pvs->pcxt->nworkers_launched);
    > +       }
    >
    > Why do we need to release all workers here? If there is a reason, we
    > should mention it as a comment.
    >
    
    Hm, I guess it was left over from previous patch versions. Actually
    we don't need to release workers here, as we will try to launch them
    immediately. It is a bug, thank you for noticing it.
    
    > ---
    > @@ -706,16 +751,16 @@
    > parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int
    > num_index_scan
    >
    >         if (vacuum)
    >             ereport(pvs->shared->elevel,
    > -                   (errmsg(ngettext("launched %d parallel vacuum
    > worker for index vacuuming (planned: %d)",
    > -                                    "launched %d parallel vacuum
    > workers for index vacuuming (planned: %d)",
    > +                   (errmsg(ngettext("launched %d parallel %svacuum
    > worker for index vacuuming (planned: %d)",
    > +                                    "launched %d parallel %svacuum
    > workers for index vacuuming (planned: %d)",
    >                                      pvs->pcxt->nworkers_launched),
    > -                           pvs->pcxt->nworkers_launched, nworkers)));
    > +                           pvs->pcxt->nworkers_launched,
    > AmAutoVacuumWorkerProcess() ? "auto" : "", nworkers)));
    >
    > The "%svacuum" part doesn't work in terms of translation. We need to
    > construct the whole sentence instead.
    > But do we need this log message
    > change in the first place? IIUC autovacuums write logs only when the
    > execution time exceed the log_autovacuum_min_duration (or its
    > reloption). The patch unconditionally sets LOG level for autovacuums
    > but I'm not sure it's consistent with other autovacuum logging
    > behavior:
    >
    > +   int         elevel = AmAutoVacuumWorkerProcess() ||
    > +       vacrel->verbose ?
    > +       INFO : DEBUG2;
    >
    >
    
    This log level is used only "for messages about parallel workers launched".
    I think that such logs relate more to the parallel workers module than
    autovacuum itself. Moreover, if we emit log "planned vs. launched" each
    time, it will simplify the task of selecting the optimal value of
    'autovacuum_max_parallel_workers' parameter. What do you think?
    
    About "%svacuum" - I guess we need to clarify what exactly the workers
    were launched for. I'll add errhint to this log, but I don't know whether such
    approach is acceptable.
    
    
    > - *   Support routines for parallel vacuum execution.
    > + *   Support routines for parallel [auto]vacuum execution.
    >
    > The patch includes the change of "vacuum" -> "[auto]vacuum" in many
    > places. While I think we need to mention that vacuumparallel.c
    > supports autovacuums I'm not sure we really need all of them. If we
    > accept this style, we would require for all subsequent changes to
    > follow it, which could increase maintenance costs.
    >
    
    Agree. I'll leave a comment which says that vacuumparallel also supports
    parallel autovacuum. All other changes like "[auto]vacuum" will be deleted.
    
    > ---
    > @@ -299,6 +301,7 @@ typedef struct
    >     WorkerInfo  av_startingWorker;
    >     AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
    >     pg_atomic_uint32 av_nworkersForBalance;
    > +   uint32 av_available_parallel_workers;
    >
    > Other field names seem to have consistent naming rules; 'av_' prefix
    > followed by name in camel case. So how about renaming it to
    > av_freeParallelWorkers or something along those lines?
    >
    > ---
    > +int
    > +ParallelAutoVacuumReserveWorkers(int nworkers)
    > +{
    >
    > Other exposed functions have "AutoVacuum" prefix, so how about
    > renaming it to AutoVacuumReserveParallelWorkers() or something along
    > those lines?
    >
    
    Agreeing with both comments, I'll rename the structure field and functions.
    
    > ---
    > +   if (AutoVacuumShmem->av_available_parallel_workers < nworkers)
    > +   {
    > +       /* Provide as many workers as we can. */
    > +       can_launch = AutoVacuumShmem->av_available_parallel_workers;
    > +       AutoVacuumShmem->av_available_parallel_workers = 0;
    > +   }
    > +   else
    > +   {
    > +       /* OK, we can provide all requested workers. */
    > +       can_launch = nworkers;
    > +       AutoVacuumShmem->av_available_parallel_workers -= nworkers;
    > +   }
    >
    > Can we simplify this logic as follows?
    >
    >  can_launch = Min(AutoVacuumShmem->av_available_parallel_workers, nworkers);
    >  AutoVacuumShmem->av_available_parallel_workers -= can_launch;
    >
    
    Sure, I'll simplify it.
    
    ---
    Thank you very much for your comments! Please, see v7 patch :
    1) Rename few functions and variables + get rid of comments like
    "[auto]vacuum" in vacuumparallel.c
    2) Simplified logic in 'parallel_vacuum_init' and
    'AutoVacuumReserveParallelWorkers' functions
    3) Refactor and bug fix in 'parallel_vacuum_process_all_indexes' function
    4) Change "planned vs. launched" logging, so it can be translated
    5) Rebased on newest commit in master branch
    
    --
    Best regards,
    Daniil Davydov
    
  37. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-07-17T19:42:27Z

    On Mon, Jul 14, 2025 at 3:49 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    >
    > > ---
    > > +   nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember this value */
    > >     DestroyParallelContext(pvs->pcxt);
    > > +
    > > +   /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > > +   if (AmAutoVacuumWorkerProcess())
    > > +       ParallelAutoVacuumReleaseWorkers(nlaunched_workers);
    > > +
    > >
    > > Why don't we release workers before destroying the parallel context?
    > >
    >
    > Destroying parallel context includes waiting for all workers to exit (after
    > which, other operations can use them).
    > If we first call ParallelAutoVacuumReleaseWorkers, some operation can
    > reasonably request all released workers. But this request can fail,
    > because there is no guarantee that workers managed to finish.
    >
    > Actually, there's nothing wrong with that, but I think releasing workers
    > only after finishing work is a more logical approach.
    >
    
    
    
    > > ---
    > > @@ -706,16 +751,16 @@
    > > parallel_vacuum_process_all_indexes(ParallelVacuumState *pvs, int
    > > num_index_scan
    > >
    > >         if (vacuum)
    > >             ereport(pvs->shared->elevel,
    > > -                   (errmsg(ngettext("launched %d parallel vacuum
    > > worker for index vacuuming (planned: %d)",
    > > -                                    "launched %d parallel vacuum
    > > workers for index vacuuming (planned: %d)",
    > > +                   (errmsg(ngettext("launched %d parallel %svacuum
    > > worker for index vacuuming (planned: %d)",
    > > +                                    "launched %d parallel %svacuum
    > > workers for index vacuuming (planned: %d)",
    > >                                      pvs->pcxt->nworkers_launched),
    > > -                           pvs->pcxt->nworkers_launched, nworkers)));
    > > +                           pvs->pcxt->nworkers_launched,
    > > AmAutoVacuumWorkerProcess() ? "auto" : "", nworkers)));
    > >
    > > The "%svacuum" part doesn't work in terms of translation. We need to
    > > construct the whole sentence instead.
    > > But do we need this log message
    > > change in the first place? IIUC autovacuums write logs only when the
    > > execution time exceed the log_autovacuum_min_duration (or its
    > > reloption). The patch unconditionally sets LOG level for autovacuums
    > > but I'm not sure it's consistent with other autovacuum logging
    > > behavior:
    > >
    > > +   int         elevel = AmAutoVacuumWorkerProcess() ||
    > > +       vacrel->verbose ?
    > > +       INFO : DEBUG2;
    > >
    > >
    >
    > This log level is used only "for messages about parallel workers launched".
    > I think that such logs relate more to the parallel workers module than
    > autovacuum itself. Moreover, if we emit log "planned vs. launched" each
    > time, it will simplify the task of selecting the optimal value of
    > 'autovacuum_max_parallel_workers' parameter. What do you think?
    
    INFO level is normally not sent to the server log. And regarding
    autovacuums, they don't write any log mentioning it started. If we
    want to write planned vs. launched I think it's better to gather these
    statistics during execution and write it together with other existing
    logs.
    
    >
    > About "%svacuum" - I guess we need to clarify what exactly the workers
    > were launched for. I'll add errhint to this log, but I don't know whether such
    > approach is acceptable.
    
    I'm not sure errhint is an appropriate place. If we write such
    information together with other existing autovacuum logs as I
    suggested above, I think we don't need to add such information to this
    log message.
    
    I've reviewed v7 patch and here are some comments:
    
    +   {
    +       {
    +           "parallel_autovacuum_workers",
    +           "Maximum number of parallel autovacuum workers that can be
    taken from bgworkers pool for processing this table. "
    +           "If value is 0 then parallel degree will computed based on
    number of indexes.",
    +           RELOPT_KIND_HEAP,
    +           ShareUpdateExclusiveLock
    +       },
    +       -1, -1, 1024
    +   },
    
    Many autovacuum related reloptions have the prefix "autovacuum". So
    how about renaming it to autovacuum_parallel_worker (change
    check_parallel_av_gucs() name too accordingly)?
    
    ---
    +bool
    +check_autovacuum_max_parallel_workers(int *newval, void **extra,
    +                                     GucSource source)
    +{
    +   if (*newval >= max_worker_processes)
    +       return false;
    +   return true;
    +}
    
    I think we don't need to strictly check the
    autovacuum_max_parallel_workers value. Instead, we can accept any
    integer value but internally cap by max_worker_processes.
    
    ---
    +/*
    + * Make sure that number of available parallel workers corresponds to the
    + * autovacuum_max_parallel_workers parameter (after it was changed).
    + */
    +static void
    +check_parallel_av_gucs(int prev_max_parallel_workers)
    +{
    
    I think this function doesn't just check the value but does adjust the
    number of available workers, so how about
    adjust_free_parallel_workers() or something along these lines?
    
    ---
    +       /*
    +        * Number of available workers must not exceed limit.
    +        *
    +        * Note, that if some parallel autovacuum workers are running at this
    +        * moment, available workers number will not exceed limit after
    +        * releasing them (see ParallelAutoVacuumReleaseWorkers).
    +       */
    +       AutoVacuumShmem->av_freeParallelWorkers =
    +           autovacuum_max_parallel_workers;
    
    I think the comment refers to the following code in
    AutoVacuumReleaseParallelWorkers():
    
    +   /*
    +    * If autovacuum_max_parallel_workers variable was reduced during parallel
    +    * autovacuum execution, we must cap available workers number by its new
    +    * value.
    +    */
    +   if (AutoVacuumShmem->av_freeParallelWorkers >
    +       autovacuum_max_parallel_workers)
    +   {
    +       AutoVacuumShmem->av_freeParallelWorkers =
    +           autovacuum_max_parallel_workers;
    +   }
    
    After the autovacuum launchers decreases av_freeParallelWorkers, it's
    not guaranteed that the autovacuum worker already reflects the new
    value from the config file when executing the
    AutoVacuumReleaseParallelWorkers(), which leds to skips the above
    codes. For example, suppose that autovacuum_max_parallel_workers is 10
    and 3 parallel workers are running by one autovacuum worker (i.e.,
    av_freeParallelWorkers = 7 now), if the user changes
    autovacuum_max_parallel_workers to 5, the autovacuum launchers adjust
    av_freeParallelWorkers to 5. However, if the worker doesn't reload the
    config file and executes AutoVacuumReleaseParallelWorkers(), it
    increases av_freeParallelWorkers to 8 and skips the adjusting logic.
    I've not tested this scenarios so I might be missing something though.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  38. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-07-20T16:43:38Z

    Hi,
    
    On Fri, Jul 18, 2025 at 2:43 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Mon, Jul 14, 2025 at 3:49 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > This log level is used only "for messages about parallel workers launched".
    > > I think that such logs relate more to the parallel workers module than
    > > autovacuum itself. Moreover, if we emit log "planned vs. launched" each
    > > time, it will simplify the task of selecting the optimal value of
    > > 'autovacuum_max_parallel_workers' parameter. What do you think?
    >
    > INFO level is normally not sent to the server log. And regarding
    > autovacuums, they don't write any log mentioning it started. If we
    > want to write planned vs. launched I think it's better to gather these
    > statistics during execution and write it together with other existing
    > logs.
    >
    > >
    > > About "%svacuum" - I guess we need to clarify what exactly the workers
    > > were launched for. I'll add errhint to this log, but I don't know whether such
    > > approach is acceptable.
    >
    > I'm not sure errhint is an appropriate place. If we write such
    > information together with other existing autovacuum logs as I
    > suggested above, I think we don't need to add such information to this
    > log message.
    >
    
    I thought about it for some time and came up with this idea :
    1)
    When gathering such statistics, we need to take into account that users
    might not want autovacuum to log something. Thus, we should collect statistics
    in "higher" level that knows about log_min_duration.
    2)
    By analogy with the rest of the statistics, we can only accumulate a
    total number
    of planned and launched parallel workers. Alternatively, we could build an array
    (one element for each index scan) of "planned vs. launched". But it will make
    the code "dirty", and I don't sure that this will be useful.
    
    This may be a discussion point, so I will separate it to another .patch file.
    
    > I've reviewed v7 patch and here are some comments:
    >
    > +   {
    > +       {
    > +           "parallel_autovacuum_workers",
    > +           "Maximum number of parallel autovacuum workers that can be
    > taken from bgworkers pool for processing this table. "
    > +           "If value is 0 then parallel degree will computed based on
    > number of indexes.",
    > +           RELOPT_KIND_HEAP,
    > +           ShareUpdateExclusiveLock
    > +       },
    > +       -1, -1, 1024
    > +   },
    >
    > Many autovacuum related reloptions have the prefix "autovacuum". So
    > how about renaming it to autovacuum_parallel_worker (change
    > check_parallel_av_gucs() name too accordingly)?
    >
    
    I have no objections.
    
    > ---
    > +bool
    > +check_autovacuum_max_parallel_workers(int *newval, void **extra,
    > +                                     GucSource source)
    > +{
    > +   if (*newval >= max_worker_processes)
    > +       return false;
    > +   return true;
    > +}
    >
    > I think we don't need to strictly check the
    > autovacuum_max_parallel_workers value. Instead, we can accept any
    > integer value but internally cap by max_worker_processes.
    >
    
    I don't think that such a limitation is excessive, but I don't see similar
    behavior in other "max_parallel_..." GUCs, so I think we can get
    rid of it. I'll replace the "check hook" with an "assign hook", where
    autovacuum_max_parallel_workers will be limited.
    
    > ---
    > +/*
    > + * Make sure that number of available parallel workers corresponds to the
    > + * autovacuum_max_parallel_workers parameter (after it was changed).
    > + */
    > +static void
    > +check_parallel_av_gucs(int prev_max_parallel_workers)
    > +{
    >
    > I think this function doesn't just check the value but does adjust the
    > number of available workers, so how about
    > adjust_free_parallel_workers() or something along these lines?
    
    I agree, it's better this way.
    
    >
    > ---
    > +       /*
    > +        * Number of available workers must not exceed limit.
    > +        *
    > +        * Note, that if some parallel autovacuum workers are running at this
    > +        * moment, available workers number will not exceed limit after
    > +        * releasing them (see ParallelAutoVacuumReleaseWorkers).
    > +       */
    > +       AutoVacuumShmem->av_freeParallelWorkers =
    > +           autovacuum_max_parallel_workers;
    >
    > I think the comment refers to the following code in
    > AutoVacuumReleaseParallelWorkers():
    >
    > +   /*
    > +    * If autovacuum_max_parallel_workers variable was reduced during parallel
    > +    * autovacuum execution, we must cap available workers number by its new
    > +    * value.
    > +    */
    > +   if (AutoVacuumShmem->av_freeParallelWorkers >
    > +       autovacuum_max_parallel_workers)
    > +   {
    > +       AutoVacuumShmem->av_freeParallelWorkers =
    > +           autovacuum_max_parallel_workers;
    > +   }
    >
    > After the autovacuum launchers decreases av_freeParallelWorkers, it's
    > not guaranteed that the autovacuum worker already reflects the new
    > value from the config file when executing the
    > AutoVacuumReleaseParallelWorkers(), which leds to skips the above
    > codes. For example, suppose that autovacuum_max_parallel_workers is 10
    > and 3 parallel workers are running by one autovacuum worker (i.e.,
    > av_freeParallelWorkers = 7 now), if the user changes
    > autovacuum_max_parallel_workers to 5, the autovacuum launchers adjust
    > av_freeParallelWorkers to 5. However, if the worker doesn't reload the
    > config file and executes AutoVacuumReleaseParallelWorkers(), it
    > increases av_freeParallelWorkers to 8 and skips the adjusting logic.
    > I've not tested this scenarios so I might be missing something though.
    >
    
    Yes, this is a possible scenario. I'll rework av_freeParallelWorkers
    calculation. Main change is that a/v worker now checks whether config
    reload is pending. Thus, it will have the relevant value of the
    autovacuum_max_parallel_workers parameter.
    
    Thank you very much for your comments! Please, see v8 patches:
    1) Rename table option.
    2) Replace check_hook with assign_hook for autovacuum_max_parallel_workers.
    3) Simplify and correct logic for handling
    autovacuum_max_parallel_workers parameter change.
    4) Rework logic with "planned vs. launched" statistics for autovacuum
    (see second patch file).
    5) Get rid of "sandbox" - I don't see the point in continuing to drag him along.
    
    --
    Best regards,
    Daniil Davydov
    
  39. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-07-21T16:40:22Z

    Thanks for the patches!
    
    I have only reviewed the v8-0001-Parallel-index-autovacuum.patch so far and
    have a few comments from my initial pass.
    
    1/ Please run pgindent.
    
    2/ Documentation is missing. There may be more, but here are the places I
    found that likely need updates for the new behavior, reloptions, GUC, etc.
    Including docs in the patch early would help clarify expected behavior.
    
    https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-BASICS
    https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
    https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
    https://www.postgresql.org/docs/current/sql-createtable.html
    https://www.postgresql.org/docs/current/sql-altertable.html
    https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES
    https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-MAINTENANCE-WORKERS
    
    One thing I am unclear on is the interaction between max_worker_processes,
    max_parallel_workers, and max_parallel_maintenance_workers. For example, does
    the following change mean that manual VACUUM PARALLEL is no longer capped by
    max_parallel_maintenance_workers?
    
    @@ -597,8 +614,8 @@ parallel_vacuum_compute_workers(Relation *indrels,
    int nindexes, int nrequested,
            parallel_workers = (nrequested > 0) ?
                    Min(nrequested, nindexes_parallel) : nindexes_parallel;
    
    -       /* Cap by max_parallel_maintenance_workers */
    -       parallel_workers = Min(parallel_workers,
    max_parallel_maintenance_workers);
    +       /* Cap by GUC variable */
    +       parallel_workers = Min(parallel_workers, max_parallel_workers);
    
    
    3/ Shouldn't this be "max_parallel_workers" instead of "bgworkers pool" ?
    
    +                       "autovacuum_parallel_workers",
    +                       "Maximum number of parallel autovacuum workers
    that can be taken from bgworkers pool for processing this table. "
    
    4/ The comment "When parallel autovacuum worker die" suggests an abnormal
    exit. "Terminates" seems clearer, since this applies to both normal and
    abnormal exits.
    
    instead of:
    + * When parallel autovacuum worker die,
    
    how about this:
    * When parallel autovacuum worker terminates,
    
    
    5/ Any reason AutoVacuumReleaseParallelWorkers cannot be called before
    DestroyParallelContext?
    
    +       nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember
    this value */
            DestroyParallelContext(pvs->pcxt);
    +
    +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    +       if (AmAutoVacuumWorkerProcess())
    +               AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    
    
    6/ Also, would it be cleaner to move AmAutoVacuumWorkerProcess() inside
    AutoVacuumReleaseParallelWorkers()?
    
    if (!AmAutoVacuumWorkerProcess())
            return;
    
    7/ It looks like the psql tab completion for autovacuum_parallel_workers is
    missing:
    
    test=# alter table t set (autovacuum_
    autovacuum_analyze_scale_factor
    autovacuum_analyze_threshold
    autovacuum_enabled
    autovacuum_freeze_max_age
    autovacuum_freeze_min_age
    autovacuum_freeze_table_age
    autovacuum_multixact_freeze_max_age
    autovacuum_multixact_freeze_min_age
    autovacuum_multixact_freeze_table_age
    autovacuum_vacuum_cost_delay
    autovacuum_vacuum_cost_limit
    autovacuum_vacuum_insert_scale_factor
    autovacuum_vacuum_insert_threshold
    autovacuum_vacuum_max_threshold
    autovacuum_vacuum_scale_factor
    autovacuum_vacuum_threshold
    
    --
    
    Sami Imseih
    Amazon Web Services (AWS)
    
    
    
    
  40. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-07-22T06:45:31Z

    Hi,
    
    On Mon, Jul 21, 2025 at 11:40 PM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I have only reviewed the v8-0001-Parallel-index-autovacuum.patch so far and
    > have a few comments from my initial pass.
    >
    > 1/ Please run pgindent.
    
    OK, I'll do it.
    
    > 2/ Documentation is missing. There may be more, but here are the places I
    > found that likely need updates for the new behavior, reloptions, GUC, etc.
    > Including docs in the patch early would help clarify expected behavior.
    >
    > https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-BASICS
    > https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
    > https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
    > https://www.postgresql.org/docs/current/sql-createtable.html
    > https://www.postgresql.org/docs/current/sql-altertable.html
    > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES
    > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-MAINTENANCE-WORKERS
    >
    
    Thanks for gathering it all together. I'll update the documentation so
    it will reflect changes in autovacuum daemon, reloptions and GUC
    parameters. So far, I don't see what we can add to vacuum-basics
    and alter-table paragraphs.
    
    I'll create separate .patch file for changes in documentation.
    
    > One thing I am unclear on is the interaction between max_worker_processes,
    > max_parallel_workers, and max_parallel_maintenance_workers. For example, does
    > the following change mean that manual VACUUM PARALLEL is no longer capped by
    > max_parallel_maintenance_workers?
    >
    > @@ -597,8 +614,8 @@ parallel_vacuum_compute_workers(Relation *indrels,
    > int nindexes, int nrequested,
    >         parallel_workers = (nrequested > 0) ?
    >                 Min(nrequested, nindexes_parallel) : nindexes_parallel;
    >
    > -       /* Cap by max_parallel_maintenance_workers */
    > -       parallel_workers = Min(parallel_workers,
    > max_parallel_maintenance_workers);
    > +       /* Cap by GUC variable */
    > +       parallel_workers = Min(parallel_workers, max_parallel_workers);
    >
    
    Oh, it is my poor choice of a name for a local variable (I'll rename it).
    This variable can get different values depending on performed operation :
    autovacuum_max_parallel_workers for parallel autovacuum and
    max_parallel_maintenance_workers for maintenance VACUUM.
    
    >
    > 3/ Shouldn't this be "max_parallel_workers" instead of "bgworkers pool" ?
    >
    > +                       "autovacuum_parallel_workers",
    > +                       "Maximum number of parallel autovacuum workers
    > that can be taken from bgworkers pool for processing this table. "
    >
    
    I don't think that we should refer to max_parallel_workers here.
    Actually, this reloption doesn't depend on max_parallel_workers at all.
    I wrote about bgworkers pool (both here and in description of
    autovacuum_max_parallel_workers parameter) in order to clarify that
    parallel  autovacuum will use dynamic workers  instead of launching
    more a/v workers.
    
    BTW, I don't really like that the comment on this option turns out to be
    very large. I'll leave only short description in reloptions.c and move
    clarification about zero value in rel.h
    Mentions of bgworkers pool will remain only in
    description of autovacuum_max_parallel_workers.
    
    > 4/ The comment "When parallel autovacuum worker die" suggests an abnormal
    > exit. "Terminates" seems clearer, since this applies to both normal and
    > abnormal exits.
    >
    > instead of:
    > + * When parallel autovacuum worker die,
    >
    > how about this:
    > * When parallel autovacuum worker terminates,
    >
    
    Sounds reasonable, I'll fix it.
    
    >
    > 5/ Any reason AutoVacuumReleaseParallelWorkers cannot be called before
    > DestroyParallelContext?
    >
    > +       nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember
    > this value */
    >         DestroyParallelContext(pvs->pcxt);
    > +
    > +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +       if (AmAutoVacuumWorkerProcess())
    > +               AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    >
    
    I wrote about it above [1], but I think I can duplicate my thoughts here :
    """
    Destroying parallel context includes waiting for all workers to exit (after
    which, other operations can use them).
    If we first call ParallelAutoVacuumReleaseWorkers, some operation can
    reasonably request all released workers. But this request can fail,
    because there is no guarantee that workers managed to finish.
    
    Actually, there's nothing wrong with that, but I think releasing workers
    only after finishing work is a more logical approach.
    """
    
    >
    > 6/ Also, would it be cleaner to move AmAutoVacuumWorkerProcess() inside
    > AutoVacuumReleaseParallelWorkers()?
    >
    > if (!AmAutoVacuumWorkerProcess())
    >         return;
    >
    
    It seems to me that the opposite is true. If there is no alternative to calling
    AmAutoVacuumWorkerProcess, it might confuse somebody. All doubts
    will disappear after viewing the AmAutoVacuumWorkerProcess code,
    but IMO code in vacuumparallel.c will become less intuitive.
    
    > 7/ It looks like the psql tab completion for autovacuum_parallel_workers is
    > missing:
    >
    > test=# alter table t set (autovacuum_
    > autovacuum_analyze_scale_factor
    > autovacuum_analyze_threshold
    > autovacuum_enabled
    > autovacuum_freeze_max_age
    > autovacuum_freeze_min_age
    > autovacuum_freeze_table_age
    > autovacuum_multixact_freeze_max_age
    > autovacuum_multixact_freeze_min_age
    > autovacuum_multixact_freeze_table_age
    > autovacuum_vacuum_cost_delay
    > autovacuum_vacuum_cost_limit
    > autovacuum_vacuum_insert_scale_factor
    > autovacuum_vacuum_insert_threshold
    > autovacuum_vacuum_max_threshold
    > autovacuum_vacuum_scale_factor
    > autovacuum_vacuum_threshold
    >
    
    Good catch, I'll fix it.
    
    Thank you for the review! Please, see v9 patches :
    1) Run pgindent + rebase patches on newest commit in master.
    2) Introduce changes for documentation.
    3) Rename local variable in parallel_vacuum_compute_workers.
    4) Shorten the description of autovacuum_parallel_workers in
        reloptions.c (move clarifications for it into rel.h).
    5) Reword "When parallel autovacuum worker die" comment.
    6) Add tab completion for autovacuum_parallel_workers table option.
    
    [1] https://www.postgresql.org/message-id/CAJDiXgi7KB7wSQ%3DUx%3DngdaCvJnJ5x-ehvTyiuZez%2B5uKHtV6iQ%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
  41. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-08-07T23:38:03Z

    On Mon, Jul 21, 2025 at 11:45 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Mon, Jul 21, 2025 at 11:40 PM Sami Imseih <samimseih@gmail.com> wrote:
    > >
    > > I have only reviewed the v8-0001-Parallel-index-autovacuum.patch so far and
    > > have a few comments from my initial pass.
    > >
    > > 1/ Please run pgindent.
    >
    > OK, I'll do it.
    >
    > > 2/ Documentation is missing. There may be more, but here are the places I
    > > found that likely need updates for the new behavior, reloptions, GUC, etc.
    > > Including docs in the patch early would help clarify expected behavior.
    > >
    > > https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-BASICS
    > > https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
    > > https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
    > > https://www.postgresql.org/docs/current/sql-createtable.html
    > > https://www.postgresql.org/docs/current/sql-altertable.html
    > > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES
    > > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-MAINTENANCE-WORKERS
    > >
    >
    > Thanks for gathering it all together. I'll update the documentation so
    > it will reflect changes in autovacuum daemon, reloptions and GUC
    > parameters. So far, I don't see what we can add to vacuum-basics
    > and alter-table paragraphs.
    >
    > I'll create separate .patch file for changes in documentation.
    >
    > > One thing I am unclear on is the interaction between max_worker_processes,
    > > max_parallel_workers, and max_parallel_maintenance_workers. For example, does
    > > the following change mean that manual VACUUM PARALLEL is no longer capped by
    > > max_parallel_maintenance_workers?
    > >
    > > @@ -597,8 +614,8 @@ parallel_vacuum_compute_workers(Relation *indrels,
    > > int nindexes, int nrequested,
    > >         parallel_workers = (nrequested > 0) ?
    > >                 Min(nrequested, nindexes_parallel) : nindexes_parallel;
    > >
    > > -       /* Cap by max_parallel_maintenance_workers */
    > > -       parallel_workers = Min(parallel_workers,
    > > max_parallel_maintenance_workers);
    > > +       /* Cap by GUC variable */
    > > +       parallel_workers = Min(parallel_workers, max_parallel_workers);
    > >
    >
    > Oh, it is my poor choice of a name for a local variable (I'll rename it).
    > This variable can get different values depending on performed operation :
    > autovacuum_max_parallel_workers for parallel autovacuum and
    > max_parallel_maintenance_workers for maintenance VACUUM.
    >
    > >
    > > 3/ Shouldn't this be "max_parallel_workers" instead of "bgworkers pool" ?
    > >
    > > +                       "autovacuum_parallel_workers",
    > > +                       "Maximum number of parallel autovacuum workers
    > > that can be taken from bgworkers pool for processing this table. "
    > >
    >
    > I don't think that we should refer to max_parallel_workers here.
    > Actually, this reloption doesn't depend on max_parallel_workers at all.
    > I wrote about bgworkers pool (both here and in description of
    > autovacuum_max_parallel_workers parameter) in order to clarify that
    > parallel  autovacuum will use dynamic workers  instead of launching
    > more a/v workers.
    >
    > BTW, I don't really like that the comment on this option turns out to be
    > very large. I'll leave only short description in reloptions.c and move
    > clarification about zero value in rel.h
    > Mentions of bgworkers pool will remain only in
    > description of autovacuum_max_parallel_workers.
    >
    > > 4/ The comment "When parallel autovacuum worker die" suggests an abnormal
    > > exit. "Terminates" seems clearer, since this applies to both normal and
    > > abnormal exits.
    > >
    > > instead of:
    > > + * When parallel autovacuum worker die,
    > >
    > > how about this:
    > > * When parallel autovacuum worker terminates,
    > >
    >
    > Sounds reasonable, I'll fix it.
    >
    > >
    > > 5/ Any reason AutoVacuumReleaseParallelWorkers cannot be called before
    > > DestroyParallelContext?
    > >
    > > +       nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember
    > > this value */
    > >         DestroyParallelContext(pvs->pcxt);
    > > +
    > > +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > > +       if (AmAutoVacuumWorkerProcess())
    > > +               AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    > >
    >
    > I wrote about it above [1], but I think I can duplicate my thoughts here :
    > """
    > Destroying parallel context includes waiting for all workers to exit (after
    > which, other operations can use them).
    > If we first call ParallelAutoVacuumReleaseWorkers, some operation can
    > reasonably request all released workers. But this request can fail,
    > because there is no guarantee that workers managed to finish.
    >
    > Actually, there's nothing wrong with that, but I think releasing workers
    > only after finishing work is a more logical approach.
    > """
    >
    > >
    > > 6/ Also, would it be cleaner to move AmAutoVacuumWorkerProcess() inside
    > > AutoVacuumReleaseParallelWorkers()?
    > >
    > > if (!AmAutoVacuumWorkerProcess())
    > >         return;
    > >
    >
    > It seems to me that the opposite is true. If there is no alternative to calling
    > AmAutoVacuumWorkerProcess, it might confuse somebody. All doubts
    > will disappear after viewing the AmAutoVacuumWorkerProcess code,
    > but IMO code in vacuumparallel.c will become less intuitive.
    >
    > > 7/ It looks like the psql tab completion for autovacuum_parallel_workers is
    > > missing:
    > >
    > > test=# alter table t set (autovacuum_
    > > autovacuum_analyze_scale_factor
    > > autovacuum_analyze_threshold
    > > autovacuum_enabled
    > > autovacuum_freeze_max_age
    > > autovacuum_freeze_min_age
    > > autovacuum_freeze_table_age
    > > autovacuum_multixact_freeze_max_age
    > > autovacuum_multixact_freeze_min_age
    > > autovacuum_multixact_freeze_table_age
    > > autovacuum_vacuum_cost_delay
    > > autovacuum_vacuum_cost_limit
    > > autovacuum_vacuum_insert_scale_factor
    > > autovacuum_vacuum_insert_threshold
    > > autovacuum_vacuum_max_threshold
    > > autovacuum_vacuum_scale_factor
    > > autovacuum_vacuum_threshold
    > >
    >
    > Good catch, I'll fix it.
    >
    > Thank you for the review! Please, see v9 patches :
    > 1) Run pgindent + rebase patches on newest commit in master.
    > 2) Introduce changes for documentation.
    > 3) Rename local variable in parallel_vacuum_compute_workers.
    > 4) Shorten the description of autovacuum_parallel_workers in
    >     reloptions.c (move clarifications for it into rel.h).
    > 5) Reword "When parallel autovacuum worker die" comment.
    > 6) Add tab completion for autovacuum_parallel_workers table option.
    
    Thank you for updating the patch. Here are some review comments.
    
    +        /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    +        if (AmAutoVacuumWorkerProcess())
    +                AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    +
    
    We release the reserved worker in parallel_vacuum_end(). However,
    parallel_vacuum_end() is called only once at the end of vacuum. I
    think we need to release the reserved worker after index vacuuming or
    cleanup, otherwise we would end up holding the reserved workers until
    the end of vacuum even if we invoke index vacuuming multiple times.
    
    ---
    +void
    +assign_autovacuum_max_parallel_workers(int newval, void *extra)
    +{
    +        autovacuum_max_parallel_workers = Min(newval, max_worker_processes);
    +}
    
    I don't think we need the assign hook for this GUC parameter. We can
    internally cap the maximum value by max_worker_processes like other
    GUC parameters such as max_parallel_maintenance_workers and
    max_parallel_workers.
    
    ---+        /* Refresh autovacuum_max_parallel_workers paremeter */
    +        CHECK_FOR_INTERRUPTS();
    +        if (ConfigReloadPending)
    +        {
    +                ConfigReloadPending = false;
    +                ProcessConfigFile(PGC_SIGHUP);
    +        }
    +
    +        LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    +
    +        /*
    +         * If autovacuum_max_parallel_workers parameter was reduced during
    +         * parallel autovacuum execution, we must cap available
    workers number by
    +         * its new value.
    +         */
    +        AutoVacuumShmem->av_freeParallelWorkers =
    +                Min(AutoVacuumShmem->av_freeParallelWorkers + nworkers,
    +                        autovacuum_max_parallel_workers);
    +
    +        LWLockRelease(AutovacuumLock);
    
    I think another race condition could occur; suppose
    autovacuum_max_parallel_workers is set to '5' and one autovacuum
    worker reserved 5 workers, meaning that
    AutoVacuumShmem->av_freeParallelWorkers is 0. Then, the user changes
    autovacuum_max_parallel_workers to 3 and reloads the conf file right
    after the autovacuum worker checks the interruption. The launcher
    processes calls adjust_free_parallel_workers() but
    av_freeParallelWorkers remains 0, and the autovacuum worker increments
    it by 5 as its autovacuum_max_parallel_workers value is still 5.
    
    I think that we can have the autovacuum_max_parallel_workers value on
    shmem, and only the launcher process can modify its value if the GUC
    is changed. Autovacuum workers simply increase or decrease the
    av_freeParallelWorkers within the range of 0 and the
    autovacuum_max_parallel_workers value on shmem. When changing
    autovacuum_max_parallel_workers and av_freeParallelWorkers values on
    shmem, the launcher process calculates the number of workers reserved
    at that time and calculate the new av_freeParallelWorkers value by
    subtracting the new autovacuum_max_parallel_workers by the number of
    reserved workers.
    
    ---
    +AutoVacuumReserveParallelWorkers(int nworkers)
    +{
    +   int         can_launch;
    
    How about renaming it to 'nreserved' or something? can_launch looks
    like it's a boolean variable to indicate whether the process can
    launch workers.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  42. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-08-14T20:40:25Z

    On Thu, Aug 7, 2025 at 4:38 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Mon, Jul 21, 2025 at 11:45 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > Hi,
    > >
    > > On Mon, Jul 21, 2025 at 11:40 PM Sami Imseih <samimseih@gmail.com> wrote:
    > > >
    > > > I have only reviewed the v8-0001-Parallel-index-autovacuum.patch so far and
    > > > have a few comments from my initial pass.
    > > >
    > > > 1/ Please run pgindent.
    > >
    > > OK, I'll do it.
    > >
    > > > 2/ Documentation is missing. There may be more, but here are the places I
    > > > found that likely need updates for the new behavior, reloptions, GUC, etc.
    > > > Including docs in the patch early would help clarify expected behavior.
    > > >
    > > > https://www.postgresql.org/docs/current/routine-vacuuming.html#VACUUM-BASICS
    > > > https://www.postgresql.org/docs/current/routine-vacuuming.html#AUTOVACUUM
    > > > https://www.postgresql.org/docs/current/runtime-config-autovacuum.html
    > > > https://www.postgresql.org/docs/current/sql-createtable.html
    > > > https://www.postgresql.org/docs/current/sql-altertable.html
    > > > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-WORKER-PROCESSES
    > > > https://www.postgresql.org/docs/current/runtime-config-resource.html#GUC-MAX-PARALLEL-MAINTENANCE-WORKERS
    > > >
    > >
    > > Thanks for gathering it all together. I'll update the documentation so
    > > it will reflect changes in autovacuum daemon, reloptions and GUC
    > > parameters. So far, I don't see what we can add to vacuum-basics
    > > and alter-table paragraphs.
    > >
    > > I'll create separate .patch file for changes in documentation.
    > >
    > > > One thing I am unclear on is the interaction between max_worker_processes,
    > > > max_parallel_workers, and max_parallel_maintenance_workers. For example, does
    > > > the following change mean that manual VACUUM PARALLEL is no longer capped by
    > > > max_parallel_maintenance_workers?
    > > >
    > > > @@ -597,8 +614,8 @@ parallel_vacuum_compute_workers(Relation *indrels,
    > > > int nindexes, int nrequested,
    > > >         parallel_workers = (nrequested > 0) ?
    > > >                 Min(nrequested, nindexes_parallel) : nindexes_parallel;
    > > >
    > > > -       /* Cap by max_parallel_maintenance_workers */
    > > > -       parallel_workers = Min(parallel_workers,
    > > > max_parallel_maintenance_workers);
    > > > +       /* Cap by GUC variable */
    > > > +       parallel_workers = Min(parallel_workers, max_parallel_workers);
    > > >
    > >
    > > Oh, it is my poor choice of a name for a local variable (I'll rename it).
    > > This variable can get different values depending on performed operation :
    > > autovacuum_max_parallel_workers for parallel autovacuum and
    > > max_parallel_maintenance_workers for maintenance VACUUM.
    > >
    > > >
    > > > 3/ Shouldn't this be "max_parallel_workers" instead of "bgworkers pool" ?
    > > >
    > > > +                       "autovacuum_parallel_workers",
    > > > +                       "Maximum number of parallel autovacuum workers
    > > > that can be taken from bgworkers pool for processing this table. "
    > > >
    > >
    > > I don't think that we should refer to max_parallel_workers here.
    > > Actually, this reloption doesn't depend on max_parallel_workers at all.
    > > I wrote about bgworkers pool (both here and in description of
    > > autovacuum_max_parallel_workers parameter) in order to clarify that
    > > parallel  autovacuum will use dynamic workers  instead of launching
    > > more a/v workers.
    > >
    > > BTW, I don't really like that the comment on this option turns out to be
    > > very large. I'll leave only short description in reloptions.c and move
    > > clarification about zero value in rel.h
    > > Mentions of bgworkers pool will remain only in
    > > description of autovacuum_max_parallel_workers.
    > >
    > > > 4/ The comment "When parallel autovacuum worker die" suggests an abnormal
    > > > exit. "Terminates" seems clearer, since this applies to both normal and
    > > > abnormal exits.
    > > >
    > > > instead of:
    > > > + * When parallel autovacuum worker die,
    > > >
    > > > how about this:
    > > > * When parallel autovacuum worker terminates,
    > > >
    > >
    > > Sounds reasonable, I'll fix it.
    > >
    > > >
    > > > 5/ Any reason AutoVacuumReleaseParallelWorkers cannot be called before
    > > > DestroyParallelContext?
    > > >
    > > > +       nlaunched_workers = pvs->pcxt->nworkers_launched; /* remember
    > > > this value */
    > > >         DestroyParallelContext(pvs->pcxt);
    > > > +
    > > > +       /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > > > +       if (AmAutoVacuumWorkerProcess())
    > > > +               AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    > > >
    > >
    > > I wrote about it above [1], but I think I can duplicate my thoughts here :
    > > """
    > > Destroying parallel context includes waiting for all workers to exit (after
    > > which, other operations can use them).
    > > If we first call ParallelAutoVacuumReleaseWorkers, some operation can
    > > reasonably request all released workers. But this request can fail,
    > > because there is no guarantee that workers managed to finish.
    > >
    > > Actually, there's nothing wrong with that, but I think releasing workers
    > > only after finishing work is a more logical approach.
    > > """
    > >
    > > >
    > > > 6/ Also, would it be cleaner to move AmAutoVacuumWorkerProcess() inside
    > > > AutoVacuumReleaseParallelWorkers()?
    > > >
    > > > if (!AmAutoVacuumWorkerProcess())
    > > >         return;
    > > >
    > >
    > > It seems to me that the opposite is true. If there is no alternative to calling
    > > AmAutoVacuumWorkerProcess, it might confuse somebody. All doubts
    > > will disappear after viewing the AmAutoVacuumWorkerProcess code,
    > > but IMO code in vacuumparallel.c will become less intuitive.
    > >
    > > > 7/ It looks like the psql tab completion for autovacuum_parallel_workers is
    > > > missing:
    > > >
    > > > test=# alter table t set (autovacuum_
    > > > autovacuum_analyze_scale_factor
    > > > autovacuum_analyze_threshold
    > > > autovacuum_enabled
    > > > autovacuum_freeze_max_age
    > > > autovacuum_freeze_min_age
    > > > autovacuum_freeze_table_age
    > > > autovacuum_multixact_freeze_max_age
    > > > autovacuum_multixact_freeze_min_age
    > > > autovacuum_multixact_freeze_table_age
    > > > autovacuum_vacuum_cost_delay
    > > > autovacuum_vacuum_cost_limit
    > > > autovacuum_vacuum_insert_scale_factor
    > > > autovacuum_vacuum_insert_threshold
    > > > autovacuum_vacuum_max_threshold
    > > > autovacuum_vacuum_scale_factor
    > > > autovacuum_vacuum_threshold
    > > >
    > >
    > > Good catch, I'll fix it.
    > >
    > > Thank you for the review! Please, see v9 patches :
    > > 1) Run pgindent + rebase patches on newest commit in master.
    > > 2) Introduce changes for documentation.
    > > 3) Rename local variable in parallel_vacuum_compute_workers.
    > > 4) Shorten the description of autovacuum_parallel_workers in
    > >     reloptions.c (move clarifications for it into rel.h).
    > > 5) Reword "When parallel autovacuum worker die" comment.
    > > 6) Add tab completion for autovacuum_parallel_workers table option.
    >
    > Thank you for updating the patch. Here are some review comments.
    >
    > +        /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +        if (AmAutoVacuumWorkerProcess())
    > +                AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    > +
    >
    > We release the reserved worker in parallel_vacuum_end(). However,
    > parallel_vacuum_end() is called only once at the end of vacuum. I
    > think we need to release the reserved worker after index vacuuming or
    > cleanup, otherwise we would end up holding the reserved workers until
    > the end of vacuum even if we invoke index vacuuming multiple times.
    >
    > ---
    > +void
    > +assign_autovacuum_max_parallel_workers(int newval, void *extra)
    > +{
    > +        autovacuum_max_parallel_workers = Min(newval, max_worker_processes);
    > +}
    >
    > I don't think we need the assign hook for this GUC parameter. We can
    > internally cap the maximum value by max_worker_processes like other
    > GUC parameters such as max_parallel_maintenance_workers and
    > max_parallel_workers.
    >
    > ---+        /* Refresh autovacuum_max_parallel_workers paremeter */
    > +        CHECK_FOR_INTERRUPTS();
    > +        if (ConfigReloadPending)
    > +        {
    > +                ConfigReloadPending = false;
    > +                ProcessConfigFile(PGC_SIGHUP);
    > +        }
    > +
    > +        LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    > +
    > +        /*
    > +         * If autovacuum_max_parallel_workers parameter was reduced during
    > +         * parallel autovacuum execution, we must cap available
    > workers number by
    > +         * its new value.
    > +         */
    > +        AutoVacuumShmem->av_freeParallelWorkers =
    > +                Min(AutoVacuumShmem->av_freeParallelWorkers + nworkers,
    > +                        autovacuum_max_parallel_workers);
    > +
    > +        LWLockRelease(AutovacuumLock);
    >
    > I think another race condition could occur; suppose
    > autovacuum_max_parallel_workers is set to '5' and one autovacuum
    > worker reserved 5 workers, meaning that
    > AutoVacuumShmem->av_freeParallelWorkers is 0. Then, the user changes
    > autovacuum_max_parallel_workers to 3 and reloads the conf file right
    > after the autovacuum worker checks the interruption. The launcher
    > processes calls adjust_free_parallel_workers() but
    > av_freeParallelWorkers remains 0, and the autovacuum worker increments
    > it by 5 as its autovacuum_max_parallel_workers value is still 5.
    >
    > I think that we can have the autovacuum_max_parallel_workers value on
    > shmem, and only the launcher process can modify its value if the GUC
    > is changed. Autovacuum workers simply increase or decrease the
    > av_freeParallelWorkers within the range of 0 and the
    > autovacuum_max_parallel_workers value on shmem. When changing
    > autovacuum_max_parallel_workers and av_freeParallelWorkers values on
    > shmem, the launcher process calculates the number of workers reserved
    > at that time and calculate the new av_freeParallelWorkers value by
    > subtracting the new autovacuum_max_parallel_workers by the number of
    > reserved workers.
    >
    > ---
    > +AutoVacuumReserveParallelWorkers(int nworkers)
    > +{
    > +   int         can_launch;
    >
    > How about renaming it to 'nreserved' or something? can_launch looks
    > like it's a boolean variable to indicate whether the process can
    > launch workers.
    
    While testing the patch, I found there are other two problems:
    
    1. when an autovacuum worker who reserved workers fails with an error,
    the reserved workers are not released. I think we need to ensure that
    all reserved workers are surely released at the end of vacuum even
    with an error.
    
    2. when an autovacuum worker (not parallel vacuum worker) who uses
    parallel vacuum gets SIGHUP, it errors out with the error message
    "parameter "max_stack_depth" cannot be set during a parallel
    operation". Autovacuum checks the configuration file reload in
    vacuum_delay_point(), and while reloading the configuration file, it
    attempts to set max_stack_depth in
    InitializeGUCOptionsFromEnvironment() (which is called by
    ProcessConfigFileInternal()). However, it cannot change
    max_stack_depth since the worker is in parallel mode but
    max_stack_depth doesn't have GUC_ALLOW_IN_PARALLEL flag. This doesn't
    happen in regular backends who are using parallel queries because they
    check the configuration file reload at the end of each SQL command.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  43. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-08-18T08:30:49Z

    Hi,
    Thank you very much for your comments!
    In this letter I'll answer both of your recent letters.
    
    On Fri, Aug 8, 2025 at 6:38 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Thank you for updating the patch. Here are some review comments.
    >
    > +        /* Release all launched (i.e. reserved) parallel autovacuum workers. */
    > +        if (AmAutoVacuumWorkerProcess())
    > +                AutoVacuumReleaseParallelWorkers(nlaunched_workers);
    > +
    >
    > We release the reserved worker in parallel_vacuum_end(). However,
    > parallel_vacuum_end() is called only once at the end of vacuum. I
    > think we need to release the reserved worker after index vacuuming or
    > cleanup, otherwise we would end up holding the reserved workers until
    > the end of vacuum even if we invoke index vacuuming multiple times.
    >
    
    Yep, you are right. It was easy to miss because typically the autovacuum
    takes only one cycle to process a table. Since both index vacuum and
    index cleanup uses the parallel_vacuum_process_all_indexes function,
    I think that both releasing and reserving should be placed there.
    
    > ---
    > +void
    > +assign_autovacuum_max_parallel_workers(int newval, void *extra)
    > +{
    > +        autovacuum_max_parallel_workers = Min(newval, max_worker_processes);
    > +}
    >
    > I don't think we need the assign hook for this GUC parameter. We can
    > internally cap the maximum value by max_worker_processes like other
    > GUC parameters such as max_parallel_maintenance_workers and
    > max_parallel_workers.
    
    Ok, I get it - we don't want to give a configuration error for no serious
    reason. Actually, we already internally capping
    autovacuum_max_parallel_workers by max_worker_processes (inside
    parallel_vacuum_compute_workers function). This is the same behavior
    as max_parallel_maintenance_workers got.
    
    I'll get rid of the assign hook and add one more capping inside autovacuum
    shmem initialization : Since max_worker_processes is PGC_POSTMASTER
    parameter, av_freeParallelWorkers must not exceed its value.
    
    >
    > ---+        /* Refresh autovacuum_max_parallel_workers paremeter */
    > +        CHECK_FOR_INTERRUPTS();
    > +        if (ConfigReloadPending)
    > +        {
    > +                ConfigReloadPending = false;
    > +                ProcessConfigFile(PGC_SIGHUP);
    > +        }
    > +
    > +        LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);
    > +
    > +        /*
    > +         * If autovacuum_max_parallel_workers parameter was reduced during
    > +         * parallel autovacuum execution, we must cap available
    > workers number by
    > +         * its new value.
    > +         */
    > +        AutoVacuumShmem->av_freeParallelWorkers =
    > +                Min(AutoVacuumShmem->av_freeParallelWorkers + nworkers,
    > +                        autovacuum_max_parallel_workers);
    > +
    > +        LWLockRelease(AutovacuumLock);
    >
    > I think another race condition could occur; suppose
    > autovacuum_max_parallel_workers is set to '5' and one autovacuum
    > worker reserved 5 workers, meaning that
    > AutoVacuumShmem->av_freeParallelWorkers is 0. Then, the user changes
    > autovacuum_max_parallel_workers to 3 and reloads the conf file right
    > after the autovacuum worker checks the interruption. The launcher
    > processes calls adjust_free_parallel_workers() but
    > av_freeParallelWorkers remains 0, and the autovacuum worker increments
    > it by 5 as its autovacuum_max_parallel_workers value is still 5.
    >
    
    I think this problem can be solved if we put AutovacuumLock acquiring
    before processing the config file, but I understand that this is a bad way.
    
    > I think that we can have the autovacuum_max_parallel_workers value on
    > shmem, and only the launcher process can modify its value if the GUC
    > is changed. Autovacuum workers simply increase or decrease the
    > av_freeParallelWorkers within the range of 0 and the
    > autovacuum_max_parallel_workers value on shmem. When changing
    > autovacuum_max_parallel_workers and av_freeParallelWorkers values on
    > shmem, the launcher process calculates the number of workers reserved
    > at that time and calculate the new av_freeParallelWorkers value by
    > subtracting the new autovacuum_max_parallel_workers by the number of
    > reserved workers.
    >
    
    Good idea, I agree. Replacing the GUC parameter with the variable in shmem
    leaves the current logic of free workers management unchanged. Essentially,
    this  is the same solution as I described above, but we are holding lock not
    during  config reloading, but during a simple value check. It makes much
    more sense.
    
    > ---
    > +AutoVacuumReserveParallelWorkers(int nworkers)
    > +{
    > +   int         can_launch;
    >
    > How about renaming it to 'nreserved' or something? can_launch looks
    > like it's a boolean variable to indicate whether the process can
    > launch workers.
    >
    
    There are no objections.
    
    On Fri, Aug 15, 2025 at 3:41 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > While testing the patch, I found there are other two problems:
    >
    > 1. when an autovacuum worker who reserved workers fails with an error,
    > the reserved workers are not released. I think we need to ensure that
    > all reserved workers are surely released at the end of vacuum even
    > with an error.
    >
    
    Agree. I'll add a try/catch block to the parallel_vacuum_process_all_indexes
    (the only place where we are reserving workers).
    
    > 2. when an autovacuum worker (not parallel vacuum worker) who uses
    > parallel vacuum gets SIGHUP, it errors out with the error message
    > "parameter "max_stack_depth" cannot be set during a parallel
    > operation". Autovacuum checks the configuration file reload in
    > vacuum_delay_point(), and while reloading the configuration file, it
    > attempts to set max_stack_depth in
    > InitializeGUCOptionsFromEnvironment() (which is called by
    > ProcessConfigFileInternal()). However, it cannot change
    > max_stack_depth since the worker is in parallel mode but
    > max_stack_depth doesn't have GUC_ALLOW_IN_PARALLEL flag. This doesn't
    > happen in regular backends who are using parallel queries because they
    > check the configuration file reload at the end of each SQL command.
    >
    
    Hm, this is a really serious problem. I see only two ways to solve it (both are
    not really good) :
    1)
    Do not allow processing of the config file during parallel autovacuum
    execution.
    2)
    Teach the autovacuum to enter parallel mode only during the index vacuum/cleanup
    phase. I'm a bit wary about it, because the design says that we should
    be in parallel
    mode during the whole parallel operation. But actually, if we can make
    sure that all
    launched workers are exited, I don't see reasons, why can't we just
    exit parallel mode
    at the end of parallel_vacuum_process_all_indexes.
    
    What do you think about it? By now, I haven't made any changes related
    to this problem.
    
    Again, thank you for the review. Please, see v10 patches (only 0001
    has been changed) :
    1) Reserve and release workers only inside parallel_vacuum_process_all_indexes.
    2) Add try/catch block to the parallel_vacuum_process_all_indexes, so we can
    release workers even after an error. This required adding a static
    variable to account
    for the total number of reserved workers (av_nworkers_reserved).
    3) Cap autovacuum_max_parallel_workers by max_worker_processes only inside
    autovacuum code. Assign hook has been removed.
    4) Use shmem value for determining the maximum number of parallel autovacuum
    workers (eliminate race condition between launcher and leader process).
    
    --
    Best regards,
    Daniil Davydov
    
  44. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-08-18T21:03:19Z

    On Mon, Aug 18, 2025 at 1:31 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    >
    > On Fri, Aug 15, 2025 at 3:41 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    >
    > > 2. when an autovacuum worker (not parallel vacuum worker) who uses
    > > parallel vacuum gets SIGHUP, it errors out with the error message
    > > "parameter "max_stack_depth" cannot be set during a parallel
    > > operation". Autovacuum checks the configuration file reload in
    > > vacuum_delay_point(), and while reloading the configuration file, it
    > > attempts to set max_stack_depth in
    > > InitializeGUCOptionsFromEnvironment() (which is called by
    > > ProcessConfigFileInternal()). However, it cannot change
    > > max_stack_depth since the worker is in parallel mode but
    > > max_stack_depth doesn't have GUC_ALLOW_IN_PARALLEL flag. This doesn't
    > > happen in regular backends who are using parallel queries because they
    > > check the configuration file reload at the end of each SQL command.
    > >
    >
    > Hm, this is a really serious problem. I see only two ways to solve it (both are
    > not really good) :
    > 1)
    > Do not allow processing of the config file during parallel autovacuum
    > execution.
    >
    > 2)
    > Teach the autovacuum to enter parallel mode only during the index vacuum/cleanup
    > phase. I'm a bit wary about it, because the design says that we should
    > be in parallel
    > mode during the whole parallel operation. But actually, if we can make
    > sure that all
    > launched workers are exited, I don't see reasons, why can't we just
    > exit parallel mode
    > at the end of parallel_vacuum_process_all_indexes.
    >
    > What do you think about it?
    
    Hmm, given that we're trying to support parallel heap vacuum on
    another thread[1] and we will probably support it in autovacuums, it
    seems to me that these approaches won't work.
    
    Another idea would be to allow autovacuum workers to process the
    config file even in parallel mode. GUC changes in the leader worker
    would not affect parallel vacuum workers, but it is fine to me. In the
    context of autovacuum, only specific GUC parameters related to
    cost-based delays need to be affected also to parallel vacuum workers.
    Probably we need some changes to compute_parallel_delay() so that
    parallel workers can compute the sleep time based on the new
    vacuum_cost_limit and vacuum_cost_delay after the leader process
    (i.e., autovacuum worker) reloads the config file.
    
    >
    > Again, thank you for the review. Please, see v10 patches (only 0001
    > has been changed) :
    > 1) Reserve and release workers only inside parallel_vacuum_process_all_indexes.
    > 2) Add try/catch block to the parallel_vacuum_process_all_indexes, so we can
    > release workers even after an error. This required adding a static
    > variable to account
    > for the total number of reserved workers (av_nworkers_reserved).
    > 3) Cap autovacuum_max_parallel_workers by max_worker_processes only inside
    > autovacuum code. Assign hook has been removed.
    > 4) Use shmem value for determining the maximum number of parallel autovacuum
    > workers (eliminate race condition between launcher and leader process).
    
    Thank you for updating the patch! I'll review the new version patches.
    
    Regards,
    
    [1] https://www.postgresql.org/message-id/CAD21AoAEfCNv-GgaDheDJ%2Bs-p_Lv1H24AiJeNoPGCmZNSwL1YA%40mail.gmail.com
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  45. Re: POC: Parallel processing of indexes in autovacuum

    Alexander Korotkov <aekorotkov@gmail.com> — 2025-09-15T18:50:24Z

    Hi!
    
    On Tue, Aug 19, 2025 at 12:04 AM Masahiko Sawada <sawada.mshk@gmail.com>
    wrote:
    >
    > On Mon, Aug 18, 2025 at 1:31 AM Daniil Davydov <3danissimo@gmail.com>
    wrote:
    > >
    > >
    > > On Fri, Aug 15, 2025 at 3:41 AM Masahiko Sawada <sawada.mshk@gmail.com>
    wrote:
    > > >
    > >
    > > > 2. when an autovacuum worker (not parallel vacuum worker) who uses
    > > > parallel vacuum gets SIGHUP, it errors out with the error message
    > > > "parameter "max_stack_depth" cannot be set during a parallel
    > > > operation". Autovacuum checks the configuration file reload in
    > > > vacuum_delay_point(), and while reloading the configuration file, it
    > > > attempts to set max_stack_depth in
    > > > InitializeGUCOptionsFromEnvironment() (which is called by
    > > > ProcessConfigFileInternal()). However, it cannot change
    > > > max_stack_depth since the worker is in parallel mode but
    > > > max_stack_depth doesn't have GUC_ALLOW_IN_PARALLEL flag. This doesn't
    > > > happen in regular backends who are using parallel queries because they
    > > > check the configuration file reload at the end of each SQL command.
    > > >
    > >
    > > Hm, this is a really serious problem. I see only two ways to solve it
    (both are
    > > not really good) :
    > > 1)
    > > Do not allow processing of the config file during parallel autovacuum
    > > execution.
    > >
    > > 2)
    > > Teach the autovacuum to enter parallel mode only during the index
    vacuum/cleanup
    > > phase. I'm a bit wary about it, because the design says that we should
    > > be in parallel
    > > mode during the whole parallel operation. But actually, if we can make
    > > sure that all
    > > launched workers are exited, I don't see reasons, why can't we just
    > > exit parallel mode
    > > at the end of parallel_vacuum_process_all_indexes.
    > >
    > > What do you think about it?
    >
    > Hmm, given that we're trying to support parallel heap vacuum on
    > another thread[1] and we will probably support it in autovacuums, it
    > seems to me that these approaches won't work.
    >
    > Another idea would be to allow autovacuum workers to process the
    > config file even in parallel mode. GUC changes in the leader worker
    > would not affect parallel vacuum workers, but it is fine to me. In the
    > context of autovacuum, only specific GUC parameters related to
    > cost-based delays need to be affected also to parallel vacuum workers.
    > Probably we need some changes to compute_parallel_delay() so that
    > parallel workers can compute the sleep time based on the new
    > vacuum_cost_limit and vacuum_cost_delay after the leader process
    > (i.e., autovacuum worker) reloads the config file.
    >
    > >
    > > Again, thank you for the review. Please, see v10 patches (only 0001
    > > has been changed) :
    > > 1) Reserve and release workers only inside
    parallel_vacuum_process_all_indexes.
    > > 2) Add try/catch block to the parallel_vacuum_process_all_indexes, so
    we can
    > > release workers even after an error. This required adding a static
    > > variable to account
    > > for the total number of reserved workers (av_nworkers_reserved).
    > > 3) Cap autovacuum_max_parallel_workers by max_worker_processes only
    inside
    > > autovacuum code. Assign hook has been removed.
    > > 4) Use shmem value for determining the maximum number of parallel
    autovacuum
    > > workers (eliminate race condition between launcher and leader process).
    >
    > Thank you for updating the patch! I'll review the new version patches.
    
    I've rebased this patchset to the current master.  That required me to move
    the new GUC definition to guc_parameters.dat.  Also, I adjusted
    typedefs.list and made pgindent.  Some notes about the patch.
    
    +   {
    +       {
    +           "autovacuum_parallel_workers",
    +           "Maximum number of parallel autovacuum workers that can be used
    for processing this table.",
    +           RELOPT_KIND_HEAP,
    +           ShareUpdateExclusiveLock
    +       },
    +       -1, -1, 1024
    +   },
    
    Should we use MAX_PARALLEL_WORKER_LIMIT instead of hard-coded 1024 here?
    
    - *   Support routines for parallel vacuum execution.
    + *   Support routines for parallel vacuum and autovacuum execution. In the
    + *   future comments, the word "vacuum" will refer to both vacuum and
    + *   autovacuum.
    
    Not sure about the usage of word "future" here.  It doesn't look clear what
    it means.  Could we use "below" or "within this file"?
    
    I see parallel_vacuum_process_all_indexes() have a TRY/CATCH block.  As I
    heard, the overhead of setting/doing jumps is platform-dependent, and not
    harmless on some platforms.  Therefore, can we skip TRY/CATCH block for
    non-autovacuum vacuum?  Possibly we could move it to AutoVacWorkerMain(),
    that would save us from repeatedly setting a jump in autovacuum workers too.
    
    In general, I think this patchset badly lack of testing.  I think it needs
    tap tests checking from the logs that autovacuum has been done in
    parallel.  Also, it would be good to set up some injection points, and
    check that reserved autovacuum parallel workers are getting released
    correctly in the case of errors.
    
    ------
    Regards,
    Alexander Korotkov
    Supabase
    
  46. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-09-16T18:30:22Z

    On Mon, Sep 15, 2025 at 11:50 AM Alexander Korotkov
    <aekorotkov@gmail.com> wrote:
    >
    > Hi!
    >
    > On Tue, Aug 19, 2025 at 12:04 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Mon, Aug 18, 2025 at 1:31 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >
    > > >
    > > > On Fri, Aug 15, 2025 at 3:41 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > > > >
    > > >
    > > > > 2. when an autovacuum worker (not parallel vacuum worker) who uses
    > > > > parallel vacuum gets SIGHUP, it errors out with the error message
    > > > > "parameter "max_stack_depth" cannot be set during a parallel
    > > > > operation". Autovacuum checks the configuration file reload in
    > > > > vacuum_delay_point(), and while reloading the configuration file, it
    > > > > attempts to set max_stack_depth in
    > > > > InitializeGUCOptionsFromEnvironment() (which is called by
    > > > > ProcessConfigFileInternal()). However, it cannot change
    > > > > max_stack_depth since the worker is in parallel mode but
    > > > > max_stack_depth doesn't have GUC_ALLOW_IN_PARALLEL flag. This doesn't
    > > > > happen in regular backends who are using parallel queries because they
    > > > > check the configuration file reload at the end of each SQL command.
    > > > >
    > > >
    > > > Hm, this is a really serious problem. I see only two ways to solve it (both are
    > > > not really good) :
    > > > 1)
    > > > Do not allow processing of the config file during parallel autovacuum
    > > > execution.
    > > >
    > > > 2)
    > > > Teach the autovacuum to enter parallel mode only during the index vacuum/cleanup
    > > > phase. I'm a bit wary about it, because the design says that we should
    > > > be in parallel
    > > > mode during the whole parallel operation. But actually, if we can make
    > > > sure that all
    > > > launched workers are exited, I don't see reasons, why can't we just
    > > > exit parallel mode
    > > > at the end of parallel_vacuum_process_all_indexes.
    > > >
    > > > What do you think about it?
    > >
    > > Hmm, given that we're trying to support parallel heap vacuum on
    > > another thread[1] and we will probably support it in autovacuums, it
    > > seems to me that these approaches won't work.
    > >
    > > Another idea would be to allow autovacuum workers to process the
    > > config file even in parallel mode. GUC changes in the leader worker
    > > would not affect parallel vacuum workers, but it is fine to me. In the
    > > context of autovacuum, only specific GUC parameters related to
    > > cost-based delays need to be affected also to parallel vacuum workers.
    > > Probably we need some changes to compute_parallel_delay() so that
    > > parallel workers can compute the sleep time based on the new
    > > vacuum_cost_limit and vacuum_cost_delay after the leader process
    > > (i.e., autovacuum worker) reloads the config file.
    > >
    > > >
    > > > Again, thank you for the review. Please, see v10 patches (only 0001
    > > > has been changed) :
    > > > 1) Reserve and release workers only inside parallel_vacuum_process_all_indexes.
    > > > 2) Add try/catch block to the parallel_vacuum_process_all_indexes, so we can
    > > > release workers even after an error. This required adding a static
    > > > variable to account
    > > > for the total number of reserved workers (av_nworkers_reserved).
    > > > 3) Cap autovacuum_max_parallel_workers by max_worker_processes only inside
    > > > autovacuum code. Assign hook has been removed.
    > > > 4) Use shmem value for determining the maximum number of parallel autovacuum
    > > > workers (eliminate race condition between launcher and leader process).
    > >
    > > Thank you for updating the patch! I'll review the new version patches.
    >
    > I've rebased this patchset to the current master.  That required me to move the new GUC definition to guc_parameters.dat.  Also, I adjusted typedefs.list and made pgindent.  Some notes about the patch.
    
    Thank you for updating the patch!
    
    > I see parallel_vacuum_process_all_indexes() have a TRY/CATCH block.  As I heard, the overhead of setting/doing jumps is platform-dependent, and not harmless on some platforms.  Therefore, can we skip TRY/CATCH block for non-autovacuum vacuum?  Possibly we could move it to AutoVacWorkerMain(), that would save us from repeatedly setting a jump in autovacuum workers too.
    
    I wonder if using the TRY/CATCH block is not enough to ensure that
    autovacuum workers release the reserved parallel workers in FATAL
    cases.
    
    > In general, I think this patchset badly lack of testing.  I think it needs tap tests checking from the logs that autovacuum has been done in parallel.  Also, it would be good to set up some injection points, and check that reserved autovacuum parallel workers are getting released correctly in the case of errors.
    
    +1
    
    IIUC the patch still has one problem in terms of reloading the
    configuration parameters during parallel mode as I mentioned
    before[1].
    
    Regards,
    
    [1] https://www.postgresql.org/message-id/CAD21AoBRRXbNJEvCjS-0XZgCEeRBzQPKmrSDjJ3wZ8TN28vaCQ%40mail.gmail.com
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  47. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-10-28T13:09:59Z

    Hi,
    
    On Tue, Sep 16, 2025 at 1:50 AM Alexander Korotkov
    <aekorotkov@gmail.com> wrote:
    >
    > I've rebased this patchset to the current master.
    > That required me to move the new GUC definition to guc_parameters.dat.
    > Also, I adjusted typedefs.list and made pgindent.
    
    Thank you for looking into it!
    
    >
    > +   {
    > +       {
    > +           "autovacuum_parallel_workers",
    > +           "Maximum number of parallel autovacuum workers that can be used for processing this table.",
    > +           RELOPT_KIND_HEAP,
    > +           ShareUpdateExclusiveLock
    > +       },
    > +       -1, -1, 1024
    > +   },
    >
    > Should we use MAX_PARALLEL_WORKER_LIMIT instead of hard-coded 1024 here?
    
    I'm afraid that we will have to include an additional header file to do this.
    As far as I know, we are trying not to do so. For now, I will leave it
    hardcoded.
    
    >
    > - *   Support routines for parallel vacuum execution.
    > + *   Support routines for parallel vacuum and autovacuum execution. In the
    > + *   future comments, the word "vacuum" will refer to both vacuum and
    > + *   autovacuum.
    >
    > Not sure about the usage of word "future" here.
    > It doesn't look clear what it means.
    > Could we use "below" or "within this file"?
    
    Agree, fixed.
    
    > I see parallel_vacuum_process_all_indexes() have a TRY/CATCH block.
    > As I heard, the overhead of setting/doing jumps is platform-dependent, and
    > not harmless on some platforms. Therefore, can we skip TRY/CATCH block
    > for non-autovacuum vacuum?  Possibly we could move it to AutoVacWorkerMain(),
    > that would save us from repeatedly setting a jump in autovacuum workers too.
    
    Good idea. I found try/catch block inside the "do_autovacuum" function that is
    obviously called only inside the autovacuum. I decided to move ReleaseAllWorkers
    call there.
    
    >
    > In general, I think this patchset badly lack of testing.  I think it needs tap tests
    > checking from the logs that autovacuum has been done in parallel.  Also, it
    > would be good to set up some injection points, and check that reserved
    > autovacuum parallel workers are getting released correctly in the case of errors.
    
    Some time ago I tried to write a test, but it looked very ugly. Your
    idea with injection points
    helped me to write much more reliable tests - see it in a new (v12)
    pack of patches.
    
    On Wed, Sep 17, 2025 at 1:31 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Mon, Sep 15, 2025 at 11:50 AM Alexander Korotkov
    > <aekorotkov@gmail.com> wrote:
    > >
    > > I see parallel_vacuum_process_all_indexes() have a TRY/CATCH block.  As I heard,
    > > the overhead of setting/doing jumps is platform-dependent, and not harmless on some
    > > platforms.  Therefore, can we skip TRY/CATCH block for non-autovacuum vacuum?
    > > Possibly we could move it to AutoVacWorkerMain(), that would save us from repeatedly
    > > setting a jump in autovacuum workers too.
    >
    > I wonder if using the TRY/CATCH block is not enough to ensure that
    > autovacuum workers release the reserved parallel workers in FATAL
    > cases.
    >
    
    That's true. I'll register "before_shmem_exit" callback for autovacuum,
    which will release workers if there are any reserved and if the a/v workers
    exits abnormally.
    
    >
    > IIUC the patch still has one problem in terms of reloading the
    > configuration parameters during parallel mode as I mentioned
    > before[1].
    >
    
    Yep. I was happy to see that you think that config file processing is OK for
    autovacuum :)
    I'll allow it for a/v leader. I've also thought about "compute_parallel_delay".
    The simplest solution that I see is to move cost-based delay parameters to
    shared state (PVShared) and create some variables such a
    VacuumSharedCostBalance, so we can use them inside vacuum_delay_point.
    What do you think about this idea?
    
    Another approaches like a "tell parallel workers that they should
    reload config"
    looks a bit too invasive IMO.
    
    
    Thanks everybody for the review! Please, see v12 patches :
    1) Implement tests for parallel autovacuum
    2) Fix error with unreleased workers - see try/catch block in do_autovacuum
    and before_shmem_exit callback registration in AutoVacWorkerMain
    3) Allow a/v leader to process config file (see guc.c)
    
    --
    Best regards,
    Daniil Davydov
    
  48. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-10-31T07:54:12Z

    Hi,
    
    On Tue, Oct 28, 2025 at 8:09 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Thanks everybody for the review! Please, see v12 patches :
    > 1) Implement tests for parallel autovacuum
    
    I forgot to add a new directory to Makefile and meson.build files.
    Fixed in v13 patches (only 0003 has changed).
    
    --
    Best regards,
    Daniil Davydov
    
  49. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2025-10-31T20:03:14Z

    On Tue, Oct 28, 2025 at 6:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > >
    > > IIUC the patch still has one problem in terms of reloading the
    > > configuration parameters during parallel mode as I mentioned
    > > before[1].
    > >
    >
    > Yep. I was happy to see that you think that config file processing is OK for
    > autovacuum :)
    > I'll allow it for a/v leader. I've also thought about "compute_parallel_delay".
    > The simplest solution that I see is to move cost-based delay parameters to
    > shared state (PVShared) and create some variables such a
    > VacuumSharedCostBalance, so we can use them inside vacuum_delay_point.
    > What do you think about this idea?
    
    I think that we need to somehow have parallel workers use the new
    vacuum delay parameters (e.g., VacuumCostPageHit and
    VacuumCostPageMiss) after the leader reloads the configuration file.
    The leader shares the initial parameters with the parallel workers
    (via DSM) before starting the workers but doesn't propagate the
    updates during the parallel operations. And the worker doesn't reload
    the configuration file.
    
    >
    > Another approaches like a "tell parallel workers that they should
    > reload config"
    > looks a bit too invasive IMO.
    >
    >
    > Thanks everybody for the review! Please, see v12 patches :
    > 1) Implement tests for parallel autovacuum
    > 2) Fix error with unreleased workers - see try/catch block in do_autovacuum
    > and before_shmem_exit callback registration in AutoVacWorkerMain
    > 3) Allow a/v leader to process config file (see guc.c)
    >
    
    Here are some review comments for 0001 patch:
    
    +static void
    +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    +{
    +        if (code != 0)
    +                AutoVacuumReleaseAllParallelWorkers();
    +}
    +
    
    AutoVacuumReleaseAllParallelWorkers() calls
    AutoVacuumReleaseParallelWorkers() only when av_nworkers_reserved > 0,
    so I think we don't need the condition 'if (code != 0)' here.
    
    ---
     +extern void AutoVacuumReleaseAllParallelWorkers(void);
    
    There is no caller of this function outside of autovacuum.h.
    
    ---
    { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
      short_desc => 'Maximum number of parallel autovacuum workers, that
    can be taken from bgworkers pool.',
      long_desc => 'This parameter is capped by "max_worker_processes"
    (not by "autovacuum_max_workers"!).',
      variable => 'autovacuum_max_parallel_workers',
      boot_val => '0',
      min => '0',
      max => 'MAX_BACKENDS',
    },
    
    Parallel vacuum in autovacuum can be used only when users set the
    autovacuum_parallel_workers storage parameter. How about using the
    default value 2 for autovacuum_max_parallel_workers GUC parameter?
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  50. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-11-20T19:31:45Z

    Hi,
    
    I started to review this patch set again, and it needed rebasing, so I
    went ahead and did that.
    
    I also have some comments:
    
    #1
    In AutoVacuumReserveParallelWorkers()
    I think here we should assert:
    
    ```
        Assert(nworkers <= AutoVacuumShmem->av_freeParallelWorkers);
    ```
    prior to:
    ```
    +       AutoVacuumShmem->av_freeParallelWorkers -= nworkers;
    ```
    
    We are capping nworkers earlier in parallel_vacuum_compute_workers()
    
    ```
      /* Cap by GUC variable */
      parallel_workers = Min(parallel_workers, max_workers);
    ```
    
    so the assert will safe-guard against someone making a faulty change
    in parallel_vacuum_compute_workers()
    
    
    #2
    In
    parallel_vacuum_process_all_indexes()
    
    ```
    +       /*
    +        * Reserve workers in autovacuum global state. Note, that we
    may be given
    +        * fewer workers than we requested.
    +        */
    +       if (AmAutoVacuumWorkerProcess() && nworkers > 0)
    +               nworkers = AutoVacuumReserveParallelWorkers(nworkers);
    ```
    
    nworkers has a double meaning. The return value of
    AutoVacuumReserveParallelWorkers
    is nreserved. I think this should be
    
    ```
    nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    ```
    
    and nreserved becomes the authoritative value for the number of parallel
    workers after that point.
    
    #3
    I noticed in the logging:
    
    ```
    2025-11-20 18:44:09.252 UTC [36787] LOG:  automatic vacuum of table
    "test.public.t": index scans: 0
            workers usage statistics for all of index scans : launched in
    total = 3, planned in total = 3
            pages: 0 removed, 503306 remain, 14442 scanned (2.87% of
    total), 0 eagerly scanned
            tuples: 101622 removed, 7557074 remain, 0 are dead but not yet removable
            removable cutoff: 1711, which was 1 XIDs old when operation ended
            frozen: 4793 pages from table (0.95% of total) had 98303 tuples frozen
            visibility map: 4822 pages set all-visible, 4745 pages set
    all-frozen (0 were all-visible)
            index scan bypassed: 8884 pages from table (1.77% of total)
    have 195512 dead item identifiers
    ```
    
    that even though index scan was bypased, we still launched parallel
    workers. I didn't dig deep into this,
    but that looks wrong. what do you think?
    
    #4
    instead of:
    
    "workers usage statistics for all of index scans : launched in total =
    0, planned in total = 0"
    
    how about:
    
    "parallel index scan : workers planned = 0, workers launched = 0"
    
    also log this after the "index scan needed:" line; so it looks like
    this. What do you think>
    
    ```
      index scan needed: 13211 pages from table (2.63% of total) had
    289482 dead item identifiers removed
      parallel index scan : workers planned = 0, workers launched = 0
      index "t_pkey": pages: 25234 in total, 0 newly deleted, 0 currently
    deleted, 0 reusable
      index "t_c1_idx": pages: 10219 in total, 0 newly deleted, 0
    currently deleted, 0 reusable
    ```
    
    
    --
    Sami Imseih
    Amazon Web Services (AWS)
    
  51. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-11-22T20:13:03Z

    Hi,
    
    On Sat, Nov 1, 2025 at 3:03 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Tue, Oct 28, 2025 at 6:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > I'll allow it for a/v leader. I've also thought about "compute_parallel_delay".
    > > The simplest solution that I see is to move cost-based delay parameters to
    > > shared state (PVShared) and create some variables such a
    > > VacuumSharedCostBalance, so we can use them inside vacuum_delay_point.
    > > What do you think about this idea?
    >
    > I think that we need to somehow have parallel workers use the new
    > vacuum delay parameters (e.g., VacuumCostPageHit and
    > VacuumCostPageMiss) after the leader reloads the configuration file.
    > The leader shares the initial parameters with the parallel workers
    > (via DSM) before starting the workers but doesn't propagate the
    > updates during the parallel operations. And the worker doesn't reload
    > the configuration file.
    
    I'm still working on it.
    
    > Here are some review comments for 0001 patch:
    >
    > +static void
    > +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    > +{
    > +        if (code != 0)
    > +                AutoVacuumReleaseAllParallelWorkers();
    > +}
    > +
    >
    > AutoVacuumReleaseAllParallelWorkers() calls
    > AutoVacuumReleaseParallelWorkers() only when av_nworkers_reserved > 0,
    > so I think we don't need the condition 'if (code != 0)' here.
    
    Yeah, I wrote it more like a hint for the reader - "we should call
    this function only
    if the process is exiting due to an error". But actually it is not
    necessary condition.
    
    >
    > ---
    >  +extern void AutoVacuumReleaseAllParallelWorkers(void);
    >
    > There is no caller of this function outside of autovacuum.h.
    >
    
    I will fix it.
    
    > ---
    > { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    > 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    >   short_desc => 'Maximum number of parallel autovacuum workers, that
    > can be taken from bgworkers pool.',
    >   long_desc => 'This parameter is capped by "max_worker_processes"
    > (not by "autovacuum_max_workers"!).',
    >   variable => 'autovacuum_max_parallel_workers',
    >   boot_val => '0',
    >   min => '0',
    >   max => 'MAX_BACKENDS',
    > },
    >
    > Parallel vacuum in autovacuum can be used only when users set the
    > autovacuum_parallel_workers storage parameter. How about using the
    > default value 2 for autovacuum_max_parallel_workers GUC parameter?
    >
    
    Sounds reasonable, +1 for it.
    
    
    On Fri, Nov 21, 2025 at 2:31 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > Hi,
    >
    > I started to review this patch set again, and it needed rebasing, so I
    > went ahead and did that.
    
    Thanks for the review and rebasing the patch!
    
    >
    > I also have some comments:
    >
    > #1
    > In AutoVacuumReserveParallelWorkers()
    > I think here we should assert:
    >
    > ```
    >     Assert(nworkers <= AutoVacuumShmem->av_freeParallelWorkers);
    > ```
    > prior to:
    > ```
    > +       AutoVacuumShmem->av_freeParallelWorkers -= nworkers;
    > ```
    >
    > We are capping nworkers earlier in parallel_vacuum_compute_workers()
    >
    > ```
    >   /* Cap by GUC variable */
    >   parallel_workers = Min(parallel_workers, max_workers);
    > ```
    >
    > so the assert will safe-guard against someone making a faulty change
    > in parallel_vacuum_compute_workers()
    >
    
    Hm, I guess it is just a bug. We should reduce av_freeParallelWorkers by the
    computed 'nreserved/ value (thus, we don't need any assertion). I'll fix it.
    
    >
    > #2
    > In
    > parallel_vacuum_process_all_indexes()
    >
    > ```
    > +       /*
    > +        * Reserve workers in autovacuum global state. Note, that we
    > may be given
    > +        * fewer workers than we requested.
    > +        */
    > +       if (AmAutoVacuumWorkerProcess() && nworkers > 0)
    > +               nworkers = AutoVacuumReserveParallelWorkers(nworkers);
    > ```
    >
    > nworkers has a double meaning. The return value of
    > AutoVacuumReserveParallelWorkers
    > is nreserved. I think this should be
    >
    > ```
    > nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    > ```
    >
    > and nreserved becomes the authoritative value for the number of parallel
    > workers after that point.
    
    Reserving parallel workers is specific for an autovacuum. If we add
    'nreserved' variable, we would have to change all conditions below in
    order not to break maintenance parallel vacuum. I think it will be confusing :
    ***
    if (nworkers > 0 || (AmAutoVacuumWorkerProcess() && nreserved > 0))
    ***
    
    Moreover, 'nworkers' reflects how many workers will be involved in vacuuming,
    and I think that capping it by 'nreserved' is not breaking this semantic.
    
    >
    > #3
    > I noticed in the logging:
    >
    > ```
    > 2025-11-20 18:44:09.252 UTC [36787] LOG:  automatic vacuum of table
    > "test.public.t": index scans: 0
    >         workers usage statistics for all of index scans : launched in
    > total = 3, planned in total = 3
    >         pages: 0 removed, 503306 remain, 14442 scanned (2.87% of
    > total), 0 eagerly scanned
    >         tuples: 101622 removed, 7557074 remain, 0 are dead but not yet removable
    >         removable cutoff: 1711, which was 1 XIDs old when operation ended
    >         frozen: 4793 pages from table (0.95% of total) had 98303 tuples frozen
    >         visibility map: 4822 pages set all-visible, 4745 pages set
    > all-frozen (0 were all-visible)
    >         index scan bypassed: 8884 pages from table (1.77% of total)
    > have 195512 dead item identifiers
    > ```
    >
    > that even though index scan was bypased, we still launched parallel
    > workers. I didn't dig deep into this,
    > but that looks wrong. what do you think?
    >
    
    We can do both index vacuuming and index cleanup in parallel. I guess that
    in your situation the vacuum was bypassed, but cleanup was called.
    
    > #4
    > instead of:
    >
    > "workers usage statistics for all of index scans : launched in total =
    > 0, planned in total = 0"
    >
    > how about:
    >
    > "parallel index scan : workers planned = 0, workers launched = 0"
    >
    > also log this after the "index scan needed:" line; so it looks like
    > this. What do you think>
    >
    > ```
    >   index scan needed: 13211 pages from table (2.63% of total) had
    > 289482 dead item identifiers removed
    >   parallel index scan : workers planned = 0, workers launched = 0
    >   index "t_pkey": pages: 25234 in total, 0 newly deleted, 0 currently
    > deleted, 0 reusable
    >   index "t_c1_idx": pages: 10219 in total, 0 newly deleted, 0
    > currently deleted, 0 reusable
    > ```
    
    Agree, it looks better.
    
    
    Thanks everybody for the comments!
    Please, see v15 patches.
    
    --
    Best regards,
    Daniil Davydov
    
  52. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2025-11-22T22:51:42Z

    > > nworkers has a double meaning. The return value of
    > > AutoVacuumReserveParallelWorkers
    > > is nreserved. I think this should be
    > >
    > > ```
    > > nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    > > ```
    > >
    > > and nreserved becomes the authoritative value for the number of parallel
    > > workers after that point.
    
    I could not find this pattern being used in the code base.
    I think it will be better and more in-line without what we generally do
    and pass-by-reference and update the value inside
    AutoVacuumReserveParallelWorkers:
    
    ```
    AutoVacuumReserveParallelWorkers(&nworkers).
    ```
    
    Maybe that's just my preference.
    
    >> ---
    >> { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    >> 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    >>   short_desc => 'Maximum number of parallel autovacuum workers, that
    >> can be taken from bgworkers pool.',
    >>   long_desc => 'This parameter is capped by "max_worker_processes"
    >> (not by "autovacuum_max_workers"!).',
    >>   variable => 'autovacuum_max_parallel_workers',
    >>   boot_val => '0',
    >>   min => '0',
    >>   max => 'MAX_BACKENDS',
    >> },
    >>
    >> Parallel vacuum in autovacuum can be used only when users set the
    >> autovacuum_parallel_workers storage parameter. How about using the
    >> default value 2 for autovacuum_max_parallel_workers GUC parameter?
    
    > Sounds reasonable, +1 for it.
    
    v15-0004 has an incorrect default value for `autovacuum_max_parallel_workers`.
    It should now be 2.
    
    +          Sets the maximum number of parallel autovacuum workers that
    +          can be used for parallel index vacuuming at one time. Is capped by
    +          <xref linkend="guc-max-worker-processes"/>. The default is 0,
    +          which means no parallel index vacuuming.
    
    --
    Sami
    
    
    
    
  53. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2025-11-23T15:02:22Z

    Hi,
    
    On Sun, Nov 23, 2025 at 5:51 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > > > nworkers has a double meaning. The return value of
    > > > AutoVacuumReserveParallelWorkers
    > > > is nreserved. I think this should be
    > > >
    > > > ```
    > > > nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    > > > ```
    > > >
    > > > and nreserved becomes the authoritative value for the number of parallel
    > > > workers after that point.
    >
    > I could not find this pattern being used in the code base.
    > I think it will be better and more in-line without what we generally do
    > and pass-by-reference and update the value inside
    > AutoVacuumReserveParallelWorkers:
    >
    > ```
    > AutoVacuumReserveParallelWorkers(&nworkers).
    > ```
    
    Maybe I just don't like functions with side effects, but this function will
    have ones anyway. I'll add logic with passing by reference as you
    suggested.
    
    >
    > >> ---
    > >> { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    > >> 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    > >>   short_desc => 'Maximum number of parallel autovacuum workers, that
    > >> can be taken from bgworkers pool.',
    > >>   long_desc => 'This parameter is capped by "max_worker_processes"
    > >> (not by "autovacuum_max_workers"!).',
    > >>   variable => 'autovacuum_max_parallel_workers',
    > >>   boot_val => '0',
    > >>   min => '0',
    > >>   max => 'MAX_BACKENDS',
    > >> },
    > >>
    > >> Parallel vacuum in autovacuum can be used only when users set the
    > >> autovacuum_parallel_workers storage parameter. How about using the
    > >> default value 2 for autovacuum_max_parallel_workers GUC parameter?
    >
    > > Sounds reasonable, +1 for it.
    >
    > v15-0004 has an incorrect default value for `autovacuum_max_parallel_workers`.
    > It should now be 2.
    >
    > +          Sets the maximum number of parallel autovacuum workers that
    > +          can be used for parallel index vacuuming at one time. Is capped by
    > +          <xref linkend="guc-max-worker-processes"/>. The default is 0,
    > +          which means no parallel index vacuuming.
    
    Thanks for noticing it! Fixed.
    
    I am sending an updated set of patches.
    
    --
    Best regards,
    Daniil Davydov
    
  54. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-01-05T18:51:11Z

    On Sun, Nov 23, 2025 at 7:02 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Sun, Nov 23, 2025 at 5:51 AM Sami Imseih <samimseih@gmail.com> wrote:
    > >
    > > > > nworkers has a double meaning. The return value of
    > > > > AutoVacuumReserveParallelWorkers
    > > > > is nreserved. I think this should be
    > > > >
    > > > > ```
    > > > > nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    > > > > ```
    > > > >
    > > > > and nreserved becomes the authoritative value for the number of parallel
    > > > > workers after that point.
    > >
    > > I could not find this pattern being used in the code base.
    > > I think it will be better and more in-line without what we generally do
    > > and pass-by-reference and update the value inside
    > > AutoVacuumReserveParallelWorkers:
    > >
    > > ```
    > > AutoVacuumReserveParallelWorkers(&nworkers).
    > > ```
    >
    > Maybe I just don't like functions with side effects, but this function will
    > have ones anyway. I'll add logic with passing by reference as you
    > suggested.
    >
    > >
    > > >> ---
    > > >> { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    > > >> 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    > > >>   short_desc => 'Maximum number of parallel autovacuum workers, that
    > > >> can be taken from bgworkers pool.',
    > > >>   long_desc => 'This parameter is capped by "max_worker_processes"
    > > >> (not by "autovacuum_max_workers"!).',
    > > >>   variable => 'autovacuum_max_parallel_workers',
    > > >>   boot_val => '0',
    > > >>   min => '0',
    > > >>   max => 'MAX_BACKENDS',
    > > >> },
    > > >>
    > > >> Parallel vacuum in autovacuum can be used only when users set the
    > > >> autovacuum_parallel_workers storage parameter. How about using the
    > > >> default value 2 for autovacuum_max_parallel_workers GUC parameter?
    > >
    > > > Sounds reasonable, +1 for it.
    > >
    > > v15-0004 has an incorrect default value for `autovacuum_max_parallel_workers`.
    > > It should now be 2.
    > >
    > > +          Sets the maximum number of parallel autovacuum workers that
    > > +          can be used for parallel index vacuuming at one time. Is capped by
    > > +          <xref linkend="guc-max-worker-processes"/>. The default is 0,
    > > +          which means no parallel index vacuuming.
    >
    > Thanks for noticing it! Fixed.
    >
    > I am sending an updated set of patches.
    
    Thank you for updating the patch! I've reviewed the 0001 patch and
    here are some comments:
    
    ---
    +        /* Remember how many workers we have reserved. */
    +        av_nworkers_reserved += *nworkers;
    
    I think we can simply assign *nworkers to av_nworkers_reserved instead
    of incrementing it as we're sure that av_nworkers_reserved is 0 at the
    beginning of this function.
    
    ---
    +static void
    +AutoVacuumReleaseAllParallelWorkers(void)
    +{
    +        /* Only leader worker can call this function. */
    +        Assert(AmAutoVacuumWorkerProcess() && !IsParallelWorker());
    +
    +        if (av_nworkers_reserved > 0)
    +                AutoVacuumReleaseParallelWorkers(av_nworkers_reserved);
    +}
    
    We can put an assertion at the end of the function to verify that this
    worker doesn't reserve any worker.
    
    ---
    +static void
    +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    +{
    +        if (code != 0)
    +                AutoVacuumReleaseAllParallelWorkers();
    +}
    
    I think it would be more future-proof if we call
    AutoVacuumReleaseAllParallelWorkers() regardless of the code if there
    is no strong reason why we check the code there.
    
    ---
    +        before_shmem_exit(autovacuum_worker_before_shmem_exit, 0);
    
         /*
          * Create a per-backend PGPROC struct in shared memory.  We must do this
          * before we can use LWLocks or access any shared memory.
          */
         InitProcess();
    
    I think it's better to register the
    autovacuum_worker_before_shmem_exit() after the process
    initialization. The function could use LWLocks to release the reserved
    workers. Given that AutoVacuumReleaseAllParallelWorkers() doesn't try
    to release the reserved worker when av_nworkers_reserved == 0, but it
    would be more future-proof to do that after the basic process
    initialization processes.
    
    How about renaming autovacuum_worker_before_shmem_exit() to
    autovacuum_worker_onexit()?
    
    ---
    IIUC the patch needs to implement some logic to propagate the updates
    of vacuum delay parameters to parallel vacuum workers. Are you still
    working on it? Or shall I draft this part on top of the 0001 patch?
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  55. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-01-05T20:44:02Z

    Hi,
    
    On Tue, Jan 6, 2026 at 1:51 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Sun, Nov 23, 2025 at 7:02 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > Hi,
    > >
    > > On Sun, Nov 23, 2025 at 5:51 AM Sami Imseih <samimseih@gmail.com> wrote:
    > > >
    > > > > > nworkers has a double meaning. The return value of
    > > > > > AutoVacuumReserveParallelWorkers
    > > > > > is nreserved. I think this should be
    > > > > >
    > > > > > ```
    > > > > > nreserved = AutoVacuumReserveParallelWorkers(nworkers);
    > > > > > ```
    > > > > >
    > > > > > and nreserved becomes the authoritative value for the number of parallel
    > > > > > workers after that point.
    > > >
    > > > I could not find this pattern being used in the code base.
    > > > I think it will be better and more in-line without what we generally do
    > > > and pass-by-reference and update the value inside
    > > > AutoVacuumReserveParallelWorkers:
    > > >
    > > > ```
    > > > AutoVacuumReserveParallelWorkers(&nworkers).
    > > > ```
    > >
    > > Maybe I just don't like functions with side effects, but this function will
    > > have ones anyway. I'll add logic with passing by reference as you
    > > suggested.
    > >
    > > >
    > > > >> ---
    > > > >> { name => 'autovacuum_max_parallel_workers', type => 'int', context =>
    > > > >> 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    > > > >>   short_desc => 'Maximum number of parallel autovacuum workers, that
    > > > >> can be taken from bgworkers pool.',
    > > > >>   long_desc => 'This parameter is capped by "max_worker_processes"
    > > > >> (not by "autovacuum_max_workers"!).',
    > > > >>   variable => 'autovacuum_max_parallel_workers',
    > > > >>   boot_val => '0',
    > > > >>   min => '0',
    > > > >>   max => 'MAX_BACKENDS',
    > > > >> },
    > > > >>
    > > > >> Parallel vacuum in autovacuum can be used only when users set the
    > > > >> autovacuum_parallel_workers storage parameter. How about using the
    > > > >> default value 2 for autovacuum_max_parallel_workers GUC parameter?
    > > >
    > > > > Sounds reasonable, +1 for it.
    > > >
    > > > v15-0004 has an incorrect default value for `autovacuum_max_parallel_workers`.
    > > > It should now be 2.
    > > >
    > > > +          Sets the maximum number of parallel autovacuum workers that
    > > > +          can be used for parallel index vacuuming at one time. Is capped by
    > > > +          <xref linkend="guc-max-worker-processes"/>. The default is 0,
    > > > +          which means no parallel index vacuuming.
    > >
    > > Thanks for noticing it! Fixed.
    > >
    > > I am sending an updated set of patches.
    >
    > Thank you for updating the patch! I've reviewed the 0001 patch and
    > here are some comments:
    
    Thank you for the review!
    
    >
    > ---
    > +        /* Remember how many workers we have reserved. */
    > +        av_nworkers_reserved += *nworkers;
    >
    > I think we can simply assign *nworkers to av_nworkers_reserved instead
    > of incrementing it as we're sure that av_nworkers_reserved is 0 at the
    > beginning of this function.
    
    Agree, it will be more clear.
    
    >
    > ---
    > +static void
    > +AutoVacuumReleaseAllParallelWorkers(void)
    > +{
    > +        /* Only leader worker can call this function. */
    > +        Assert(AmAutoVacuumWorkerProcess() && !IsParallelWorker());
    > +
    > +        if (av_nworkers_reserved > 0)
    > +                AutoVacuumReleaseParallelWorkers(av_nworkers_reserved);
    > +}
    >
    > We can put an assertion at the end of the function to verify that this
    > worker doesn't reserve any worker.
    
    It's not a problem to add this assertion, but I have doubts : we have a
    function that promises to release a given number of workers, but we are
    still checking whether a specified number of workers have been released.
    
    I suggest another place for assertion - see comment below.
    
    >
    > ---
    > +static void
    > +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    > +{
    > +        if (code != 0)
    > +                AutoVacuumReleaseAllParallelWorkers();
    > +}
    >
    > I think it would be more future-proof if we call
    > AutoVacuumReleaseAllParallelWorkers() regardless of the code if there
    > is no strong reason why we check the code there.
    
    I think we can leave "code != 0" so as not to confuse the readers, but
    add the assertion that at the end of the function all workers have been
    released. Thus, we are telling that 1) in normal processing we must not
    have reserved workers and 2) even after a FATAL error we are sure
    that we don't have reserved workers.
    
    >
    > ---
    > +        before_shmem_exit(autovacuum_worker_before_shmem_exit, 0);
    >
    >      /*
    >       * Create a per-backend PGPROC struct in shared memory.  We must do this
    >       * before we can use LWLocks or access any shared memory.
    >       */
    >      InitProcess();
    >
    > I think it's better to register the
    > autovacuum_worker_before_shmem_exit() after the process
    > initialization. The function could use LWLocks to release the reserved
    > workers. Given that AutoVacuumReleaseAllParallelWorkers() doesn't try
    > to release the reserved worker when av_nworkers_reserved == 0, but it
    > would be more future-proof to do that after the basic process
    > initialization processes.
    
    My bad, I miss the comment above InitProcess. Agree with you.
    Just in case, callback registration will be invoked after BaseInit.
    
    >
    > How about renaming autovacuum_worker_before_shmem_exit() to
    > autovacuum_worker_onexit()?
    
    We also have "on_shmem_exit" callbacks. Maybe "onexit" naming can confuse
    somebody?..
    Since the function name does not cross line length boundary anywhere, I suggest
    leaving the current naming.
    
    > ---
    > IIUC the patch needs to implement some logic to propagate the updates
    > of vacuum delay parameters to parallel vacuum workers.
    
    Yep.
    
    > Are you still working on it? Or shall I draft this part on top of the
    > 0001 patch?
    
    I thought about some "beautiful" approach, but for now I have
    only one idea - force parallel a/v workers to get values for these
    parameters from shmem (which obviously can be modified by the
    leader a/v process). I'll post this patch in the near future.
    
    
    Please, see v17 patches (only 0001 has been changed).
    
    --
    Best regards,
    Daniil Davydov
    
  56. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-01-07T09:51:06Z

    Hi,
    
    On Tue, Jan 6, 2026 at 3:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > On Tue, Jan 6, 2026 at 1:51 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Are you still working on it? Or shall I draft this part on top of the
    > > 0001 patch?
    >
    > I thought about some "beautiful" approach, but for now I have
    > only one idea - force parallel a/v workers to get values for these
    > parameters from shmem (which obviously can be modified by the
    > leader a/v process). I'll post this patch in the near future.
    >
    
    I am posting a draft version of the patch (see 0005) that allows parallel
    leader to propagate changes of cost-based parameters to its parallel
    workers. It is a very rough fix, but it reflects my idea that is to have some
    shared state from which parallel workers can get values for the parameters
    (and which only leader worker can modify, obviously).
    
    I have also added a test that shows that this idea is working - the test
    ensures that parallel workers can change its parameters if they have been
    changed for the leader worker.
    
    Note that so far the work is in progress - this logic works only for
    vacuum_cost_delay and vacuum_cost_limits parameters. I think that we
    should agree on an idea first, and only then apply logic for all appropriate
    parameters.
    
    What do you think?
    
    --
    Best regards,
    Daniil Davydov
    
  57. Re: POC: Parallel processing of indexes in autovacuum

    zengman <zengman@halodbtech.com> — 2026-01-07T13:51:05Z

    Hi,
    
    I noticed one thing: autovacuum_max_parallel_workers is initialized to 0 in globals.c, 
    but its GUC default (boot_val) is '2' in guc_parameters.dat. While GUC overrides it on startup, 
    this mismatch may cause confusion. Perhaps we should modify this to match the approach for max_parallel_workers.
    
    --
    Regards,
    Man Zeng
    www.openhalo.org
  58. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-01-07T20:52:04Z

    Hi,
    
    On Wed, Jan 7, 2026 at 8:51 PM zengman <zengman@halodbtech.com> wrote:
    >
    > I noticed one thing: autovacuum_max_parallel_workers is initialized to 0 in globals.c,
    > but its GUC default (boot_val) is '2' in guc_parameters.dat. While GUC overrides it on startup,
    > this mismatch may cause confusion. Perhaps we should modify this to match the approach for max_parallel_workers.
    >
    
    Good catch, thank you!
    I'll fix it in the next version of the patch.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  59. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-01-15T02:13:16Z

    On Wed, Jan 7, 2026 at 1:51 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Tue, Jan 6, 2026 at 3:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > On Tue, Jan 6, 2026 at 1:51 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > > >
    > > > Are you still working on it? Or shall I draft this part on top of the
    > > > 0001 patch?
    > >
    > > I thought about some "beautiful" approach, but for now I have
    > > only one idea - force parallel a/v workers to get values for these
    > > parameters from shmem (which obviously can be modified by the
    > > leader a/v process). I'll post this patch in the near future.
    > >
    >
    > I am posting a draft version of the patch (see 0005) that allows parallel
    > leader to propagate changes of cost-based parameters to its parallel
    > workers. It is a very rough fix, but it reflects my idea that is to have some
    > shared state from which parallel workers can get values for the parameters
    > (and which only leader worker can modify, obviously).
    >
    > I have also added a test that shows that this idea is working - the test
    > ensures that parallel workers can change its parameters if they have been
    > changed for the leader worker.
    >
    > Note that so far the work is in progress - this logic works only for
    > vacuum_cost_delay and vacuum_cost_limits parameters. I think that we
    > should agree on an idea first, and only then apply logic for all appropriate
    > parameters.
    >
    > What do you think?
    
    Thank you for updating the patches! Here are review comments.
    
    * 0001 patch
    
    +static void
    +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    +{
    +   if (code != 0)
    +       AutoVacuumReleaseAllParallelWorkers();
    +
    +   Assert(av_nworkers_reserved == 0);
    +}
    
    While adding the assertion here makes sense, the assertion won't work
    in non-assertion builds. I guess it's safer to call
    AutoVacuumReleaseAllParallelWorkers() regardless of the code to ensure
    that no autovacuum workers exit while holding parallel workers.
    
    ---
    +   before_shmem_exit(autovacuum_worker_before_shmem_exit, 0);
    
    I think it would be better to set this callback later like before the
    main loop of processing the tables as it makes no sense even if we set
    it very early.
    
    ---
    +   /*
    +    * Cap the number of free workers by new parameter's value, if needed.
    +    */
    +   AutoVacuumShmem->av_freeParallelWorkers =
    +       Min(AutoVacuumShmem->av_freeParallelWorkers,
    +           autovacuum_max_parallel_workers);
    +
    +   if (autovacuum_max_parallel_workers > prev_max_parallel_workers)
    +   {
    +       /*
    +        * If user wants to increase number of parallel autovacuum workers, we
    +        * must increase number of free workers.
    +        */
    +       AutoVacuumShmem->av_freeParallelWorkers +=
    +           (autovacuum_max_parallel_workers - prev_max_parallel_workers);
    +   }
    
    Suppose the previous autovacuum_max_parallel_workers is 5 and there
    are 2 workers are reserved (i.e., there are 3 free parallel workers),
    if the autovacuum_max_parallel_workers changes to 2, the new
    AutoVacuumShmem->av_freeParallelWorkers would be 2 based on the above
    codes, but I believe that the new number of free workers should be 0
    as there are already 2 workers are running. What do you think? I guess
    we can calculate the new number of free workers by:
    
    Max((autovacuum_max_parallel_workers - prev_max_parallel_workers) +
    AutoVacuumShmem->av_freeParallelWorkers), 0)
    
    ---
    I've attached a patch proposing some minor changes.
    
    
    * 0002 patch
    
    +   /*
    +    * Number of planned and actually launched parallel workers for all index
    +    * scans, or NULL
    +    */
    +   PVWorkersUsage *workers_usage;
    
    I think that LVRelState can have PVWorkersUsage instead of a pointer to it.
    
    ---
    +       /*
    +        * Allocate space for workers usage statistics. Thus, we explicitly
    +        * make clear that such statistics must be accumulated. For now, this
    +        * is used only by autovacuum leader worker, because it must log it in
    +        * the end of table processing.
    +        */
    +       vacrel->workers_usage = AmAutoVacuumWorkerProcess() ?
    +           (PVWorkersUsage *) palloc0(sizeof(PVWorkersUsage)) :
    +           NULL;
    
    I think we can report the worker statistics even in VACUUM VERBOSE
    logs. Currently VACUUM VERBOSE reports the worker usage just during
    index vacuuming but it would make sense to report the overall
    statistics in vacuum logs. It would help make VACUUM VERBOSE logs and
    autovacuum logs consistent.
    
    But we don't need to report the worker usage if we didn't use the
    parallel vacuum (i.e., if npanned == 0).
    
    ---
    +       /* Remember these values, if we asked to. */
    +       if (wusage != NULL)
    +       {
    +           wusage->nlaunched += pvs->pcxt->nworkers_launched;
    +           wusage->nplanned += nworkers;
    +       }
    
    This code runs after the attempt to reserve parallel workers.
    Consequently, if we fail to reserve any workers due to
    autovacuum_max_parallel_workers, we report the status as if parallel
    vacuum wasn't planned at all. I think knowing the number of workers
    that were planned but not reserved would provide valuable insight for
    users tuning autovacuum_max_parallel_workers.
    
    ---
    +           if (vacrel->workers_usage)
    +               appendStringInfo(&buf,
    +                                _("parallel index vacuum/cleanup :
    workers planned = %d, workers launched = %d\n"),
    +                                vacrel->workers_usage->nplanned,
    +                                vacrel->workers_usage->nlaunched);
    
    Since these numbers are the total number of workers planned and
    launched, how about changing it to something "parallel index
    vacuum/cleanup: %d workers were planned and %d workers were launched
    in total"?
    
    
    * 0003 patch
    
    +typedef enum AVLeaderFaulureType
    +{
    +   FAIL_NONE,
    +   FAIL_ERROR,
    +   FAIL_FATAL,
    +}          AVLeaderFaulureType;
    
    I'm concerned that it is somewhat overwrapped with what injection
    points does as we can set 'error' to injection_points_attach(). For
    the FATAL error, we can terminate the autovacuum worker by using
    pg_terminate_backend() that keeps waiting due to
    injection_point_attach() with action='wait'.
    
    ---
    +   /*
    +    * Injection point to help exercising number of available parallel
    +    * autovacuum workers.
    +    */
    +   INJECTION_POINT("autovacuum-set-free-parallel-workers-num",
    +                   &AutoVacuumShmem->av_freeParallelWorkers);
    
    This injection point is added to two places. IIUC the purpose of this
    function is to update the free_parallel_workers of InjPointState. And
    that value is taken by get_parallel_autovacuum_free_workers() SQL
    function during the TAP test. I guess it's better to have
    get_parallel_autovacuum_free_workers() function to direcly check
    av_freeParallelWorkers with a proper locking.
    
    ---
    It would be great if we could test the av_freeParallelWorkers
    adjustment when max_parallel_maintenance_workers changes.
    
    
    * 0005 patch
    
    +typedef struct PVSharedCostParams
    +{
    +   slock_t     spinlock; /* protects all fields below */
    +
    +   /* Copies of corresponding parameters from autovacuum leader process */
    +   double  cost_delay;
    +   int     cost_limit;
    +}      PVSharedCostParams;
    
    Since Parallel workers don't reload the config file I think other
    vacuum delay related parameters such as VacuumCostPage{Miss|Hit|Dirty}
    also needs to be shared by the leader.
    
    ---
    +   if (!AmAutoVacuumWorkerProcess())
    +   {
    +       /*
    +        * If we are autovacuum parallel worker, check whether cost-based
    +        * parameters had changed in leader worker.
    +        * If so, vacuum_cost_delay and vacuum_cost_limit will be set to the
    +        * values which leader worker is operating on.
    +        *
    +        * Do it before checking VacuumCostActive, because its value might be
    +        * changed after leader's parameters consumption.
    +        */
    +       parallel_vacuum_fix_cost_based_params();
    +   }
    
    We need to add checks to prevent the normal backend running the VACUUM
    command from calling parallel_vacuum_fix_cost_based_params().
    
    IIUC autovacuum parallel workers would call
    parallel_vacuum_fix_cost_based_params() and update their
    vacuum_cost_{delay|limit} every vacuum_delay_point().
    
    ---
    +/*
    + * Function to be called from parallel autovacuum worker in order to sync
    + * some cost-based delay parameter with the leader worker.
    + */
    +bool
    +parallel_vacuum_fix_cost_based_params(void)
    +{
    
    The 'fix' doesn't sound right to me as it's not broken actually. How
    about something like parallel_vacuum_update_shared_delay_params?
    
    +   Assert(IsParallelWorker() && !AmAutoVacuumWorkerProcess());
    +
    +   SpinLockAcquire(&pv_shared_cost_params->spinlock);
    +
    +   vacuum_cost_delay = pv_shared_cost_params->cost_delay;
    +   vacuum_cost_limit = pv_shared_cost_params->cost_limit;
    +
    +   SpinLockRelease(&pv_shared_cost_params->spinlock);
    
    IIUC autovacuum parallel workers seems to update their
    vacuum_cost_{delay|limit} every vacuum_delay_point(), which seems not
    good. Can we somehow avoid unnecessary updates?
    
    ---
    +
    +   if (vacuum_cost_delay > 0 && !VacuumFailsafeActive)
    +       VacuumCostActive = true;
    +
    
    Should we consider the case of disabling VacuumCostActive as well?
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  60. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-01-16T14:10:54Z

    Hi,
    
    On Thu, Jan 15, 2026 at 9:13 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Thank you for updating the patches! Here are review comments.
    >
    
    Thank you for the review!
    
    >
    > +static void
    > +autovacuum_worker_before_shmem_exit(int code, Datum arg)
    > +{
    > +   if (code != 0)
    > +       AutoVacuumReleaseAllParallelWorkers();
    > +
    > +   Assert(av_nworkers_reserved == 0);
    > +}
    >
    > While adding the assertion here makes sense, the assertion won't work
    > in non-assertion builds. I guess it's safer to call
    > AutoVacuumReleaseAllParallelWorkers() regardless of the code to ensure
    > that no autovacuum workers exit while holding parallel workers.
    >
    
    OK, I agree.
    
    > ---
    > +   before_shmem_exit(autovacuum_worker_before_shmem_exit, 0);
    >
    > I think it would be better to set this callback later like before the
    > main loop of processing the tables as it makes no sense even if we set
    > it very early.
    
    Yeah, agree. I'll also add a comment for it, because we already have a
    "ReleaseAllParallelWorkers" function call in the try/catch block below.
    
    >
    > ---
    > +   /*
    > +    * Cap the number of free workers by new parameter's value, if needed.
    > +    */
    > +   AutoVacuumShmem->av_freeParallelWorkers =
    > +       Min(AutoVacuumShmem->av_freeParallelWorkers,
    > +           autovacuum_max_parallel_workers);
    > +
    > +   if (autovacuum_max_parallel_workers > prev_max_parallel_workers)
    > +   {
    > +       /*
    > +        * If user wants to increase number of parallel autovacuum workers, we
    > +        * must increase number of free workers.
    > +        */
    > +       AutoVacuumShmem->av_freeParallelWorkers +=
    > +           (autovacuum_max_parallel_workers - prev_max_parallel_workers);
    > +   }
    >
    > Suppose the previous autovacuum_max_parallel_workers is 5 and there
    > are 2 workers are reserved (i.e., there are 3 free parallel workers),
    > if the autovacuum_max_parallel_workers changes to 2, the new
    > AutoVacuumShmem->av_freeParallelWorkers would be 2 based on the above
    > codes, but I believe that the new number of free workers should be 0
    > as there are already 2 workers are running. What do you think? I guess
    > we can calculate the new number of free workers by:
    >
    > Max((autovacuum_max_parallel_workers - prev_max_parallel_workers) +
    > AutoVacuumShmem->av_freeParallelWorkers), 0)
    >
    
    If av_max_parallel_workers was changed to 2, then we not only set
    freeParallelWorkers to 2 but also set maxParallelWorkers to 2.
    Thus, when previously reserved two workers are released, av leader will
    encounter this code:
    
    /*
     * If the maximum number of parallel workers was reduced during execution,
     * we must cap available workers number by its new value.
     */
    AutoVacuumShmem->av_freeParallelWorkers =
        Min(AutoVacuumShmem->av_freeParallelWorkers + nworkers,
               AutoVacuumShmem->av_maxParallelWorkers);
    
    I.e. freeParallelWorkers will be left as "2".
    
    The formula you suggested is also correct, but if you have no objections,
    I would prefer not to change the existing logic. It seems more reliable for
    me when av leader explicitly can consider such a situation.
    
    > ---
    > I've attached a patch proposing some minor changes.
    >
    
    Thanks! I agree with all fixes except a single one:
    - * NOTE: We will try to provide as many workers as requested, even if caller
    - * will occupy all available workers.
    
    I think that this is a pretty important point. I'll leave this NOTE in the
    v19 patch set. Do you mind?
    
    >
    > +   /*
    > +    * Number of planned and actually launched parallel workers for all index
    > +    * scans, or NULL
    > +    */
    > +   PVWorkersUsage *workers_usage;
    >
    > I think that LVRelState can have PVWorkersUsage instead of a pointer to it.
    >
    
    Previously I used the NULL value of this pointer as a flag that we don't need
    to log workers usage. Now I'll add boolean flag for this purpose (IIUC,
    "nplanned > 0" condition is not enough to determine whether we should log
    workers usage, because VACUUM PARALLEL can be called without VERBOSE).
    
    > ---
    > +       /*
    > +        * Allocate space for workers usage statistics. Thus, we explicitly
    > +        * make clear that such statistics must be accumulated. For now, this
    > +        * is used only by autovacuum leader worker, because it must log it in
    > +        * the end of table processing.
    > +        */
    > +       vacrel->workers_usage = AmAutoVacuumWorkerProcess() ?
    > +           (PVWorkersUsage *) palloc0(sizeof(PVWorkersUsage)) :
    > +           NULL;
    >
    > I think we can report the worker statistics even in VACUUM VERBOSE
    > logs. Currently VACUUM VERBOSE reports the worker usage just during
    > index vacuuming but it would make sense to report the overall
    > statistics in vacuum logs. It would help make VACUUM VERBOSE logs and
    > autovacuum logs consistent.
    >
    
    Agree.
    
    > But we don't need to report the worker usage if we didn't use the
    > parallel vacuum (i.e., if npanned == 0).
    >
    
    As I wrote above - we don't need to log workers usage if the VERBOSE option
    is not specified (even if nplanned > 0). Am I missing something?
    
    > ---
    > +       /* Remember these values, if we asked to. */
    > +       if (wusage != NULL)
    > +       {
    > +           wusage->nlaunched += pvs->pcxt->nworkers_launched;
    > +           wusage->nplanned += nworkers;
    > +       }
    >
    > This code runs after the attempt to reserve parallel workers.
    > Consequently, if we fail to reserve any workers due to
    > autovacuum_max_parallel_workers, we report the status as if parallel
    > vacuum wasn't planned at all. I think knowing the number of workers
    > that were planned but not reserved would provide valuable insight for
    > users tuning autovacuum_max_parallel_workers.
    >
    
    100% agree.
    
    > ---
    > +           if (vacrel->workers_usage)
    > +               appendStringInfo(&buf,
    > +                                _("parallel index vacuum/cleanup :
    > workers planned = %d, workers launched = %d\n"),
    > +                                vacrel->workers_usage->nplanned,
    > +                                vacrel->workers_usage->nlaunched);
    >
    > Since these numbers are the total number of workers planned and
    > launched, how about changing it to something "parallel index
    > vacuum/cleanup: %d workers were planned and %d workers were launched
    > in total"?
    >
    
    Agree.
    
    >
    > +typedef enum AVLeaderFaulureType
    > +{
    > +   FAIL_NONE,
    > +   FAIL_ERROR,
    > +   FAIL_FATAL,
    > +}          AVLeaderFaulureType;
    >
    > I'm concerned that it is somewhat overwrapped with what injection
    > points does as we can set 'error' to injection_points_attach(). For
    > the FATAL error, we can terminate the autovacuum worker by using
    > pg_terminate_backend() that keeps waiting due to
    > injection_point_attach() with action='wait'.
    >
    
    Oh, I didn't know about the possibility of testing FATAL errors with
    pg_terminate_backend. After reading your letter I found this pattern
    in signal_autovacuum.pl. This is beautiful.
    Thank you, I'll rework these tests.
    
    > ---
    > +   /*
    > +    * Injection point to help exercising number of available parallel
    > +    * autovacuum workers.
    > +    */
    > +   INJECTION_POINT("autovacuum-set-free-parallel-workers-num",
    > +                   &AutoVacuumShmem->av_freeParallelWorkers);
    >
    > This injection point is added to two places. IIUC the purpose of this
    > function is to update the free_parallel_workers of InjPointState. And
    > that value is taken by get_parallel_autovacuum_free_workers() SQL
    > function during the TAP test. I guess it's better to have
    > get_parallel_autovacuum_free_workers() function to direcly check
    > av_freeParallelWorkers with a proper locking.
    >
    
    Agree.
    
    > ---
    > It would be great if we could test the av_freeParallelWorkers
    > adjustment when max_parallel_maintenance_workers changes.
    >
    
    You mean "when autovacuum_max_parallel_workers changes"?
    I'll add a test for it.
    
    >
    > * 0005 patch
    >
    > +typedef struct PVSharedCostParams
    > +{
    > +   slock_t     spinlock; /* protects all fields below */
    > +
    > +   /* Copies of corresponding parameters from autovacuum leader process */
    > +   double  cost_delay;
    > +   int     cost_limit;
    > +}      PVSharedCostParams;
    >
    > Since Parallel workers don't reload the config file I think other
    > vacuum delay related parameters such as VacuumCostPage{Miss|Hit|Dirty}
    > also needs to be shared by the leader.
    >
    
    Yes, I remember it. I didn't add them in the previous patch because it was
    experimental. I'll add all appropriate parameters in v19.
    
    > ---
    > +   if (!AmAutoVacuumWorkerProcess())
    > +   {
    > +       /*
    > +        * If we are autovacuum parallel worker, check whether cost-based
    > +        * parameters had changed in leader worker.
    > +        * If so, vacuum_cost_delay and vacuum_cost_limit will be set to the
    > +        * values which leader worker is operating on.
    > +        *
    > +        * Do it before checking VacuumCostActive, because its value might be
    > +        * changed after leader's parameters consumption.
    > +        */
    > +       parallel_vacuum_fix_cost_based_params();
    > +   }
    >
    > We need to add checks to prevent the normal backend running the VACUUM
    > command from calling parallel_vacuum_fix_cost_based_params().
    >
    
    We already have such check inside the "fix_cost_based" function :
    /* Check whether we are running parallel autovacuum */
    if (pv_shared_cost_params == NULL)
        return false;
    
    We also have this comment:
    * If we are autovacuum parallel worker, check whether cost-based
    * parameters had changed in leader worker.
    
    As an alternative, I'll add comment explicitly saying that process will
    immediately return if it not parallel autovacuum participant.
    
    > IIUC autovacuum parallel workers would call
    > parallel_vacuum_fix_cost_based_params() and update their
    > vacuum_cost_{delay|limit} every vacuum_delay_point().
    >
    
    Yep.
    
    > ---
    > +/*
    > + * Function to be called from parallel autovacuum worker in order to sync
    > + * some cost-based delay parameter with the leader worker.
    > + */
    > +bool
    > +parallel_vacuum_fix_cost_based_params(void)
    > +{
    >
    > The 'fix' doesn't sound right to me as it's not broken actually. How
    > about something like parallel_vacuum_update_shared_delay_params?
    >
    
    Agree.
    
    > +   Assert(IsParallelWorker() && !AmAutoVacuumWorkerProcess());
    > +
    > +   SpinLockAcquire(&pv_shared_cost_params->spinlock);
    > +
    > +   vacuum_cost_delay = pv_shared_cost_params->cost_delay;
    > +   vacuum_cost_limit = pv_shared_cost_params->cost_limit;
    > +
    > +   SpinLockRelease(&pv_shared_cost_params->spinlock);
    >
    > IIUC autovacuum parallel workers seems to update their
    > vacuum_cost_{delay|limit} every vacuum_delay_point(), which seems not
    > good. Can we somehow avoid unnecessary updates?
    
    More precisely, parallel worker *reads* leader's parameters every delay_point.
    Obviously, this does not mean that the parameters will necessarily be updated.
    
    But I don't see anything wrong with this logic. We just every time get the most
    relevant parameters from the leader. Of course we can introduce some
    signaling mechanism, but it will have the same effect as in the current code.
    
    > ---
    > +
    > +   if (vacuum_cost_delay > 0 && !VacuumFailsafeActive)
    > +       VacuumCostActive = true;
    > +
    >
    > Should we consider the case of disabling VacuumCostActive as well?
    >
    
    I think that we should. I'll add VacuumUpdateCosts function call instead
    of write this logic manually. IIUC, it will not break anything.
    
    
    Again, thank you very much for the review!
    Please, see v19 patches which including all above comments
    and zengman's notice. Main changes :
    1) Fixes for before_shmem_exit callback
    2) Some comments reword + pgindent on all files
    3) Workers usage can also be reported for VACUUM PARALLEL
    4) Deeply reworked tests
    5) Propagation (from leader to worker) of all cost-based delay parameters
    
    I have also changed structure of the patch set - now test and documentation
    are the last patches to be applied.
    
    --
    Best regards,
    Daniil Davydov
    
  61. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-01-16T22:20:14Z

    On Fri, Jan 16, 2026 at 6:11 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Jan 15, 2026 at 9:13 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > >
    > > ---
    > > +   /*
    > > +    * Cap the number of free workers by new parameter's value, if needed.
    > > +    */
    > > +   AutoVacuumShmem->av_freeParallelWorkers =
    > > +       Min(AutoVacuumShmem->av_freeParallelWorkers,
    > > +           autovacuum_max_parallel_workers);
    > > +
    > > +   if (autovacuum_max_parallel_workers > prev_max_parallel_workers)
    > > +   {
    > > +       /*
    > > +        * If user wants to increase number of parallel autovacuum workers, we
    > > +        * must increase number of free workers.
    > > +        */
    > > +       AutoVacuumShmem->av_freeParallelWorkers +=
    > > +           (autovacuum_max_parallel_workers - prev_max_parallel_workers);
    > > +   }
    > >
    > > Suppose the previous autovacuum_max_parallel_workers is 5 and there
    > > are 2 workers are reserved (i.e., there are 3 free parallel workers),
    > > if the autovacuum_max_parallel_workers changes to 2, the new
    > > AutoVacuumShmem->av_freeParallelWorkers would be 2 based on the above
    > > codes, but I believe that the new number of free workers should be 0
    > > as there are already 2 workers are running. What do you think? I guess
    > > we can calculate the new number of free workers by:
    > >
    > > Max((autovacuum_max_parallel_workers - prev_max_parallel_workers) +
    > > AutoVacuumShmem->av_freeParallelWorkers), 0)
    > >
    >
    > If av_max_parallel_workers was changed to 2, then we not only set
    > freeParallelWorkers to 2 but also set maxParallelWorkers to 2.
    > Thus, when previously reserved two workers are released, av leader will
    > encounter this code:
    >
    > /*
    >  * If the maximum number of parallel workers was reduced during execution,
    >  * we must cap available workers number by its new value.
    >  */
    > AutoVacuumShmem->av_freeParallelWorkers =
    >     Min(AutoVacuumShmem->av_freeParallelWorkers + nworkers,
    >            AutoVacuumShmem->av_maxParallelWorkers);
    >
    > I.e. freeParallelWorkers will be left as "2".
    >
    > The formula you suggested is also correct, but if you have no objections,
    > I would prefer not to change the existing logic. It seems more reliable for
    > me when av leader explicitly can consider such a situation.
    
    Looking at AutoVacuumReserveParallelWorkers(), it seems that we don't
    check the av_maxParallelWorkers() there. Is it possible that two more
    workers would be reserved even while the existing 2 workers are
    running?
    
     /* Provide as many workers as we can. */
     *nworkers = Min(AutoVacuumShmem->av_freeParallelWorkers, *nworkers);
     AutoVacuumShmem->av_freeParallelWorkers -= *nworkers;
    
    Some review comments on v19-0001 patch:
    
    +       /* Release all the reserved parallel workers for autovacuum */
    +       if (AmAutoVacuumWorkerProcess() && pvs->pcxt->nworkers_launched > 0)
    +           AutoVacuumReleaseParallelWorkers(pvs->pcxt->nworkers_launched);
    
    Since we want to release all reserved workers here, I think it's clear
    if we use AutoVacuumReleaseAllParallelWorkers() and we add
    Assert(av_nworkers_reserved == 0) at the end of
    AutoVacuumReleaseAllParallelWorkers(). This way, we can ensure that
    all workers are released and it makes the codes more readable. What do
    you think?
    
    I've attached the patch proposing this change (please find
    v19-0001_masahiko.patch).
    
    ---
    +#autovacuum_max_parallel_workers = 2    # disabled by default and limited by
    +                                        # max_worker_processes
    
    It's odd to me that the comment says it's disabled by default while
    being set to 2. I think we can rewrite the comment to:
    
    +#autovacuum_max_parallel_workers = 2    # limited by max_worker_processes
    
    BTW it seems to me that this GUC should be capped by
    max_parallel_workers instead of max_worker_processes, no?
    
    ---
    +  long_desc => 'This parameter is capped by "max_worker_processes"
    (not by "autovacuum_max_workers"!).',
    
    I'm concerned that this kind of description might not be appropriate
    to the description in long_desc. Looking at long_desc contents of
    other GUC parameters, we describe the detail of the parameters (e.g.,
    "0 means xxx" or detailed explanation of the effect). Probably we can
    remove this line.
    
    >
    > > ---
    > > I've attached a patch proposing some minor changes.
    > >
    >
    > Thanks! I agree with all fixes except a single one:
    > - * NOTE: We will try to provide as many workers as requested, even if caller
    > - * will occupy all available workers.
    >
    > I think that this is a pretty important point. I'll leave this NOTE in the
    > v19 patch set. Do you mind?
    
    No, I agree with that.
    
    >
    > >
    > > +   /*
    > > +    * Number of planned and actually launched parallel workers for all index
    > > +    * scans, or NULL
    > > +    */
    > > +   PVWorkersUsage *workers_usage;
    > >
    > > I think that LVRelState can have PVWorkersUsage instead of a pointer to it.
    > >
    >
    > Previously I used the NULL value of this pointer as a flag that we don't need
    > to log workers usage. Now I'll add boolean flag for this purpose (IIUC,
    > "nplanned > 0" condition is not enough to determine whether we should log
    > workers usage, because VACUUM PARALLEL can be called without VERBOSE).
    
    Can't we simply not report the worker usage if nplanned is 0?
    
    >
    > > ---
    > > +       /*
    > > +        * Allocate space for workers usage statistics. Thus, we explicitly
    > > +        * make clear that such statistics must be accumulated. For now, this
    > > +        * is used only by autovacuum leader worker, because it must log it in
    > > +        * the end of table processing.
    > > +        */
    > > +       vacrel->workers_usage = AmAutoVacuumWorkerProcess() ?
    > > +           (PVWorkersUsage *) palloc0(sizeof(PVWorkersUsage)) :
    > > +           NULL;
    > >
    > > I think we can report the worker statistics even in VACUUM VERBOSE
    > > logs. Currently VACUUM VERBOSE reports the worker usage just during
    > > index vacuuming but it would make sense to report the overall
    > > statistics in vacuum logs. It would help make VACUUM VERBOSE logs and
    > > autovacuum logs consistent.
    > >
    >
    > Agree.
    >
    > > But we don't need to report the worker usage if we didn't use the
    > > parallel vacuum (i.e., if npanned == 0).
    > >
    >
    > As I wrote above - we don't need to log workers usage if the VERBOSE option
    > is not specified (even if nplanned > 0). Am I missing something?
    
    No. My point is that even when the VERBOSE option is specified, we can
    skip reporting the worker usage if the parallel vacuum is not even
    planned. That is, I think we can do like:
    
        if (vacrel->workers_usage.nplanned > 0)
            appendStringInfo(&buf,
                             _("parallel index vacuum/cleanup: %d workers
    were planned and %d workers were launched in total\n"),
                             vacrel->workers_usage.nplanned,
                             vacrel->workers_usage.nlaunched);
    
    >
    > > ---
    > > +       /* Remember these values, if we asked to. */
    > > +       if (wusage != NULL)
    > > +       {
    > > +           wusage->nlaunched += pvs->pcxt->nworkers_launched;
    > > +           wusage->nplanned += nworkers;
    > > +       }
    > >
    > > This code runs after the attempt to reserve parallel workers.
    > > Consequently, if we fail to reserve any workers due to
    > > autovacuum_max_parallel_workers, we report the status as if parallel
    > > vacuum wasn't planned at all. I think knowing the number of workers
    > > that were planned but not reserved would provide valuable insight for
    > > users tuning autovacuum_max_parallel_workers.
    > >
    >
    > 100% agree.
    
    Thank you for updating the patch. I think that we need the explanation
    of what nlaunched and nplanned actually mean in the PVWorkersUsage
    definition:
    
    +typedef struct PVWorkersUsage
    +{
    +   int         nlaunched;
    +   int         nplanned;
    +} PVWorkersUsage;
    
    I'm concerned that readers might be confused that nplanned is not the
    number of parallel workers we actually planned to launch.
    
    Or it might make sense to track these three values: planned, reserved,
    and launched. For example, suppose max_worker_processes = 10 and
    autovacuum_max_parallel_workers = 5, if two autovacuum workers try to
    reserve 3 workers each, one worker can reserve and launch 3 and
    another worker can reserve and launch 2. The autovacuum logs would be
    "3 planned and 3 launched" and "3 planned and 2 launched". Users can
    deal with the shortage of parallel workers by increasing
    autovacuum_max_parallel_workers. On the other hand, if some bgworkers
    are being used by other components (.e.g, parallel queries, logical
    replication etc.) and there are only 2 free bgworkers, the autovacuum
    worker can reserve 3 but can launch only 2, and other worker can
    reserve 2 but cannot launch any workers. The autovacuum logs would be
    "3 planned and 2 launched" and "3 planned and 0 launched". Here
    increasing autovacuum_max_parallel_workers resolves the shortage of
    parallel workers, but users would have to increase
    max_worker_processes instead. If we can report the worker usage like
    "3 planned, 3 reserved, and 2 launched" and "3 planned, 2 reserved,
    and 0 launched", users would realize the need to increase
    max_worker_processes. Of course, the "xxx reserved" information would
    not be necessary for VACUUM VERBOSE logs.
    
    >
    > >
    > > +typedef enum AVLeaderFaulureType
    > > +{
    > > +   FAIL_NONE,
    > > +   FAIL_ERROR,
    > > +   FAIL_FATAL,
    > > +}          AVLeaderFaulureType;
    > >
    > > I'm concerned that it is somewhat overwrapped with what injection
    > > points does as we can set 'error' to injection_points_attach(). For
    > > the FATAL error, we can terminate the autovacuum worker by using
    > > pg_terminate_backend() that keeps waiting due to
    > > injection_point_attach() with action='wait'.
    > >
    >
    > Oh, I didn't know about the possibility of testing FATAL errors with
    > pg_terminate_backend. After reading your letter I found this pattern
    > in signal_autovacuum.pl. This is beautiful.
    > Thank you, I'll rework these tests.
    
    +1
    
    >
    > > ---
    > > It would be great if we could test the av_freeParallelWorkers
    > > adjustment when max_parallel_maintenance_workers changes.
    > >
    >
    > You mean "when autovacuum_max_parallel_workers changes"?
    > I'll add a test for it.
    
    Yes, thanks!
    
    >
    > > ---
    > > +   if (!AmAutoVacuumWorkerProcess())
    > > +   {
    > > +       /*
    > > +        * If we are autovacuum parallel worker, check whether cost-based
    > > +        * parameters had changed in leader worker.
    > > +        * If so, vacuum_cost_delay and vacuum_cost_limit will be set to the
    > > +        * values which leader worker is operating on.
    > > +        *
    > > +        * Do it before checking VacuumCostActive, because its value might be
    > > +        * changed after leader's parameters consumption.
    > > +        */
    > > +       parallel_vacuum_fix_cost_based_params();
    > > +   }
    > >
    > > We need to add checks to prevent the normal backend running the VACUUM
    > > command from calling parallel_vacuum_fix_cost_based_params().
    > >
    >
    > We already have such check inside the "fix_cost_based" function :
    > /* Check whether we are running parallel autovacuum */
    > if (pv_shared_cost_params == NULL)
    >     return false;
    >
    > We also have this comment:
    > * If we are autovacuum parallel worker, check whether cost-based
    > * parameters had changed in leader worker.
    >
    > As an alternative, I'll add comment explicitly saying that process will
    > immediately return if it not parallel autovacuum participant.
    
    Why don't we add IsInParallelMode() or IsParallelWorker() check before
    calling parallel_vacuum_update_shared_delay_params()?
    
    Some review comments on v19-0003 patch:
    
    +bool
    +parallel_vacuum_update_shared_delay_params(void)
    +{
    +   /* Check whether we are running parallel autovacuum */
    +   if (pv_shared_cost_params == NULL)
    +       return false;
    +
    +   Assert(IsParallelWorker() && !AmAutoVacuumWorkerProcess());
    
    These codes are a bit odd to me in two points:
    
    1. A process can never be both a parallel worker and an autovacuum worker.
    
    2. If pv_shared_cost_parame == NULL, even autovacuum workers and
    non-parallel workers can call this function, but it seems to be
    unexpected function call given the subsequent assertion. If we want to
    have an assertion to ensure that a function is called only by
    processes we expect or allow, I think we should add an assertion to
    the beginning of function. How about rewriting these parts to:
    
        Assert(IsParallelWorker());
    
        /* Check whether we are running parallel autovacuum */
        if (pv_shared_cost_params == NULL)
            return false;
    
    ---
    +        * Note, that this function has no effect if we are non-autovacuum
    +        * parallel worker.
    +        */
    
    I don't think this kind of comment should be noted here since if we
    change the parallel_vacuum_update_shared_delay_params() behavior in
    the future, such comments would get easily out-of-sync.
    
    >
    > > +   Assert(IsParallelWorker() && !AmAutoVacuumWorkerProcess());
    > > +
    > > +   SpinLockAcquire(&pv_shared_cost_params->spinlock);
    > > +
    > > +   vacuum_cost_delay = pv_shared_cost_params->cost_delay;
    > > +   vacuum_cost_limit = pv_shared_cost_params->cost_limit;
    > > +
    > > +   SpinLockRelease(&pv_shared_cost_params->spinlock);
    > >
    > > IIUC autovacuum parallel workers seems to update their
    > > vacuum_cost_{delay|limit} every vacuum_delay_point(), which seems not
    > > good. Can we somehow avoid unnecessary updates?
    >
    > More precisely, parallel worker *reads* leader's parameters every delay_point.
    > Obviously, this does not mean that the parameters will necessarily be updated.
    >
    > But I don't see anything wrong with this logic. We just every time get the most
    > relevant parameters from the leader. Of course we can introduce some
    > signaling mechanism, but it will have the same effect as in the current code.
    
    Although the parameter propagation itself is working correctly, the
    current implementation seems suboptimal performance-wise. Acquiring an
    additional spinlock and updating the local variables for every block
    seems too costly to me. IIUC we would end up incurring these costs
    even when vacuum delays are disabled. I think we need to find a better
    way.
    
    For example, we can have a generation of these parameters. That is,
    the leader increments the generation (stored in PVSharedCostParams)
    whenever updating them after reloading the configuration file, and
    workers maintain its generation of the parameters currently used. If
    the worker's generation < the global generation, it updates its
    parameters along with its generation. I think we can implement the
    generation using pg_atomic_u32, making the check for parameter updates
    lock-free. There might be better ideas, though.
    
    I'll review the patches for regression tests and the documentation.
    
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  62. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-01-17T14:52:32Z

    Hi,
    
    On Sat, Jan 17, 2026 at 5:20 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Looking at AutoVacuumReserveParallelWorkers(), it seems that we don't
    > check the av_maxParallelWorkers() there. Is it possible that two more
    > workers would be reserved even while the existing 2 workers are
    > running?
    >
    >  /* Provide as many workers as we can. */
    >  *nworkers = Min(AutoVacuumShmem->av_freeParallelWorkers, *nworkers);
    >  AutoVacuumShmem->av_freeParallelWorkers -= *nworkers;
    >
    
    OK, I got it. You are right. I'll use the formula that you mentioned in the
    previous letter. I'll also add a test for it.
    
    >
    > +       /* Release all the reserved parallel workers for autovacuum */
    > +       if (AmAutoVacuumWorkerProcess() && pvs->pcxt->nworkers_launched > 0)
    > +           AutoVacuumReleaseParallelWorkers(pvs->pcxt->nworkers_launched);
    >
    > Since we want to release all reserved workers here, I think it's clear
    > if we use AutoVacuumReleaseAllParallelWorkers() and we add
    > Assert(av_nworkers_reserved == 0) at the end of
    > AutoVacuumReleaseAllParallelWorkers(). This way, we can ensure that
    > all workers are released and it makes the codes more readable. What do
    > you think?
    >
    
    Agree that this will be more clear for the readers.
    
    > I've attached the patch proposing this change (please find
    > v19-0001_masahiko.patch).
    
    Thank you, I'll apply this patch. A few things in the patch that I changed :
    1)
    + * The caller must call AutoVacuumReleaseParallelWorkers() to release the...
    I think that we also should mention AutoVacuumReleaseAllParallelWorkers.
    2)
    + * Similar to AutoVacuumReleaseParallelWorkers(), but this function releases...
    If you don't mind, I'll leave the "same as above" formulation since this is
    typical for the postgres code.
    
    >
    > +#autovacuum_max_parallel_workers = 2    # disabled by default and limited by
    > +                                        # max_worker_processes
    >
    > It's odd to me that the comment says it's disabled by default while
    > being set to 2. I think we can rewrite the comment to:
    >
    > +#autovacuum_max_parallel_workers = 2    # limited by max_worker_processes
    >
    
    Good catch. I forgot to change this comment.
    
    > BTW it seems to me that this GUC should be capped by
    > max_parallel_workers instead of max_worker_processes, no?
    >
    
    I explained my point about it here [1] and here [2]. What do you think?
    
    > ---
    > +  long_desc => 'This parameter is capped by "max_worker_processes"
    > (not by "autovacuum_max_workers"!).',
    >
    > I'm concerned that this kind of description might not be appropriate
    > to the description in long_desc. Looking at long_desc contents of
    > other GUC parameters, we describe the detail of the parameters (e.g.,
    > "0 means xxx" or detailed explanation of the effect). Probably we can
    > remove this line.
    >
    
    Agree.
    
    > >
    > > Previously I used the NULL value of this pointer as a flag that we don't need
    > > to log workers usage. Now I'll add boolean flag for this purpose (IIUC,
    > > "nplanned > 0" condition is not enough to determine whether we should log
    > > workers usage, because VACUUM PARALLEL can be called without VERBOSE).
    >
    > Can't we simply not report the worker usage if nplanned is 0?
    >
    > >
    > > As I wrote above - we don't need to log workers usage if the VERBOSE option
    > > is not specified (even if nplanned > 0). Am I missing something?
    >
    > No. My point is that even when the VERBOSE option is specified, we can
    > skip reporting the worker usage if the parallel vacuum is not even
    > planned. That is, I think we can do like:
    >
    >     if (vacrel->workers_usage.nplanned > 0)
    >         appendStringInfo(&buf,
    >                          _("parallel index vacuum/cleanup: %d workers
    > were planned and %d workers were launched in total\n"),
    >                          vacrel->workers_usage.nplanned,
    >                          vacrel->workers_usage.nlaunched);
    >
    
    Sorry, I forgot that we accumulate the messages for logging only if
    "instrument == true". It will cut off the case when manual vacuum is
    called without the VERBOSE option.
    
    >
    > Thank you for updating the patch. I think that we need the explanation
    > of what nlaunched and nplanned actually mean in the PVWorkersUsage
    > definition
    >
    > +typedef struct PVWorkersUsage
    > +{
    > +   int         nlaunched;
    > +   int         nplanned;
    > +} PVWorkersUsage;
    >
    > I'm concerned that readers might be confused that nplanned is not the
    > number of parallel workers we actually planned to launch.
    >
    > Or it might make sense to track these three values: planned, reserved,
    > and launched. For example, suppose max_worker_processes = 10 and
    > autovacuum_max_parallel_workers = 5, if two autovacuum workers try to
    > reserve 3 workers each, one worker can reserve and launch 3 and
    > another worker can reserve and launch 2. The autovacuum logs would be
    > "3 planned and 3 launched" and "3 planned and 2 launched". Users can
    > deal with the shortage of parallel workers by increasing
    > autovacuum_max_parallel_workers. On the other hand, if some bgworkers
    > are being used by other components (.e.g, parallel queries, logical
    > replication etc.) and there are only 2 free bgworkers, the autovacuum
    > worker can reserve 3 but can launch only 2, and other worker can
    > reserve 2 but cannot launch any workers. The autovacuum logs would be
    > "3 planned and 2 launched" and "3 planned and 0 launched". Here
    > increasing autovacuum_max_parallel_workers resolves the shortage of
    > parallel workers, but users would have to increase
    > max_worker_processes instead. If we can report the worker usage like
    > "3 planned, 3 reserved, and 2 launched" and "3 planned, 2 reserved,
    > and 0 launched", users would realize the need to increase
    > max_worker_processes. Of course, the "xxx reserved" information would
    > not be necessary for VACUUM VERBOSE logs.
    >
    
    Hm, I think that reporting of "nreserved" would make it easier for the user to
    understand what is going on. Thanks for the detailed explanation, I'll
    implement it.
    
    > >
    > > We already have such check inside the "fix_cost_based" function :
    > > /* Check whether we are running parallel autovacuum */
    > > if (pv_shared_cost_params == NULL)
    > >     return false;
    > >
    > > We also have this comment:
    > > * If we are autovacuum parallel worker, check whether cost-based
    > > * parameters had changed in leader worker.
    > >
    > > As an alternative, I'll add comment explicitly saying that process will
    > > immediately return if it not parallel autovacuum participant.
    >
    > Why don't we add IsInParallelMode() or IsParallelWorker() check before
    > calling parallel_vacuum_update_shared_delay_params()?
    
    Considering your suggestion below, I will add this check.
    
    >
    > +bool
    > +parallel_vacuum_update_shared_delay_params(void)
    > +{
    > +   /* Check whether we are running parallel autovacuum */
    > +   if (pv_shared_cost_params == NULL)
    > +       return false;
    > +
    > +   Assert(IsParallelWorker() && !AmAutoVacuumWorkerProcess());
    >
    > These codes are a bit odd to me in two points:
    >
    > 1. A process can never be both a parallel worker and an autovacuum worker.
    >
    > 2. If pv_shared_cost_parame == NULL, even autovacuum workers and
    > non-parallel workers can call this function, but it seems to be
    > unexpected function call given the subsequent assertion. If we want to
    > have an assertion to ensure that a function is called only by
    > processes we expect or allow, I think we should add an assertion to
    > the beginning of function. How about rewriting these parts to:
    >
    >     Assert(IsParallelWorker());
    >
    >     /* Check whether we are running parallel autovacuum */
    >     if (pv_shared_cost_params == NULL)
    >         return false;
    >
    
    Agree, it will be much more clear.
    
    > ---
    > +        * Note, that this function has no effect if we are non-autovacuum
    > +        * parallel worker.
    > +        */
    >
    > I don't think this kind of comment should be noted here since if we
    > change the parallel_vacuum_update_shared_delay_params() behavior in
    > the future, such comments would get easily out-of-sync.
    >
    
    If behavior will be changed, then all comments for this function will need to
    be changed, actually. Don't get me wrong - I just think that this Note is
    important for the readers. But if you doubt its usefulness, I don't
    mind deleting it.
    
    > > > IIUC autovacuum parallel workers seems to update their
    > > > vacuum_cost_{delay|limit} every vacuum_delay_point(), which seems not
    > > > good. Can we somehow avoid unnecessary updates?
    > >
    > > More precisely, parallel worker *reads* leader's parameters every delay_point.
    > > Obviously, this does not mean that the parameters will necessarily be updated.
    > >
    > > But I don't see anything wrong with this logic. We just every time get the most
    > > relevant parameters from the leader. Of course we can introduce some
    > > signaling mechanism, but it will have the same effect as in the current code.
    >
    > Although the parameter propagation itself is working correctly, the
    > current implementation seems suboptimal performance-wise. Acquiring an
    > additional spinlock and updating the local variables for every block
    > seems too costly to me. IIUC we would end up incurring these costs
    > even when vacuum delays are disabled. I think we need to find a better
    > way.
    >
    > For example, we can have a generation of these parameters. That is,
    > the leader increments the generation (stored in PVSharedCostParams)
    > whenever updating them after reloading the configuration file, and
    > workers maintain its generation of the parameters currently used. If
    > the worker's generation < the global generation, it updates its
    > parameters along with its generation. I think we can implement the
    > generation using pg_atomic_u32, making the check for parameter updates
    > lock-free. There might be better ideas, though.
    >
    
    OK, I see your point. Considering that we need to check some shared state (in
    order to understand whether we should update our params), an atomic variable
    seem to be the best solution.
    
    
    Thank you for the review! Please, see v20 patches. Main changes :
    1) Add new formula for freeParallelWorkers computation
    2) Add 'nreserved' logging for parallel autovacuum
    3) Add atomic variable to speed up checking shared params state change
    4) New test for autovacuum_max_parallel_workers parameter change
    5) Fully get rid of "custom" injection points in tests
    
    BTW, I think that we need more fixes for documentation, so I'll
    take a look at it in the near future.
    
    [1] https://www.postgresql.org/message-id/CAJDiXgiYiX%2BazuR76DcVx8fZn57m_4v6cB14-GW34mWa%3DqudFQ%40mail.gmail.com
    [2] https://www.postgresql.org/message-id/CAJDiXgjX%2BbO%3DdEZxpnsh588N3BsQ%3D7MHX3YQSJS6FxqGq4zMqQ%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
  63. Re: POC: Parallel processing of indexes in autovacuum

    Sami Imseih <samimseih@gmail.com> — 2026-01-21T22:22:11Z

    Hi,
    
    I took a look at v20-0001,0002 and 0003 and have some comments.
    
    v20-0001:
    
    1/
    
    ```
    +
    +       /*
    +        * Cap or increase number of free parallel workers according to the
    +        * parameter change.
    +        */
    +       AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    +
    +       /*
    +        * Don't allow number of free workers to become less than zero if the
    +        * patameter was decreased.
    +        */
    +       AutoVacuumShmem->av_freeParallelWorkers =
    +               Max(AutoVacuumShmem->av_freeParallelWorkers, 0);
    ```
    
    This can probably be simplified to:
    
    ```
    AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    ```
    
    v20-0002:
    
    1/
    
    I don't think showing "reserved" in the logging is needed and could be
    confusing.
    
    ```
    parallel index vacuum/cleanup: 3 workers were planned, 3 workers were
    reserved and 3 workers were launched in total
    ```
    
    Also, even though the table has `autovacuum_parallel_workers = 2`, I
    see 3 workers.
    This is because one worker was for cleanup due to a gin index on the
    table. I think it's better
    to show separate lines for index vacuuming and index cleanup, just
    like VACUUM VERBOSE.
    
    ```
    INFO:  launched 2 parallel vacuum workers for index vacuuming (planned: 2)
    INFO:  launched 1 parallel vacuum worker for index cleanup (planned: 1)
    ```
    
    otherwise it will lead the user to think 3 workers were allocated for
    either vacuuming or cleanup.
    
    
    v20-0003:
    
    1/
    
    inside vacuum_delay_point, I would re-organize the checks to
    first run the code block for the a/v worker:
    
    ```
    if (ConfigReloadPending && AmAutoVacuumWorkerProcess())
    ```
    
    then the a/v/ parallel worker:
    
    ```
    if (!AmAutoVacuumWorkerProcess() && IsParallelWorker())
    ```
    
    But I am also wondering if we should have a specific backend_type
    for "autovacuum parallel worker" to differentiate that from the
    existing "autovacuum worker".
    
    and also we can have a helper macro like:
    ```
    #define AmAutoVacuumParallelWorkerProcess() (MyBackendType ==
    B_AUTOVAC_PARALLEL_WORKER)
    ```
    
    What do you think?
    
    2/
    
    Add
    ```
    +typedef struct PVSharedCostParams
    ````
    
    to src/tools/pgindent/typedefs.list
    
    3/
    
    +               pg_atomic_init_u32(&shared->cost_params.generation, 0);
    +               SpinLockInit(&shared->cost_params.spinlock);
    +               pv_shared_cost_params = &(shared->cost_params);
    
    NIT: move SpinLockInit last
    
    4/
    
    Instead of:
    
    ```
    +       params_generation =
    pg_atomic_read_u32(&pv_shared_cost_params->generation);
    +
    ```
    and then later on:
    ````
    +       /*
    +        * Increase generation of the parameters, i.e. let parallel workers know
    +        * that they should re-read shared cost params.
    +        */
    +       pg_atomic_write_u32(&pv_shared_cost_params->generation,
    +                                               params_generation + 1);
    +
    +       SpinLockRelease(&pv_shared_cost_params->spinlock);
    ```
    
    why can't we just do:
    
    pg_atomic_fetch_add_u32(&pv_shared_cost_params->generation, 1);
    
    Also, do the pg_atomic_fetch_add_u32 outside of the spinlock. right?
    
    
    --
    Sami Imseih
    Amazon Web Services (AWS)
    
    
    
    
  64. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-01-21T22:28:24Z

    On Sat, Jan 17, 2026 at 6:52 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    >
    >
    > > I've attached the patch proposing this change (please find
    > > v19-0001_masahiko.patch).
    >
    > Thank you, I'll apply this patch. A few things in the patch that I changed :
    > 1)
    > + * The caller must call AutoVacuumReleaseParallelWorkers() to release the...
    > I think that we also should mention AutoVacuumReleaseAllParallelWorkers.
    > 2)
    > + * Similar to AutoVacuumReleaseParallelWorkers(), but this function releases...
    > If you don't mind, I'll leave the "same as above" formulation since this is
    > typical for the postgres code.
    >
    
    Agreed.
    
    >
    > > BTW it seems to me that this GUC should be capped by
    > > max_parallel_workers instead of max_worker_processes, no?
    > >
    >
    > I explained my point about it here [1] and here [2]. What do you think?
    
    I agree that autovacuum_max_parallel_workers should not be capped by
    other GUC parametres when setting a value. However, these messages
    seem not explain why this parameter is limited by max_worker_processes
    instead of max_parallel_workers. You mentioned:
    
    > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > waste time initializing a parallel context if there is no chance that
    > the request will succeed.
    > But it's worth remembering that actually the
    > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > capped by 'max_parallel_workers'.
    
    It doesn't make sense to me that we limit
    autovacuum_max_parallel_workers by max_worker_processes TBH. When
    users want to have more parallel vacuum workers for autovacuum and the
    VACUUM command, they would have to consider max_worker_processes,
    max_parallel_workers, and max_parallel_maintenance_workers separately.
    Given that max_parallel_workers is controlling the number of
    max_worker_processes that can be used in parallel operations, I
    believe that parallel vacuum workers for autovacuum should also be
    taken from that pool.
    
    >
    > > ---
    > > +        * Note, that this function has no effect if we are non-autovacuum
    > > +        * parallel worker.
    > > +        */
    > >
    > > I don't think this kind of comment should be noted here since if we
    > > change the parallel_vacuum_update_shared_delay_params() behavior in
    > > the future, such comments would get easily out-of-sync.
    > >
    >
    > If behavior will be changed, then all comments for this function will need to
    > be changed, actually. Don't get me wrong - I just think that this Note is
    > important for the readers. But if you doubt its usefulness, I don't
    > mind deleting it.
    
    I still could not figure out why it should be mentioned here instead
    of at the comment of parallel_vacuum_update_shared_delay_params().
    Readers can notice that calling
    parallel_vacuum_update_shared_delay_params() for parallel vacuum
    worker for the VACUUM command has no effect when reading the function.
    In my opinion, we should mention here why we call
    parallel_vacuum_update_shared_delay_params() but should not mention
    what the called function does because it should have been described in
    that function.
    
    BTW can we expose pv_shared_cost_params so that we can check it in
    vacuum_delay_point() before trying to call
    parallel_vacuum_update_shared_delay_params()?
    
    >
    > > > > IIUC autovacuum parallel workers seems to update their
    > > > > vacuum_cost_{delay|limit} every vacuum_delay_point(), which seems not
    > > > > good. Can we somehow avoid unnecessary updates?
    > > >
    > > > More precisely, parallel worker *reads* leader's parameters every delay_point.
    > > > Obviously, this does not mean that the parameters will necessarily be updated.
    > > >
    > > > But I don't see anything wrong with this logic. We just every time get the most
    > > > relevant parameters from the leader. Of course we can introduce some
    > > > signaling mechanism, but it will have the same effect as in the current code.
    > >
    > > Although the parameter propagation itself is working correctly, the
    > > current implementation seems suboptimal performance-wise. Acquiring an
    > > additional spinlock and updating the local variables for every block
    > > seems too costly to me. IIUC we would end up incurring these costs
    > > even when vacuum delays are disabled. I think we need to find a better
    > > way.
    > >
    > > For example, we can have a generation of these parameters. That is,
    > > the leader increments the generation (stored in PVSharedCostParams)
    > > whenever updating them after reloading the configuration file, and
    > > workers maintain its generation of the parameters currently used. If
    > > the worker's generation < the global generation, it updates its
    > > parameters along with its generation. I think we can implement the
    > > generation using pg_atomic_u32, making the check for parameter updates
    > > lock-free. There might be better ideas, though.
    > >
    >
    > OK, I see your point. Considering that we need to check some shared state (in
    > order to understand whether we should update our params), an atomic variable
    > seem to be the best solution.
    >
    >
    > Thank you for the review! Please, see v20 patches. Main changes :
    > 1) Add new formula for freeParallelWorkers computation
    > 2) Add 'nreserved' logging for parallel autovacuum
    > 3) Add atomic variable to speed up checking shared params state change
    > 4) New test for autovacuum_max_parallel_workers parameter change
    > 5) Fully get rid of "custom" injection points in tests
    >
    
    The 0001 patch looks mostly good to me except for the above comment
    (max_worker_processes vs. max_parallel_workers) and the following
    point:
    
    +   nfree_workers =
    +       autovacuum_max_parallel_workers - prev_max_parallel_workers +
    +       AutoVacuumShmem->av_freeParallelWorkers;
    +
    +   /*
    +    * Cap or increase number of free parallel workers according to the
    +    * parameter change.
    +    */
    +   AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    +
    +   /*
    +    * Don't allow number of free workers to become less than zero if the
    +    * patameter was decreased.
    +    */
    +   AutoVacuumShmem->av_freeParallelWorkers =
    +       Max(AutoVacuumShmem->av_freeParallelWorkers, 0);
    
    Why does it do Max(x, 0) twice?
    
    * 0002 patch:
    
    +           if (vacrel->workers_usage.nplanned > 0 &&
    +               AmAutoVacuumWorkerProcess())
    +           {
    +               /* Worker usage stats for parallel autovacuum */
    +               appendStringInfo(&buf,
    +                                _("parallel index vacuum/cleanup: %d
    workers were planned, %d workers were reserved and %d workers were
    launched in total\n"),
    +                                vacrel->workers_usage.nplanned,
    +                                vacrel->workers_usage.nreserved,
    +                                vacrel->workers_usage.nlaunched);
    +           }
    +           else if (vacrel->workers_usage.nplanned > 0)
    +           {
    +               /* Worker usage stats for manual VACUUM (PARALLEL) */
    +               appendStringInfo(&buf,
    +                                _("parallel index vacuum/cleanup: %d
    workers were planned and %d workers were launched in total\n"),
    +                                vacrel->workers_usage.nplanned,
    +                                vacrel->workers_usage.nlaunched);
    +           }
    
    Can we refactoring these codes to:
    
    if (vacrel->workers_usage.nplanned > 0)
    {
        if (AmAutoVacuumWorkerProcess())
            appendStringInfo(...);
        else
            appendStringInfo(...);
    
    * 0003 patch:
    
    +   if (!AmAutoVacuumWorkerProcess() && IsParallelWorker())
    +   {
    
    We can just check IsParallelWorker() here.
    
    ---
    +extern void parallel_vacuum_update_shared_delay_params(void);
    +extern void parallel_vacuum_propagate_cost_based_params(void);
    
    I think it's better to have similar names to these functions for
    consistency and readability. How about the following?
    
    parallel_vacuum_update_delay_params();
    parallel_vacuum_propagate_delay_params();
    
    ---
    +
    +   params_generation = pg_atomic_read_u32(&pv_shared_cost_params->generation);
    +
    +   SpinLockAcquire(&pv_shared_cost_params->spinlock);
    +
    +   pv_shared_cost_params->cost_delay = vacuum_cost_delay;
    +   pv_shared_cost_params->cost_limit = vacuum_cost_limit;
    +   pv_shared_cost_params->cost_page_dirty = VacuumCostPageDirty;
    +   pv_shared_cost_params->cost_page_hit = VacuumCostPageHit;
    +   pv_shared_cost_params->cost_page_miss = VacuumCostPageMiss;
    
    I think we can check if the new cost-based delay parameters are really
    changed before changing the shared values. If users don't change
    cost-based delay parameters, we don't need to increment the generation
    at all.
    
    ---
    +   pg_atomic_write_u32(&pv_shared_cost_params->generation,
    +                       params_generation + 1);
    
    We can use pg_atomic_add_fetch_u32() instead.
    
    ---
    +/*
    + * Only autovacuum leader can reload config file. We use this structure in
    + * parallel autovacuum for keeping worker's parameters in sync with leader's
    + * parameters.
    + */
    +typedef struct PVSharedCostParams
    
    I'd suggest writing the overall description first (e.g., what the
    struct holds and what the function does etc), and then describing the
    details and notes etc. For instance, readers might be confused when
    reading the first sentence "Only autovacuum leader can reload config
    file" as the struct definition is not related to the autovacuum
    implementation fact that autovacuum workers can reload the config file
    during the work. We would need to mention such detail somewhere in the
    comments but I think it should not be the first sentence. How about
    rewriting it to something like:
    
    +/*
    + * Struct for cost-based vacuum delay related parameters to share among an
    + * autovacuum worker and its parallel vacuum workers.
    + */
    
    ---
    +   slock_t     spinlock;       /* protects all fields below */
    
    It's convention to name 'mutex' as a field name.
    
    ---
    +static PVSharedCostParams * pv_shared_cost_params = NULL;
    +
    +/* See comments for structure above for the explanation. */
    +static uint32 shared_params_generation_local = 0;
    
    I think it's preferable to move these definitions of static variables
    right before the function prototypes.
    
    ---
    +   /*
    +    * If 'true' then we are running parallel autovacuum. Otherwise, we are
    +    * running parallel maintenence VACUUM.
    +    */
    +   bool        am_parallel_autovacuum;
    
    How about renaming it to use_shared_delay_params? I think it conveys
    better what the field is used for.
    
    * 0004 patch:
    
    The patch introduces 5 injection points, which seems overkill to me
    for implementing the tests. IIUC we can implement the test2 with two
    injection points: 'autovacuum-start-parallel-vacuum' (set right before
    lazy_scan_heap() call) and
    'autovacuum-leader-before-indexes-processing'.
    
    1. stop the autovacuum worker at 'autovacuum-start-parallel-vacuum'.
    2. change delay params and reload the conf.
    3. let the autovacuum worker process tables (vacuum_delay_point() is
    called during the heap scan).
    4. stop the autovacuum worker at 'autovacuum-leader-before-indexes-processing'.
    5. let parallel workers process indexes (vacuum_delay_point() is
    called during index vacuuming).
    
    For test3, I think we can write a DEBUG2 log in
    adjust_free_parallel_workers() and check it during the test instead of
    introducing the test-only function.
    
    For test4 and test5, we check the number of free workers using
    get_parallel_autovacuum_free_workers(). However, since autovacuum
    could retry to vacuum the table again, the test could fail.
    
    And here are some general comments and suggestions:
    
    +use warnings FATAL => 'all';
    +use PostgreSQL::Test::Cluster;
    +use PostgreSQL::Test::Utils;
    +use Test::More;
    
    We need comments to explain what we test with this test file.
    
    ---
    +   $node->safe_psql('postgres', qq{
    +       UPDATE test_autovac SET col_1 = $test_number;
    +       ANALYZE test_autovac;
    +   });
    
    Why do we need to execute ANALYZE as well?
    
    ---
    +   $node->wait_for_log($expected_log);
    +   truncate $node->logfile, 0 or die "truncate failed: $!";
    +}
    
    Truncating all logs every after test would decrease the debuggability
    much. We can pass the offset as the start point to wait for the
    contents.
    
    ---
    +# Insert specified tuples num into the table
    +$node->safe_psql('postgres', qq{
    +   DO \$\$
    +   DECLARE
    +       i INTEGER;
    +   BEGIN
    +       FOR i IN 1..$initial_rows_num LOOP
    +           INSERT INTO test_autovac VALUES (i, i + 1, i + 2, i + 3);
    +       END LOOP;
    +   END \$\$;
    +});
    
    We can use generate_series() here. And it's faster to load the data
    and then create indexes.
    
    ---
    +$node->psql('postgres',
    +   "SELECT get_parallel_autovacuum_free_workers();",
    +   stdout => \$psql_out,
    +);
    
    Please use pgsql_safe() instead.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  65. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-02-10T15:03:45Z

    Hi,
    
    Thanks everyone for the review!
    
    **Comments on the 0001 patch**
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    >
    > +       AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    > +
    > +       /*
    > +        * Don't allow number of free workers to become less than zero if the
    > +        * patameter was decreased.
    > +        */
    > +       AutoVacuumShmem->av_freeParallelWorkers =
    > +               Max(AutoVacuumShmem->av_freeParallelWorkers, 0);
    > ```
    >
    > This can probably be simplified to:
    >
    > ```
    > AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    > ```
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > +   /*
    > +    * Cap or increase number of free parallel workers according to the
    > +    * parameter change.
    > +    */
    > +   AutoVacuumShmem->av_freeParallelWorkers = Max(nfree_workers, 0);
    > +
    > +   /*
    > +    * Don't allow number of free workers to become less than zero if the
    > +    * patameter was decreased.
    > +    */
    > +   AutoVacuumShmem->av_freeParallelWorkers =
    > +       Max(AutoVacuumShmem->av_freeParallelWorkers, 0);
    >
    > Why does it do Max(x, 0) twice?
    
    Agreed, I missed this one. Surely it can be simplified.
    
    --
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Sat, Jan 17, 2026 at 6:52 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > > waste time initializing a parallel context if there is no chance that
    > > the request will succeed.
    > > But it's worth remembering that actually the
    > > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > > capped by 'max_parallel_workers'.
    >
    > It doesn't make sense to me that we limit
    > autovacuum_max_parallel_workers by max_worker_processes TBH. When
    > users want to have more parallel vacuum workers for autovacuum and the
    > VACUUM command, they would have to consider max_worker_processes,
    > max_parallel_workers, and max_parallel_maintenance_workers separately.
    > Given that max_parallel_workers is controlling the number of
    > max_worker_processes that can be used in parallel operations, I
    > believe that parallel vacuum workers for autovacuum should also be
    > taken from that pool.
    
    Maybe I don't quite understand the meaning of "limited by". For example,
    we have a max_parallel_workers_per_gather parameter, which is limited
    by max_parallel_workers. But actually we can set this parameter to a value
    that is higher than max_parallel_workers. The limitation is that for Gather
    node we cannot request more workers than are available in bgworkers pool
    (where number of free workers is always <= max_parallel_workers). Thus,
    limitation actually exists only for bgworkers pool, on which other parallel
    operations depend. In particular, whatever values we set for the
    autovacuum_max_parallel_workers parameter, it always will depend only
    on bgworkers pool.
    
    I'll give in to your opinion and add a limitation by max_parallel_workers.
    But I still don't understand where the point is in explicit limitation by
    max_parallel_workers, if we already have this limitation implicitly?
    It seems a bit redundant for me. I hope I've conveyed my point correctly.
    
    **Comments on the 0002 patch**
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I don't think showing "reserved" in the logging is needed and could be
    > confusing.
    >
    
    The rationale for this is in the previous letter of Masahiko-san, and I
    agree with it. Do you think it can be confusing because users
    aren't familiar with the "reserved workers" in terms of postgres?
    I think that we can write about it in documentation, so users will
    be ready for it.
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > I think it's better
    > to show separate lines for index vacuuming and index cleanup, just
    > like VACUUM VERBOSE.
    >
    > ```
    > INFO:  launched 2 parallel vacuum workers for index vacuuming (planned: 2)
    > INFO:  launched 1 parallel vacuum worker for index cleanup (planned: 1)
    > ```
    >
    
    Actually, we already have such a logging (see
    parallel_vacuum_process_all_indexes function) for both VACUUM
    PARALLEL and parallel autovacuum. I think that in addition we can
    split the final log message (with total parallel vacuum stats) into two
    lines : for vacuum and cleanup respectively. Please, see these changes
    in the 0002 patch.
    
    --
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Can we refactoring these codes to:
    >
    > if (vacrel->workers_usage.nplanned > 0)вв
    > {
    >     if (AmAutoVacuumWorkerProcess())
    >         appendStringInfo(...);
    >     else
    >         appendStringInfo(...);
    
    I agree.
    
    **Comments on the 0003 patch**
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Sat, Jan 17, 2026 at 6:52 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > If behavior will be changed, then all comments for this function will need to
    > > be changed, actually. Don't get me wrong - I just think that this Note is
    > > important for the readers. But if you doubt its usefulness, I don't
    > > mind deleting it.
    >
    > I still could not figure out why it should be mentioned here instead
    > of at the comment of parallel_vacuum_update_shared_delay_params().
    > Readers can notice that calling
    > parallel_vacuum_update_shared_delay_params() for parallel vacuum
    > worker for the VACUUM command has no effect when reading the function.
    > In my opinion, we should mention here why we call
    > parallel_vacuum_update_shared_delay_params() but should not mention
    > what the called function does because it should have been described in
    > that function.
    >
    
    OK, I agree.
    
    > BTW can we expose pv_shared_cost_params so that we can check it in
    > vacuum_delay_point() before trying to call
    > parallel_vacuum_update_shared_delay_params()?
    >
    
    I would prefer not to do so. IMO it would be better if we'll encapsulate the
    shared delay parameters logic inside a single file.
    
    > +   if (!AmAutoVacuumWorkerProcess() && IsParallelWorker())
    > +   {
    >
    > We can just check IsParallelWorker() here.
    
    I agree.
    
    --
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > +extern void parallel_vacuum_update_shared_delay_params(void);
    > +extern void parallel_vacuum_propagate_cost_based_params(void);
    >
    > I think it's better to have similar names to these functions for
    > consistency and readability. How about the following?
    >
    > parallel_vacuum_update_delay_params();
    > parallel_vacuum_propagate_delay_params();
    >
    
    Yep, 100% agree - I just forgot to do it. if you don't mind, I would leave
    the word "shared" in the function names.
    
    --
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > +   params_generation = pg_atomic_read_u32(&pv_shared_cost_params->generation);
    > +
    > +   SpinLockAcquire(&pv_shared_cost_params->spinlock);
    > +
    > +   pv_shared_cost_params->cost_delay = vacuum_cost_delay;
    > +   pv_shared_cost_params->cost_limit = vacuum_cost_limit;
    > +   pv_shared_cost_params->cost_page_dirty = VacuumCostPageDirty;
    > +   pv_shared_cost_params->cost_page_hit = VacuumCostPageHit;
    > +   pv_shared_cost_params->cost_page_miss = VacuumCostPageMiss;
    >
    > I think we can check if the new cost-based delay parameters are really
    > changed before changing the shared values. If users don't change
    > cost-based delay parameters, we don't need to increment the generation
    > at all.
    >
    
    I agree.
    
    --
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > +/*
    > + * Only autovacuum leader can reload config file. We use this structure in
    > + * parallel autovacuum for keeping worker's parameters in sync with leader's
    > + * parameters.
    > + */
    > +typedef struct PVSharedCostParams
    >
    > I'd suggest writing the overall description first (e.g., what the
    > struct holds and what the function does etc), and then describing the
    > details and notes etc. For instance, readers might be confused when
    > reading the first sentence "Only autovacuum leader can reload config
    > file" as the struct definition is not related to the autovacuum
    > implementation fact that autovacuum workers can reload the config file
    > during the work. We would need to mention such detail somewhere in the
    > comments but I think it should not be the first sentence. How about
    > rewriting it to something like:
    >
    > +/*
    > + * Struct for cost-based vacuum delay related parameters to share among an
    > + * autovacuum worker and its parallel vacuum workers.
    > + */
    >
    
    Yep, you are right.
    
    > +   slock_t     spinlock;       /* protects all fields below */
    >
    > It's convention to name 'mutex' as a field name.
    >
    
    OK.
    
    --
    
    > +static PVSharedCostParams * pv_shared_cost_params = NULL;
    > +
    > +/* See comments for structure above for the explanation. */
    > +static uint32 shared_params_generation_local = 0;
    >
    > I think it's preferable to move these definitions of static variables
    > right before the function prototypes.
    >
    
    I agree.
    
    --
    
    > +   /*
    > +    * If 'true' then we are running parallel autovacuum. Otherwise, we are
    > +    * running parallel maintenence VACUUM.
    > +    */
    > +   bool        am_parallel_autovacuum;
    >
    > How about renaming it to use_shared_delay_params? I think it conveys
    > better what the field is used for.
    
    I think that we should leave this name, because in the future some other
    behavior differences may occur between manual VACUUM and autovacuum.
    If so, we will already have an "am_autovacuum" field which we can use in
    the code.
    The existing logic with the "am_autovacuum" name is also LGTM - we should
    use shared delay params only because we are running parallel autovacuum.
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > inside vacuum_delay_point, I would re-organize the checks to
    > first run the code block for the a/v worker:
    >
    > ```
    > if (ConfigReloadPending && AmAutoVacuumWorkerProcess())
    > ```
    >
    > then the a/v/ parallel worker:
    >
    > ```
    > if (!AmAutoVacuumWorkerProcess() && IsParallelWorker())
    > ```
    >
    
    Besides ConfigReloadPending we also must check VacuumCostActive.
    I placed the call of update_shared_delay_params function *before* checking
    VacuumCostActive, because parallel worker can change value of this variable
    inside of this function. Also we should call functions related to a/v worker
    only *after* checking the VacuumCostActive. Thus, the parallel a/v worker
    logic should be called before leader a/v worker logic.
    Am I missing something?
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > But I am also wondering if we should have a specific backend_type
    > for "autovacuum parallel worker" to differentiate that from the
    > existing "autovacuum worker".
    >
    > and also we can have a helper macro like:
    > ```
    > #define AmAutoVacuumParallelWorkerProcess() (MyBackendType ==
    > B_AUTOVAC_PARALLEL_WORKER)
    > ```
    >
    > What do you think?
    >
    
    I don't think that we should do it, because the workers (that are launched
    by a/v worker) are technically no different from other bgworkers, that are
    launched for other purposes. Since we easily can distinguish a/v parallel
    worker from others, I suggest we leave it as it is.
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > Add
    > ```
    > +typedef struct PVSharedCostParams
    > ````
    >
    > to src/tools/pgindent/typedefs.list
    >
    
    I agree. I'll also add all new structures to the typedefs.list
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    >
    > +               pg_atomic_init_u32(&shared->cost_params.generation, 0);
    > +               SpinLockInit(&shared->cost_params.spinlock);
    > +               pv_shared_cost_params = &(shared->cost_params);
    >
    > NIT: move SpinLockInit last
    
    I think that we should init the pointer to the shared->cost_params when
    all of this structure's fields are initialized. Thus, I guess that SpinLockInit
    should be placed before the "pv_shared_cost_params = ...".
    
    Here it doesn't actually make any difference where to place it, but I think
    It's a little more beautiful.
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    >
    > Instead of:
    >
    > ```
    > +       params_generation =
    > pg_atomic_read_u32(&pv_shared_cost_params->generation);
    > +
    > ```
    > and then later on:
    > ````
    > +       /*
    > +        * Increase generation of the parameters, i.e. let parallel workers know
    > +        * that they should re-read shared cost params.
    > +        */
    > +       pg_atomic_write_u32(&pv_shared_cost_params->generation,
    > +                                               params_generation + 1);
    > +
    > +       SpinLockRelease(&pv_shared_cost_params->spinlock);
    > ```
    >
    > why can't we just do:
    >
    > pg_atomic_fetch_add_u32(&pv_shared_cost_params->generation, 1);
    >
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > +   pg_atomic_write_u32(&pv_shared_cost_params->generation,
    > +                       params_generation + 1);
    >
    > We can use pg_atomic_add_fetch_u32() instead.
    >
    
    Yep, agreed.
    
    --
    
    On Thu, Jan 22, 2026 at 5:22 AM Sami Imseih <samimseih@gmail.com> wrote:
    >
    > Also, do the pg_atomic_fetch_add_u32 outside of the spinlock. right?
    >
    
    Sure. Somehow I missed it.
    
    **Comments on the 0004 patch**
    
    On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > The patch introduces 5 injection points, which seems overkill to me
    > for implementing the tests. IIUC we can implement the test2 with two
    > injection points: 'autovacuum-start-parallel-vacuum' (set right before
    > lazy_scan_heap() call) and
    > 'autovacuum-leader-before-indexes-processing'.
    >
    > 1. stop the autovacuum worker at 'autovacuum-start-parallel-vacuum'.
    > 2. change delay params and reload the conf.
    > 3. let the autovacuum worker process tables (vacuum_delay_point() is
    > called during the heap scan).
    > 4. stop the autovacuum worker at 'autovacuum-leader-before-indexes-processing'.
    > 5. let parallel workers process indexes (vacuum_delay_point() is
    > called during index vacuuming).
    
    OK, I'll do it.
    
    --
    
    > For test3, I think we can write a DEBUG2 log in
    > adjust_free_parallel_workers() and check it during the test instead of
    > introducing the test-only function.
    >
    
    > Truncating all logs every after test would decrease the debuggability
    > much. We can pass the offset as the start point to wait for the
    > contents.
    >
    
    I've combined two of your above comments purposely. I agree that truncating
    all logs is a bad decision and we need to solve this in a different way. But the
    problem will occur If we want to 1) use logging instead of a test-only function
    and 2) use offsets as the start point to wait for the contents in the logfile.
    
    Imagine that we (using the described approach) need to wait until the end of
    parallel index processing and determine the current number of free parallel
    workers.
    
    IIUC, It'll look like this :
    wait_for_av_log("autovacuum processing finished");
    wait_for_av_log("number of free workers = N");
    
    But when we call wait_for_av_log first time, we will advance "offset" to the
    end of logfile and thus we will miss the "number of free workers = N". The
    only way to avoid it is to write a function that will determine the exact
    position of "autovacuum processing finished" in the logfile. Isn't it too much?
    
    I think that using wait_for_av_log("autovacuum processing finished"); +
    SELECT get_parallel_autovacuum_free_workers(); will be much more
    demonstrably and simply.
    
    Moreover, the AutoVacuumGetFreeParallelWorkers function doesn't
    seem completely useless in isolation from tests. I suggest leaving
    this function and its usage in the tests. I can remove the "For testing
    purpose only!" comment, so everyone will be free to use this function
    in the future.
    
    > For test4 and test5, we check the number of free workers using
    > get_parallel_av_free_workers(). However, since autovacuum
    > could retry to vacuum the table again, the test could fail.
    
    Yep, good catch.
    
    1)
    Test 5 can be stabilized as follows :
    We can attach to the "autovacuum-start-parallel-vacuum" injection point in
    the "wait" mode. Thereby when we terminate the first a/v leader, we are
    guaranteed that no other a/v leader will reach release/reserve functions.
    And then we are free to call the get_parallel_autovacuum_free_workers
    function. I'll additionally describe this logic in the test.
    
    2)
    In the test 4 I found another problem : when a/v leader errors out, it will
    exit() pretty soon. And during exit() it will call the before_shmem_exit hook.
    Thus, we cannot be sure that parallel workers has been released exactly
    in the try/catch block. In order to guarantee it, I think that we should log
    something inside the try/catch block. I added a pretty controversial loggin
    code for it, but it is the best I came up with.
    
    In the test 4 the above idea will look something like this:
    $log_start = $node->wait_for_log(
        qr/error triggered for injection point / .
        qr/autovacuum-leader-before-indexes-processing/,
        $log_start
    );
    $log_start = $node->wait_for_log(
        qr/2 parallel autovacuum workers has been released after occured error/,
        $log_start
    );
    
    Above I described a problem that may occur when we advance
    "logfile offset" too far after the first wait_for_log call. Here, this problem
    doesn't occur because the autovacuum launcher infinitely tries to
    vacuum the table, so other "N workers released" messages occur.
    
    --
    
    > And here are some general comments and suggestions:
    >
    > +use warnings FATAL => 'all';
    > +use PostgreSQL::Test::Cluster;
    > +use PostgreSQL::Test::Utils;
    > +use Test::More;
    >
    > We need comments to explain what we test with this test file.
    >
    
    OK, I'll add it. I suppose I can limit myself to a simple
    "Test parallel autovacuum behavior", because the specific test scenarios
    are described below.
    
    --
    
    > +   $node->safe_psql('postgres', qq{
    > +       UPDATE test_autovac SET col_1 = $test_number;
    > +       ANALYZE test_autovac;
    > +   });
    >
    > Why do we need to execute ANALYZE as well?
    
    I added ANALYZE just in case. But I see that statistics of deleted and
    updated tuples is accumulated at the end of the transaction, so I agree
    that we can get rid of ANALYZE here.
    
    --
    
    > +# Insert specified tuples num into the table
    > +$node->safe_psql('postgres', qq{
    > +   DO \$\$
    > +   DECLARE
    > +       i INTEGER;
    > +   BEGIN
    > +       FOR i IN 1..$initial_rows_num LOOP
    > +           INSERT INTO test_autovac VALUES (i, i + 1, i + 2, i + 3);
    > +       END LOOP;
    > +   END \$\$;
    > +});
    >
    > We can use generate_series() here. And it's faster to load the data
    > and then create indexes.
    
    OK, I'll fix it.
    
    --
    
    > +$node->psql('postgres',
    > +   "SELECT get_parallel_autovacuum_free_workers();",
    > +   stdout => \$psql_out,
    > +);
    >
    > Please use pgsql_safe() instead.
    
    Sure!
    
    --
    
    Again, thanks everyone for the review!
    I hope I didn't miss anything.
    Please, see updated sets of patches.
    
    This time I'll try something experimental - besides the patches I'll also
    post differences between corresponding patches from v20 and v21.
    I.e. you can apply v20--v21-diff-for-0001 on the v20-0001 patch and
    get the v21-0001 patch. There are a lot of changes, so I guess it will
    help you during review.  Please, let me know whether it is useful for you.
    
    --
    Best regards,
    Daniil Davydov
    
  66. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-02-25T23:59:05Z

    On Wed, Feb 11, 2026 at 12:04 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > On Thu, Jan 22, 2026 at 5:29 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Sat, Jan 17, 2026 at 6:52 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >
    > > > I will keep the 'max_worker_processes' limit, so autovacuum will not
    > > > waste time initializing a parallel context if there is no chance that
    > > > the request will succeed.
    > > > But it's worth remembering that actually the
    > > > 'autovacuum_max_parallel_workers' parameter will always be implicitly
    > > > capped by 'max_parallel_workers'.
    > >
    > > It doesn't make sense to me that we limit
    > > autovacuum_max_parallel_workers by max_worker_processes TBH. When
    > > users want to have more parallel vacuum workers for autovacuum and the
    > > VACUUM command, they would have to consider max_worker_processes,
    > > max_parallel_workers, and max_parallel_maintenance_workers separately.
    > > Given that max_parallel_workers is controlling the number of
    > > max_worker_processes that can be used in parallel operations, I
    > > believe that parallel vacuum workers for autovacuum should also be
    > > taken from that pool.
    >
    > Maybe I don't quite understand the meaning of "limited by". For example,
    > we have a max_parallel_workers_per_gather parameter, which is limited
    > by max_parallel_workers. But actually we can set this parameter to a value
    > that is higher than max_parallel_workers. The limitation is that for Gather
    > node we cannot request more workers than are available in bgworkers pool
    > (where number of free workers is always <= max_parallel_workers). Thus,
    > limitation actually exists only for bgworkers pool, on which other parallel
    > operations depend. In particular, whatever values we set for the
    > autovacuum_max_parallel_workers parameter, it always will depend only
    > on bgworkers pool.
    
    Right, parallel workers are actually taken from bgworkers pool.
    
    >
    > I'll give in to your opinion and add a limitation by max_parallel_workers.
    > But I still don't understand where the point is in explicit limitation by
    > max_parallel_workers, if we already have this limitation implicitly?
    > It seems a bit redundant for me. I hope I've conveyed my point correctly.
    
    max_worker_processes controls the number of available bgworkers in the
    database cluster and bg workers are used for parallel queries, logical
    replication, or any other extensions as well. Also, it requires a
    server restart to change. max_parallel_workers controls "how many
    bgworkers can be used for parallel queries in total?" and is a
    PGC_USERSET parameter. I think it's easier for users to tune parallel
    query related parameters since all bgworkers for parallel queries
    (i.e., parallel workers) are taken from max_parallel_workers pool. For
    example, if users want to disable all parallel queries, they can do
    that by setting max_parallel_workers to 0. If parallel vacuum workers
    for autovacuums are taken from max_worker_processes pool (i.e.,
    without max_paralle_workers limit), users would need to set both
    max_parallel_workers and autovacuum_max_parallel_workers to 0.
    
    > --
    >
    > > +   /*
    > > +    * If 'true' then we are running parallel autovacuum. Otherwise, we are
    > > +    * running parallel maintenence VACUUM.
    > > +    */
    > > +   bool        am_parallel_autovacuum;
    > >
    > > How about renaming it to use_shared_delay_params? I think it conveys
    > > better what the field is used for.
    >
    > I think that we should leave this name, because in the future some other
    > behavior differences may occur between manual VACUUM and autovacuum.
    > If so, we will already have an "am_autovacuum" field which we can use in
    > the code.
    > The existing logic with the "am_autovacuum" name is also LGTM - we should
    > use shared delay params only because we are running parallel autovacuum.
    
    It may occur but we can change the field name when it really comes.
    
    I'm slightly concerned that we've been using am_xxx variables in a
    different way. For instance, am_walsender is a global variable that is
    set to true only in wal sender processes. Also we have a bunch of
    AmXXProcess() macros that checks the global variable MyBackendType, to
    check the kinds of the current process. That is, the subject of 'am'
    is typically the process, I guess. On the other hand,
    am_parallel_autovacuum is stored in DSM space and indicates whether a
    parallel vacuum is invoked by manual VACUUM or autovacuum.
    
    >
    > > Truncating all logs every after test would decrease the debuggability
    > > much. We can pass the offset as the start point to wait for the
    > > contents.
    > >
    >
    > I've combined two of your above comments purposely. I agree that truncating
    > all logs is a bad decision and we need to solve this in a different way. But the
    > problem will occur If we want to 1) use logging instead of a test-only function
    > and 2) use offsets as the start point to wait for the contents in the logfile.
    >
    > Imagine that we (using the described approach) need to wait until the end of
    > parallel index processing and determine the current number of free parallel
    > workers.
    >
    > IIUC, It'll look like this :
    > wait_for_av_log("autovacuum processing finished");
    > wait_for_av_log("number of free workers = N");
    >
    > But when we call wait_for_av_log first time, we will advance "offset" to the
    > end of logfile and thus we will miss the "number of free workers = N". The
    > only way to avoid it is to write a function that will determine the exact
    > position of "autovacuum processing finished" in the logfile. Isn't it too much?
    >
    > I think that using wait_for_av_log("autovacuum processing finished"); +
    > SELECT get_parallel_autovacuum_free_workers(); will be much more
    > demonstrably and simply.
    >
    > Moreover, the AutoVacuumGetFreeParallelWorkers function doesn't
    > seem completely useless in isolation from tests. I suggest leaving
    > this function and its usage in the tests. I can remove the "For testing
    > purpose only!" comment, so everyone will be free to use this function
    > in the future.
    
    Agreed. The updated test scenario looks reasonable to me.
    
    >
    > 1)
    > Test 5 can be stabilized as follows :
    > We can attach to the "autovacuum-start-parallel-vacuum" injection point in
    > the "wait" mode. Thereby when we terminate the first a/v leader, we are
    > guaranteed that no other a/v leader will reach release/reserve functions.
    > And then we are free to call the get_parallel_autovacuum_free_workers
    > function. I'll additionally describe this logic in the test.
    >
    > 2)
    > In the test 4 I found another problem : when a/v leader errors out, it will
    > exit() pretty soon. And during exit() it will call the before_shmem_exit hook.
    > Thus, we cannot be sure that parallel workers has been released exactly
    > in the try/catch block. In order to guarantee it, I think that we should log
    > something inside the try/catch block. I added a pretty controversial loggin
    > code for it, but it is the best I came up with.
    >
    > In the test 4 the above idea will look something like this:
    > $log_start = $node->wait_for_log(
    >     qr/error triggered for injection point / .
    >     qr/autovacuum-leader-before-indexes-processing/,
    >     $log_start
    > );
    > $log_start = $node->wait_for_log(
    >     qr/2 parallel autovacuum workers has been released after occured error/,
    >     $log_start
    > );
    >
    > Above I described a problem that may occur when we advance
    > "logfile offset" too far after the first wait_for_log call. Here, this problem
    > doesn't occur because the autovacuum launcher infinitely tries to
    > vacuum the table, so other "N workers released" messages occur.
    
    If we write the log "%d parallel autovacuum workers have been
    released" in AutoVacuumReleaseParallelWorkres(), can we simplify both
    tests (4 and 5) further?
    
    I've reviewed all patches. The 0001 patch looks good to me.
    
    0002 patch:
    
    +                                       /* Worker usage stats for
    parallel autovacuum. */
    +                                       appendStringInfo(&buf,
    +
      _("parallel index vacuum: %d workers were planned, %d workers were
    reserved and %d workers were launched in total\n"),
    +
      vacrel->workers_usage.vacuum.nplanned,
    +
      vacrel->workers_usage.vacuum.nreserved,
    +
      vacrel->workers_usage.vacuum.nlaunched);
    
    These log messages need to take care of plural forms but it seems to
    be too long if we use errmsg_plural() for each number. So how about
    something like:
    
    parallel workers: index: %d planned, %d reserved, %d launched in total
    parallel workers: cleanup %d planned, %d reserved, %d launched
    
    (Index cleanup is executed at most once so we don't need "in total" in
    the message.)
    
    0003 patch:
    
    +typedef struct CostParamsData
    +{
    +        double         cost_delay;
    +        int                    cost_limit;
    +        int                    cost_page_dirty;
    +        int                    cost_page_hit;
    +        int                    cost_page_miss;
    +} CostParamsData;
    
    The name CostParamsData sounds too generic and I guess it could
    conflict with optimizer-related struct names in the future. How about
    renaming it to VacuumDelayParams?
    
    ---
    +        SpinLockAcquire(&pv_shared_cost_params->mutex);
    +
    +        shared_params_data = pv_shared_cost_params->params_data;
    +
    +        VacuumCostDelay = shared_params_data.cost_delay;
    +        VacuumCostLimit = shared_params_data.cost_limit;
    +        VacuumCostPageDirty = shared_params_data.cost_page_dirty;
    +        VacuumCostPageHit = shared_params_data.cost_page_hit;
    +        VacuumCostPageMiss = shared_params_data.cost_page_miss;
    +
    +        SpinLockRelease(&pv_shared_cost_params->mutex);
    
    If we copy the shared values in pv_shared_cost_params, we should
    release the spinlock earlier, i.e., before updating VacuumCostXXX
    variables. But I don't think we would even need to set these values in
    the local variables in this case as updating 4 local variables is
    fairly cheap.
    
    ---
    +        FillCostParamsData(&local_params_data);
    +        SpinLockAcquire(&pv_shared_cost_params->mutex);
    +
    +        if (CostParamsDataEqual(pv_shared_cost_params->params_data,
    +                                                        local_params_data))
    +        {
    
    IIUC it stores cost-based vacuum delay parameters into the
    local_params_data only for using CostParamsDataEqual() macro. I think
    it's better to directly compare values in pv_shared_cost_params and
    the cost-based vacuum delay parameters.
    
    0004 patch:
    
    +        if (nworkers > 0)
    +
    INJECTION_POINT("autovacuum-leader-before-indexes-processing", NULL);
    
    I think it's better to use #ifdef USE_INJECTION_POINTS here.
    
    ---
    +#ifdef USE_INJECTION_POINTS
    +/*
    + * Log values of the related to cost-based delay parameters. It is used for
    
    s/values of the related to/values related to/
    
    ---
    + * testing purpose.
    + */
    +static void
    +parallel_vacuum_report_cost_based_params(void)
    +{
    +       StringInfoData buf;
    +
    +       /* Simulate config reload during normal processing */
    +       pg_atomic_add_fetch_u32(VacuumActiveNWorkers, 1);
    +       vacuum_delay_point(false);
    +       pg_atomic_sub_fetch_u32(VacuumActiveNWorkers, 1);
    
    Calling vacuum_delay_point() here feels a bit arbitrary to me. Since
    parallel vacuum workers are calling
    parallel_vacuum_report_cost_based_params() after
    parallel_vacuum_process_safe_indexes(), I think we don't necessarily
    call vacuum_delay_point() here.
    
    ---
    +       appendStringInfo(&buf, "Vacuum cost-based delay parameters of
    parallel worker:\n");
    +       appendStringInfo(&buf, "vacuum_cost_limit = %d\n",vacuum_cost_limit);
    +       appendStringInfo(&buf, "vacuum_cost_delay = %g\n", vacuum_cost_delay);
    +       appendStringInfo(&buf, "vacuum_cost_page_miss = %d\n",
    VacuumCostPageMiss);
    +       appendStringInfo(&buf, "vacuum_cost_page_dirty = %d\n",
    VacuumCostPageDirty);
    +       appendStringInfo(&buf, "vacuum_cost_page_hit = %d\n",
    VacuumCostPageHit);
    
    I'd write these messages directly in elog() instead of using
    StringInfoData, which is simpler and can save palloc()/pfree().
    
    ---
    +       ereport(DEBUG2, errmsg("%s", buf.data));
    
    Let's use elog() instead of ereport().
    
    ---
    +# Create role with pg_signal_autovacuum_worker for terminating
    autovacuum worker.
    +$node->safe_psql('postgres', qq{
    +        CREATE ROLE regress_worker_role;
    +        GRANT pg_signal_autovacuum_worker TO regress_worker_role;
    +        SET ROLE regress_worker_role;
    +});
    +
    +$node->safe_psql('postgres', qq{
    +        SELECT pg_terminate_backend('$av_pid');
    +});
    
    These two safe_psql calls use separate connections, meaning that
    pg_terminate_backend() is executed by the superuser rather than
    regress_worker_role. I think we don't need to create the
    regrss_worker_role and we can use the superuser in this test case.
    
    ---
    We would add more autovacuum related tests to the test_autovacuum
    directory in the future. Given that the 001_basic.pl focuses on
    parallel vacuum tests, how about renaming it to 001_parallel_vacuum.pl
    or something?
    
    > This time I'll try something experimental - besides the patches I'll also
    > post differences between corresponding patches from v20 and v21.
    > I.e. you can apply v20--v21-diff-for-0001 on the v20-0001 patch and
    > get the v21-0001 patch. There are a lot of changes, so I guess it will
    > help you during review.  Please, let me know whether it is useful for you.
    
    It was helpful to easily see the changes from the previous version. Thank you!
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  67. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-02-27T13:49:15Z

    Hi,
    
    On Thu, Feb 26, 2026 at 6:59 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > For example, if users want to disable all parallel queries, they can do
    > that by setting max_parallel_workers to 0. If parallel vacuum workers
    > for autovacuums are taken from max_worker_processes pool (i.e.,
    > without max_paralle_workers limit), users would need to set both
    > max_parallel_workers and autovacuum_max_parallel_workers to 0.
    >
    
    This is kinda off-topic already, but I really want to clarify this question.
    
    If parallel a/v workers are not limited by max_parallel_workers and the
    user wants to disable all parallel operations, it is still enough to set
    max_parallel_workers to 0. In this case parallel a/v could not acquire any
    workers from bgworkers pool, and thus the user's goal is reached (and there
    is no need to set autovacuum_max_parallel_workers to 0).
    
    **Comments on the 0002 patch**
    
    >
    > +                                       /* Worker usage stats for
    > parallel autovacuum. */
    > +                                       appendStringInfo(&buf,
    > +
    >   _("parallel index vacuum: %d workers were planned, %d workers were
    > reserved and %d workers were launched in total\n"),
    > +
    >   vacrel->workers_usage.vacuum.nplanned,
    > +
    >   vacrel->workers_usage.vacuum.nreserved,
    > +
    >   vacrel->workers_usage.vacuum.nlaunched);
    >
    > These log messages need to take care of plural forms but it seems to
    > be too long if we use errmsg_plural() for each number. So how about
    > something like:
    >
    > parallel workers: index: %d planned, %d reserved, %d launched in total
    > parallel workers: cleanup %d planned, %d reserved, %d launched
    >
    > (Index cleanup is executed at most once so we don't need "in total" in
    > the message.)
    
    Oh, I forgot about plural form preservation. Agree with your suggestion.
    
    **Comments on the 0003 patch**
    
    >
    > +typedef struct CostParamsData
    > +{
    > +        double         cost_delay;
    > +        int                    cost_limit;
    > +        int                    cost_page_dirty;
    > +        int                    cost_page_hit;
    > +        int                    cost_page_miss;
    > +} CostParamsData;
    >
    > The name CostParamsData sounds too generic and I guess it could
    > conflict with optimizer-related struct names in the future. How about
    > renaming it to VacuumDelayParams?
    
    I agree with the idea to rename this structure. But maybe we should rename
    it to "VacuumCostParams"? This name conveys the contents of the structure
    better, because enabling these parameters is called "VacuumCostActive".
    
    > +        SpinLockAcquire(&pv_shared_cost_params->mutex);
    > +
    > +        shared_params_data = pv_shared_cost_params->params_data;
    > +
    > +        VacuumCostDelay = shared_params_data.cost_delay;
    > +        VacuumCostLimit = shared_params_data.cost_limit;
    > +        VacuumCostPageDirty = shared_params_data.cost_page_dirty;
    > +        VacuumCostPageHit = shared_params_data.cost_page_hit;
    > +        VacuumCostPageMiss = shared_params_data.cost_page_miss;
    > +
    > +        SpinLockRelease(&pv_shared_cost_params->mutex);
    >
    > If we copy the shared values in pv_shared_cost_params, we should
    > release the spinlock earlier, i.e., before updating VacuumCostXXX
    > variables. But I don't think we would even need to set these values in
    > the local variables in this case as updating 4 local variables is
    > fairly cheap.
    >
    
    Do you mean that we can release spinlock because we already copied the values
    from the shared state to the local variable "shared_params_data"? I added this
    variable as an alias for the long string "pv_shared_cost_params->params_data"
    and I guess that compiler will get rid of it.
    
    But now it doesn't seem like a good solution to me anymore. I'll get rid of
    the local variable and copy the values directly from the shared state
    (under spinlock).
    
    > ---
    > +        FillCostParamsData(&local_params_data);
    > +        SpinLockAcquire(&pv_shared_cost_params->mutex);
    > +
    > +        if (CostParamsDataEqual(pv_shared_cost_params->params_data,
    > +                                                        local_params_data))
    > +        {
    >
    > IIUC it stores cost-based vacuum delay parameters into the
    > local_params_data only for using CostParamsDataEqual() macro. I think
    > it's better to directly compare values in pv_shared_cost_params and
    > the cost-based vacuum delay parameters.
    
    I agree.
    
    > > > How about renaming it to use_shared_delay_params? I think it conveys
    > > > better what the field is used for.
    > >
    > > I think that we should leave this name, because in the future some other
    > > behavior differences may occur between manual VACUUM and autovacuum.
    > > If so, we will already have an "am_autovacuum" field which we can use in
    > > the code.
    > > The existing logic with the "am_autovacuum" name is also LGTM - we should
    > > use shared delay params only because we are running parallel autovacuum.
    >
    > It may occur but we can change the field name when it really comes.
    >
    > I'm slightly concerned that we've been using am_xxx variables in a
    > different way. For instance, am_walsender is a global variable that is
    > set to true only in wal sender processes. Also we have a bunch of
    > AmXXProcess() macros that checks the global variable MyBackendType, to
    > check the kinds of the current process. That is, the subject of 'am'
    > is typically the process, I guess. On the other hand,
    > am_parallel_autovacuum is stored in DSM space and indicates whether a
    > parallel vacuum is invoked by manual VACUUM or autovacuum.
    
    Yeah, I agree that "am_xxx" is not the best choice.
    What about a simple "bool is_autovacuum"?
    
    **Comments on the 0004 patch**
    
    > If we write the log "%d parallel autovacuum workers have been
    > released" in AutoVacuumReleaseParallelWorkres(), can we simplify both
    > tests (4 and 5) further?
    >
    
    It won't help the 4th test, because ReleaseParallelWorkers is called
    due to both ERROR and shmem_exit, but we want to be sure that
    workers are released in the try/catch block (i.e. before the shmem_exit).
    I thought that we could pass some additional info to the
    "ReleaseAllParallelWorkers" such as "bool error_occured", but I decided
    not to do so.
    
    Also, I don't know whether the 5th test needs this log at all, because in
    the end we are checking the number of free parallel workers. If a killed
    a/v leader doesn't release parallel workers, we'll notice it.
    
    > +        if (nworkers > 0)
    > +
    > INJECTION_POINT("autovacuum-leader-before-indexes-processing", NULL);
    >
    > I think it's better to use #ifdef USE_INJECTION_POINTS here.
    >
    
    Agree. I'll also fix it in vacuumlazy.c
    
    > +#ifdef USE_INJECTION_POINTS
    > +/*
    > + * Log values of the related to cost-based delay parameters. It is used for
    >
    > s/values of the related to/values related to/
    >
    
    OK
    
    > + * testing purpose.
    > + */
    > +static void
    > +parallel_vacuum_report_cost_based_params(void)
    > +{
    > +       StringInfoData buf;
    > +
    > +       /* Simulate config reload during normal processing */
    > +       pg_atomic_add_fetch_u32(VacuumActiveNWorkers, 1);
    > +       vacuum_delay_point(false);
    > +       pg_atomic_sub_fetch_u32(VacuumActiveNWorkers, 1);
    >
    > Calling vacuum_delay_point() here feels a bit arbitrary to me. Since
    > parallel vacuum workers are calling
    > parallel_vacuum_report_cost_based_params() after
    > parallel_vacuum_process_safe_indexes(), I think we don't necessarily
    > call vacuum_delay_point() here.
    >
    
    Sure! It is left from the previous implementation of the test. I'll remove
    this call.
    
    > +       appendStringInfo(&buf, "Vacuum cost-based delay parameters of
    > parallel worker:\n");
    > +       appendStringInfo(&buf, "vacuum_cost_limit = %d\n",vacuum_cost_limit);
    > +       appendStringInfo(&buf, "vacuum_cost_delay = %g\n", vacuum_cost_delay);
    > +       appendStringInfo(&buf, "vacuum_cost_page_miss = %d\n",
    > VacuumCostPageMiss);
    > +       appendStringInfo(&buf, "vacuum_cost_page_dirty = %d\n",
    > VacuumCostPageDirty);
    > +       appendStringInfo(&buf, "vacuum_cost_page_hit = %d\n",
    > VacuumCostPageHit);
    >
    > I'd write these messages directly in elog() instead of using
    > StringInfoData, which is simpler and can save palloc()/pfree().
    >
    
    OK
    
    > +       ereport(DEBUG2, errmsg("%s", buf.data));
    >
    > Let's use elog() instead of ereport().
    >
    
    I suppose this is suggested because we don't want to translate error
    messages of DEBUG level. Did I understand you correctly?
    
    > +# Create role with pg_signal_autovacuum_worker for terminating
    > autovacuum worker.
    > +$node->safe_psql('postgres', qq{
    > +        CREATE ROLE regress_worker_role;
    > +        GRANT pg_signal_autovacuum_worker TO regress_worker_role;
    > +        SET ROLE regress_worker_role;
    > +});
    > +
    > +$node->safe_psql('postgres', qq{
    > +        SELECT pg_terminate_backend('$av_pid');
    > +});
    >
    > These two safe_psql calls use separate connections, meaning that
    > pg_terminate_backend() is executed by the superuser rather than
    > regress_worker_role. I think we don't need to create the
    > regrss_worker_role and we can use the superuser in this test case.
    >
    
    Hm, looks like another one piece of code from my previous attempts to
    implement this test. I'll remove it.
    
    > We would add more autovacuum related tests to the test_autovacuum
    > directory in the future. Given that the 001_basic.pl focuses on
    > parallel vacuum tests, how about renaming it to 001_parallel_vacuum.pl
    > or something?
    >
    
    Agree, I'll rename it.
    
    > > This time I'll try something experimental - besides the patches I'll also
    > > post differences between corresponding patches from v20 and v21.
    > > I.e. you can apply v20--v21-diff-for-0001 on the v20-0001 patch and
    > > get the v21-0001 patch. There are a lot of changes, so I guess it will
    > > help you during review.  Please, let me know whether it is useful for you.
    >
    > It was helpful to easily see the changes from the previous version. Thank you!
    >
    
    I'm glad to hear it :) I will keep this tradition alive.
    
    
    Thank you very much for the review!
    Please, see updated set of patches and diffs between v21 and v22.
    
    --
    Best regards,
    Daniil Davydov
    
  68. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-02-28T01:56:48Z

    On Fri, Feb 27, 2026 at 5:49 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Feb 26, 2026 at 6:59 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > For example, if users want to disable all parallel queries, they can do
    > > that by setting max_parallel_workers to 0. If parallel vacuum workers
    > > for autovacuums are taken from max_worker_processes pool (i.e.,
    > > without max_paralle_workers limit), users would need to set both
    > > max_parallel_workers and autovacuum_max_parallel_workers to 0.
    > >
    >
    > This is kinda off-topic already, but I really want to clarify this question.
    >
    > If parallel a/v workers are not limited by max_parallel_workers and the
    > user wants to disable all parallel operations, it is still enough to set
    > max_parallel_workers to 0. In this case parallel a/v could not acquire any
    > workers from bgworkers pool, and thus the user's goal is reached (and there
    > is no need to set autovacuum_max_parallel_workers to 0).
    
    IIUC earlier patches defined autovacuum_max_parallel_workers with the
    limit by max_worker_processes. Suppose we set:
    
    - max_worker_processes = 8
    - autovacuum_max_parallel_workers = 4
    - max_parallel_workers = 4
    
    If we want to disable all parallel operations, we would need to set
    max_parallel_workers to 0 as well as either
    autovacuum_max_parallel_workers to 0, no? This is because if we set
    only max_parallel_workers to 0, autovacuum workers still can take
    parallel vacuum workers from the max_worker_processes pool. I might be
    missing something though.
    
    >
    > **Comments on the 0003 patch**
    >
    > >
    > > +typedef struct CostParamsData
    > > +{
    > > +        double         cost_delay;
    > > +        int                    cost_limit;
    > > +        int                    cost_page_dirty;
    > > +        int                    cost_page_hit;
    > > +        int                    cost_page_miss;
    > > +} CostParamsData;
    > >
    > > The name CostParamsData sounds too generic and I guess it could
    > > conflict with optimizer-related struct names in the future. How about
    > > renaming it to VacuumDelayParams?
    >
    > I agree with the idea to rename this structure. But maybe we should rename
    > it to "VacuumCostParams"? This name conveys the contents of the structure
    > better, because enabling these parameters is called "VacuumCostActive".
    
    +1
    
    >
    > > +        SpinLockAcquire(&pv_shared_cost_params->mutex);
    > > +
    > > +        shared_params_data = pv_shared_cost_params->params_data;
    > > +
    > > +        VacuumCostDelay = shared_params_data.cost_delay;
    > > +        VacuumCostLimit = shared_params_data.cost_limit;
    > > +        VacuumCostPageDirty = shared_params_data.cost_page_dirty;
    > > +        VacuumCostPageHit = shared_params_data.cost_page_hit;
    > > +        VacuumCostPageMiss = shared_params_data.cost_page_miss;
    > > +
    > > +        SpinLockRelease(&pv_shared_cost_params->mutex);
    > >
    > > If we copy the shared values in pv_shared_cost_params, we should
    > > release the spinlock earlier, i.e., before updating VacuumCostXXX
    > > variables. But I don't think we would even need to set these values in
    > > the local variables in this case as updating 4 local variables is
    > > fairly cheap.
    > >
    >
    > Do you mean that we can release spinlock because we already copied the values
    > from the shared state to the local variable "shared_params_data"?
    
    Yes.
    
    > I added this
    > variable as an alias for the long string "pv_shared_cost_params->params_data"
    > and I guess that compiler will get rid of it.
    >
    > But now it doesn't seem like a good solution to me anymore. I'll get rid of
    > the local variable and copy the values directly from the shared state
    > (under spinlock).
    
    Thanks.
    
    >
    > > > > How about renaming it to use_shared_delay_params? I think it conveys
    > > > > better what the field is used for.
    > > >
    > > > I think that we should leave this name, because in the future some other
    > > > behavior differences may occur between manual VACUUM and autovacuum.
    > > > If so, we will already have an "am_autovacuum" field which we can use in
    > > > the code.
    > > > The existing logic with the "am_autovacuum" name is also LGTM - we should
    > > > use shared delay params only because we are running parallel autovacuum.
    > >
    > > It may occur but we can change the field name when it really comes.
    > >
    > > I'm slightly concerned that we've been using am_xxx variables in a
    > > different way. For instance, am_walsender is a global variable that is
    > > set to true only in wal sender processes. Also we have a bunch of
    > > AmXXProcess() macros that checks the global variable MyBackendType, to
    > > check the kinds of the current process. That is, the subject of 'am'
    > > is typically the process, I guess. On the other hand,
    > > am_parallel_autovacuum is stored in DSM space and indicates whether a
    > > parallel vacuum is invoked by manual VACUUM or autovacuum.
    >
    > Yeah, I agree that "am_xxx" is not the best choice.
    > What about a simple "bool is_autovacuum"?
    
    +1
    
    >
    > **Comments on the 0004 patch**
    >
    > > If we write the log "%d parallel autovacuum workers have been
    > > released" in AutoVacuumReleaseParallelWorkres(), can we simplify both
    > > tests (4 and 5) further?
    > >
    >
    > It won't help the 4th test, because ReleaseParallelWorkers is called
    > due to both ERROR and shmem_exit, but we want to be sure that
    > workers are released in the try/catch block (i.e. before the shmem_exit).
    
    We already call AutoVacuumReleaseAllParallelWorker() in the PG_CATCH()
    block in do_autovacuum(). If we write the log in
    AutoVacuumReleaseParallelWorkers(), the tap test is able to check the
    log, no?
    
    > Also, I don't know whether the 5th test needs this log at all, because in
    > the end we are checking the number of free parallel workers. If a killed
    > a/v leader doesn't release parallel workers, we'll notice it.
    
    If we can check the log written at process shutdown time, I think we
    can somewhat simplify the test 5 logic by not attaching
    'autovacuum-start-parallel-vacuum' injection point.
    
    1. attach 'autovacuum-leader-before-indexes-processing' injection point.
    2. wait for an av worker to stop at the injection point.
    3. terminate the av worker.
    4. verify from the log if the workers have been released.
    5. disable parallel autovacuum.
    6. check the free workers (should be 10).
    
    Step 5 and 6 seems to be optional though.
    
    >
    > > +       ereport(DEBUG2, errmsg("%s", buf.data));
    > >
    > > Let's use elog() instead of ereport().
    > >
    >
    > I suppose this is suggested because we don't want to translate error
    > messages of DEBUG level. Did I understand you correctly?
    
    We use ereport() for DEBUG level messages in many places actually. I
    suggested it because this message is not a user-facing message.
    
    > Please, see updated set of patches and diffs between v21 and v22.
    
    Thank you for updating the patches! Here are review comments on the
    v22 patch set.
    
    * 0001 patch:
    
    +   /*
    +    * Max number of parallel autovacuum workers. If value is 0 then parallel
    +    * degree will computed based on number of indexes.
    +    */
    +   int         autovacuum_parallel_workers;
    
    I'm a bit concerned that the above description doesn't explain what
    number of parallel vacuum workers are used in >0 as it mentioned only
    the maximum number. How about rewording it to:
    
    Target number of parallel autovacuum workers. -1 by default disables
    parallel vacuum during autovacuum. 0 means choose the parallel degree
    based on the number of indexes.
    
    * 0002 patch:
    
    +   PVWorkersUsage workers_usage;
        /* Counters that follow are only for scanned_pages */
        int64       tuples_deleted; /* # deleted from table */
    
    Let's insert a new line between the new line and the existing line.
    
    ---
    +
    +               if (AmAutoVacuumWorkerProcess())
    +               {
    +                   /* Worker usage stats for parallel autovacuum. */
    +                   appendStringInfo(&buf,
    +                                    _("parallel workers: index
    vacuum: %d planned, %d reserved, %d launched in total\n"),
    +                                    vacrel->workers_usage.vacuum.nplanned,
    +                                    vacrel->workers_usage.vacuum.nreserved,
    +                                    vacrel->workers_usage.vacuum.nlaunched);
    +               }
    +               else
    +               {
    +                   /* Worker usage stats for manual VACUUM (PARALLEL). */
    +                   appendStringInfo(&buf,
    +                                    _("parallel workers: index
    vacuum: %d planned, %d launched in total\n"),
    +                                    vacrel->workers_usage.vacuum.nplanned,
    +                                    vacrel->workers_usage.vacuum.nlaunched);
    +               }
    +           }
    
    These comments are very obvious so I don't think we need them.
    Instead, I think it would be good to explain why we don't need to
    report "reserved" numbers in the manual vacuum cases.
    
    ---
    +           if (vacrel->workers_usage.vacuum.nplanned > 0)
    +           {
    +               /* Stats for vacuum phase of index vacuuming. */
    
    and
    
    +           if (vacrel->workers_usage.cleanup.nplanned > 0)
    +           {
    +               /* Stats for cleanup phase of index vacuuming. */
    +
    
    I don't think we need these comments (the second one has a typo
    though) as it's obvious.
    
    ---
      */
     void
     parallel_vacuum_bulkdel_all_indexes(ParallelVacuumState *pvs, long
    num_table_tuples,
    -                                   int num_index_scans)
    +                                   int num_index_scans, PVWorkersUsage *wusage)
    
    Please add a brief description of wusage to the function comment. We
    can add comments to both parallel_vacuum_bulkldel_all_indexes() and
    parallel_vacuum_cleanup_all_indexes() or only
    parallel_vacuum_process_all_indexes().
    
    
    
    ---
    @@ -2070,6 +2070,8 @@ PVIndStats
     PVIndVacStatus
     PVOID
     PVShared
    +PVWorkersUsage
    +PVWorkersStats
     PX_Alias
     PX_Cipher
     PX_Combo
    @@ -2408,6 +2410,7 @@ PullFilterOps
     PushFilter
     PushFilterOps
     PushFunction
    +PVWorkersUsage
     PyCFunction
     PyMethodDef
     PyModuleDef
    
    PVWorkersUsage is added twice
    
    * 0003 patch:
    
    +#define VacCostParamsEquals(params) \
    +   (vacuum_cost_delay == (params).cost_delay && \
    +    vacuum_cost_limit == (params).cost_limit && \
    +    VacuumCostPageDirty == (params).cost_page_dirty && \
    +    VacuumCostPageHit == (params).cost_page_hit && \
    +    VacuumCostPageMiss == (params).cost_page_miss)
    
    I'm not sure this macro helps reduce lines of code or improve
    readability as it's used only once and it's slightly unnatural to me
    that *Equals macro takes only one argument.
    
    * 0004 patch:
    
    +#include "commands/vacuum.h"
    +#include "fmgr.h"
    +#include "miscadmin.h"
    +#include "postmaster/autovacuum.h"
    +#include "storage/shmem.h"
    +#include "storage/ipc.h"
    +#include "storage/lwlock.h"
    +#include "utils/builtins.h"
    +#include "utils/injection_point.h"
    
    We can remove some unnecessary header includes. ISTM we need only
    fmgr.h, autovacuum.h, and injection_point.h.
    
    ---
    +   const char *msg_format =
    +       _("Parallel autovacuum worker cost params: cost_limit=%d,
    cost_delay=%g, cost_page_miss=%d, cost_page_dirty=%d,
    cost_page_hit=%d");
    +
    
    I don't think we need the translation for this message as it's not a
    user-facing one.
    
    We don't capitalize the first letter in the error message.
    
    ---
    +   ereport(DEBUG2,
    +           (errmsg("number of free parallel autovacuum workers is set
    to %u due to config reload",
    +                   AutoVacuumShmem->av_freeParallelWorkers),
    +            errhidecontext(true)));
    
    Why do we need to add errhidecontext(true) here?
    
    ---
    +    'tests': [
    +      't/001_basic.pl',
    +    ],
    
    Need to be updated to the new filename.
    
    ---
    + * Copyright (c) 2020-2025, PostgreSQL Global Development Group
    
    Please update the copyright years.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  69. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-01T14:46:42Z

    Hi,
    
    On Sat, Feb 28, 2026 at 8:57 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > IIUC earlier patches defined autovacuum_max_parallel_workers with the
    > limit by max_worker_processes. Suppose we set:
    >
    > - max_worker_processes = 8
    > - autovacuum_max_parallel_workers = 4
    > - max_parallel_workers = 4
    >
    > If we want to disable all parallel operations, we would need to set
    > max_parallel_workers to 0 as well as either
    > autovacuum_max_parallel_workers to 0, no? This is because if we set
    > only max_parallel_workers to 0, autovacuum workers still can take
    > parallel vacuum workers from the max_worker_processes pool. I might be
    > missing something though.
    >
    
    Even if av_max_parallel_workers is limited by max_worker_processes,
    it is enough to set max_parallel_workers to 0 to disable parallel
    autovacuum.
    
    When a/v leader wants to create supportive workers, it calls
    "RegisterDynamicBackgroundWorker" function, which contain following
    logic :
    /*
     * If this is a parallel worker, check whether there are already too many
     * parallel workers; if so, don't register another one.
     */
    if (parallel && (BackgroundWorkerData->parallel_register_count -
                           BackgroundWorkerData->parallel_terminate_count) >=
        max_parallel_workers)
    {
    ....
    }
    
    Thus, a/v leader cannot launch any workers if max_parallel_workers is set to 0.
    
    > > > If we write the log "%d parallel autovacuum workers have been
    > > > released" in AutoVacuumReleaseParallelWorkres(), can we simplify both
    > > > tests (4 and 5) further?
    > > >
    > >
    > > It won't help the 4th test, because ReleaseParallelWorkers is called
    > > due to both ERROR and shmem_exit, but we want to be sure that
    > > workers are released in the try/catch block (i.e. before the shmem_exit).
    >
    > We already call AutoVacuumReleaseAllParallelWorker() in the PG_CATCH()
    > block in do_autovacuum(). If we write the log in
    > AutoVacuumReleaseParallelWorkers(), the tap test is able to check the
    > log, no?
    >
    
    Not quite. Assume that we add "%d workers have been released" log to the
    ReleaseAllParallelWorkers. Then we trigger an error for a/v leader and wait
    for this log (we are expecting that workers will be released inside the
    try/catch block).
    
    Even if there is a bug in the code and a/v leader cannot release parallel
    workers due to occured error, one day it will finish vacuuming and call
    "proc_exit". During "proc_exit" the "before_shmem_exit_hook" along with
    the "ReleaseAllParallelWorkers" will be called.
    
    I.e. we will see the desired log, and we will mistakenly consider this test
    passed.
    
    > > Also, I don't know whether the 5th test needs this log at all, because in
    > > the end we are checking the number of free parallel workers. If a killed
    > > a/v leader doesn't release parallel workers, we'll notice it.
    >
    > If we can check the log written at process shutdown time, I think we
    > can somewhat simplify the test 5 logic by not attaching
    > 'autovacuum-start-parallel-vacuum' injection point.
    >
    > 1. attach 'autovacuum-leader-before-indexes-processing' injection point.
    > 2. wait for an av worker to stop at the injection point.
    > 3. terminate the av worker.
    > 4. verify from the log if the workers have been released.
    > 5. disable parallel autovacuum.
    > 6. check the free workers (should be 10).
    >
    > Step 5 and 6 seems to be optional though.
    
    OK, I see your point. But I'm afraid that the "%d released" log can't help
     us here for the reason I described above :
    "%d released" can be called from several places and we cannot be sure
    which one has emitted this log.
    
    I suppose to do the same as we did for try/catch block - add logging inside
    the "autovacuum_worker_before_shmem_exit" with some unique message.
    Thus, we will be sure that the workers are released precisely in the
    "before_shmem_exit_hook".
    
    The alternative is to pass some additional information to the
    "ReleaseAllParallelWorkers" function (to supplement the log it emits), but it
    doesn't seem like a good solution to me.
    
    **Comments on the 0001 patch**
    
    > +   /*
    > +    * Max number of parallel autovacuum workers. If value is 0 then parallel
    > +    * degree will computed based on number of indexes.
    > +    */
    > +   int         autovacuum_parallel_workers;
    >
    > I'm a bit concerned that the above description doesn't explain what
    > number of parallel vacuum workers are used in >0 as it mentioned only
    > the maximum number. How about rewording it to:
    >
    > Target number of parallel autovacuum workers. -1 by default disables
    > parallel vacuum during autovacuum. 0 means choose the parallel degree
    > based on the number of indexes.
    >
    
    I agree.
    
    **Comments on the 0002 patch**
    
    > +   PVWorkersUsage workers_usage;
    >     /* Counters that follow are only for scanned_pages */
    >     int64       tuples_deleted; /* # deleted from table */
    >
    > Let's insert a new line between the new line and the existing line.
    >
    
    OK
    
    > +               if (AmAutoVacuumWorkerProcess())
    > +               {
    > +                   /* Worker usage stats for parallel autovacuum. */
    > +                   appendStringInfo(&buf,
    > +                                    _("parallel workers: index
    > vacuum: %d planned, %d reserved, %d launched in total\n"),
    > +                                    vacrel->workers_usage.vacuum.nplanned,
    > +                                    vacrel->workers_usage.vacuum.nreserved,
    > +                                    vacrel->workers_usage.vacuum.nlaunched);
    > +               }
    > +               else
    > +               {
    > +                   /* Worker usage stats for manual VACUUM (PARALLEL). */
    > +                   appendStringInfo(&buf,
    > +                                    _("parallel workers: index
    > vacuum: %d planned, %d launched in total\n"),
    > +                                    vacrel->workers_usage.vacuum.nplanned,
    > +                                    vacrel->workers_usage.vacuum.nlaunched);
    > +               }
    > +           }
    >
    > These comments are very obvious so I don't think we need them.
    
    I agree.
    
    > Instead, I think it would be good to explain why we don't need to
    > report "reserved" numbers in the manual vacuum cases.
    >
    
    I think that we can clarify somewhere why the "reserved" statistic
    is collected only for autovacuum. PVWorkersStats is an appropriate
    place for it. Thus, there will be no need to write something during
    constructing the log.
    
    > ---
    > +           if (vacrel->workers_usage.vacuum.nplanned > 0)
    > +           {
    > +               /* Stats for vacuum phase of index vacuuming. */
    >
    > and
    >
    > +           if (vacrel->workers_usage.cleanup.nplanned > 0)
    > +           {
    > +               /* Stats for cleanup phase of index vacuuming. */
    > +
    >
    > I don't think we need these comments (the second one has a typo
    > though) as it's obvious.
    >
    
    I agree.
    
    >   */
    >  void
    >  parallel_vacuum_bulkdel_all_indexes(ParallelVacuumState *pvs, long
    > num_table_tuples,
    > -                                   int num_index_scans)
    > +                                   int num_index_scans, PVWorkersUsage *wusage)
    >
    > Please add a brief description of wusage to the function comment. We
    > can add comments to both parallel_vacuum_bulkldel_all_indexes() and
    > parallel_vacuum_cleanup_all_indexes() or only
    > parallel_vacuum_process_all_indexes().
    >
    
    OK. I think that adding a comment only to the
    parallel_vacuum_process_all_indexes will be more appropriate.
    (I'm not sure if the comment I came up with looks good, but I couldn't
    formulate it better).
    
    >
    > PVWorkersUsage is added twice
    >
    
    Oops
    
    **Comments on the 0003 patch**
    
    >
    > +#define VacCostParamsEquals(params) \
    > +   (vacuum_cost_delay == (params).cost_delay && \
    > +    vacuum_cost_limit == (params).cost_limit && \
    > +    VacuumCostPageDirty == (params).cost_page_dirty && \
    > +    VacuumCostPageHit == (params).cost_page_hit && \
    > +    VacuumCostPageMiss == (params).cost_page_miss)
    >
    > I'm not sure this macro helps reduce lines of code or improve
    > readability as it's used only once and it's slightly unnatural to me
    > that *Equals macro takes only one argument.
    >
    
    I agree, it looks a bit odd. I'll remove it.
    Moreover, this shmem state can be updated only by the a/v leader worker,
    so I'll allow it to read shared variables without holding a spinlock.
    It seems pretty reliable, what do you think?
    
    **Comments on the 0004 patch**
    
    > +#include "commands/vacuum.h"
    > +#include "fmgr.h"
    > +#include "miscadmin.h"
    > +#include "postmaster/autovacuum.h"
    > +#include "storage/shmem.h"
    > +#include "storage/ipc.h"
    > +#include "storage/lwlock.h"
    > +#include "utils/builtins.h"
    > +#include "utils/injection_point.h"
    >
    > We can remove some unnecessary header includes. ISTM we need only
    > fmgr.h, autovacuum.h, and injection_point.h.
    >
    
    Agree, I'll remove unused includes.
    
    > +   const char *msg_format =
    > +       _("Parallel autovacuum worker cost params: cost_limit=%d,
    > cost_delay=%g, cost_page_miss=%d, cost_page_dirty=%d,
    > cost_page_hit=%d");
    > +
    >
    > I don't think we need the translation for this message as it's not a
    > user-facing one.
    >
    > We don't capitalize the first letter in the error message.
    >
    
    I agree.
    
    > ---
    > +   ereport(DEBUG2,
    > +           (errmsg("number of free parallel autovacuum workers is set
    > to %u due to config reload",
    > +                   AutoVacuumShmem->av_freeParallelWorkers),
    > +            errhidecontext(true)));
    >
    > Why do we need to add errhidecontext(true) here?
    >
    
    I thought we don't need to write redundant info to the logfile. But I
    don't see that other DEBUG2 messages are hiding context, so
    I'll remove it.
    
    BTW, do we want to use "elog" here too?
    
    > ---
    > +    'tests': [
    > +      't/001_basic.pl',
    > +    ],
    >
    > Need to be updated to the new filename.
    >
    > ---
    > + * Copyright (c) 2020-2025, PostgreSQL Global Development Group
    >
    > Please update the copyright years.
    >
    
    Yeah, I forgot about it. Will fix it.
    
    
    Thank you very much for the review!
    Please, see the updated set of patches.
    
    --
    Best regards,
    Daniil Davydov
    
  70. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-02T22:25:39Z

    On Sun, Mar 1, 2026 at 6:46 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Sat, Feb 28, 2026 at 8:57 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > IIUC earlier patches defined autovacuum_max_parallel_workers with the
    > > limit by max_worker_processes. Suppose we set:
    > >
    > > - max_worker_processes = 8
    > > - autovacuum_max_parallel_workers = 4
    > > - max_parallel_workers = 4
    > >
    > > If we want to disable all parallel operations, we would need to set
    > > max_parallel_workers to 0 as well as either
    > > autovacuum_max_parallel_workers to 0, no? This is because if we set
    > > only max_parallel_workers to 0, autovacuum workers still can take
    > > parallel vacuum workers from the max_worker_processes pool. I might be
    > > missing something though.
    > >
    >
    > Even if av_max_parallel_workers is limited by max_worker_processes,
    > it is enough to set max_parallel_workers to 0 to disable parallel
    > autovacuum.
    >
    > When a/v leader wants to create supportive workers, it calls
    > "RegisterDynamicBackgroundWorker" function, which contain following
    > logic :
    > /*
    >  * If this is a parallel worker, check whether there are already too many
    >  * parallel workers; if so, don't register another one.
    >  */
    > if (parallel && (BackgroundWorkerData->parallel_register_count -
    >                        BackgroundWorkerData->parallel_terminate_count) >=
    >     max_parallel_workers)
    > {
    > ....
    > }
    >
    > Thus, a/v leader cannot launch any workers if max_parallel_workers is set to 0.
    
    Right. But this fact would actually support that limiting
    autovacuum_max_parallel_workers by max_parallel_workers is more
    appropriate, no?
    
    >
    > > > > If we write the log "%d parallel autovacuum workers have been
    > > > > released" in AutoVacuumReleaseParallelWorkres(), can we simplify both
    > > > > tests (4 and 5) further?
    > > > >
    > > >
    > > > It won't help the 4th test, because ReleaseParallelWorkers is called
    > > > due to both ERROR and shmem_exit, but we want to be sure that
    > > > workers are released in the try/catch block (i.e. before the shmem_exit).
    > >
    > > We already call AutoVacuumReleaseAllParallelWorker() in the PG_CATCH()
    > > block in do_autovacuum(). If we write the log in
    > > AutoVacuumReleaseParallelWorkers(), the tap test is able to check the
    > > log, no?
    > >
    >
    > Not quite. Assume that we add "%d workers have been released" log to the
    > ReleaseAllParallelWorkers. Then we trigger an error for a/v leader and wait
    > for this log (we are expecting that workers will be released inside the
    > try/catch block).
    >
    > Even if there is a bug in the code and a/v leader cannot release parallel
    > workers due to occured error, one day it will finish vacuuming and call
    > "proc_exit". During "proc_exit" the "before_shmem_exit_hook" along with
    > the "ReleaseAllParallelWorkers" will be called.
    
    What bugs are you concerned about in this case? I'm not sure what you
    meant by "a/v leader cannot release parallel workers due to occured
    error". It sounds like you mentioned a case where there is a bug in
    AutoVacuumReleaseParallelWorkers() but if there is the bug and the
    leader failed to release parallel workers, we would end up not writing
    these elogs in either case.
    
    >
    > > > Also, I don't know whether the 5th test needs this log at all, because in
    > > > the end we are checking the number of free parallel workers. If a killed
    > > > a/v leader doesn't release parallel workers, we'll notice it.
    > >
    > > If we can check the log written at process shutdown time, I think we
    > > can somewhat simplify the test 5 logic by not attaching
    > > 'autovacuum-start-parallel-vacuum' injection point.
    > >
    > > 1. attach 'autovacuum-leader-before-indexes-processing' injection point.
    > > 2. wait for an av worker to stop at the injection point.
    > > 3. terminate the av worker.
    > > 4. verify from the log if the workers have been released.
    > > 5. disable parallel autovacuum.
    > > 6. check the free workers (should be 10).
    > >
    > > Step 5 and 6 seems to be optional though.
    >
    > OK, I see your point. But I'm afraid that the "%d released" log can't help
    >  us here for the reason I described above :
    > "%d released" can be called from several places and we cannot be sure
    > which one has emitted this log.
    >
    > I suppose to do the same as we did for try/catch block - add logging inside
    > the "autovacuum_worker_before_shmem_exit" with some unique message.
    > Thus, we will be sure that the workers are released precisely in the
    > "before_shmem_exit_hook".
    >
    > The alternative is to pass some additional information to the
    > "ReleaseAllParallelWorkers" function (to supplement the log it emits), but it
    > doesn't seem like a good solution to me.
    
    I'm not sure if it's important to check how
    AutoVacuumReleaseAllParallelWorkers() has been called (either in
    PG_CATCH() block or by autovacuum_worker_before_shmem_exit()). We
    would end up having to add a unique message to each caller of
    AutoVacuumReleaseAllParallelWorkers() in the future. I guess it's more
    important to make sure that all workers have been released in the end.
    
    In that sense, it would make more sense to check that all workers have
    actually been released (i.e., checking by
    get_parallel_autovacuum_free_workers()) after a parallel vacuum
    instead of checking workers being released by debug logs. That is, we
    can check at each test end if get_parallel_autovacuum_free_workers()
    returns the expected number after disabling parallel autovacuum.
    
    >
    > > +               if (AmAutoVacuumWorkerProcess())
    > > +               {
    > > +                   /* Worker usage stats for parallel autovacuum. */
    > > +                   appendStringInfo(&buf,
    > > +                                    _("parallel workers: index
    > > vacuum: %d planned, %d reserved, %d launched in total\n"),
    > > +                                    vacrel->workers_usage.vacuum.nplanned,
    > > +                                    vacrel->workers_usage.vacuum.nreserved,
    > > +                                    vacrel->workers_usage.vacuum.nlaunched);
    > > +               }
    > > +               else
    > > +               {
    > > +                   /* Worker usage stats for manual VACUUM (PARALLEL). */
    > > +                   appendStringInfo(&buf,
    > > +                                    _("parallel workers: index
    > > vacuum: %d planned, %d launched in total\n"),
    > > +                                    vacrel->workers_usage.vacuum.nplanned,
    > > +                                    vacrel->workers_usage.vacuum.nlaunched);
    > > +               }
    > > +           }
    > >
    > > These comments are very obvious so I don't think we need them.
    >
    > I agree.
    >
    > > Instead, I think it would be good to explain why we don't need to
    > > report "reserved" numbers in the manual vacuum cases.
    > >
    >
    > I think that we can clarify somewhere why the "reserved" statistic
    > is collected only for autovacuum. PVWorkersStats is an appropriate
    > place for it. Thus, there will be no need to write something during
    > constructing the log.
    
    On second thoughts on the "planned" and "reserved", can we consider
    what the patch implemented as "reserved" as the "planned" in
    autovacuum cases? That is, in autovacuum cases, the "planned" number
    considers the number of parallel degrees based on the number of
    indexes (or autovacuum_parallel_workers value) as well as the number
    of workers that have actually been reserved. In cases of
    autovacuum_max_parallel_workers shortage, users would notice by seeing
    logs that enough workers are not planned in the first place against
    the number of indexes on the table. That might be less confusing for
    users rather than introducing a new "reserved" concept in the vacuum
    logs. Also, it slightly helps simplify the codes.
    
    >
    > **Comments on the 0003 patch**
    >
    > >
    > > +#define VacCostParamsEquals(params) \
    > > +   (vacuum_cost_delay == (params).cost_delay && \
    > > +    vacuum_cost_limit == (params).cost_limit && \
    > > +    VacuumCostPageDirty == (params).cost_page_dirty && \
    > > +    VacuumCostPageHit == (params).cost_page_hit && \
    > > +    VacuumCostPageMiss == (params).cost_page_miss)
    > >
    > > I'm not sure this macro helps reduce lines of code or improve
    > > readability as it's used only once and it's slightly unnatural to me
    > > that *Equals macro takes only one argument.
    > >
    >
    > I agree, it looks a bit odd. I'll remove it.
    > Moreover, this shmem state can be updated only by the a/v leader worker,
    > so I'll allow it to read shared variables without holding a spinlock.
    > It seems pretty reliable, what do you think?
    
    Right. It's safe for the leader to read these fields without locks.
    
    
    > > ---
    > > +   ereport(DEBUG2,
    > > +           (errmsg("number of free parallel autovacuum workers is set
    > > to %u due to config reload",
    > > +                   AutoVacuumShmem->av_freeParallelWorkers),
    > > +            errhidecontext(true)));
    > >
    > > Why do we need to add errhidecontext(true) here?
    > >
    >
    > I thought we don't need to write redundant info to the logfile. But I
    > don't see that other DEBUG2 messages are hiding context, so
    > I'll remove it.
    >
    > BTW, do we want to use "elog" here too?
    
    +1
    
    Here are some comments:
    
    * 0001 patch:
    
      * of the worker list (see above).
    @@ -299,6 +308,8 @@ typedef struct
            WorkerInfo      av_startingWorker;
            AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
            pg_atomic_uint32 av_nworkersForBalance;
    +       uint32          av_freeParallelWorkers;
    +       uint32          av_maxParallelWorkers;
     } AutoVacuumShmemStruct;
    
    We should use int32 instead of uint32.
    
    * 0003 patch:
    
    I've attached the proposed changes to the 0003 patch, which includes:
    
    - removal of VacuumCostParams as it's not necessary.
    - comment updates.
    - other cosmetic updates.
    
    * 0004 patch:
    
    +#ifdef USE_INJECTION_POINTS
    +   /*
    +    * If we are parallel autovacuum worker, we can consume delay parameters
    +    * during index processing (via vacuum_delay_point call). This logging
    +    * allows tests to ensure this.
    +    */
    +   if (shared->is_autovacuum)
    +       elog(DEBUG2,
    +            "parallel autovacuum worker cost params: cost_limit=%d,
    cost_delay=%g, cost_page_miss=%d, cost_page_dirty=%d,
    cost_page_hit=%d",
    +            vacuum_cost_limit,
    +            vacuum_cost_delay,
    +            VacuumCostPageMiss,
    +            VacuumCostPageDirty,
    +            VacuumCostPageHit);
    +#endif
    
    While it's true that we use these logs only during the regression
    tests that are enabled only when injection points are also enabled,
    these logs themselves are not related to the injection points. I'd
    recommend writing these logs when the worker refreshes its local delay
    parameters (i.e., in parallel_vacuum_update_shared_delay_params()).
    
    ---
    +$node->append_conf('postgresql.conf', qq{
    +   max_worker_processes = 20
    +   max_parallel_workers = 20
    +   max_parallel_maintenance_workers = 20
    +   autovacuum_max_parallel_workers = 20
    +   log_min_messages = debug2
    +   log_autovacuum_min_duration = 0
    +   autovacuum_naptime = '1s'
    +   min_parallel_index_scan_size = 0
    +   shared_preload_libraries=test_autovacuum
    +});
    
    It would be better to set log_autovacuum_min_duration = 0 to the
    specific table instead of setting globally.
    
    ---
    +   uint32      nfree_workers;
    +
    +#ifndef USE_INJECTION_POINTS
    +   ereport(ERROR, errmsg("injection points not supported"));
    +#endif
    +
    +   nfree_workers = AutoVacuumGetFreeParallelWorkers();
    +
    +   PG_RETURN_UINT32(nfree_workers);
    +}
    
    As I commented above, I think we should use int32 for the number of
    parallel free workers. So let's change it here too.
    
    ---
    +PG_FUNCTION_INFO_V1(get_parallel_autovacuum_free_workers);
    +Datum
    +get_parallel_autovacuum_free_workers(PG_FUNCTION_ARGS)
    +{
    +   uint32      nfree_workers;
    +
    +#ifndef USE_INJECTION_POINTS
    +   ereport(ERROR, errmsg("injection points not supported"));
    +#endif
    +
    
    I think we don't necessarily need to check the USE_INJECTION_POINTS in
    this function as we already have the check in the tap tests. The
    function itself is actually workable even without injection points.
    
    ---
    +# Copyright (c) 2024-2025, PostgreSQL Global Development Group
    +
    
    Please update the copyright year here too.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  71. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-04T06:58:49Z

    Hi,
    
    On Tue, Mar 3, 2026 at 5:26 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Sun, Mar 1, 2026 at 6:46 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > Thus, a/v leader cannot launch any workers if max_parallel_workers is set to 0.
    >
    > Right. But this fact would actually support that limiting
    > autovacuum_max_parallel_workers by max_parallel_workers is more
    > appropriate, no?
    >
    
    av_max_parallel_workers is really limited by max_parallel_workers only
    during shmem init. After that we can change it to a value that is higher
    than max_parallel_workers, and nothing bad will happen (obviously).
    
    So, my point was : why should we have this explicit limitation if it
    1) doesn't guard us from something bad and 2) can be violated at any time
    (via ALTER SYSTEM SET ...).
    
    Now it seems to me that limiting our parameter by max_parallel_workers is
    more about grouping of logically related parameters, not a practical necessity.
    
    > > Even if there is a bug in the code and a/v leader cannot release parallel
    > > workers due to occured error, one day it will finish vacuuming and call
    > > "proc_exit". During "proc_exit" the "before_shmem_exit_hook" along with
    > > the "ReleaseAllParallelWorkers" will be called.
    >
    > What bugs are you concerned about in this case? I'm not sure what you
    > meant by "a/v leader cannot release parallel workers due to occured
    > error". It sounds like you mentioned a case where there is a bug in
    > AutoVacuumReleaseParallelWorkers() but if there is the bug and the
    > leader failed to release parallel workers, we would end up not writing
    > these elogs in either case.
    >
    
    Not precisely. I mean a bug that causes a/v leader to not call
    AutoVacuumReleaseParallelWorkers in the try/catch block.
    I'll continue my thoughts below.
    
    > > I suppose to do the same as we did for try/catch block - add logging inside
    > > the "autovacuum_worker_before_shmem_exit" with some unique message.
    > > Thus, we will be sure that the workers are released precisely in the
    > > "before_shmem_exit_hook".
    > >
    > > The alternative is to pass some additional information to the
    > > "ReleaseAllParallelWorkers" function (to supplement the log it emits), but it
    > > doesn't seem like a good solution to me.
    >
    > I'm not sure if it's important to check how
    > AutoVacuumReleaseAllParallelWorkers() has been called (either in
    > PG_CATCH() block or by autovacuum_worker_before_shmem_exit()). We
    > would end up having to add a unique message to each caller of
    > AutoVacuumReleaseAllParallelWorkers() in the future. I guess it's more
    > important to make sure that all workers have been released in the end.
    >
    > In that sense, it would make more sense to check that all workers have
    > actually been released (i.e., checking by
    > get_parallel_autovacuum_free_workers()) after a parallel vacuum
    > instead of checking workers being released by debug logs. That is, we
    > can check at each test end if get_parallel_autovacuum_free_workers()
    > returns the expected number after disabling parallel autovacuum.
    >
    
    Sure, at first we want to check whether all workers have been
    released. But the ability to release them precisely in the try/catch
    block is also important, because if it doesn't - a/v worker can "hold"
    these workers until it finishes vacuuming of other tables (which can
    take a lot of time). Such a situation will surely degrade performance,
    so I think that we must check whether we can release workers precisely
    during ERROR handling. Do you agree with it?
    
    I understand your concerns about adding a unique log message for each
    ReleaseAll call. But I cannot imagine a new situation when we need to
    emergency release workers. If you think that it might be possible, I can
    propose adding a new optional parameter to the "ReleaseAll" function -
    something like "char *context_msg", which will be added to the elog placed
    inside this function.
    
    > On second thoughts on the "planned" and "reserved", can we consider
    > what the patch implemented as "reserved" as the "planned" in
    > autovacuum cases? That is, in autovacuum cases, the "planned" number
    > considers the number of parallel degrees based on the number of
    > indexes (or autovacuum_parallel_workers value) as well as the number
    > of workers that have actually been reserved. In cases of
    > autovacuum_max_parallel_workers shortage, users would notice by seeing
    > logs that enough workers are not planned in the first place against
    > the number of indexes on the table. That might be less confusing for
    > users rather than introducing a new "reserved" concept in the vacuum
    > logs. Also, it slightly helps simplify the codes.
    
    Yeah, it sounds tempting. But in this case we're shifting more responsibility
    to the user. For instance :
    If av_max_workers = 5 and there are two a/v leaders each of which is trying
    to launch 3 parallel workers, we will see logs like "3 planned, 3 launched",
    "2 planned, 2 launched". IMHO, such a log doesn't imply that there is a
    shortage of workers. I.e. this is the user's responsibility to notice that the
    second a/v leader could launch more than 2 workers for processing of the
    table with (N + 2) indexes.
    In this case even our previous version of logging will give more information
    to the user : "3 planned, 3 launched", "3 planned, 2 launched".
    
    If we don't want to create a new "reserved" concept, maybe we can rename
    it to something more intuitive? For example, "n_abandoned" - number of
    workers that we were unable to launch due to av_max_parallel_workers
    shortage. If n_abandoned is 0 and n_launched < n_planned, the user can
    conclude that he should increase the max_parallel_workers parameter.
    And vica versa, if n_launched == n_planned and n_abandoned > 0, the
    user can conclude that he should increase the
    autovacuum_max_parallel_workers parameter.
    
    What do you think?
    
    **Comments on the 0001 patch**
    
    >   * of the worker list (see above).
    > @@ -299,6 +308,8 @@ typedef struct
    >         WorkerInfo      av_startingWorker;
    >         AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
    >         pg_atomic_uint32 av_nworkersForBalance;
    > +       uint32          av_freeParallelWorkers;
    > +       uint32          av_maxParallelWorkers;
    >  } AutoVacuumShmemStruct;
    >
    > We should use int32 instead of uint32.
    
    I don't mind, but I don't quite understand the reason. We assume that the
    minimal value for both variables is 0. Why shouldn't we use unsigned
    data type?
    
    **Comments on the 0003 patch**
    
    > I've attached the proposed changes to the 0003 patch, which includes:
    >
    > - removal of VacuumCostParams as it's not necessary.
    > - comment updates.
    > - other cosmetic updates.
    
    Thank you! Most of the proposals are LGTM, but I'll edit a few comments.
    
    **Comments on the 0004 patch**
    
    > +#ifdef USE_INJECTION_POINTS
    > +   /*
    > +    * If we are parallel autovacuum worker, we can consume delay parameters
    > +    * during index processing (via vacuum_delay_point call). This logging
    > +    * allows tests to ensure this.
    > +    */
    > +   if (shared->is_autovacuum)
    > +       elog(DEBUG2,
    > +            "parallel autovacuum worker cost params: cost_limit=%d,
    > cost_delay=%g, cost_page_miss=%d, cost_page_dirty=%d,
    > cost_page_hit=%d",
    > +            vacuum_cost_limit,
    > +            vacuum_cost_delay,
    > +            VacuumCostPageMiss,
    > +            VacuumCostPageDirty,
    > +            VacuumCostPageHit);
    > +#endif
    >
    > While it's true that we use these logs only during the regression
    > tests that are enabled only when injection points are also enabled,
    > these logs themselves are not related to the injection points. I'd
    > recommend writing these logs when the worker refreshes its local delay
    > parameters (i.e., in parallel_vacuum_update_shared_delay_params()).
    >
    
    I agree (thought about it too).
    
    > +$node->append_conf('postgresql.conf', qq{
    > +   max_worker_processes = 20
    > +   max_parallel_workers = 20
    > +   max_parallel_maintenance_workers = 20
    > +   autovacuum_max_parallel_workers = 20
    > +   log_min_messages = debug2
    > +   log_autovacuum_min_duration = 0
    > +   autovacuum_naptime = '1s'
    > +   min_parallel_index_scan_size = 0
    > +   shared_preload_libraries=test_autovacuum
    > +});
    >
    > It would be better to set log_autovacuum_min_duration = 0 to the
    > specific table instead of setting globally.
    >
    
    I agree.
    
    > +   uint32      nfree_workers;
    > +
    > +#ifndef USE_INJECTION_POINTS
    > +   ereport(ERROR, errmsg("injection points not supported"));
    > +#endif
    > +
    > +   nfree_workers = AutoVacuumGetFreeParallelWorkers();
    > +
    > +   PG_RETURN_UINT32(nfree_workers);
    > +}
    >
    > As I commented above, I think we should use int32 for the number of
    > parallel free workers. So let's change it here too.
    
    No problem. But again, why do we avoid unsigned integer?
    
    > +PG_FUNCTION_INFO_V1(get_parallel_autovacuum_free_workers);
    > +Datum
    > +get_parallel_autovacuum_free_workers(PG_FUNCTION_ARGS)
    > +{
    > +   uint32      nfree_workers;
    > +
    > +#ifndef USE_INJECTION_POINTS
    > +   ereport(ERROR, errmsg("injection points not supported"));
    > +#endif
    > +
    >
    > I think we don't necessarily need to check the USE_INJECTION_POINTS in
    > this function as we already have the check in the tap tests. The
    > function itself is actually workable even without injection points.
    >
    
    I agree. It is left from the previous tests implementation.
    
    > +# Copyright (c) 2024-2025, PostgreSQL Global Development Group
    > +
    >
    > Please update the copyright year here too.
    
    I keep forgetting about the meson file, sorry.
    
    
    Thank you very much for the review!
    Please, see an updated set of patches.
    
    --
    Best regards,
    Daniil Davydov
    
  72. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-10T18:13:34Z

    On Tue, Mar 3, 2026 at 10:59 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Tue, Mar 3, 2026 at 5:26 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Sun, Mar 1, 2026 at 6:46 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >
    > > > Thus, a/v leader cannot launch any workers if max_parallel_workers is set to 0.
    > >
    > > Right. But this fact would actually support that limiting
    > > autovacuum_max_parallel_workers by max_parallel_workers is more
    > > appropriate, no?
    > >
    >
    > av_max_parallel_workers is really limited by max_parallel_workers only
    > during shmem init. After that we can change it to a value that is higher
    > than max_parallel_workers, and nothing bad will happen (obviously).
    >
    > So, my point was : why should we have this explicit limitation if it
    > 1) doesn't guard us from something bad and 2) can be violated at any time
    > (via ALTER SYSTEM SET ...).
    >
    > Now it seems to me that limiting our parameter by max_parallel_workers is
    > more about grouping of logically related parameters, not a practical necessity.
    
    I believe there is also a benefit for users when they want to disable
    all parallel behavior. If av_max_parallel_workers is in
    max_parallel_worker group, they would have to set just
    max_parallel_workers to 0. Otherwise, they would have to set both
    max_parallel_workers and av_max_parallel_workers.
    
    >
    > > > I suppose to do the same as we did for try/catch block - add logging inside
    > > > the "autovacuum_worker_before_shmem_exit" with some unique message.
    > > > Thus, we will be sure that the workers are released precisely in the
    > > > "before_shmem_exit_hook".
    > > >
    > > > The alternative is to pass some additional information to the
    > > > "ReleaseAllParallelWorkers" function (to supplement the log it emits), but it
    > > > doesn't seem like a good solution to me.
    > >
    > > I'm not sure if it's important to check how
    > > AutoVacuumReleaseAllParallelWorkers() has been called (either in
    > > PG_CATCH() block or by autovacuum_worker_before_shmem_exit()). We
    > > would end up having to add a unique message to each caller of
    > > AutoVacuumReleaseAllParallelWorkers() in the future. I guess it's more
    > > important to make sure that all workers have been released in the end.
    > >
    > > In that sense, it would make more sense to check that all workers have
    > > actually been released (i.e., checking by
    > > get_parallel_autovacuum_free_workers()) after a parallel vacuum
    > > instead of checking workers being released by debug logs. That is, we
    > > can check at each test end if get_parallel_autovacuum_free_workers()
    > > returns the expected number after disabling parallel autovacuum.
    > >
    >
    > Sure, at first we want to check whether all workers have been
    > released. But the ability to release them precisely in the try/catch
    > block is also important, because if it doesn't - a/v worker can "hold"
    > these workers until it finishes vacuuming of other tables (which can
    > take a lot of time). Such a situation will surely degrade performance,
    > so I think that we must check whether we can release workers precisely
    > during ERROR handling. Do you agree with it?
    
    I agree that we need to make sure that parallel workers are released
    even during ERROR handling, but I don't think it's important to check
    the places where AutoVacuumReleaesAllParallelWorkers() is called, by
    using regression tests. It's more important and future-proof that we
    check if all workers are released according to the shmem data. In
    other words, even if we call AutoVacuumReleaseAllParallelWorkers() in
    an unexpected call path in an ERROR case, it's still okay if we
    successfully release all workers in the end. These regression tests
    should test these database behavior but not what specific code path
    taken. If we can check if all workers are released by checking the
    shmem, why do we need to check further where they are released?
    
    >
    > I understand your concerns about adding a unique log message for each
    > ReleaseAll call. But I cannot imagine a new situation when we need to
    > emergency release workers. If you think that it might be possible, I can
    > propose adding a new optional parameter to the "ReleaseAll" function -
    > something like "char *context_msg", which will be added to the elog placed
    > inside this function.
    
    I think we should not make the function complex just for testing
    purposes. My point is that what we should be testing is the behavior
    -- specifically whether parallel workers are released at the expected
    timing -- rather than focusing on whether a specific code path was
    executed.
    
    >
    > > On second thoughts on the "planned" and "reserved", can we consider
    > > what the patch implemented as "reserved" as the "planned" in
    > > autovacuum cases? That is, in autovacuum cases, the "planned" number
    > > considers the number of parallel degrees based on the number of
    > > indexes (or autovacuum_parallel_workers value) as well as the number
    > > of workers that have actually been reserved. In cases of
    > > autovacuum_max_parallel_workers shortage, users would notice by seeing
    > > logs that enough workers are not planned in the first place against
    > > the number of indexes on the table. That might be less confusing for
    > > users rather than introducing a new "reserved" concept in the vacuum
    > > logs. Also, it slightly helps simplify the codes.
    >
    > Yeah, it sounds tempting. But in this case we're shifting more responsibility
    > to the user. For instance :
    > If av_max_workers = 5 and there are two a/v leaders each of which is trying
    > to launch 3 parallel workers, we will see logs like "3 planned, 3 launched",
    > "2 planned, 2 launched". IMHO, such a log doesn't imply that there is a
    > shortage of workers. I.e. this is the user's responsibility to notice that the
    > second a/v leader could launch more than 2 workers for processing of the
    > table with (N + 2) indexes.
    > In this case even our previous version of logging will give more information
    > to the user : "3 planned, 3 launched", "3 planned, 2 launched".
    >
    > If we don't want to create a new "reserved" concept, maybe we can rename
    > it to something more intuitive? For example, "n_abandoned" - number of
    > workers that we were unable to launch due to av_max_parallel_workers
    > shortage. If n_abandoned is 0 and n_launched < n_planned, the user can
    > conclude that he should increase the max_parallel_workers parameter.
    > And vica versa, if n_launched == n_planned and n_abandoned > 0, the
    > user can conclude that he should increase the
    > autovacuum_max_parallel_workers parameter.
    >
    > What do you think?
    
    While I agree that showing only two numbers might lack some
    information for users, I guess the same is true for
    max_parallel_maintenance_workers or other parallel queries related to
    GUC parameters. For instance, suppose we set
    max_parallel_maintenance_workers to 2, if the table has (large enough)
    4 indexes, we would plan to execute a parallel vacuum with 2 workers
    instead of 4 due to max_parallel_maintenance_worker shortage and it's
    even possible that only 1 worker can launch due to
    max_worker_processes shortage. In this case, we currently consider
    that 2 workers are planned. Isn't it the same situation as the case
    where we reserved 2 parallel vacuum workers for autovacuum for the
    table with 4 indexes?
    
    >
    > **Comments on the 0001 patch**
    >
    > >   * of the worker list (see above).
    > > @@ -299,6 +308,8 @@ typedef struct
    > >         WorkerInfo      av_startingWorker;
    > >         AutoVacuumWorkItem av_workItems[NUM_WORKITEMS];
    > >         pg_atomic_uint32 av_nworkersForBalance;
    > > +       uint32          av_freeParallelWorkers;
    > > +       uint32          av_maxParallelWorkers;
    > >  } AutoVacuumShmemStruct;
    > >
    > > We should use int32 instead of uint32.
    >
    > I don't mind, but I don't quite understand the reason. We assume that the
    > minimal value for both variables is 0. Why shouldn't we use unsigned
    > data type?
    
    Unsigned integers should be used for bit masks, flags, or when we need
    to handle more than INT_MAX. Signed integers are preferable in other
    cases as we're using signed integers for controlling the number of
    workers and autovacuum_max_parallel_workers is defined as signed int
    (which could be stored to AutoVacuumShmem->av_maxParallelWorkers).
    
    Here are some review comments.
    
    * 0001 patch:
    
    +        /* Cannot release more workers than reserved */
    +        Assert(nworkers <= av_nworkers_reserved);
    
    I think it's better to use Min() to cap the number of workers to be
    released by av_nworkers_reserved as Assert() won't work in release
    builds.
    
    * 0004 patch:
    
    Can we write the same test cases while not relying on the 0002 patch
    (i.e., worker usage logging)? We check the worker usage log at two
    places in the regression tests. The idea is that we write the number
    of workers planned, reserved, and launched in DEBUG log level and
    check these logs in the regression tests. The patch 0001, 0003, and
    0004 can be merged before push while we might want more discussion on
    the 0002 patch.
    
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  73. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-11T11:28:30Z

    Hi,
    
    On Wed, Mar 11, 2026 at 1:14 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Tue, Mar 3, 2026 at 10:59 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > So, my point was : why should we have this explicit limitation if it
    > > 1) doesn't guard us from something bad and 2) can be violated at any time
    > > (via ALTER SYSTEM SET ...).
    > >
    > > Now it seems to me that limiting our parameter by max_parallel_workers is
    > > more about grouping of logically related parameters, not a practical necessity.
    >
    > I believe there is also a benefit for users when they want to disable
    > all parallel behavior. If av_max_parallel_workers is in
    > max_parallel_worker group, they would have to set just
    > max_parallel_workers to 0. Otherwise, they would have to set both
    > max_parallel_workers and av_max_parallel_workers.
    >
    
    OK, thank you for the explanation!
    
    > I agree that we need to make sure that parallel workers are released
    > even during ERROR handling, but I don't think it's important to check
    > the places where AutoVacuumReleaesAllParallelWorkers() is called, by
    > using regression tests. It's more important and future-proof that we
    > check if all workers are released according to the shmem data. In
    > other words, even if we call AutoVacuumReleaseAllParallelWorkers() in
    > an unexpected call path in an ERROR case, it's still okay if we
    > successfully release all workers in the end. These regression tests
    > should test these database behavior but not what specific code path
    > taken.
    
    Indeed, I can't remember where else in the tests we check the passage
    along specific code paths in this way.
    
    > If we can check if all workers are released by checking the
    > shmem, why do we need to check further where they are released?
    
    My point of view was that this code path is so important that we need to
    test it (important in terms of performance).
    
    But of course even if for some reason we cannot release workers inside
    the try/catch block, we can still be sure that they will be released somewhere
    else, because we have tested it.
    
    > I think we should not make the function complex just for testing
    > purposes. My point is that what we should be testing is the behavior
    > -- specifically whether parallel workers are released at the expected
    > timing -- rather than focusing on whether a specific code path was
    > executed.
    
    You've convinced me :)
    I'll add a log to the "ReleaseWorkers" function and tests will only
    search for it.
    
    > While I agree that showing only two numbers might lack some
    > information for users, I guess the same is true for
    > max_parallel_maintenance_workers or other parallel queries related to
    > GUC parameters. For instance, suppose we set
    > max_parallel_maintenance_workers to 2, if the table has (large enough)
    > 4 indexes, we would plan to execute a parallel vacuum with 2 workers
    > instead of 4 due to max_parallel_maintenance_worker shortage and it's
    > even possible that only 1 worker can launch due to
    > max_worker_processes shortage. In this case, we currently consider
    > that 2 workers are planned. Isn't it the same situation as the case
    > where we reserved 2 parallel vacuum workers for autovacuum for the
    > table with 4 indexes?
    
    I don't think that examples with other "max_parallel_" parameters will be
    appropriate, because these parameters are limiting the number of parallel
    workers for *single* operation/executor node/... .  At the same time,
    av_max_parallel_workers limits the total number of parallel workers across
    all a/v leaders.
    
    Regarding the situation that you provided :
    The number of planned workers is reduced inside the
    parallel_vacuum_compute_workers due to the max_parallel_maintenance_workers
    limit.  I.e. we cannot plan more workers than required by the config, and
    it's completely OK No one expects the number of "planned workers" to be more
    than max_parallel_maintenance_workers.
    
    IMO there is no need to make efforts to track the shortage of
    max_parallel_maintenance_workers for the VACUUM (PARALLEL), because this
    parameter just plays the role of a limiter. We will consider only the
    shortage of max_parallel_workers, that can be determined by looking at
    "planned vs. launched".
    
    And here is a difference with a parallel autovacuum :
    av_max_parallel_workers is considered twice : in the
    "parallel_vacuum_compute_workers" and "ReserveWorkers" functions.
    So the low number of launched workers can be explained by the shortage of
    both av_max_parallel_workers and max_parallel_workers. Since we want to
    distinguish between these cases, we have added the "nreserved" concept.
    
    I see that few modules can report something like "out of background worker
    slots" when they cannot launch more workers due to max_parallel_workers
    shortage (but modules depending on the "parallel.c" logic don't do so).
    This fact gave me another idea :
    If we don't want to log "nreserved" or some other similar value, maybe
    we should add logging after the "ReserveWorkers" function? I.e. if some
    workers cannot be reserved, we can emit a log like "out of parallel
    autovacuum workers. you should increase the av_max_parallel_workers
    parameter". Having this log can help the user distinguish between
    max_parallel_workers/av_max_parallel_workers shortage situations.
    What do you think?
    
    Summary :
    1)
    I think that we should not look at maintenance vacuum while
    considering how to inform the user about parameters shortage for autovacuum,
    because we have a more complicated situation in case of autovacuum.
    2)
    I suggest adding a separate log that will be emitted every time we are
    unable to start workers due to a shortage of av_max_parallel_workers.
    
    > > I don't mind, but I don't quite understand the reason. We assume that the
    > > minimal value for both variables is 0. Why shouldn't we use unsigned
    > > data type?
    >
    > Unsigned integers should be used for bit masks, flags, or when we need
    > to handle more than INT_MAX. Signed integers are preferable in other
    > cases as we're using signed integers for controlling the number of
    > workers and autovacuum_max_parallel_workers is defined as signed int
    > (which could be stored to AutoVacuumShmem->av_maxParallelWorkers).
    
    I understood, thank you.
    
    > * 0001 patch:
    >
    > +        /* Cannot release more workers than reserved */
    > +        Assert(nworkers <= av_nworkers_reserved);
    >
    > I think it's better to use Min() to cap the number of workers to be
    > released by av_nworkers_reserved as Assert() won't work in release
    > builds.
    
    I agree.
    
    > * 0004 patch:
    >
    > Can we write the same test cases while not relying on the 0002 patch
    > (i.e., worker usage logging)? We check the worker usage log at two
    > places in the regression tests. The idea is that we write the number
    > of workers planned, reserved, and launched in DEBUG log level and
    > check these logs in the regression tests. The patch 0001, 0003, and
    > 0004 can be merged before push while we might want more discussion on
    > the 0002 patch.
    
    Possibly we can introduce a new injection point, or a new log for it.
    But I assume that the subject of discussion in patch 0002 is the
    "nreserved" logic, and "nlaunched/nplanned" logic does not raise any
    questions.
    
    I suggest splitting the 0002 patch into two parts : 1) basic logic and
    2) additional logic with nreserved or something else. The second part can be
    discussed in isolation from the patch set. If we do this, we may not have to
    change the tests. What do you think?
    
    
    Thank you for the review!
    Please, see the updated set of patches.
    I haven't touched patch 0002 yet, because I'd like to hear your opinion on
    my suggestions above first.
    
    --
    Best regards,
    Daniil Davydov
    
  74. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-11T19:05:16Z

    On Wed, Mar 11, 2026 at 4:28 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    >
    > > While I agree that showing only two numbers might lack some
    > > information for users, I guess the same is true for
    > > max_parallel_maintenance_workers or other parallel queries related to
    > > GUC parameters. For instance, suppose we set
    > > max_parallel_maintenance_workers to 2, if the table has (large enough)
    > > 4 indexes, we would plan to execute a parallel vacuum with 2 workers
    > > instead of 4 due to max_parallel_maintenance_worker shortage and it's
    > > even possible that only 1 worker can launch due to
    > > max_worker_processes shortage. In this case, we currently consider
    > > that 2 workers are planned. Isn't it the same situation as the case
    > > where we reserved 2 parallel vacuum workers for autovacuum for the
    > > table with 4 indexes?
    >
    > I don't think that examples with other "max_parallel_" parameters will be
    > appropriate, because these parameters are limiting the number of parallel
    > workers for *single* operation/executor node/... .  At the same time,
    > av_max_parallel_workers limits the total number of parallel workers across
    > all a/v leaders.
    >
    > Regarding the situation that you provided :
    > The number of planned workers is reduced inside the
    > parallel_vacuum_compute_workers due to the max_parallel_maintenance_workers
    > limit.  I.e. we cannot plan more workers than required by the config, and
    > it's completely OK No one expects the number of "planned workers" to be more
    > than max_parallel_maintenance_workers.
    >
    > IMO there is no need to make efforts to track the shortage of
    > max_parallel_maintenance_workers for the VACUUM (PARALLEL), because this
    > parameter just plays the role of a limiter. We will consider only the
    > shortage of max_parallel_workers, that can be determined by looking at
    > "planned vs. launched".
    >
    > And here is a difference with a parallel autovacuum :
    > av_max_parallel_workers is considered twice : in the
    > "parallel_vacuum_compute_workers" and "ReserveWorkers" functions.
    > So the low number of launched workers can be explained by the shortage of
    > both av_max_parallel_workers and max_parallel_workers. Since we want to
    > distinguish between these cases, we have added the "nreserved" concept.
    >
    > I see that few modules can report something like "out of background worker
    > slots" when they cannot launch more workers due to max_parallel_workers
    > shortage (but modules depending on the "parallel.c" logic don't do so).
    > This fact gave me another idea :
    > If we don't want to log "nreserved" or some other similar value, maybe
    > we should add logging after the "ReserveWorkers" function? I.e. if some
    > workers cannot be reserved, we can emit a log like "out of parallel
    > autovacuum workers. you should increase the av_max_parallel_workers
    > parameter". Having this log can help the user distinguish between
    > max_parallel_workers/av_max_parallel_workers shortage situations.
    > What do you think?
    
    My point is that the process of determining the number of workers
    planned to launch is somewhat unclear to users in both cases. We
    consider not only GUCs such as max_parallel_maintenance_workers but
    also index AM definitions (i.e., amparallelvacuumoption) and index
    sizes etc. But I agree that providing more detailed logs might help
    users understand and notice the av_max_parallel_workers shortage.
    
    BTW thes discussion made me think to change av_max_parallel_workers to
    control the number of workers per-autovacuum worker instead (with
    renaming it to say max_parallel_workers_per_autovacuum_worker). Users
    can compute the maximum number of parallel workers the system requires
    by (autovacuum_worker_slots *
    max_parallel_workers_per_autovacuum_worker). We would no longer need
    the reservation and release logic. I'd like to hear your opinion.
    
    >
    > Summary :
    > 1)
    > I think that we should not look at maintenance vacuum while
    > considering how to inform the user about parameters shortage for autovacuum,
    > because we have a more complicated situation in case of autovacuum.
    > 2)
    > I suggest adding a separate log that will be emitted every time we are
    > unable to start workers due to a shortage of av_max_parallel_workers.
    
    For (2), do you mean that the worker writes these logs regardless of
    log_autovacuum_min_duration setting? I'm concerned that the server
    logs would be flooded with these logs especially when multiple
    autovacuum workers are working very actively and the system is facing
    a shortage of av_max_parallel_workers.
    
    >
    > > * 0004 patch:
    > >
    > > Can we write the same test cases while not relying on the 0002 patch
    > > (i.e., worker usage logging)? We check the worker usage log at two
    > > places in the regression tests. The idea is that we write the number
    > > of workers planned, reserved, and launched in DEBUG log level and
    > > check these logs in the regression tests. The patch 0001, 0003, and
    > > 0004 can be merged before push while we might want more discussion on
    > > the 0002 patch.
    >
    > Possibly we can introduce a new injection point, or a new log for it.
    > But I assume that the subject of discussion in patch 0002 is the
    > "nreserved" logic, and "nlaunched/nplanned" logic does not raise any
    > questions.
    >
    > I suggest splitting the 0002 patch into two parts : 1) basic logic and
    > 2) additional logic with nreserved or something else. The second part can be
    > discussed in isolation from the patch set. If we do this, we may not have to
    > change the tests. What do you think?
    
    Assuming the basic logic means nlaunched/nplanned logic, yes, it would
    be a nice idea. I think user-facing logging stuff can be developed as
    an improvement independent from the main parallel autovacuum patch.
    It's ideal if we can implement the main patch (with tests) without
    relying on the user-facing logging.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  75. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-16T12:33:49Z

    Hi,
    
    On Thu, Mar 12, 2026 at 2:05 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > BTW thes discussion made me think to change av_max_parallel_workers to
    > control the number of workers per-autovacuum worker instead (with
    > renaming it to say max_parallel_workers_per_autovacuum_worker). Users
    > can compute the maximum number of parallel workers the system requires
    > by (autovacuum_worker_slots *
    > max_parallel_workers_per_autovacuum_worker). We would no longer need
    > the reservation and release logic. I'd like to hear your opinion.
    >
    
    IIUC, one of the main autovacuum's goals is to be "inconspicuous" for the
    rest of the system. I mean that it should not try to vacuum all the tables
    as fast as possible. Instead it should try to interfere with other backends
    as little as possible and try to avoid high resource consumption (assuming
    there is no hazard of wraparound).
    
    I propose to reason based on the case for which the parallel a/v will
    actually be used :
    We have a 3 tables which has 80+ indexes each and require a
    parallel a/v. Ideally, each of these tables should be processed with 20
    parallel workers. This is a real example which can be encountered in
    different productions, where such tables take up about half of all the data
    in the database.
    
    How parallel a/v will handle such a situation?
    1. Our current implementation
    We can set av_max_parallel_workers to 60 and autovacuum_parallel_workers
    reloption to 20 for each table.
    2. Proposed idea
    We can set max_parallel_workers_per_av_worker to 20 and
    autovacuum_parallel_workers reloption to 20 for each table.
    
    In both cases we have guarantee that all tables will be processed with the
    desired number of parallel workers. And both cases allows us to limit the
    CPU consumption via reducing the "av_max_parallel_workers" parameter (for
    current implementation) or via reducing the "autovacuum_parallel_workers"
    reloption for each table (for proposed idea). So basically I don't see whether
    current approach has a big advantages over the idea you proposed.
    
    I also asked my friend, who is many years working with the clients with big
    productions. He said that this is super important to process such huge tables
    with maximum "intensity". I.e. each a/v worker should have ability to launch
    as many parallel workers as required. I guess that this is an argument in
    favor of your idea.
    
    The only argument against this idea that I could come up with is that some
    users may abuse our parallel a/v feature. For instance, the user can set
    "autovacuum_parallel_workers" reloption not only for large tables, but also
    for many smaller ones. In this case the max_parallel_workers_per_av_worker
    must be pretty large (in order to process the huge table). Thus, the user
    can face a situation when all a/v workers are launching additional parallel
    workers => there is high CPU consumption and possibly max_parallel_workers
    shortage. The only way to deal with it is to go through a large amount of
    smaller tables and reduce "autovacuum_parallel_workers" reloption for each
    of them. IMHO, this is a pretty unpleasant experience for the user. On the
    other hand, the user himself is to blame for the occurrence of such a
    situation.
    
    Let's summarize.
    Proposed idea has several strong advantages over current implementation.
    The only disadvantage I came up with can be avoided by writing recommendations
    on how to use this feature in the documentation. So, if I didn't messed up
    anything and you don't have any doubts, I would rather implement the
    proposed idea.
    
    > > 2)
    > > I suggest adding a separate log that will be emitted every time we are
    > > unable to start workers due to a shortage of av_max_parallel_workers.
    >
    > For (2), do you mean that the worker writes these logs regardless of
    > log_autovacuum_min_duration setting? I'm concerned that the server
    > logs would be flooded with these logs especially when multiple
    > autovacuum workers are working very actively and the system is facing
    > a shortage of av_max_parallel_workers.
    
    Oh, I didn't take that into account. But this is not a problem - we can
    accumulate such statistics just as we do now for the "nreserved" ones. And
    then we will log this value with all other stats.
    
    > > Possibly we can introduce a new injection point, or a new log for it.
    > > But I assume that the subject of discussion in patch 0002 is the
    > > "nreserved" logic, and "nlaunched/nplanned" logic does not raise any
    > > questions.
    > >
    > > I suggest splitting the 0002 patch into two parts : 1) basic logic and
    > > 2) additional logic with nreserved or something else. The second part can be
    > > discussed in isolation from the patch set. If we do this, we may not have to
    > > change the tests. What do you think?
    >
    > Assuming the basic logic means nlaunched/nplanned logic, yes, it would
    > be a nice idea. I think user-facing logging stuff can be developed as
    > an improvement independent from the main parallel autovacuum patch.
    > It's ideal if we can implement the main patch (with tests) without
    > relying on the user-facing logging.
    
    OK, actually we can do it.
    
    
    
    Thank you very much for the review!
    Please, see attached patches. The changes are :
    1) Fixed segfault with accessing outdated pv_shared_cost_params pointer.
    2) "Logging for autovacuum" is divided into two patches - basic logging
    (nplanned/nlaunched) and advanced logging (nreserved).
    3) Tests are now independent of logging.
    
    By now I didn't try to change the core logic. I think that first we need to
    agree on the use of the new GUC parameter.
    
    --
    Best regards,
    Daniil Davydov
    
  76. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-16T16:46:19Z

    On Mon, Mar 16, 2026 at 5:34 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Mar 12, 2026 at 2:05 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > BTW thes discussion made me think to change av_max_parallel_workers to
    > > control the number of workers per-autovacuum worker instead (with
    > > renaming it to say max_parallel_workers_per_autovacuum_worker). Users
    > > can compute the maximum number of parallel workers the system requires
    > > by (autovacuum_worker_slots *
    > > max_parallel_workers_per_autovacuum_worker). We would no longer need
    > > the reservation and release logic. I'd like to hear your opinion.
    > >
    >
    > IIUC, one of the main autovacuum's goals is to be "inconspicuous" for the
    > rest of the system. I mean that it should not try to vacuum all the tables
    > as fast as possible. Instead it should try to interfere with other backends
    > as little as possible and try to avoid high resource consumption (assuming
    > there is no hazard of wraparound).
    >
    > I propose to reason based on the case for which the parallel a/v will
    > actually be used :
    > We have a 3 tables which has 80+ indexes each and require a
    > parallel a/v. Ideally, each of these tables should be processed with 20
    > parallel workers. This is a real example which can be encountered in
    > different productions, where such tables take up about half of all the data
    > in the database.
    >
    > How parallel a/v will handle such a situation?
    > 1. Our current implementation
    > We can set av_max_parallel_workers to 60 and autovacuum_parallel_workers
    > reloption to 20 for each table.
    > 2. Proposed idea
    > We can set max_parallel_workers_per_av_worker to 20 and
    > autovacuum_parallel_workers reloption to 20 for each table.
    >
    > In both cases we have guarantee that all tables will be processed with the
    > desired number of parallel workers. And both cases allows us to limit the
    > CPU consumption via reducing the "av_max_parallel_workers" parameter (for
    > current implementation) or via reducing the "autovacuum_parallel_workers"
    > reloption for each table (for proposed idea). So basically I don't see whether
    > current approach has a big advantages over the idea you proposed.
    >
    > I also asked my friend, who is many years working with the clients with big
    > productions. He said that this is super important to process such huge tables
    > with maximum "intensity". I.e. each a/v worker should have ability to launch
    > as many parallel workers as required. I guess that this is an argument in
    > favor of your idea.
    >
    > The only argument against this idea that I could come up with is that some
    > users may abuse our parallel a/v feature. For instance, the user can set
    > "autovacuum_parallel_workers" reloption not only for large tables, but also
    > for many smaller ones. In this case the max_parallel_workers_per_av_worker
    > must be pretty large (in order to process the huge table). Thus, the user
    > can face a situation when all a/v workers are launching additional parallel
    > workers => there is high CPU consumption and possibly max_parallel_workers
    > shortage. The only way to deal with it is to go through a large amount of
    > smaller tables and reduce "autovacuum_parallel_workers" reloption for each
    > of them. IMHO, this is a pretty unpleasant experience for the user. On the
    > other hand, the user himself is to blame for the occurrence of such a
    > situation.
    >
    > Let's summarize.
    > Proposed idea has several strong advantages over current implementation.
    > The only disadvantage I came up with can be avoided by writing recommendations
    > on how to use this feature in the documentation. So, if I didn't messed up
    > anything and you don't have any doubts, I would rather implement the
    > proposed idea.
    
    Thank you for the analysis on the new idea.
    
    While both ideas can achieve our goal of this feature in general, the
    new idea doesn't require an additional layer of reserve/release logic
    on top of the existing bgworker pool, which is good. I've not tried
    coding this idea but I believe the patch can be simplified very much.
    So I agree to move to this idea.
    
    >
    > > > 2)
    > > > I suggest adding a separate log that will be emitted every time we are
    > > > unable to start workers due to a shortage of av_max_parallel_workers.
    > >
    > > For (2), do you mean that the worker writes these logs regardless of
    > > log_autovacuum_min_duration setting? I'm concerned that the server
    > > logs would be flooded with these logs especially when multiple
    > > autovacuum workers are working very actively and the system is facing
    > > a shortage of av_max_parallel_workers.
    >
    > Oh, I didn't take that into account. But this is not a problem - we can
    > accumulate such statistics just as we do now for the "nreserved" ones. And
    > then we will log this value with all other stats.
    >
    > > > Possibly we can introduce a new injection point, or a new log for it.
    > > > But I assume that the subject of discussion in patch 0002 is the
    > > > "nreserved" logic, and "nlaunched/nplanned" logic does not raise any
    > > > questions.
    > > >
    > > > I suggest splitting the 0002 patch into two parts : 1) basic logic and
    > > > 2) additional logic with nreserved or something else. The second part can be
    > > > discussed in isolation from the patch set. If we do this, we may not have to
    > > > change the tests. What do you think?
    > >
    > > Assuming the basic logic means nlaunched/nplanned logic, yes, it would
    > > be a nice idea. I think user-facing logging stuff can be developed as
    > > an improvement independent from the main parallel autovacuum patch.
    > > It's ideal if we can implement the main patch (with tests) without
    > > relying on the user-facing logging.
    >
    > OK, actually we can do it.
    >
    >
    >
    > Thank you very much for the review!
    > Please, see attached patches. The changes are :
    > 1) Fixed segfault with accessing outdated pv_shared_cost_params pointer.
    > 2) "Logging for autovacuum" is divided into two patches - basic logging
    > (nplanned/nlaunched) and advanced logging (nreserved).
    > 3) Tests are now independent of logging.
    
    Thank you for updating the patches. I'll wait for the new
    implementation and will review the patches as soon as the patches are
    updated.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  77. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-16T20:54:43Z

    Hi,
    
    On Mon, Mar 16, 2026 at 11:46 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > While both ideas can achieve our goal of this feature in general, the
    > new idea doesn't require an additional layer of reserve/release logic
    > on top of the existing bgworker pool, which is good. I've not tried
    > coding this idea but I believe the patch can be simplified very much.
    > So I agree to move to this idea.
    >
    
    OK, let's do it!
    
    Please, see an updated set of patches. Main changes are :
    0001 patch - removed all logic related to the parallel workers reserving.
    0002 patch - no changes regarding v26.
    0003 patch - no changes regarding v26.
    0004 patch - removed all stuff related to the "test_autovacuum" extension.
                 Also removed 3th, 4th and 5th tests, because they were related
                 only to the workers reserving logic.
    0005 patch - minor changes reflecting the new GUC parameter's purpose.
    
    I have maintained the independence of the tests from the user-facing logging.
    Instead of "nworkers released" logs I have added a single log at the end of
    one round of parallel processing :
    "av worker: finished parallel index processing with N parallel workers".
    This is the only code that I added rather than deleted within the 0001 patch.
    
    I hope I didn't miss anything.
    
    --
    Best regards,
    Daniil Davydov
    
  78. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-17T16:50:48Z

    On Mon, Mar 16, 2026 at 1:54 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Mon, Mar 16, 2026 at 11:46 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > While both ideas can achieve our goal of this feature in general, the
    > > new idea doesn't require an additional layer of reserve/release logic
    > > on top of the existing bgworker pool, which is good. I've not tried
    > > coding this idea but I believe the patch can be simplified very much.
    > > So I agree to move to this idea.
    > >
    >
    > OK, let's do it!
    >
    > Please, see an updated set of patches. Main changes are :
    > 0001 patch - removed all logic related to the parallel workers reserving.
    > 0002 patch - no changes regarding v26.
    > 0003 patch - no changes regarding v26.
    > 0004 patch - removed all stuff related to the "test_autovacuum" extension.
    >              Also removed 3th, 4th and 5th tests, because they were related
    >              only to the workers reserving logic.
    > 0005 patch - minor changes reflecting the new GUC parameter's purpose.
    >
    > I have maintained the independence of the tests from the user-facing logging.
    > Instead of "nworkers released" logs I have added a single log at the end of
    > one round of parallel processing :
    > "av worker: finished parallel index processing with N parallel workers".
    > This is the only code that I added rather than deleted within the 0001 patch.
    >
    > I hope I didn't miss anything.
    
    Thank you for updating the patch!
    
    I find the current behavior of the autovacuum_parallel_workers storage
    parameter somewhat unintuitive for users. The documentation currently
    states:
    
    +     <para>
    +      Sets the maximum number of parallel autovacuum workers that can process
    +      indexes of this table.
    +      The default value is -1, which means no parallel index vacuuming for
    +      this table. If value is 0 then parallel degree will computed based on
    +      number of indexes.
    +      Note that the computed number of workers may not actually be available at
    +      run time. If this occurs, the autovacuum will run with fewer workers
    +      than expected.
    +     </para>
    
    It is quite confusing that setting the value to 0 does not actually
    disable the parallel vacuum. In many other PostgreSQL parameters, 0
    typically means "off" or "no workers." I think that this parameter
    should behave as follows:
    
    -1: Use the value of autovacuum_max_parallel_workers (GUC) as the
    limit (fallback).
    >=0: Use the specified value as the limit, capped by autovacuum_max_parallel_workers. (Specifically, setting this to 0 would disable parallel vacuum for the table).
    
    Currently, the patch implements parallel autovacuum as an "opt-in"
    style. That is, even after setting the GUC to >0, users must manually
    set the storage parameter for each table. This assumes that users
    already know exactly which tables need parallel vacuum.
    
    However, I believe it would be more intuitive to let the system decide
    which tables are eligible for parallel vacuum based on index size and
    count (via min_parallel_index_scan_size, etc.), rather than forcing
    manual per-table configuration. Therefore, I'm thinking we might want
    to make it "opt-out" style by default instead:
    
    - Set the default value of the storage parameter to -1 (i.e., fallback to GUC).
    - the default value of the GUC autovacuum_max_parallel_workers at 0.
    
    With this configuration:
    
    - Parallel autovacuum is disabled by default.
    - Users can enable it globally by simply setting the GUC to >0.
    - Users can still disable it for specific tables by setting the
    storage parameter to 0.
    
    What do you think?
    
    * 0001 patch
    
    +{ name => 'autovacuum_max_parallel_workers', type => 'int', context
    => 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    +  short_desc => 'Maximum number of parallel workers that a single
    autovacuum worker can take from bgworkers pool.',
    +  variable => 'autovacuum_max_parallel_workers',
    +  boot_val => '2',
    +  min => '0',
    +  max => 'MAX_BACKENDS',
    +},
    
    How about rephrasing the short description to "Maximum number of
    parallel processes per autovacuum operation."?
    
    The maximum value should be MAX_PARALLEL_WORKER_LIMIT.
    
    ---
    * 0002 patch:
    
    I think that it's better to rename PVWorkersStats and PVWorkersUsage
    to PVWorkerStats and PVWorkerUsage (making Worker singular).
    
    I've attached the patch for minor fixes including the above comments.
    
    ---
    * 0004 patch:
    
    +               if (AmAutoVacuumWorkerProcess())
    +                       elog(DEBUG2,
    +                                ngettext("autovacuum worker: finished
    parallel index processing with %d parallel worker",
    +                                                 "autovacuum worker:
    finished parallel index processing with %d parallel workers",
    +                                                 nworkers),
    +                                nworkers);
    
    Now that having planned and launched logs in autovacuum logs is
    straightforward, let's use these logs in the tests instead and make it
    the first patch. We can apply it independently.
    
    ---
    We check only the server logs throughout the new tap tests. I think we
    should also confirm that the autovacuum successfully completes. I've
    attached the proposed change to the tap tests.
    
    The attached 0003 and 0006 patches are fixup changes on top v27. Other
    patches don't have any change from the previous v27 patch set.
    
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  79. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-18T09:23:39Z

    Hi,
    
    On Tue, Mar 17, 2026 at 11:51 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > I find the current behavior of the autovacuum_parallel_workers storage
    > parameter somewhat unintuitive for users. The documentation currently
    > states:
    >
    > +     <para>
    > +      Sets the maximum number of parallel autovacuum workers that can process
    > +      indexes of this table.
    > +      The default value is -1, which means no parallel index vacuuming for
    > +      this table. If value is 0 then parallel degree will computed based on
    > +      number of indexes.
    > +      Note that the computed number of workers may not actually be available at
    > +      run time. If this occurs, the autovacuum will run with fewer workers
    > +      than expected.
    > +     </para>
    >
    > It is quite confusing that setting the value to 0 does not actually
    > disable the parallel vacuum. In many other PostgreSQL parameters, 0
    > typically means "off" or "no workers." I think that this parameter
    > should behave as follows:
    >
    > -1: Use the value of autovacuum_max_parallel_workers (GUC) as the
    > limit (fallback).
    > >=0: Use the specified value as the limit, capped by autovacuum_max_parallel_workers. (Specifically, setting this to 0 would disable parallel vacuum for the table).
    >
    
    Actually we have several places in the code where "-1" means disabled and "0"
    means choosing a parallel degree based on the number of indexes. Since this
    is an inner logic, I agree that we should make our parameter more intuitive
    to the user. But this will make the code a bit confusing.
    
    > Currently, the patch implements parallel autovacuum as an "opt-in"
    > style. That is, even after setting the GUC to >0, users must manually
    > set the storage parameter for each table. This assumes that users
    > already know exactly which tables need parallel vacuum.
    >
    > However, I believe it would be more intuitive to let the system decide
    > which tables are eligible for parallel vacuum based on index size and
    > count (via min_parallel_index_scan_size, etc.), rather than forcing
    > manual per-table configuration. Therefore, I'm thinking we might want
    > to make it "opt-out" style by default instead:
    >
    > - Set the default value of the storage parameter to -1 (i.e., fallback to GUC).
    > - the default value of the GUC autovacuum_max_parallel_workers at 0.
    >
    > With this configuration:
    >
    > - Parallel autovacuum is disabled by default.
    > - Users can enable it globally by simply setting the GUC to >0.
    > - Users can still disable it for specific tables by setting the
    > storage parameter to 0.
    >
    > What do you think?
    
    I'm afraid that I can't agree with you here. As I wrote above [1], the
    parallel a/v feature will be useful when a user has a few huge tables with
    a big amount of indexes. Only these tables require parallel processing and a
    user knows about it.
    
    If we implement the feature as you suggested, then after setting the
    av_max_parallel_workers to N > 0, the user will have to manually disable
    processing for all tables except the largest ones. This will need to be done
    to ensure that parallel workers are launched specifically to process the
    largest tables and not wasting on the processing of little ones.
    
    I.e. I'm proposing a design that will require manual actions to *enable*
    parallel a/v for several large tables rather than *disable* it for all of
    the rest tables in the cluster. I'm sure that's what users want.
    
    Allowing the system to decide which tables to process in parallel is a good
    way from a design perspective. But I'm thinking of the following example :
    Imagine that we have a threshold, when exceeded, parallel a/v is used.
    Several a/v workers encounter tables which exceed this threshold by 1_000 and
    each of these workers decides to launch a few parallel workers. Another a/v
    worker encounters a table which is beyond this threshold by 1_000_000 and
    tries to launch N parallel workers, but facing the max_parallel_workers
    shortage. Thus, processing of this table will take a very long time to
    complete due to lack of resources. The only way for users to avoid it is to
    disable parallel a/v for all tables, which exceeds the threshold and are not
    of particular interest.
    
    I cannot imagine how our heuristics can handle such situations. IMHO the
    situation will come down to the fact that users will manually disable
    parallel a/v for a big amount of tables. I guess it can be pretty frustrating.
    
    What do you think?
    
    >
    > +{ name => 'autovacuum_max_parallel_workers', type => 'int', context
    > => 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    > +  short_desc => 'Maximum number of parallel workers that a single
    > autovacuum worker can take from bgworkers pool.',
    > +  variable => 'autovacuum_max_parallel_workers',
    > +  boot_val => '2',
    > +  min => '0',
    > +  max => 'MAX_BACKENDS',
    > +},
    >
    > How about rephrasing the short description to "Maximum number of
    > parallel processes per autovacuum operation."?
    
    I'm not sure if this phrase will be understandable to the user.
    I don't see any places where we would define the "autovacuum operation"
    concept, so I suppose it could be ambiguous. What about "Maximum number of
    parallel processes per autovacuuming of one table"?
    
    >
    > The maximum value should be MAX_PARALLEL_WORKER_LIMIT.
    >
    
    Sure!
    
    >
    > I think that it's better to rename PVWorkersStats and PVWorkersUsage
    > to PVWorkerStats and PVWorkerUsage (making Worker singular).
    >
    > I've attached the patch for minor fixes including the above comments.
    >
    
    I agree with all proposed fixes. Thank you!
    
    >
    > +               if (AmAutoVacuumWorkerProcess())
    > +                       elog(DEBUG2,
    > +                                ngettext("autovacuum worker: finished
    > parallel index processing with %d parallel worker",
    > +                                                 "autovacuum worker:
    > finished parallel index processing with %d parallel workers",
    > +                                                 nworkers),
    > +                                nworkers);
    >
    > Now that having planned and launched logs in autovacuum logs is
    > straightforward, let's use these logs in the tests instead and make it
    > the first patch. We can apply it independently.
    >
    
    OK, I agree.
    
    > We check only the server logs throughout the new tap tests. I think we
    > should also confirm that the autovacuum successfully completes. I've
    > attached the proposed change to the tap tests.
    >
    
    I agree with proposed changes. BTW, don't we need to reduce the strings
    length to 80 characters in the tests? In some tests, this rule is followed,
    and in some it is not.
    
    --
    Thank you very much for the review and proposed patches!
    Please, see an updated set of patches. Note that the "logging for autovacuum"
    is considered as the first patch now.
    
    [1] https://www.postgresql.org/message-id/CAJDiXghaazbrQMZZS08d9Ffh2y4w05TgH9dpBhqChv1qNTp%2BxA%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
  80. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-18T19:49:17Z

    On Wed, Mar 18, 2026 at 2:23 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Tue, Mar 17, 2026 at 11:51 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > I find the current behavior of the autovacuum_parallel_workers storage
    > > parameter somewhat unintuitive for users. The documentation currently
    > > states:
    > >
    > > +     <para>
    > > +      Sets the maximum number of parallel autovacuum workers that can process
    > > +      indexes of this table.
    > > +      The default value is -1, which means no parallel index vacuuming for
    > > +      this table. If value is 0 then parallel degree will computed based on
    > > +      number of indexes.
    > > +      Note that the computed number of workers may not actually be available at
    > > +      run time. If this occurs, the autovacuum will run with fewer workers
    > > +      than expected.
    > > +     </para>
    > >
    > > It is quite confusing that setting the value to 0 does not actually
    > > disable the parallel vacuum. In many other PostgreSQL parameters, 0
    > > typically means "off" or "no workers." I think that this parameter
    > > should behave as follows:
    > >
    > > -1: Use the value of autovacuum_max_parallel_workers (GUC) as the
    > > limit (fallback).
    > > >=0: Use the specified value as the limit, capped by autovacuum_max_parallel_workers. (Specifically, setting this to 0 would disable parallel vacuum for the table).
    > >
    >
    > Actually we have several places in the code where "-1" means disabled and "0"
    > means choosing a parallel degree based on the number of indexes. Since this
    > is an inner logic, I agree that we should make our parameter more intuitive
    > to the user. But this will make the code a bit confusing.
    
    Yes, we already have such a code for PARALLEL option for the VACUUM command:
    
    /*
     * Disable parallel vacuum, if user has specified parallel degree
     * as zero.
     */
    if (nworkers == 0)
        params.nworkers = -1;
    else
        params.nworkers = nworkers;
    
    I guess it's better that autovacuum codes also somewhat follow this
    code for better consistency.
    
    >
    > > Currently, the patch implements parallel autovacuum as an "opt-in"
    > > style. That is, even after setting the GUC to >0, users must manually
    > > set the storage parameter for each table. This assumes that users
    > > already know exactly which tables need parallel vacuum.
    > >
    > > However, I believe it would be more intuitive to let the system decide
    > > which tables are eligible for parallel vacuum based on index size and
    > > count (via min_parallel_index_scan_size, etc.), rather than forcing
    > > manual per-table configuration. Therefore, I'm thinking we might want
    > > to make it "opt-out" style by default instead:
    > >
    > > - Set the default value of the storage parameter to -1 (i.e., fallback to GUC).
    > > - the default value of the GUC autovacuum_max_parallel_workers at 0.
    > >
    > > With this configuration:
    > >
    > > - Parallel autovacuum is disabled by default.
    > > - Users can enable it globally by simply setting the GUC to >0.
    > > - Users can still disable it for specific tables by setting the
    > > storage parameter to 0.
    > >
    > > What do you think?
    >
    > I'm afraid that I can't agree with you here. As I wrote above [1], the
    > parallel a/v feature will be useful when a user has a few huge tables with
    > a big amount of indexes. Only these tables require parallel processing and a
    > user knows about it.
    
    Isn't it a case where users need to increase
    min_parallel_index_scan_size? Suppose that there are two tables that
    are big enough and have enough indexes, it's more natural to me to use
    parallel vacuum for both tables without user manual settings.
    
    > If we implement the feature as you suggested, then after setting the
    > av_max_parallel_workers to N > 0, the user will have to manually disable
    > processing for all tables except the largest ones. This will need to be done
    > to ensure that parallel workers are launched specifically to process the
    > largest tables and not wasting on the processing of little ones.
    >
    > I.e. I'm proposing a design that will require manual actions to *enable*
    > parallel a/v for several large tables rather than *disable* it for all of
    > the rest tables in the cluster. I'm sure that's what users want.
    >
    > Allowing the system to decide which tables to process in parallel is a good
    > way from a design perspective. But I'm thinking of the following example :
    > Imagine that we have a threshold, when exceeded, parallel a/v is used.
    > Several a/v workers encounter tables which exceed this threshold by 1_000 and
    > each of these workers decides to launch a few parallel workers. Another a/v
    > worker encounters a table which is beyond this threshold by 1_000_000 and
    > tries to launch N parallel workers, but facing the max_parallel_workers
    > shortage. Thus, processing of this table will take a very long time to
    > complete due to lack of resources. The only way for users to avoid it is to
    > disable parallel a/v for all tables, which exceeds the threshold and are not
    > of particular interest.
    
    I think the same thing happens even with the current design as long as
    users misconfigure max_parallel_workers, no? Setting
    autovacuum_max_parallel_workers to >0 would mean that users want to
    give additional resources for autovacuums in general, I think it makes
    sense to use parallel vacuum even for tables which exceed the
    threshold by 1000.
    
    Users who want to use parallel autovacuum would have to set
    max_parallel_workers (and max_worker_processes) high enough so that
    each autovacuum worker can use parallel workers. If resource
    contention occurs, it's a sign that the limits are not configured
    properly.
    
    > >
    > > +{ name => 'autovacuum_max_parallel_workers', type => 'int', context
    > > => 'PGC_SIGHUP', group => 'VACUUM_AUTOVACUUM',
    > > +  short_desc => 'Maximum number of parallel workers that a single
    > > autovacuum worker can take from bgworkers pool.',
    > > +  variable => 'autovacuum_max_parallel_workers',
    > > +  boot_val => '2',
    > > +  min => '0',
    > > +  max => 'MAX_BACKENDS',
    > > +},
    > >
    > > How about rephrasing the short description to "Maximum number of
    > > parallel processes per autovacuum operation."?
    >
    > I'm not sure if this phrase will be understandable to the user.
    > I don't see any places where we would define the "autovacuum operation"
    > concept, so I suppose it could be ambiguous. What about "Maximum number of
    > parallel processes per autovacuuming of one table"?
    
    "autovacuuming of one table" sounds unnatural to me. How about
    "Maximum number of parallel workers that can be used by a single
    autovacuum worker."?
    
    >
    > > We check only the server logs throughout the new tap tests. I think we
    > > should also confirm that the autovacuum successfully completes. I've
    > > attached the proposed change to the tap tests.
    > >
    >
    > I agree with proposed changes. BTW, don't we need to reduce the strings
    > length to 80 characters in the tests? In some tests, this rule is followed,
    > and in some it is not.
    
    Yeah, pgperltidy should be run for new tests.
    
    > Thank you very much for the review and proposed patches!
    > Please, see an updated set of patches. Note that the "logging for autovacuum"
    > is considered as the first patch now.
    
    Thank you for updating the patches!
    
    The 0001 patch looks good to me. I've updated the commit message and
    attached it. I'm going to push the patch, barring any objections.
    
    While we need more discussion on the above points (opt-in vs.
    opt-out), I think that the rest of the patches are getting close.
    Regarding the documentation changes, I find that the current patch
    needs more explanation at appropriate sections. I think we need to:
    
    1. describe the new autovacuum_max_parallel_workers GUC parameter (in
    config.sgml)
    2. describe the new autovacuum_parallel_workers storage parameter (in
    create_table.sgml)
    3. mention that autovacuum could use parallel vacuum (in maintenance.sgml).
    
    I think that part 1 should include the basic explanation of the GUC
    parameter as well as how the number of workers is decided (which could
    be similar to the description for PARALLEL options of the VACUUM
    command). Part 2 can explain the storage parameter as follow:
    
      Per-table value for <xref linkend="guc-autovacuum-max-parallel-workers"/>
      parameter. If -1 is specified,
    <varname>autovacuum_max_parallel_workers</varname>
      value will be used. The default value is 0.
    
    Part 3 can briefly mention that autovacuum can perform parallel vacuum
    with parallel workers capped by autovacuum_max_parallel_workers as
    follow:
    
      For tables with the <xref linkend="reloption-autovacuum-parallel-workers"/>
      storage parameter set, an autovacuum worker can perform index vacuuming and
      index cleanup with background workers. The number of workers launched by
      a single autovacuum worker is limited by the
      <xref linkend="guc-autovacuum-max-parallel-workers"/>.
    
    What do you think?
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  81. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-19T14:28:57Z

    Hi,
    
    On Thu, Mar 19, 2026 at 2:49 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Yes, we already have such a code for PARALLEL option for the VACUUM command.
    >
    > I guess it's better that autovacuum codes also somewhat follow this
    > code for better consistency.
    >
    
    I agree. You can find it in the v29-0002 patch.
    
    > > I'm afraid that I can't agree with you here. As I wrote above [1], the
    > > parallel a/v feature will be useful when a user has a few huge tables with
    > > a big amount of indexes. Only these tables require parallel processing and a
    > > user knows about it.
    >
    > Isn't it a case where users need to increase
    > min_parallel_index_scan_size? Suppose that there are two tables that
    > are big enough and have enough indexes, it's more natural to me to use
    > parallel vacuum for both tables without user manual settings.
    >
    
    Do you mean that the user can increase this parameter so that smaller tables
    are not considered for the parallel a/v? If so, I don't think it will always
    be handy. When I say "smaller tables" I mean that they are small relative to
    super huge tables. But actually these "smaller tables" can be pretty big and
    require a parallel index scan within parallel queries or VACUUM PARALLEL (not
    an autovacuum). Increasing the min_scan_size parameter can decrease
    performance of the queries that are relying on the ability to scan indexes
    of such tables in parallel. Separated parameter such as
    "autovacuum_min_parallel_index_scan_size" could help here, but I don't think
    that we want to introduce many new GUC parameters for a single feature.
    
    > > If we implement the feature as you suggested, then after setting the
    > > av_max_parallel_workers to N > 0, the user will have to manually disable
    > > processing for all tables except the largest ones. This will need to be done
    > > to ensure that parallel workers are launched specifically to process the
    > > largest tables and not wasting on the processing of little ones.
    > >
    > > I.e. I'm proposing a design that will require manual actions to *enable*
    > > parallel a/v for several large tables rather than *disable* it for all of
    > > the rest tables in the cluster. I'm sure that's what users want.
    > >
    > > Allowing the system to decide which tables to process in parallel is a good
    > > way from a design perspective. But I'm thinking of the following example :
    > > Imagine that we have a threshold, when exceeded, parallel a/v is used.
    > > Several a/v workers encounter tables which exceed this threshold by 1_000 and
    > > each of these workers decides to launch a few parallel workers. Another a/v
    > > worker encounters a table which is beyond this threshold by 1_000_000 and
    > > tries to launch N parallel workers, but facing the max_parallel_workers
    > > shortage. Thus, processing of this table will take a very long time to
    > > complete due to lack of resources. The only way for users to avoid it is to
    > > disable parallel a/v for all tables, which exceeds the threshold and are not
    > > of particular interest.
    >
    > I think the same thing happens even with the current design as long as
    > users misconfigure max_parallel_workers, no? Setting
    > autovacuum_max_parallel_workers to >0 would mean that users want to
    > give additional resources for autovacuums in general, I think it makes
    > sense to use parallel vacuum even for tables which exceed the
    > threshold by 1000.
    >
    > Users who want to use parallel autovacuum would have to set
    > max_parallel_workers (and max_worker_processes) high enough so that
    > each autovacuum worker can use parallel workers. If resource
    > contention occurs, it's a sign that the limits are not configured
    > properly.
    >
    
    Yeah, currently user can misconfigure max_parallel_workers, so (for example)
    multiple VACUUM PARALLEL operations running at the same time will face with
    a shortage of parallel workers. But I guess that every system has some sane
    limit for this parameter's value. If we want to ensure that all a/v leaders
    are guaranteed to launch as many parallel workers as required, we might need
    to increase the max_parallel_workers too much (and cross the sane limit).
    IMHO it may be unacceptable for many systems in production, because it will
    undermine the stability.
    
    I don't have direct evidence of my words, so I'll try to get the opinion of
    the people who will use the parallel a/v feature in big productions.
    
    > > I'm not sure if this phrase will be understandable to the user.
    > > I don't see any places where we would define the "autovacuum operation"
    > > concept, so I suppose it could be ambiguous. What about "Maximum number of
    > > parallel processes per autovacuuming of one table"?
    >
    > "autovacuuming of one table" sounds unnatural to me. How about
    > "Maximum number of parallel workers that can be used by a single
    > autovacuum worker."?
    >
    
    It sounds good, I agree.
    
    > >
    > > > We check only the server logs throughout the new tap tests. I think we
    > > > should also confirm that the autovacuum successfully completes. I've
    > > > attached the proposed change to the tap tests.
    > > >
    > >
    > > I agree with proposed changes. BTW, don't we need to reduce the strings
    > > length to 80 characters in the tests? In some tests, this rule is followed,
    > > and in some it is not.
    >
    > Yeah, pgperltidy should be run for new tests.
    >
    
    OK. I'll do it.
    
    > The 0001 patch looks good to me. I've updated the commit message and
    > attached it. I'm going to push the patch, barring any objections.
    >
    
    Great news!
    
    > Regarding the documentation changes, I find that the current patch
    > needs more explanation at appropriate sections. I think we need to:
    >
    > 1. describe the new autovacuum_max_parallel_workers GUC parameter (in
    > config.sgml)
    > 2. describe the new autovacuum_parallel_workers storage parameter (in
    > create_table.sgml)
    > 3. mention that autovacuum could use parallel vacuum (in maintenance.sgml).
    >
    
    I agree.
    
    > I think that part 1 should include the basic explanation of the GUC
    > parameter as well as how the number of workers is decided (which could
    > be similar to the description for PARALLEL options of the VACUUM
    > command).
    
    IMHO, the description of the method for determining the number of parallel
    workers will look more appropriate in part 3.
    
    BTW, do we need to mention that this parameter can be overridden by the
    per-table setting?
    
    > Part 2 can explain the storage parameter as follow:
    >
    >   Per-table value for <xref linkend="guc-autovacuum-max-parallel-workers"/>
    >   parameter. If -1 is specified,
    > <varname>autovacuum_max_parallel_workers</varname>
    >   value will be used. The default value is 0.
    >
    
    It looks very compact and beautiful, I agree.
    Actually, if -1 is specified then we are "choosing the parallel degree based
    on the number of indexes". We have several places in the code with such
    phrasing. I don't really like it because 1) even if value != -1 we are still
    taking the number of indexes into account and 2) basically it is the same as
    to say "limited by GUC parameter". I don't want to touch existing comments
    in the vacuumparallel.c but in our patch I'd like to say that "GUC parameter's
    value will be used". I hope this will not cause any misunderstanding among
    readers.
    
    > Part 3 can briefly mention that autovacuum can perform parallel vacuum
    > with parallel workers capped by autovacuum_max_parallel_workers as
    > follow:
    >
    >   For tables with the <xref linkend="reloption-autovacuum-parallel-workers"/>
    >   storage parameter set, an autovacuum worker can perform index vacuuming and
    >   index cleanup with background workers. The number of workers launched by
    >   a single autovacuum worker is limited by the
    >   <xref linkend="guc-autovacuum-max-parallel-workers"/>.
    
    I suggest adding here also a description of the method for calculating the
    number of parallel workers. If so, I feel that this part of documentation will
    be completely the same as in VACUUM PARALLEL (except a few little details).
    Maybe we can create some dedicated subchapter in the "Routine vacuuming" where
    we describe how the number of parallel workers is decided. Lets call it
    something like "24.1.7 Parallel Vacuuming". Both VACUUM PARALLEL and parallel
    autovacuum can refer to this subchapter. I think it will be much easier to
    maintain. What do you think?
    
    --
    
    Thank you very much for the comments and prepared patch!
    Please, see an updated set of patches (I didn't touch patches 0001, 0003 and
    0005).
    
    The 0001 patch contains a pretty controversial fix for the
    "autovacuum_parallel_workers" description, but I didn't come up with anything
    better.
    
    --
    Best regards,
    Daniil Davydov
    
  82. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-19T23:58:05Z

    On Thu, Mar 19, 2026 at 7:29 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Mar 19, 2026 at 2:49 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Yes, we already have such a code for PARALLEL option for the VACUUM command.
    > >
    > > I guess it's better that autovacuum codes also somewhat follow this
    > > code for better consistency.
    > >
    >
    > I agree. You can find it in the v29-0002 patch.
    >
    > > > I'm afraid that I can't agree with you here. As I wrote above [1], the
    > > > parallel a/v feature will be useful when a user has a few huge tables with
    > > > a big amount of indexes. Only these tables require parallel processing and a
    > > > user knows about it.
    > >
    > > Isn't it a case where users need to increase
    > > min_parallel_index_scan_size? Suppose that there are two tables that
    > > are big enough and have enough indexes, it's more natural to me to use
    > > parallel vacuum for both tables without user manual settings.
    > >
    >
    > Do you mean that the user can increase this parameter so that smaller tables
    > are not considered for the parallel a/v? If so, I don't think it will always
    > be handy. When I say "smaller tables" I mean that they are small relative to
    > super huge tables. But actually these "smaller tables" can be pretty big and
    > require a parallel index scan within parallel queries or VACUUM PARALLEL (not
    > an autovacuum).
    
    I think that if these small tables are actually big, these are also
    eligible for using parallel autovacuums.
    
    > > > If we implement the feature as you suggested, then after setting the
    > > > av_max_parallel_workers to N > 0, the user will have to manually disable
    > > > processing for all tables except the largest ones. This will need to be done
    > > > to ensure that parallel workers are launched specifically to process the
    > > > largest tables and not wasting on the processing of little ones.
    > > >
    > > > I.e. I'm proposing a design that will require manual actions to *enable*
    > > > parallel a/v for several large tables rather than *disable* it for all of
    > > > the rest tables in the cluster. I'm sure that's what users want.
    > > >
    > > > Allowing the system to decide which tables to process in parallel is a good
    > > > way from a design perspective. But I'm thinking of the following example :
    > > > Imagine that we have a threshold, when exceeded, parallel a/v is used.
    > > > Several a/v workers encounter tables which exceed this threshold by 1_000 and
    > > > each of these workers decides to launch a few parallel workers. Another a/v
    > > > worker encounters a table which is beyond this threshold by 1_000_000 and
    > > > tries to launch N parallel workers, but facing the max_parallel_workers
    > > > shortage. Thus, processing of this table will take a very long time to
    > > > complete due to lack of resources. The only way for users to avoid it is to
    > > > disable parallel a/v for all tables, which exceeds the threshold and are not
    > > > of particular interest.
    > >
    > > I think the same thing happens even with the current design as long as
    > > users misconfigure max_parallel_workers, no? Setting
    > > autovacuum_max_parallel_workers to >0 would mean that users want to
    > > give additional resources for autovacuums in general, I think it makes
    > > sense to use parallel vacuum even for tables which exceed the
    > > threshold by 1000.
    > >
    > > Users who want to use parallel autovacuum would have to set
    > > max_parallel_workers (and max_worker_processes) high enough so that
    > > each autovacuum worker can use parallel workers. If resource
    > > contention occurs, it's a sign that the limits are not configured
    > > properly.
    > >
    >
    > Yeah, currently user can misconfigure max_parallel_workers, so (for example)
    > multiple VACUUM PARALLEL operations running at the same time will face with
    > a shortage of parallel workers. But I guess that every system has some sane
    > limit for this parameter's value. If we want to ensure that all a/v leaders
    > are guaranteed to launch as many parallel workers as required, we might need
    > to increase the max_parallel_workers too much (and cross the sane limit).
    > IMHO it may be unacceptable for many systems in production, because it will
    > undermine the stability.
    
    I understand the concern that if max_parallel_workers (and/or
    max_worker_processes) value are not high enough to ensure each
    autovacuum workers can launch autovacuum_max_parallel_workers, an
    autovacuum on the very large table might not be able to launch the
    full workers in case where some parallel workers are already being
    used by others (e.g., another autovacuum on a different
    slightly-smaller table etc.). But I'm not sure that the opt-out style
    can handle these cases. Even if there are two huge tables and users
    set parallel_vacuum_workers to both tables, there is no guarantee that
    autovacuums on these tables can use the full workers, as long as
    max_parallel_workers value is not enough.
    
    >
    > > The 0001 patch looks good to me. I've updated the commit message and
    > > attached it. I'm going to push the patch, barring any objections.
    > >
    >
    > Great news!
    
    Pushed the 0001 patch.
    
    >
    > BTW, do we need to mention that this parameter can be overridden by the
    > per-table setting?
    
    IIUC the per-table setting is not actually overwriting the GUC
    parameter value, but it works as an additional cap. For instance, if
    autovacuum_max_parallel_workers is 2 and autovacuum_parallel_workers
    is 5, we cap the parallel degree by 2, which is a similar behavior to
    other parallel operations such as the parallel_workers storage
    parameter. BTW it actually works in a somewhat different way than
    other autovacuum-related storage parameters; the per-table parameters
    overwrite GUC values. I decided to use the former behavior because
    autovacuum_max_parallel_workers can work as a global switch to disable
    all parallel autovacuum behavior on the system.
    
    
    > > Part 3 can briefly mention that autovacuum can perform parallel vacuum
    > > with parallel workers capped by autovacuum_max_parallel_workers as
    > > follow:
    > >
    > >   For tables with the <xref linkend="reloption-autovacuum-parallel-workers"/>
    > >   storage parameter set, an autovacuum worker can perform index vacuuming and
    > >   index cleanup with background workers. The number of workers launched by
    > >   a single autovacuum worker is limited by the
    > >   <xref linkend="guc-autovacuum-max-parallel-workers"/>.
    >
    > I suggest adding here also a description of the method for calculating the
    > number of parallel workers. If so, I feel that this part of documentation will
    > be completely the same as in VACUUM PARALLEL (except a few little details).
    > Maybe we can create some dedicated subchapter in the "Routine vacuuming" where
    > we describe how the number of parallel workers is decided. Lets call it
    > something like "24.1.7 Parallel Vacuuming". Both VACUUM PARALLEL and parallel
    > autovacuum can refer to this subchapter. I think it will be much easier to
    > maintain. What do you think?
    
    Describing the parallel vacuum in a new chapter in section 24.1 sounds
    like a good idea.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  83. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-25T07:45:47Z

    Hi,
    
    > > Yeah, currently user can misconfigure max_parallel_workers, so (for example)
    > > multiple VACUUM PARALLEL operations running at the same time will face with
    > > a shortage of parallel workers. But I guess that every system has some sane
    > > limit for this parameter's value. If we want to ensure that all a/v leaders
    > > are guaranteed to launch as many parallel workers as required, we might need
    > > to increase the max_parallel_workers too much (and cross the sane limit).
    > > IMHO it may be unacceptable for many systems in production, because it will
    > > undermine the stability.
    >
    > I understand the concern that if max_parallel_workers (and/or
    > max_worker_processes) value are not high enough to ensure each
    > autovacuum workers can launch autovacuum_max_parallel_workers, an
    > autovacuum on the very large table might not be able to launch the
    > full workers in case where some parallel workers are already being
    > used by others (e.g., another autovacuum on a different
    > slightly-smaller table etc.). But I'm not sure that the opt-out style
    > can handle these cases. Even if there are two huge tables and users
    > set parallel_vacuum_workers to both tables, there is no guarantee that
    > autovacuums on these tables can use the full workers, as long as
    > max_parallel_workers value is not enough.
    >
    
    I guess you mean the "opt-in" style here?
    
    Sure, even opt-in style doesn't give us an unbreakable guarantee that huge
    tables will be processed with the desired number of parallel workers. But IMHO
    "opt-in" greatly increases the probability of this. Searching for arguments in
    favor of opt-in style, I asked for help from another person who has been
    managing the setup of highload systems for decades. He promised to share his
    opinion next week.
    
    > >
    > > BTW, do we need to mention that this parameter can be overridden by the
    > > per-table setting?
    >
    > IIUC the per-table setting is not actually overwriting the GUC
    > parameter value, but it works as an additional cap. For instance, if
    > autovacuum_max_parallel_workers is 2 and autovacuum_parallel_workers
    > is 5, we cap the parallel degree by 2, which is a similar behavior to
    > other parallel operations such as the parallel_workers storage
    > parameter. BTW it actually works in a somewhat different way than
    > other autovacuum-related storage parameters; the per-table parameters
    > overwrite GUC values. I decided to use the former behavior because
    > autovacuum_max_parallel_workers can work as a global switch to disable
    > all parallel autovacuum behavior on the system.
    >
    
    Yep, you are right. I am misworded. Let me reformulate my question :
    Do we need to mention that this parameter can be capped by the per-table
    setting?
    
    >
    > > > Part 3 can briefly mention that autovacuum can perform parallel vacuum
    > > > with parallel workers capped by autovacuum_max_parallel_workers as
    > > > follow:
    > > >
    > > >   For tables with the <xref linkend="reloption-autovacuum-parallel-workers"/>
    > > >   storage parameter set, an autovacuum worker can perform index vacuuming and
    > > >   index cleanup with background workers. The number of workers launched by
    > > >   a single autovacuum worker is limited by the
    > > >   <xref linkend="guc-autovacuum-max-parallel-workers"/>.
    > >
    > > I suggest adding here also a description of the method for calculating the
    > > number of parallel workers. If so, I feel that this part of documentation will
    > > be completely the same as in VACUUM PARALLEL (except a few little details).
    > > Maybe we can create some dedicated subchapter in the "Routine vacuuming" where
    > > we describe how the number of parallel workers is decided. Lets call it
    > > something like "24.1.7 Parallel Vacuuming". Both VACUUM PARALLEL and parallel
    > > autovacuum can refer to this subchapter. I think it will be much easier to
    > > maintain. What do you think?
    >
    > Describing the parallel vacuum in a new chapter in section 24.1 sounds
    > like a good idea.
    
    OK, then I'll do it.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  84. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-25T22:42:30Z

    On Wed, Mar 25, 2026 at 12:45 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > > > Yeah, currently user can misconfigure max_parallel_workers, so (for example)
    > > > multiple VACUUM PARALLEL operations running at the same time will face with
    > > > a shortage of parallel workers. But I guess that every system has some sane
    > > > limit for this parameter's value. If we want to ensure that all a/v leaders
    > > > are guaranteed to launch as many parallel workers as required, we might need
    > > > to increase the max_parallel_workers too much (and cross the sane limit).
    > > > IMHO it may be unacceptable for many systems in production, because it will
    > > > undermine the stability.
    > >
    > > I understand the concern that if max_parallel_workers (and/or
    > > max_worker_processes) value are not high enough to ensure each
    > > autovacuum workers can launch autovacuum_max_parallel_workers, an
    > > autovacuum on the very large table might not be able to launch the
    > > full workers in case where some parallel workers are already being
    > > used by others (e.g., another autovacuum on a different
    > > slightly-smaller table etc.). But I'm not sure that the opt-out style
    > > can handle these cases. Even if there are two huge tables and users
    > > set parallel_vacuum_workers to both tables, there is no guarantee that
    > > autovacuums on these tables can use the full workers, as long as
    > > max_parallel_workers value is not enough.
    > >
    >
    > I guess you mean the "opt-in" style here?
    
    Oops, yes. I wanted it to mean "opt-in" style.
    
    >
    > Sure, even opt-in style doesn't give us an unbreakable guarantee that huge
    > tables will be processed with the desired number of parallel workers. But IMHO
    > "opt-in" greatly increases the probability of this.
    
    Cost-based vacuum delay parameters shared between the autovacuum
    leader and its parallel workers.
    
    >  Searching for arguments in
    > favor of opt-in style, I asked for help from another person who has been
    > managing the setup of highload systems for decades. He promised to share his
    > opinion next week.
    
    Given that we have one and half weeks before the feature freeze, I
    think it's better to complete the project first before waiting for
    his/her comments next week. Even if we finish this feature with the
    opt-out style, we can hear more opinions on it and change the default
    behavior as the change would be privial. What do you think?
    
    I've squashed all patches except for the documentation patch as I
    assume you're working on it. The attached fixup patch contains several
    changes: using opt-out style, comment improvements, and fixing typos
    etc.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  85. Re: POC: Parallel processing of indexes in autovacuum

    Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com> — 2026-03-27T03:54:22Z

    Hi,
    
    On Wed, Mar 25, 2026 at 3:43 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Given that we have one and half weeks before the feature freeze, I
    > think it's better to complete the project first before waiting for
    > his/her comments next week. Even if we finish this feature with the
    > opt-out style, we can hear more opinions on it and change the default
    > behavior as the change would be privial. What do you think?
    >
    > I've squashed all patches except for the documentation patch as I
    > assume you're working on it. The attached fixup patch contains several
    > changes: using opt-out style, comment improvements, and fixing typos
    > etc.
    
    +1 for enabling this feature by default. When enough CPU is available,
    vacuuming multiple indexes of a table in parallel in autovacuum
    definitely speeds things up. This way we will also get field
    experience using this feature.
    
    Thank you for sending the latest patches. I quickly reviewed the v31
    patches. Here are some comments.
    
    1/ +       {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
    
    I haven't looked at the whole thread, but do we all think we need this
    as a relopt? IMHO, we can wait for field experience and introduce this
    later. I'm having a hard time finding a use-case where one wants to
    disable the indexes at the table level. If there was already an
    agreement, I agree to commit to that decision.
    
    2/  +   /*
    +    * If 'true' then we are running parallel autovacuum. Otherwise, we are
    +    * running parallel maintenence VACUUM.
    +    */
    +   bool        is_autovacuum;
    +
    
    The variable name looks a bit confusing. How about we rely on
    AmAutoVacuumWorkerProcess() and avoid the bool in shared memory?
    
    --
    Bharath Rupireddy
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  86. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-28T11:10:44Z

    Hi,
    
    On Thu, Mar 26, 2026 at 5:43 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Wed, Mar 25, 2026 at 12:45 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > >  Searching for arguments in
    > > favor of opt-in style, I asked for help from another person who has been
    > > managing the setup of highload systems for decades. He promised to share his
    > > opinion next week.
    >
    > Given that we have one and half weeks before the feature freeze, I
    > think it's better to complete the project first before waiting for
    > his/her comments next week. Even if we finish this feature with the
    > opt-out style, we can hear more opinions on it and change the default
    > behavior as the change would be privial. What do you think?
    >
    
    Sure, if we can change the default value after the feature freeze, I don't
    mind leaving our parameter in opt-out style by now.
    
    > I've squashed all patches except for the documentation patch as I
    > assume you're working on it. The attached fixup patch contains several
    > changes: using opt-out style, comment improvements, and fixing typos
    > etc.
    >
    
    Thank you very much for the proposed fixes!
    I like the way you have changed nparallel_workers calculation (autovacuum.c).
    Forcing parallel workers to always read shared cost params at the first time
    is a good decision. All comments changes are also LGTM.
    
    The only place that I have changed is reloptions.c :
    As you have explained, it is not appropriate to use the "overrides" wording
    in the reloption's description, so I decided to return an old one.
    
    On Fri, Mar 27, 2026 at 10:54 AM Bharath Rupireddy
    <bharath.rupireddyforpostgres@gmail.com> wrote:
    >
    > Hi,
    >
    > On Wed, Mar 25, 2026 at 3:43 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Given that we have one and half weeks before the feature freeze, I
    > > think it's better to complete the project first before waiting for
    > > his/her comments next week. Even if we finish this feature with the
    > > opt-out style, we can hear more opinions on it and change the default
    > > behavior as the change would be privial. What do you think?
    > >
    > > I've squashed all patches except for the documentation patch as I
    > > assume you're working on it. The attached fixup patch contains several
    > > changes: using opt-out style, comment improvements, and fixing typos
    > > etc.
    >
    > +1 for enabling this feature by default. When enough CPU is available,
    > vacuuming multiple indexes of a table in parallel in autovacuum
    > definitely speeds things up.
    
    Yes, for sure. But I have concerns that enabling parallel a/v for everyone
    will cause the parallel workers shortage during processing of the most huge
    tables.
    
    > Thank you for sending the latest patches. I quickly reviewed the v31
    > patches. Here are some comments.
    >
    > 1/ +       {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
    >
    > I haven't looked at the whole thread, but do we all think we need this
    > as a relopt? IMHO, we can wait for field experience and introduce this
    > later.
    
    I think that we should leave both reloption and the config parameter.
    Getting rid from the reloption will greatly reduce the ability of users to
    tune this feature. I'm afraid that this may lead to people not using parallel
    autovacuum.
    
    > I'm having a hard time finding a use-case where one wants to
    > disable the indexes at the table level. If there was already an
    > agreement, I agree to commit to that decision.
    
    You can read discussion from [1] to the current message in order to dive into
    the question.
    
    To make the long story short, I think that the most common use case for this
    feature is allowing parallel a/v for 2-3 tables, each of which has ~100
    indexes. The rest of the tables do not require parallel processing (at least
    it's a much lower priority for them).
    
    At the same time, Masahiko-san thinks that only the system should decide which
    tables will be processed in parallel. System's decision should be based on the
    number of indexes and a few other config parameters (e.g.
    min_parallel_index_scan_size). Thus, possibly many tables will be able to be
    processed in parallel.
    
    (Both opinions are pretty simplified).
    
    >
    > 2/  +   /*
    > +    * If 'true' then we are running parallel autovacuum. Otherwise, we are
    > +    * running parallel maintenence VACUUM.
    > +    */
    > +   bool        is_autovacuum;
    > +
    >
    > The variable name looks a bit confusing. How about we rely on
    > AmAutoVacuumWorkerProcess() and avoid the bool in shared memory?
    
    This variable is needed for parallel workers, which are taken from the
    bgworkers pool. I.e. AmAutovacuumWorker() will return 'false' for them.
    We need the "is_autovacuum" variable in order to understand exactly what this
    process was started for (VACUUM PARALLEL or parallel autovacuum).
    
    
    Thanks everyone for the review!
    Please, see an updated set of patches :
    As I promised, I created a dedicated chapter for Parallel Vacuum description.
    Both maintenance VACUUM and autovacuum now refer to this chapter.
    
    I am pretty inexperienced in the documentation writing, so forgive me if
    something is out of code style.
    
    [1] https://www.postgresql.org/message-id/CAJDiXggH1bW%3D4n%2B55CGLvs_sRU4SYNXwYLZ37wvJ5H_3yURSPw%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
  87. Re: POC: Parallel processing of indexes in autovacuum

    SATYANARAYANA NARLAPURAM <satyanarlapuram@gmail.com> — 2026-03-30T00:17:21Z

    Hi
    
    On Sat, Mar 28, 2026 at 4:11 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    
    > Hi,
    >
    > On Thu, Mar 26, 2026 at 5:43 AM Masahiko Sawada <sawada.mshk@gmail.com>
    > wrote:
    > >
    > > On Wed, Mar 25, 2026 at 12:45 AM Daniil Davydov <3danissimo@gmail.com>
    > wrote:
    > > >
    > > >  Searching for arguments in
    > > > favor of opt-in style, I asked for help from another person who has
    > been
    > > > managing the setup of highload systems for decades. He promised to
    > share his
    > > > opinion next week.
    > >
    > > Given that we have one and half weeks before the feature freeze, I
    > > think it's better to complete the project first before waiting for
    > > his/her comments next week. Even if we finish this feature with the
    > > opt-out style, we can hear more opinions on it and change the default
    > > behavior as the change would be privial. What do you think?
    > >
    >
    > Sure, if we can change the default value after the feature freeze, I don't
    > mind leaving our parameter in opt-out style by now.
    >
    > > I've squashed all patches except for the documentation patch as I
    > > assume you're working on it. The attached fixup patch contains several
    > > changes: using opt-out style, comment improvements, and fixing typos
    > > etc.
    > >
    >
    > Thank you very much for the proposed fixes!
    > I like the way you have changed nparallel_workers calculation
    > (autovacuum.c).
    > Forcing parallel workers to always read shared cost params at the first
    > time
    > is a good decision. All comments changes are also LGTM.
    >
    > The only place that I have changed is reloptions.c :
    > As you have explained, it is not appropriate to use the "overrides" wording
    > in the reloption's description, so I decided to return an old one.
    >
    > On Fri, Mar 27, 2026 at 10:54 AM Bharath Rupireddy
    > <bharath.rupireddyforpostgres@gmail.com> wrote:
    > >
    > > Hi,
    > >
    > > On Wed, Mar 25, 2026 at 3:43 PM Masahiko Sawada <sawada.mshk@gmail.com>
    > wrote:
    > > >
    > > > Given that we have one and half weeks before the feature freeze, I
    > > > think it's better to complete the project first before waiting for
    > > > his/her comments next week. Even if we finish this feature with the
    > > > opt-out style, we can hear more opinions on it and change the default
    > > > behavior as the change would be privial. What do you think?
    > > >
    > > > I've squashed all patches except for the documentation patch as I
    > > > assume you're working on it. The attached fixup patch contains several
    > > > changes: using opt-out style, comment improvements, and fixing typos
    > > > etc.
    > >
    > > +1 for enabling this feature by default. When enough CPU is available,
    > > vacuuming multiple indexes of a table in parallel in autovacuum
    > > definitely speeds things up.
    >
    > Yes, for sure. But I have concerns that enabling parallel a/v for everyone
    > will cause the parallel workers shortage during processing of the most huge
    > tables.
    >
    > > Thank you for sending the latest patches. I quickly reviewed the v31
    > > patches. Here are some comments.
    > >
    > > 1/ +       {"autovacuum_parallel_workers", RELOPT_TYPE_INT,
    > >
    > > I haven't looked at the whole thread, but do we all think we need this
    > > as a relopt? IMHO, we can wait for field experience and introduce this
    > > later.
    >
    > I think that we should leave both reloption and the config parameter.
    > Getting rid from the reloption will greatly reduce the ability of users to
    > tune this feature. I'm afraid that this may lead to people not using
    > parallel
    > autovacuum.
    >
    > > I'm having a hard time finding a use-case where one wants to
    > > disable the indexes at the table level. If there was already an
    > > agreement, I agree to commit to that decision.
    >
    > You can read discussion from [1] to the current message in order to dive
    > into
    > the question.
    >
    > To make the long story short, I think that the most common use case for
    > this
    > feature is allowing parallel a/v for 2-3 tables, each of which has ~100
    > indexes. The rest of the tables do not require parallel processing (at
    > least
    > it's a much lower priority for them).
    >
    > At the same time, Masahiko-san thinks that only the system should decide
    > which
    > tables will be processed in parallel. System's decision should be based on
    > the
    > number of indexes and a few other config parameters (e.g.
    > min_parallel_index_scan_size). Thus, possibly many tables will be able to
    > be
    > processed in parallel.
    >
    > (Both opinions are pretty simplified).
    >
    > >
    > > 2/  +   /*
    > > +    * If 'true' then we are running parallel autovacuum. Otherwise, we
    > are
    > > +    * running parallel maintenence VACUUM.
    > > +    */
    > > +   bool        is_autovacuum;
    > > +
    > >
    > > The variable name looks a bit confusing. How about we rely on
    > > AmAutoVacuumWorkerProcess() and avoid the bool in shared memory?
    >
    > This variable is needed for parallel workers, which are taken from the
    > bgworkers pool. I.e. AmAutovacuumWorker() will return 'false' for them.
    > We need the "is_autovacuum" variable in order to understand exactly what
    > this
    > process was started for (VACUUM PARALLEL or parallel autovacuum).
    >
    >
    > Thanks everyone for the review!
    > Please, see an updated set of patches :
    > As I promised, I created a dedicated chapter for Parallel Vacuum
    > description.
    > Both maintenance VACUUM and autovacuum now refer to this chapter.
    >
    > I am pretty inexperienced in the documentation writing, so forgive me if
    > something is out of code style.
    >
    > [1]
    > https://www.postgresql.org/message-id/CAJDiXggH1bW%3D4n%2B55CGLvs_sRU4SYNXwYLZ37wvJ5H_3yURSPw%40mail.gmail.com
    
    
    Thank you for working on this, very useful feature. Sharing a few thoughts:
    
    1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM
    resources in parallel_vacuum_compute_workers?
    2. Is it intentional that other autovacuum workers not yield cost limits to
    the parallel auto vacuum workers? Cost limits are distributed first equally
    to the autovacuum workers.
    and then they share that. Therefore, parallel workers will be heavily
    throttled. IIUC, this problem doesn't exist with manual vacuum.
     If we don't fix this, at least we should document this.
    3. Additionally, is there a point where, based on the cost limits,
    launching additional workers becomes counterproductive compared to running
    fewer workers and preventing it?
    4. Would it make sense to add a table level override to disable parallelism
    or set parallel worker count?
    
    
    I ran some perf tests to show the improvements with parallel vacuum and
    shared below.
    System Configuration
    --------------------
    Hardware:
      CPU: 16 cores
      RAM: 128 GB
      Storage: NVMe SSDs
      OS: Ubuntu Linux
    
    Workload Description
    --------------------
    Table: avtest
      - 5,000,000 rows
      - 9 columns: id (bigint PK), col1-col5 (int), col6 (text), col7
    (timestamp),
        padding (text, 50 bytes)
      - 8 indexes:
          avtest_pkey  (col: id)        107 MB
          idx_av_col7  (col: col7)      107 MB
          idx_av_col2  (col: col2)       56 MB
          idx_av_col4  (col: col4)       56 MB
          idx_av_col5  (col: col5)       56 MB
          idx_av_col1  (col: col1)       56 MB
          idx_av_col3  (col: col3)       56 MB
          idx_av_col6  (col: col6)       35 MB
      - Total size: 1171 MB
    
    Each test iteration:
      1. Delete 2,000,000 rows (40%) using: DELETE WHERE id % 5 IN (1, 2)
      2. CHECKPOINT to flush dirty pages
      3. Trigger autovacuum by setting autovacuum_vacuum_threshold = 100 and
         autovacuum_vacuum_scale_factor = 0 on the table
      4. Wait for autovacuum to complete (detected via server log)
      5. Re-insert the deleted rows and VACUUM to restore the table for the
    next run
    
    
    Test Methodology
    ----------------
    Worker configurations tested: 0, 2, 4, 7 parallel workers
      (7 is the maximum: nindexes - 1, since the leader always handles one
    index)
    
    Two experiments were run with different cost-based vacuum delay settings:
    
      Experiment A: cost_limit=200, cost_delay=2ms
      Experiment B: cost_limit=60,  cost_delay=2ms
    
    Common server settings for both experiments:
      shared_buffers        = 120 GB  (entire dataset fits in shared buffers)
      maintenance_work_mem  = 1 GB
      max_wal_size          = 100 GB  (prevents checkpoints during vacuum)
      min_wal_size          = 10 GB
      checkpoint_timeout    = 1 hour  (prevents time-based checkpoints)
      wal_buffers           = 128 MB
      max_parallel_workers  = 16
      max_worker_processes  = 24
      autovacuum_naptime    = 1s
    
    Between every single run:
      1. PostgreSQL server is fully stopped (pg_ctl stop -m fast)
      2. OS page cache is dropped (echo 3 > /proc/sys/vm/drop_caches)
      3. Server is restarted with a clean log file
      4. After DELETE and CHECKPOINT, the server is stopped again, OS caches
         dropped again, and the server restarted -- so vacuum starts fully cold
      5. The autovacuum_max_parallel_workers GUC is reloaded via pg_ctl reload
    
    Each configuration was tested for 5 iterations.
    
    Timing is extracted from the PostgreSQL server log "system usage" line that
    autovacuum emits at completion. This reports elapsed wall-clock time and CPU
    time for the autovacuum worker leader process.
    
    
    Results: Experiment A (cost_limit=200, cost_delay=2ms)
    ------------------------------------------------------
    
    Workers  Iter1    Iter2    Iter3    Iter4    Iter5    Avg(s)  Speedup
    -------  ------   ------   ------   ------   ------   ------  -------
    0        66.21    79.11    66.27    77.11    66.30    71.00   1.00x
    2        66.55    53.27    52.66    55.74    55.71    56.78   1.25x
    4        51.50    51.74    65.07    52.06    70.25    58.12   1.22x
    7        50.05    50.35    50.04    50.12    50.07    50.12   1.41x
    
    CPU usage (leader process only):
    Workers  Avg CPU user  Avg CPU sys
    -------  -----------   ----------
    0        3.04s         1.70s
    2        1.24s         1.50s
    4        0.78s         1.49s
    7        0.79s         1.48s
    
    
    Results: Experiment B (cost_limit=60, cost_delay=2ms)
    -----------------------------------------------------
    
    Workers  Iter1    Iter2    Iter3    Iter4    Iter5    Avg(s)  Speedup
    -------  ------   ------   ------   ------   ------   ------  -------
    0        199.00   195.26   191.44   191.90   191.67   193.85  1.00x
    2        160.68   181.33   176.85   167.84   159.47   169.23  1.14x
    4        154.02   165.02   174.33   164.16   156.53   162.81  1.19x
    7        148.49   158.68   160.66   154.37   149.20   154.28  1.25x
    
    CPU usage (leader process only):
    Workers  Avg CPU user  Avg CPU sys
    -------  -----------   ----------
    0        3.06s         1.90s
    2        1.28s         1.72s
    4        0.80s         1.69s
    7        0.82s         1.68s
    
    
    
    *Observations:*
    
    1. Parallel autovacuum provides consistent speedup. With cost_limit=200 and
       7 workers, vacuum completes 1.41x faster (71s -> 50s). With
    cost_limit=60,
       the speedup is 1.25x (194s -> 154s).
    2. I see the benefit comes from parallelizing index vacuum. With 8 indexes
    totaling
       ~530 MB, parallel workers scan indexes concurrently instead of the leader
       scanning them one by one. The leader's CPU user time drops from ~3s to
       ~0.8s as index work is offloaded
    
    
    
    
    
    Thanks,
    Satya
    
  88. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-30T08:44:22Z

    Hi,
    
    On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    <satyanarlapuram@gmail.com> wrote:
    >
    > Thank you for working on this, very useful feature. Sharing a few thoughts:
    >
    > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM resources in parallel_vacuum_compute_workers?
    
    Actually, autovacuum_max_parallel_workers is already limited by
    max_parallel_workers. It is not clear for me why we allow setting this GUC
    higher than max_parallel_workers, but if this happens, I think it is a user's
    misconfiguration.
    
    > 2. Is it intentional that other autovacuum workers not yield cost limits to the parallel auto vacuum workers? Cost limits are distributed first equally to the autovacuum workers.
    > and then they share that. Therefore, parallel workers will be heavily throttled. IIUC, this problem doesn't exist with manual vacuum.
    >  If we don't fix this, at least we should document this.
    
    Parallel a/v workers inherit cost based parameters (including the
    vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    low value for parallel operation? If so, user can manually increase the
    vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    much (due to cost delay).
    
    BTW, describing the cost limit propagation to the parallel a/v workers is
    worth mentioning in the documentation. I'll add it in the next patch version.
    
    > 3. Additionally, is there a point where, based on the cost limits, launching additional workers becomes counterproductive compared to running fewer workers and preventing it?
    
    I don't think that we can possibly find a universal limit that will be
    appropriate for all possible configurations. By now we are using a pretty
    simple formula for parallel degree calculation. Since user have several ways
    to affect this formula, I guess that there will be no problems with it (except
    my concerns about opt-out style).
    
    > 4. Would it make sense to add a table level override to disable parallelism or set parallel worker count?
    
    We already have the "autovacuum_parallel_workers" reloption that is used as
    an additional limit for the number of parallel workers. In particular, this
    reloption can be used to disable parallelism at all.
    
    >
    > I ran some perf tests to show the improvements with parallel vacuum and shared below.
    
    Thank you very much!
    
    > Observations:
    >
    > 1. Parallel autovacuum provides consistent speedup. With cost_limit=200 and
    >    7 workers, vacuum completes 1.41x faster (71s -> 50s). With cost_limit=60,
    >    the speedup is 1.25x (194s -> 154s).
    > 2. I see the benefit comes from parallelizing index vacuum. With 8 indexes totaling
    >    ~530 MB, parallel workers scan indexes concurrently instead of the leader
    >    scanning them one by one. The leader's CPU user time drops from ~3s to
    >    ~0.8s as index work is offloaded
    >
    
    1.41 speedup with 7 parallel workers may not seem like a great win, but it is
    a whole time of autovacuum operation (not only index bulkdel/cleanup) with
    pretty small indexes.
    
    May I ask you to run the same test with a higher table's size (several dozen
    gigabytes)? I think the results will be more "expressive".
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  89. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-30T10:40:05Z

    Hi,
    
    On Mon, Mar 30, 2026 at 3:44 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > BTW, describing the cost limit propagation to the parallel a/v workers is
    > worth mentioning in the documentation. I'll add it in the next patch version.
    >
    
    You can find these changes in the v33 patch.
    
    I mentioned cost delay parameters propagation only in "The Autovacuum Daemon"
    chapter. I am not sure that we also should write about parallel workers in the
    "Vacuuming" chapter (within cost based parameters description) since VACUUM
    PARALLEL doesn't do so.
    
    The only change in the 0001 patch is removing redundant empty line
    inside autovacuum.c .
    
    --
    Best regards,
    Daniil Davydov
    
  90. Re: POC: Parallel processing of indexes in autovacuum

    SATYANARAYANA NARLAPURAM <satyanarlapuram@gmail.com> — 2026-03-31T00:14:39Z

    Hi
    
    On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    
    > Hi,
    >
    > On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    > <satyanarlapuram@gmail.com> wrote:
    > >
    > > Thank you for working on this, very useful feature. Sharing a few
    > thoughts:
    > >
    > > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM
    > resources in parallel_vacuum_compute_workers?
    >
    > Actually, autovacuum_max_parallel_workers is already limited by
    > max_parallel_workers. It is not clear for me why we allow setting this GUC
    > higher than max_parallel_workers, but if this happens, I think it is a
    > user's
    > misconfiguration.
    >
    > > 2. Is it intentional that other autovacuum workers not yield cost limits
    > to the parallel auto vacuum workers? Cost limits are distributed first
    > equally to the autovacuum workers.
    > > and then they share that. Therefore, parallel workers will be heavily
    > throttled. IIUC, this problem doesn't exist with manual vacuum.
    > >  If we don't fix this, at least we should document this.
    >
    > Parallel a/v workers inherit cost based parameters (including the
    > vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    > low value for parallel operation? If so, user can manually increase the
    > vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    > much (due to cost delay).
    >
    > BTW, describing the cost limit propagation to the parallel a/v workers is
    > worth mentioning in the documentation. I'll add it in the next patch
    > version.
    >
    > > 3. Additionally, is there a point where, based on the cost limits,
    > launching additional workers becomes counterproductive compared to running
    > fewer workers and preventing it?
    >
    > I don't think that we can possibly find a universal limit that will be
    > appropriate for all possible configurations. By now we are using a pretty
    > simple formula for parallel degree calculation. Since user have several
    > ways
    > to affect this formula, I guess that there will be no problems with it
    > (except
    > my concerns about opt-out style).
    >
    > > 4. Would it make sense to add a table level override to disable
    > parallelism or set parallel worker count?
    >
    > We already have the "autovacuum_parallel_workers" reloption that is used as
    > an additional limit for the number of parallel workers. In particular, this
    > reloption can be used to disable parallelism at all.
    >
    > >
    > > I ran some perf tests to show the improvements with parallel vacuum and
    > shared below.
    >
    > Thank you very much!
    >
    > > Observations:
    > >
    > > 1. Parallel autovacuum provides consistent speedup. With cost_limit=200
    > and
    > >    7 workers, vacuum completes 1.41x faster (71s -> 50s). With
    > cost_limit=60,
    > >    the speedup is 1.25x (194s -> 154s).
    > > 2. I see the benefit comes from parallelizing index vacuum. With 8
    > indexes totaling
    > >    ~530 MB, parallel workers scan indexes concurrently instead of the
    > leader
    > >    scanning them one by one. The leader's CPU user time drops from ~3s to
    > >    ~0.8s as index work is offloaded
    > >
    >
    > 1.41 speedup with 7 parallel workers may not seem like a great win, but it
    > is
    > a whole time of autovacuum operation (not only index bulkdel/cleanup) with
    > pretty small indexes.
    >
    > May I ask you to run the same test with a higher table's size (several
    > dozen
    > gigabytes)? I think the results will be more "expressive".
    >
    
    I ran it with a Billion rows in a table with 8 indexes. The improvement
    with 7 workers is 1.8x.
    Please note that there is a fixed overhead in other vacuum steps, for
    example heap scan.
    In the environments where cost-based delay is used (the default), benefits
    will be modest
    unless vacuum_cost_delay is set to sufficiently large value.
    
    
    *Hardware:*  CPU:     Intel Xeon Platinum 8573C, 1 socket × 8 cores × 2
    threads = 16 vCPUs
      RAM:     128 GB (131,900 MB)
      Swap:    None
    
    *Workload Description*
    
    *Table Schema:*
      CREATE TABLE avtest (
          id       bigint PRIMARY KEY,
          col1     int,           -- random()*1e9
          col2     int,           -- random()*1e9
          col3     int,           -- random()*1e9
          col4     int,           -- random()*1e9
          col5     int,           -- random()*1e9
          col6     text,          -- 'text_' || random()*1e6  (short text ~10
    chars)
          col7     timestamp,     -- now() - random()*365 days
          padding  text           -- repeat('x', 50)
      ) WITH (fillfactor = 90);
    
    *Indexes (8 total):*
      avtest_pkey   — btree on (id)        bigint
      idx_av_col1   — btree on (col1)      int
      idx_av_col2   — btree on (col2)      int
      idx_av_col3   — btree on (col3)      int
      idx_av_col4   — btree on (col4)      int
      idx_av_col5   — btree on (col5)      int
      idx_av_col6   — btree on (col6)      text
      idx_av_col7   — btree on (col7)      timestamp
    
    Dead Tuple Generation:
      DELETE FROM avtest WHERE id % 5 IN (1, 2);
      This deletes exactly 40% of rows, uniformly distributed across all pages.
    
    Vacuum Trigger:
      Autovacuum is triggered naturally by lowering the threshold to 0 and
    setting
      scale_factor to a value that causes immediate launch after the DELETE.
    
    Worker Configurations Tested:
      0 workers  — leader-only vacuum (baseline, no parallelism)
      2 workers  — leader + 2 parallel workers (3 processes total)
      4 workers  — leader + 4 parallel workers (5 processes total)
      7 workers  — leader + 7 parallel workers (8 processes total, 1 per index)
    
    Dataset:
      Rows:         1,000,000,000
      Heap size:    139 GB
      Total size:   279 GB (heap + 8 indexes)
      Dead tuples:  400,000,000 (40%)
    
    Index Sizes:
      avtest_pkey    21 GB   (bigint)
      idx_av_col7    21 GB   (timestamp)
      idx_av_col1    18 GB   (int)
      idx_av_col2    18 GB   (int)
      idx_av_col3    18 GB   (int)
      idx_av_col4    18 GB   (int)
      idx_av_col5    18 GB   (int)
      idx_av_col6     7 GB   (text — shorter keys, smaller index)
      Total indexes: 139 GB
    
    Server Settings:
      shared_buffers                = 96GB
      maintenance_work_mem          = 1GB
      max_wal_size                  = 100GB
      checkpoint_timeout            = 1h
      autovacuum_vacuum_cost_delay  = 0ms (NO throttling)
      autovacuum_vacuum_cost_limit  = 1000
    
    
    *Summary:*
    
    Workers  Avg(s)    Min(s)    Max(s)    Speedup   Time Saved
    -------  ------    ------    ------    -------   ----------
    0        1645.93   1645.01   1646.84    1.00x          —
    2        1276.35   1275.64   1277.05    1.29x     369.58s (6.2 min)
    4        1052.62   1048.92   1056.32    1.56x     593.31s (9.9 min)
    7         892.23    886.59    897.86    1.84x     753.70s (12.6 min)
    
    
     Thanks,
    Satya
    
  91. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-31T07:09:19Z

    On Mon, Mar 30, 2026 at 3:40 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Mon, Mar 30, 2026 at 3:44 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > BTW, describing the cost limit propagation to the parallel a/v workers is
    > > worth mentioning in the documentation. I'll add it in the next patch version.
    > >
    >
    > You can find these changes in the v33 patch.
    >
    > I mentioned cost delay parameters propagation only in "The Autovacuum Daemon"
    > chapter. I am not sure that we also should write about parallel workers in the
    > "Vacuuming" chapter (within cost based parameters description) since VACUUM
    > PARALLEL doesn't do so.
    >
    > The only change in the 0001 patch is removing redundant empty line
    > inside autovacuum.c .
    
    Thank you for updating the patch!
    
    I've made some changes to the documentation part, merged two patches
    into one, and updated the commit message. Please review the attached
    patch.
    
    Regards,
    
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  92. Re: POC: Parallel processing of indexes in autovacuum

    SATYANARAYANA NARLAPURAM <satyanarlapuram@gmail.com> — 2026-03-31T07:46:20Z

    Hi
    
    On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    
    > Hi,
    >
    > On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    > <satyanarlapuram@gmail.com> wrote:
    > >
    > > Thank you for working on this, very useful feature. Sharing a few
    > thoughts:
    > >
    > > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM
    > resources in parallel_vacuum_compute_workers?
    >
    > Actually, autovacuum_max_parallel_workers is already limited by
    > max_parallel_workers. It is not clear for me why we allow setting this GUC
    > higher than max_parallel_workers, but if this happens, I think it is a
    > user's
    > misconfiguration.
    
    
    Isn’t there a wasted effort here if user misconfigures because anyway we
    cannot launch that many workers? I suggest making a check here.
    
    >
    >
    > > 2. Is it intentional that other autovacuum workers not yield cost limits
    > to the parallel auto vacuum workers? Cost limits are distributed first
    > equally to the autovacuum workers.
    > > and then they share that. Therefore, parallel workers will be heavily
    > throttled. IIUC, this problem doesn't exist with manual vacuum.
    > >  If we don't fix this, at least we should document this.
    >
    > Parallel a/v workers inherit cost based parameters (including the
    > vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    > low value for parallel operation? If so, user can manually increase the
    > vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    > much (due to cost delay).
    
    
    They don’t inherit but share, isn’t it?
    
    
    >
    > BTW, describing the cost limit propagation to the parallel a/v workers is
    > worth mentioning in the documentation. I'll add it in the next patch
    > version.
    
    
    Yes, that helps
    
    >
    >
    > > 3. Additionally, is there a point where, based on the cost limits,
    > launching additional workers becomes counterproductive compared to running
    > fewer workers and preventing it?
    >
    > I don't think that we can possibly find a universal limit that will be
    > appropriate for all possible configurations. By now we are using a pretty
    > simple formula for parallel degree calculation. Since user have several
    > ways
    > to affect this formula, I guess that there will be no problems with it
    > (except
    > my concerns about opt-out style).
    
    
    Thanks,
    Satya
    
    >
    >
    
  93. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-31T13:26:29Z

    Hi,
    
    On Tue, Mar 31, 2026 at 2:46 PM SATYANARAYANA NARLAPURAM
    <satyanarlapuram@gmail.com> wrote:
    >
    > On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >>
    >> Actually, autovacuum_max_parallel_workers is already limited by
    >> max_parallel_workers. It is not clear for me why we allow setting this GUC
    >> higher than max_parallel_workers, but if this happens, I think it is a user's
    >> misconfiguration.
    >
    > Isn’t there a wasted effort here if user misconfigures because anyway we cannot launch that many workers? I suggest making a check here.
    
    We have a pretty long discussion about this in the above messages. I also
    think that the user have too many ways to misconfigure postgres. But we
    don't consider such misconfiguration as our fault.
    
    >> Parallel a/v workers inherit cost based parameters (including the
    >> vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    >> low value for parallel operation? If so, user can manually increase the
    >> vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    >> much (due to cost delay).
    >
    >
    > They don’t inherit but share, isn’t it?
    >
    
    Yeah, let me clarify. At the beginning of parallel a/v, the leader a/v worker
    creates and initializes a shared structure, where its cost based parameters
    are stored. Then, all parallel workers will read them from shmem and update
    their parameters accordingly.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  94. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-03-31T14:18:34Z

    Hi,
    
    On Tue, Mar 31, 2026 at 2:09 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > I've made some changes to the documentation part, merged two patches
    > into one, and updated the commit message. Please review the attached
    > patch.
    >
    
    Great, thank you very much!
    
    Again, I don't know how to write the documentation well, so you can ignore
    my comments :
    
    > +    <command>VACUUM</command> can perform index vacuuming and index cleanup
    Don't we need to mention autovacuum here too? I thought that VACUUM in the
    context means "manual VACUUM command".
    
    > + ...applies specifically to the index vacuuming and index cleanup phases...
    Maybe we can refer to "vacuum-phases" here?
    
    All other changes look good to me.
    
    !!!
    > Searching for arguments in
    > favor of opt-in style, I asked for help from another person who has been
    > managing the setup of highload systems for decades. He promised to share his
    > opinion next week.
    
    I talked to Anton Doroshkevich today.
    He confirmed that as a rule there are *hundreds of thousands* of tables in the
    system, the vast majority of which do not need to be vacuumed in parallel mode.
    
    He also suggested the following : let the reloption overlap the value of the
    GUC parameter. I.e. even if av_max_parallel_workers parameters is 0 the user
    still can set the av_parallel_workers to 10 for some table, and autovacuum
    will process this table in parallel.
    
    I remember that you want to use the GUC parameter as a global switch, and this
    approach will break this logic. But according to Anton's words, it is okay if
    the GUC parameter cannot disable parallel a/v for all tables instantly. It will
    become an administrator's responsibility to manually turn off parallel a/v for
    several tables (again, it is completely OK). Thus, this feature can be handy
    for all use cases.
    
    I hope it doesn't look like as an adapting to the needs of a specific user.
    A lot of super-large productions are migrating to postgres now, and I believe
    that we should ensure their comfort too.
    
    What do you think? Can postgres have such a logic?
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  95. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-03-31T21:19:41Z

    On Tue, Mar 31, 2026 at 7:18 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Tue, Mar 31, 2026 at 2:09 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > I've made some changes to the documentation part, merged two patches
    > > into one, and updated the commit message. Please review the attached
    > > patch.
    > >
    >
    > Great, thank you very much!
    >
    > Again, I don't know how to write the documentation well, so you can ignore
    > my comments :
    >
    > > +    <command>VACUUM</command> can perform index vacuuming and index cleanup
    > Don't we need to mention autovacuum here too? I thought that VACUUM in the
    > context means "manual VACUUM command".
    
    I think that the documentation explains that the autovacuum daemon is
    a worker automatically executing VACUUM and ANALYZE commands.
    
    >
    > > + ...applies specifically to the index vacuuming and index cleanup phases...
    > Maybe we can refer to "vacuum-phases" here?
    
    Agreed.
    
    >
    > All other changes look good to me.
    >
    > !!!
    > > Searching for arguments in
    > > favor of opt-in style, I asked for help from another person who has been
    > > managing the setup of highload systems for decades. He promised to share his
    > > opinion next week.
    >
    > I talked to Anton Doroshkevich today.
    
    Thank you for sharing!
    
    > He confirmed that as a rule there are *hundreds of thousands* of tables in the
    > system, the vast majority of which do not need to be vacuumed in parallel mode.
    
    I'm still struggling to see the technical justification; why would a
    user want to avoid parallel vacuuming on eligible tables if they have
    already explicitly allowed the system to use more resources by setting
    autovacuum_max_parallel_workers to >0? If resource contention occurs,
    it is typically a sign that the global parameters need re-tuning. As I
    mentioned, the same contention can occur even with an opt-in style if
    multiple tables are manually configured.
    
    Also, I'm concerned that opt-in style could confuse users since
    parallel vacuum is enabled by default in VACUUM command.
    
    > He also suggested the following : let the reloption overlap the value of the
    > GUC parameter. I.e. even if av_max_parallel_workers parameters is 0 the user
    > still can set the av_parallel_workers to 10 for some table, and autovacuum
    > will process this table in parallel.
    >
    > I remember that you want to use the GUC parameter as a global switch, and this
    > approach will break this logic. But according to Anton's words, it is okay if
    > the GUC parameter cannot disable parallel a/v for all tables instantly. It will
    > become an administrator's responsibility to manually turn off parallel a/v for
    > several tables (again, it is completely OK). Thus, this feature can be handy
    > for all use cases.
    
    While some autovacuum parameters do override GUCs, those are typically
    local to the process (like cost delay). Parallel workers, however, are
    a shared system-wide resource. In a multi-tenant environment, allowing
    a single table's reloption to bypass the
    autovacuum_max_parallel_workers = 0 limit could lead to unexpected
    exhaustion of the worker pool. I think that this GUC should act as a
    reliable global switch for resource management.
    
    > I hope it doesn't look like as an adapting to the needs of a specific user.
    > A lot of super-large productions are migrating to postgres now, and I believe
    > that we should ensure their comfort too.
    
    I'm not prioritizing one specific use case over another. I believe
    that there are also users who want to use parallel vacuum on hundreds
    of thousands of tables. We should consider a better solution while
    checking it from multiple perspectives such as the usability, the
    robustness and consistency with the existing features and behaviors
    etc.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  96. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-01T07:44:25Z

    Hi,
    
    On Wed, Apr 1, 2026 at 4:20 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Tue, Mar 31, 2026 at 7:18 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > > +    <command>VACUUM</command> can perform index vacuuming and index cleanup
    > > Don't we need to mention autovacuum here too? I thought that VACUUM in the
    > > context means "manual VACUUM command".
    >
    > I think that the documentation explains that the autovacuum daemon is
    > a worker automatically executing VACUUM and ANALYZE commands.
    >
    
    Yeah, that's true. Then I agree with this change.
    
    >
    > > He confirmed that as a rule there are *hundreds of thousands* of tables in the
    > > system, the vast majority of which do not need to be vacuumed in parallel mode.
    >
    > I'm still struggling to see the technical justification; why would a
    > user want to avoid parallel vacuuming on eligible tables if they have
    > already explicitly allowed the system to use more resources by setting
    > autovacuum_max_parallel_workers to >0?
    
    Here I am talking about "introductory data". I.e. the situation that the user
    has before considering our parameter usage. Based on this situation, it seems
    to me that not everyone will want to turn on parallel a/v (because of resource
    shortage hazard).
    
    > If resource contention occurs,
    > it is typically a sign that the global parameters need re-tuning. As I
    > mentioned, the same contention can occur even with an opt-in style if
    > multiple tables are manually configured.
    >
    
    Yep, we already discussed it and I agree with you. I think that in the case of
    opt-in style the resource contention will be much more controlled. But actually
    the opt-in style in the form in which I originally proposed it, no longer seems
    like a good idea to me. Classic opt-in style will deprive us of support for
    half of the parallel a/v use cases. Anton's proposal seems to me like a good
    balance between the two styles.
    
    > > He also suggested the following : let the reloption overlap the value of the
    > > GUC parameter. I.e. even if av_max_parallel_workers parameters is 0 the user
    > > still can set the av_parallel_workers to 10 for some table, and autovacuum
    > > will process this table in parallel.
    > >
    > > I remember that you want to use the GUC parameter as a global switch, and this
    > > approach will break this logic. But according to Anton's words, it is okay if
    > > the GUC parameter cannot disable parallel a/v for all tables instantly. It will
    > > become an administrator's responsibility to manually turn off parallel a/v for
    > > several tables (again, it is completely OK). Thus, this feature can be handy
    > > for all use cases.
    >
    > While some autovacuum parameters do override GUCs, those are typically
    > local to the process (like cost delay). Parallel workers, however, are
    > a shared system-wide resource. In a multi-tenant environment, allowing
    > a single table's reloption to bypass the
    > autovacuum_max_parallel_workers = 0 limit could lead to unexpected
    > exhaustion of the worker pool.
    
    Will this exhaustion really be unexpected? If we describe such an ability in
    the documentation, and the user uses it, then everything is fair. Even if
    administrator forgets that he enabled av_parallel_workers reloption somewhere,
    then he can :
    1)
    Check the logfile (if log level is not too high) searching for logs like
    "parallel workers: index vacuum: N planned, N launched in total".
    2)
    Run a query that selects all tables which have av_parallel_workers > 0.
    
    >I think that this GUC should act as a
    > reliable global switch for resource management.
    
    I agree that the "global switch" is an attractive idea and we should strive
    for it. But our parameter *can* play the role of the switch if users don't
    manually touch the av_parallel_workers reloption. But if they do - well, it is
    their responsibility to turn the reloption off.
    
    >
    > > I hope it doesn't look like as an adapting to the needs of a specific user.
    > > A lot of super-large productions are migrating to postgres now, and I believe
    > > that we should ensure their comfort too.
    >
    > I'm not prioritizing one specific use case over another. I believe
    > that there are also users who want to use parallel vacuum on hundreds
    > of thousands of tables. We should consider a better solution while
    > checking it from multiple perspectives such as the usability, the
    > robustness and consistency with the existing features and behaviors
    > etc.
    
    For those users who want to use parallel a/v for hundreds of thousands of
    tables we have the default value "-1" which allows parallel a/v everywhere via
    GUC parameter manipulation.
    
    For those users who want to parallel a/v on several specific tables we can
    allow setting reloption that will override the GUC.
    
    I guess that the question is : "Is it normal if the GUC parameter will lose
    ability to turn off parallel a/v everywhere after the user has manually raised
    the value for the av_parallel_workers reloption on a few tables?". If the
    answer is "Yes", I don't see any obstacles for us to allow overriding the GUC
    parameter via reloption.
    
    
    Thank you very much for your comments!
    Please, see an updated patch.
    
    --
    Best regards,
    Daniil Davydov
    
  97. Re: POC: Parallel processing of indexes in autovacuum

    Alexander Korotkov <aekorotkov@gmail.com> — 2026-04-01T12:10:39Z

    Hi, Daniil!
    
    On Wed, Apr 1, 2026 at 10:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > Thank you very much for your comments!
    > Please, see an updated patch.
    
    Thank you for your work on this subject!  I've some notes about the patch.
    
    1) The changes in guc.c allows autovacuum parallel leader to accept
    changes in not just cost-based GUCs, but any GUCs.  That should be no
    problem, because parallel workers have their own copies of GUC
    variables, but I think this worth comment.
    
    2) Maximum value for autovacuum_parallel_workers reloption is defined
    as literally 1024, while max value for autovacuum_max_parallel_workers
    is defined as MAX_PARALLEL_WORKER_LIMIT (also 1024).  Should we define
    max value for reloption as MAX_PARALLEL_WORKER_LIMIT as well?
    
    3) Some paragraphs were moved from vacuum.sgml to maintenance.sgml.
    It particular it references <replaceable
    class="parameter">integer</replaceable, which is related to PARALLEL
    option syntax: (PARALLEL integer).  Now it becoming unclear and needs
    to be revised.
    
    4) I also think  maintenance.sgml should mention the new reloption.
    
    5) I think it worth having a test which check that setting
    autovacuum_parallel_workers to 0 disables the parallel autovacuum for
    given table.
    
    6) Minor grammar issue in PVSharedCostParams comment, it must be
    "vacuum workers compare" (plural subject).
    
    ------
    Regards,
    Alexander Korotkov
    Supabase
    
    
    
    
  98. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-01T18:54:26Z

    On Mon, Mar 30, 2026 at 5:14 PM SATYANARAYANA NARLAPURAM
    <satyanarlapuram@gmail.com> wrote:
    >
    > Hi
    >
    > On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >>
    >> Hi,
    >>
    >> On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    >> <satyanarlapuram@gmail.com> wrote:
    >> >
    >> > Thank you for working on this, very useful feature. Sharing a few thoughts:
    >> >
    >> > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM resources in parallel_vacuum_compute_workers?
    >>
    >> Actually, autovacuum_max_parallel_workers is already limited by
    >> max_parallel_workers. It is not clear for me why we allow setting this GUC
    >> higher than max_parallel_workers, but if this happens, I think it is a user's
    >> misconfiguration.
    >>
    >> > 2. Is it intentional that other autovacuum workers not yield cost limits to the parallel auto vacuum workers? Cost limits are distributed first equally to the autovacuum workers.
    >> > and then they share that. Therefore, parallel workers will be heavily throttled. IIUC, this problem doesn't exist with manual vacuum.
    >> >  If we don't fix this, at least we should document this.
    >>
    >> Parallel a/v workers inherit cost based parameters (including the
    >> vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    >> low value for parallel operation? If so, user can manually increase the
    >> vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    >> much (due to cost delay).
    >>
    >> BTW, describing the cost limit propagation to the parallel a/v workers is
    >> worth mentioning in the documentation. I'll add it in the next patch version.
    >>
    >> > 3. Additionally, is there a point where, based on the cost limits, launching additional workers becomes counterproductive compared to running fewer workers and preventing it?
    >>
    >> I don't think that we can possibly find a universal limit that will be
    >> appropriate for all possible configurations. By now we are using a pretty
    >> simple formula for parallel degree calculation. Since user have several ways
    >> to affect this formula, I guess that there will be no problems with it (except
    >> my concerns about opt-out style).
    >>
    >> > 4. Would it make sense to add a table level override to disable parallelism or set parallel worker count?
    >>
    >> We already have the "autovacuum_parallel_workers" reloption that is used as
    >> an additional limit for the number of parallel workers. In particular, this
    >> reloption can be used to disable parallelism at all.
    >>
    >> >
    >> > I ran some perf tests to show the improvements with parallel vacuum and shared below.
    >>
    >> Thank you very much!
    >>
    >> > Observations:
    >> >
    >> > 1. Parallel autovacuum provides consistent speedup. With cost_limit=200 and
    >> >    7 workers, vacuum completes 1.41x faster (71s -> 50s). With cost_limit=60,
    >> >    the speedup is 1.25x (194s -> 154s).
    >> > 2. I see the benefit comes from parallelizing index vacuum. With 8 indexes totaling
    >> >    ~530 MB, parallel workers scan indexes concurrently instead of the leader
    >> >    scanning them one by one. The leader's CPU user time drops from ~3s to
    >> >    ~0.8s as index work is offloaded
    >> >
    >>
    >> 1.41 speedup with 7 parallel workers may not seem like a great win, but it is
    >> a whole time of autovacuum operation (not only index bulkdel/cleanup) with
    >> pretty small indexes.
    >>
    >> May I ask you to run the same test with a higher table's size (several dozen
    >> gigabytes)? I think the results will be more "expressive".
    >
    >
    > I ran it with a Billion rows in a table with 8 indexes. The improvement with 7 workers is 1.8x.
    > Please note that there is a fixed overhead in other vacuum steps, for example heap scan.
    > In the environments where cost-based delay is used (the default), benefits will be modest
    > unless vacuum_cost_delay is set to sufficiently large value.
    >
    > Hardware:
    >   CPU:     Intel Xeon Platinum 8573C, 1 socket × 8 cores × 2 threads = 16 vCPUs
    >   RAM:     128 GB (131,900 MB)
    >   Swap:    None
    >
    > Workload Description
    >
    > Table Schema:
    >   CREATE TABLE avtest (
    >       id       bigint PRIMARY KEY,
    >       col1     int,           -- random()*1e9
    >       col2     int,           -- random()*1e9
    >       col3     int,           -- random()*1e9
    >       col4     int,           -- random()*1e9
    >       col5     int,           -- random()*1e9
    >       col6     text,          -- 'text_' || random()*1e6  (short text ~10 chars)
    >       col7     timestamp,     -- now() - random()*365 days
    >       padding  text           -- repeat('x', 50)
    >   ) WITH (fillfactor = 90);
    >
    > Indexes (8 total):
    >   avtest_pkey   — btree on (id)        bigint
    >   idx_av_col1   — btree on (col1)      int
    >   idx_av_col2   — btree on (col2)      int
    >   idx_av_col3   — btree on (col3)      int
    >   idx_av_col4   — btree on (col4)      int
    >   idx_av_col5   — btree on (col5)      int
    >   idx_av_col6   — btree on (col6)      text
    >   idx_av_col7   — btree on (col7)      timestamp
    >
    > Dead Tuple Generation:
    >   DELETE FROM avtest WHERE id % 5 IN (1, 2);
    >   This deletes exactly 40% of rows, uniformly distributed across all pages.
    >
    > Vacuum Trigger:
    >   Autovacuum is triggered naturally by lowering the threshold to 0 and setting
    >   scale_factor to a value that causes immediate launch after the DELETE.
    >
    > Worker Configurations Tested:
    >   0 workers  — leader-only vacuum (baseline, no parallelism)
    >   2 workers  — leader + 2 parallel workers (3 processes total)
    >   4 workers  — leader + 4 parallel workers (5 processes total)
    >   7 workers  — leader + 7 parallel workers (8 processes total, 1 per index)
    >
    > Dataset:
    >   Rows:         1,000,000,000
    >   Heap size:    139 GB
    >   Total size:   279 GB (heap + 8 indexes)
    >   Dead tuples:  400,000,000 (40%)
    >
    > Index Sizes:
    >   avtest_pkey    21 GB   (bigint)
    >   idx_av_col7    21 GB   (timestamp)
    >   idx_av_col1    18 GB   (int)
    >   idx_av_col2    18 GB   (int)
    >   idx_av_col3    18 GB   (int)
    >   idx_av_col4    18 GB   (int)
    >   idx_av_col5    18 GB   (int)
    >   idx_av_col6     7 GB   (text — shorter keys, smaller index)
    >   Total indexes: 139 GB
    >
    > Server Settings:
    >   shared_buffers                = 96GB
    >   maintenance_work_mem          = 1GB
    >   max_wal_size                  = 100GB
    >   checkpoint_timeout            = 1h
    >   autovacuum_vacuum_cost_delay  = 0ms (NO throttling)
    >   autovacuum_vacuum_cost_limit  = 1000
    >
    >
    > Summary:
    >
    > Workers  Avg(s)    Min(s)    Max(s)    Speedup   Time Saved
    > -------  ------    ------    ------    -------   ----------
    > 0        1645.93   1645.01   1646.84    1.00x          —
    > 2        1276.35   1275.64   1277.05    1.29x     369.58s (6.2 min)
    > 4        1052.62   1048.92   1056.32    1.56x     593.31s (9.9 min)
    > 7         892.23    886.59    897.86    1.84x     753.70s (12.6 min)
    >
    
    Thank you for sharing the performance test results!
    
    While the benchmark results look good to me, have you compared the
    performance differences between parallel vacuum in the VACUUM command
    (with the PARALLEL option) and parallel vacuum in autovacuum? Since
    parallel autovacuum introduces some logic to check for delay parameter
    updates, I thought it was worth verifying if this adds any overhead.
    
    BTW, in my view, the most challenging part of this patch is the
    propagation logic for vacuum delay parameters. This propagation is
    necessary because, unlike manual VACUUM, autovacuum workers can reload
    their configuration during operation. We must ensure that parallel
    workers stay synchronized with these updated parameters.
    
    The current patch implements this in vacuumparallel.c: the leader
    shares delay parameters in DSM and updates them (if any vacuum delay
    parameters are updated) after a config reload, while workers poll for
    updates at every vacuum_delay_point() call to refresh their local
    variables.
    
    Another possible approach would be an event-driven model where the
    leader notifies workers after updating shared parameters—for example,
    by adding a shm_mq between the leader (as the sender) and each worker
    (as the receiver).
    
    I've compared these two ideas and opted for the former (polling).
    While a polling approach could theoretically be costly, the current
    implementation is self-contained within the parallel vacuum logic and
    does not touch the core parallel query infrastructure. The
    notification approach might look more elegant, but I'm concerned it
    adds unnecessary complexity just for the autovacuum case. Since the
    polling is essentially just checking an atomic variable, the overhead
    should be negligible.
    
    To verify this, I conducted benchmarks comparing the whole execution
    time and index vacuuming duration.
    
    Setup:
    
    - Disabled (auto) vacuum delays and buffer usage limits.
    - Parallel autovacuum with 1 worker on a table with 2 indexes (approx.
    4 GB each).
    - 5 runs.
    
    Case 1: The latest patch (with polling)
    
    Average: 3.95s (Index: 1.54s)
    Median: 3.62s (Index: 1.37s)
    
    Case 2: The latest patch without polling
    
    Average: 3.98s (Index: 1.56s)
    Median: 3.70s (Index: 1.40s)
    
    Note that in order to simulate the code that doesn't have the polling,
    I reverted the following change:
    
    -   if (InterruptPending ||
    -       (!VacuumCostActive && !ConfigReloadPending))
    +   if (InterruptPending)
    +       return;
    +
    +   if (IsParallelWorker())
    +   {
    +       /*
    +        * Update cost-based vacuum delay parameters for a parallel autovacuum
    +        * worker if any changes are detected.
    +        */
    +       parallel_vacuum_update_shared_delay_params();
    +   }
    +
    +   if (!VacuumCostActive && !ConfigReloadPending)
    
    The parallel vacuum workers don't check the shared vacuum delay
    parameter at all, which is still fine as I disabled vacuum delays.
    
    Overall, the results show no noticeable overhead from the polling approach.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  99. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-01T21:24:29Z

    Hi,
    
    On Wed, Apr 1, 2026 at 7:10 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
    >
    > Thank you for your work on this subject!  I've some notes about the patch.
    >
    
    Thank you very much for the review!
    
    > 1) The changes in guc.c allows autovacuum parallel leader to accept
    > changes in not just cost-based GUCs, but any GUCs.  That should be no
    > problem, because parallel workers have their own copies of GUC
    > variables, but I think this worth comment.
    
    OK, I will clarify it in the code.
    
    > 2) Maximum value for autovacuum_parallel_workers reloption is defined
    > as literally 1024, while max value for autovacuum_max_parallel_workers
    > is defined as MAX_PARALLEL_WORKER_LIMIT (also 1024).  Should we define
    > max value for reloption as MAX_PARALLEL_WORKER_LIMIT as well?
    
    I agree.
    
    > 3) Some paragraphs were moved from vacuum.sgml to maintenance.sgml.
    > It particular it references <replaceable
    > class="parameter">integer</replaceable, which is related to PARALLEL
    > option syntax: (PARALLEL integer).  Now it becoming unclear and needs
    > to be revised.
    
    Good catch! You are right.
    
    > 4) I also think  maintenance.sgml should mention the new reloption.
    
    Do you mean that we should mention it in the "parallel-vacuum" chapter? If so,
    I think that we should also mention that max_parallel_maintenance_workers can
    affect the parallel degree of manual VACUUM command. Yes, we have already
    written about this in the description of the PARALLEL option. But now the
    "vacuum-parallel" chapter doesn't mention limiting by GUC for manual VACUUM and
    limiting by reloption for autovacuum. IMHO it is better to have redundancy than
    an incomplete description.
    
    > 5) I think it worth having a test which check that setting
    > autovacuum_parallel_workers to 0 disables the parallel autovacuum for
    > given table.
    
    I see that VACUUM (PARALLEL) doesn't have such a test. Both manual VACUUM and
    autovacuum have similar logic with parallelism disabling. Is the increase in
    test completion time really worth checking these logic? I don't mind adding a
    new test, actually. Just want to make sure that this is necessary.
    
    > 6) Minor grammar issue in PVSharedCostParams comment, it must be
    > "vacuum workers compare" (plural subject).
    
    Yep, I'll fix it.
    
    
    Please, see an updated patch.
    
    --
    Best regards,
    Daniil Davydov
    
  100. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-01T21:43:47Z

    Hi,
    
    On Thu, Apr 2, 2026 at 1:55 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Overall, the results show no noticeable overhead from the polling approach.
    
    Great news! Thank you for these measurements!
    
    BTW, I caught myself thinking that Tom Lane and maybe some other people might
    not like our parameter propagation logic. We are not building any new
    capability, but supplying an utilitarian solution for a single feature.
    Perhaps someone will not consider this a good way to develop new features.
    
    However, I don't think that this is something bad. We have a pretty simple
    logic which does not interfere with some other infrastructure. On the other
    hand, maybe I am thinking in terms of bigtech product development, where
    results (but not the design) are often the most important thing.
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  101. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-01T23:15:33Z

    On Wed, Apr 1, 2026 at 2:24 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Wed, Apr 1, 2026 at 7:10 PM Alexander Korotkov <aekorotkov@gmail.com> wrote:
    > >
    > > Thank you for your work on this subject!  I've some notes about the patch.
    > >
    >
    > Thank you very much for the review!
    >
    > > 1) The changes in guc.c allows autovacuum parallel leader to accept
    > > changes in not just cost-based GUCs, but any GUCs.  That should be no
    > > problem, because parallel workers have their own copies of GUC
    > > variables, but I think this worth comment.
    >
    > OK, I will clarify it in the code.
    >
    > > 2) Maximum value for autovacuum_parallel_workers reloption is defined
    > > as literally 1024, while max value for autovacuum_max_parallel_workers
    > > is defined as MAX_PARALLEL_WORKER_LIMIT (also 1024).  Should we define
    > > max value for reloption as MAX_PARALLEL_WORKER_LIMIT as well?
    >
    > I agree.
    >
    > > 3) Some paragraphs were moved from vacuum.sgml to maintenance.sgml.
    > > It particular it references <replaceable
    > > class="parameter">integer</replaceable, which is related to PARALLEL
    > > option syntax: (PARALLEL integer).  Now it becoming unclear and needs
    > > to be revised.
    >
    > Good catch! You are right.
    >
    > > 4) I also think  maintenance.sgml should mention the new reloption.
    >
    > Do you mean that we should mention it in the "parallel-vacuum" chapter? If so,
    > I think that we should also mention that max_parallel_maintenance_workers can
    > affect the parallel degree of manual VACUUM command. Yes, we have already
    > written about this in the description of the PARALLEL option. But now the
    > "vacuum-parallel" chapter doesn't mention limiting by GUC for manual VACUUM and
    > limiting by reloption for autovacuum. IMHO it is better to have redundancy than
    > an incomplete description.
    >
    > > 5) I think it worth having a test which check that setting
    > > autovacuum_parallel_workers to 0 disables the parallel autovacuum for
    > > given table.
    >
    > I see that VACUUM (PARALLEL) doesn't have such a test. Both manual VACUUM and
    > autovacuum have similar logic with parallelism disabling. Is the increase in
    > test completion time really worth checking these logic? I don't mind adding a
    > new test, actually. Just want to make sure that this is necessary.
    >
    > > 6) Minor grammar issue in PVSharedCostParams comment, it must be
    > > "vacuum workers compare" (plural subject).
    >
    > Yep, I'll fix it.
    >
    >
    > Please, see an updated patch.
    
    Thank you for updating the patch! I found a bug in the following code:
    
    @@ -457,6 +534,9 @@ parallel_vacuum_end(ParallelVacuumState *pvs,
    IndexBulkDeleteResult **istats)
      DestroyParallelContext(pvs->pcxt);
      ExitParallelMode();
    
    + if (AmAutoVacuumWorkerProcess())
    + pv_shared_cost_params = NULL;
    +
    
    If an autovacuum worker raises an error during parallel vacuum, it
    doesn't pv_shared_cost_params. Then, if it doesn't use parallel vacuum
    on the next table to vacuum, it would end up with SEGV as it attempts
    to propagate the vacuum delay parameters.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  102. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-02T09:22:17Z

    On Wed, Apr 1, 2026 at 2:43 PM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Apr 2, 2026 at 1:55 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Overall, the results show no noticeable overhead from the polling approach.
    >
    > Great news! Thank you for these measurements!
    >
    > BTW, I caught myself thinking that Tom Lane and maybe some other people might
    > not like our parameter propagation logic. We are not building any new
    > capability, but supplying an utilitarian solution for a single feature.
    > Perhaps someone will not consider this a good way to develop new features.
    
    Agreed, this is the one of the reasons why I summarized and conducted
    the performance evaluation.
    
    I've also experimented with the idea of using proc signal to propagate
    the vacuum delay parameters update. The attached patch can be aplied
    on top of v36 patch. While it requires adding a new function to
    parallel.c and requires a bit more codes, it uses the exisiting
    infrastructure for the propagation. Given that this propagation logic
    works only when parallel vacuum workers are running, testing the
    propagation logic in TAP tests using injection points becomes
    complicated, so I removed. What do you think about this idea?
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  103. Re: POC: Parallel processing of indexes in autovacuum

    Alexander Korotkov <aekorotkov@gmail.com> — 2026-04-02T11:02:40Z

    Hi!
    
    On Wed, Apr 1, 2026 at 9:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Mon, Mar 30, 2026 at 5:14 PM SATYANARAYANA NARLAPURAM
    > <satyanarlapuram@gmail.com> wrote:
    > >
    > > Hi
    > >
    > > On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >>
    > >> Hi,
    > >>
    > >> On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    > >> <satyanarlapuram@gmail.com> wrote:
    > >> >
    > >> > Thank you for working on this, very useful feature. Sharing a few thoughts:
    > >> >
    > >> > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM resources in parallel_vacuum_compute_workers?
    > >>
    > >> Actually, autovacuum_max_parallel_workers is already limited by
    > >> max_parallel_workers. It is not clear for me why we allow setting this GUC
    > >> higher than max_parallel_workers, but if this happens, I think it is a user's
    > >> misconfiguration.
    > >>
    > >> > 2. Is it intentional that other autovacuum workers not yield cost limits to the parallel auto vacuum workers? Cost limits are distributed first equally to the autovacuum workers.
    > >> > and then they share that. Therefore, parallel workers will be heavily throttled. IIUC, this problem doesn't exist with manual vacuum.
    > >> >  If we don't fix this, at least we should document this.
    > >>
    > >> Parallel a/v workers inherit cost based parameters (including the
    > >> vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    > >> low value for parallel operation? If so, user can manually increase the
    > >> vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    > >> much (due to cost delay).
    > >>
    > >> BTW, describing the cost limit propagation to the parallel a/v workers is
    > >> worth mentioning in the documentation. I'll add it in the next patch version.
    > >>
    > >> > 3. Additionally, is there a point where, based on the cost limits, launching additional workers becomes counterproductive compared to running fewer workers and preventing it?
    > >>
    > >> I don't think that we can possibly find a universal limit that will be
    > >> appropriate for all possible configurations. By now we are using a pretty
    > >> simple formula for parallel degree calculation. Since user have several ways
    > >> to affect this formula, I guess that there will be no problems with it (except
    > >> my concerns about opt-out style).
    > >>
    > >> > 4. Would it make sense to add a table level override to disable parallelism or set parallel worker count?
    > >>
    > >> We already have the "autovacuum_parallel_workers" reloption that is used as
    > >> an additional limit for the number of parallel workers. In particular, this
    > >> reloption can be used to disable parallelism at all.
    > >>
    > >> >
    > >> > I ran some perf tests to show the improvements with parallel vacuum and shared below.
    > >>
    > >> Thank you very much!
    > >>
    > >> > Observations:
    > >> >
    > >> > 1. Parallel autovacuum provides consistent speedup. With cost_limit=200 and
    > >> >    7 workers, vacuum completes 1.41x faster (71s -> 50s). With cost_limit=60,
    > >> >    the speedup is 1.25x (194s -> 154s).
    > >> > 2. I see the benefit comes from parallelizing index vacuum. With 8 indexes totaling
    > >> >    ~530 MB, parallel workers scan indexes concurrently instead of the leader
    > >> >    scanning them one by one. The leader's CPU user time drops from ~3s to
    > >> >    ~0.8s as index work is offloaded
    > >> >
    > >>
    > >> 1.41 speedup with 7 parallel workers may not seem like a great win, but it is
    > >> a whole time of autovacuum operation (not only index bulkdel/cleanup) with
    > >> pretty small indexes.
    > >>
    > >> May I ask you to run the same test with a higher table's size (several dozen
    > >> gigabytes)? I think the results will be more "expressive".
    > >
    > >
    > > I ran it with a Billion rows in a table with 8 indexes. The improvement with 7 workers is 1.8x.
    > > Please note that there is a fixed overhead in other vacuum steps, for example heap scan.
    > > In the environments where cost-based delay is used (the default), benefits will be modest
    > > unless vacuum_cost_delay is set to sufficiently large value.
    > >
    > > Hardware:
    > >   CPU:     Intel Xeon Platinum 8573C, 1 socket × 8 cores × 2 threads = 16 vCPUs
    > >   RAM:     128 GB (131,900 MB)
    > >   Swap:    None
    > >
    > > Workload Description
    > >
    > > Table Schema:
    > >   CREATE TABLE avtest (
    > >       id       bigint PRIMARY KEY,
    > >       col1     int,           -- random()*1e9
    > >       col2     int,           -- random()*1e9
    > >       col3     int,           -- random()*1e9
    > >       col4     int,           -- random()*1e9
    > >       col5     int,           -- random()*1e9
    > >       col6     text,          -- 'text_' || random()*1e6  (short text ~10 chars)
    > >       col7     timestamp,     -- now() - random()*365 days
    > >       padding  text           -- repeat('x', 50)
    > >   ) WITH (fillfactor = 90);
    > >
    > > Indexes (8 total):
    > >   avtest_pkey   — btree on (id)        bigint
    > >   idx_av_col1   — btree on (col1)      int
    > >   idx_av_col2   — btree on (col2)      int
    > >   idx_av_col3   — btree on (col3)      int
    > >   idx_av_col4   — btree on (col4)      int
    > >   idx_av_col5   — btree on (col5)      int
    > >   idx_av_col6   — btree on (col6)      text
    > >   idx_av_col7   — btree on (col7)      timestamp
    > >
    > > Dead Tuple Generation:
    > >   DELETE FROM avtest WHERE id % 5 IN (1, 2);
    > >   This deletes exactly 40% of rows, uniformly distributed across all pages.
    > >
    > > Vacuum Trigger:
    > >   Autovacuum is triggered naturally by lowering the threshold to 0 and setting
    > >   scale_factor to a value that causes immediate launch after the DELETE.
    > >
    > > Worker Configurations Tested:
    > >   0 workers  — leader-only vacuum (baseline, no parallelism)
    > >   2 workers  — leader + 2 parallel workers (3 processes total)
    > >   4 workers  — leader + 4 parallel workers (5 processes total)
    > >   7 workers  — leader + 7 parallel workers (8 processes total, 1 per index)
    > >
    > > Dataset:
    > >   Rows:         1,000,000,000
    > >   Heap size:    139 GB
    > >   Total size:   279 GB (heap + 8 indexes)
    > >   Dead tuples:  400,000,000 (40%)
    > >
    > > Index Sizes:
    > >   avtest_pkey    21 GB   (bigint)
    > >   idx_av_col7    21 GB   (timestamp)
    > >   idx_av_col1    18 GB   (int)
    > >   idx_av_col2    18 GB   (int)
    > >   idx_av_col3    18 GB   (int)
    > >   idx_av_col4    18 GB   (int)
    > >   idx_av_col5    18 GB   (int)
    > >   idx_av_col6     7 GB   (text — shorter keys, smaller index)
    > >   Total indexes: 139 GB
    > >
    > > Server Settings:
    > >   shared_buffers                = 96GB
    > >   maintenance_work_mem          = 1GB
    > >   max_wal_size                  = 100GB
    > >   checkpoint_timeout            = 1h
    > >   autovacuum_vacuum_cost_delay  = 0ms (NO throttling)
    > >   autovacuum_vacuum_cost_limit  = 1000
    > >
    > >
    > > Summary:
    > >
    > > Workers  Avg(s)    Min(s)    Max(s)    Speedup   Time Saved
    > > -------  ------    ------    ------    -------   ----------
    > > 0        1645.93   1645.01   1646.84    1.00x          —
    > > 2        1276.35   1275.64   1277.05    1.29x     369.58s (6.2 min)
    > > 4        1052.62   1048.92   1056.32    1.56x     593.31s (9.9 min)
    > > 7         892.23    886.59    897.86    1.84x     753.70s (12.6 min)
    > >
    >
    > Thank you for sharing the performance test results!
    >
    > While the benchmark results look good to me, have you compared the
    > performance differences between parallel vacuum in the VACUUM command
    > (with the PARALLEL option) and parallel vacuum in autovacuum? Since
    > parallel autovacuum introduces some logic to check for delay parameter
    > updates, I thought it was worth verifying if this adds any overhead.
    >
    > BTW, in my view, the most challenging part of this patch is the
    > propagation logic for vacuum delay parameters. This propagation is
    > necessary because, unlike manual VACUUM, autovacuum workers can reload
    > their configuration during operation. We must ensure that parallel
    > workers stay synchronized with these updated parameters.
    >
    > The current patch implements this in vacuumparallel.c: the leader
    > shares delay parameters in DSM and updates them (if any vacuum delay
    > parameters are updated) after a config reload, while workers poll for
    > updates at every vacuum_delay_point() call to refresh their local
    > variables.
    >
    > Another possible approach would be an event-driven model where the
    > leader notifies workers after updating shared parameters—for example,
    > by adding a shm_mq between the leader (as the sender) and each worker
    > (as the receiver).
    >
    > I've compared these two ideas and opted for the former (polling).
    > While a polling approach could theoretically be costly, the current
    > implementation is self-contained within the parallel vacuum logic and
    > does not touch the core parallel query infrastructure. The
    > notification approach might look more elegant, but I'm concerned it
    > adds unnecessary complexity just for the autovacuum case. Since the
    > polling is essentially just checking an atomic variable, the overhead
    > should be negligible.
    >
    > To verify this, I conducted benchmarks comparing the whole execution
    > time and index vacuuming duration.
    >
    > Setup:
    >
    > - Disabled (auto) vacuum delays and buffer usage limits.
    > - Parallel autovacuum with 1 worker on a table with 2 indexes (approx.
    > 4 GB each).
    > - 5 runs.
    >
    > Case 1: The latest patch (with polling)
    >
    > Average: 3.95s (Index: 1.54s)
    > Median: 3.62s (Index: 1.37s)
    >
    > Case 2: The latest patch without polling
    >
    > Average: 3.98s (Index: 1.56s)
    > Median: 3.70s (Index: 1.40s)
    >
    > Note that in order to simulate the code that doesn't have the polling,
    > I reverted the following change:
    >
    > -   if (InterruptPending ||
    > -       (!VacuumCostActive && !ConfigReloadPending))
    > +   if (InterruptPending)
    > +       return;
    > +
    > +   if (IsParallelWorker())
    > +   {
    > +       /*
    > +        * Update cost-based vacuum delay parameters for a parallel autovacuum
    > +        * worker if any changes are detected.
    > +        */
    > +       parallel_vacuum_update_shared_delay_params();
    > +   }
    > +
    > +   if (!VacuumCostActive && !ConfigReloadPending)
    >
    > The parallel vacuum workers don't check the shared vacuum delay
    > parameter at all, which is still fine as I disabled vacuum delays.
    >
    > Overall, the results show no noticeable overhead from the polling approach.
    
    I would say this polling approach is very cheap.  When there are no
    updates, it only has to check a single 32-bit value from shared
    memory.  And that value doesn't get updated frequently; it's good for
    caching.  No wonder we see no measurable overhead.
    
    Regarding the event-driven approach, given that the parallel worker
    process is busy with other jobs (doing actual vacuuming), it would
    anyway have to poll for new events from time to time.  Thus, I don't
    think it's possible to organize polling for new events any cheaper
    than the current approach of polling for updates in shmem.  If the
    worker process was just waiting for GUC updates without any other
    jobs, then, for instance, waiting on the latch would be cheaper than
    polling in a loop, but that's not our case.
    
    I don't see the current polling approach for GUC updates as
    performance problematic.
    
    ------
    Regards,
    Alexander Korotkov
    Supabase
    
    
    
    
  104. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-02T15:10:46Z

    Hi,
    
    On Thu, Apr 2, 2026 at 6:16 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Thank you for updating the patch! I found a bug in the following code:
    >
    > @@ -457,6 +534,9 @@ parallel_vacuum_end(ParallelVacuumState *pvs,
    > IndexBulkDeleteResult **istats)
    >   DestroyParallelContext(pvs->pcxt);
    >   ExitParallelMode();
    >
    > + if (AmAutoVacuumWorkerProcess())
    > + pv_shared_cost_params = NULL;
    > +
    >
    > If an autovacuum worker raises an error during parallel vacuum, it
    > doesn't pv_shared_cost_params. Then, if it doesn't use parallel vacuum
    > on the next table to vacuum, it would end up with SEGV as it attempts
    > to propagate the vacuum delay parameters.
    
    Ouch. Indeed, I did not foresee this.
    Thank you for noticing it!
    
    I think we should add some cleanup for autovacuum near the ParallelContext
    cleanup, since they are interconnected. I also want to return our tests that
    are triggering ERROR/PANIC in the leader worker in order to check whether all
    resources are released. I hope I will be able to get to that by tomorrow
    evening.
    
    --
    Best regards,
    Daniil Davydov
    
  105. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-02T23:00:11Z

    On Thu, Apr 2, 2026 at 8:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Thu, Apr 2, 2026 at 6:16 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Thank you for updating the patch! I found a bug in the following code:
    > >
    > > @@ -457,6 +534,9 @@ parallel_vacuum_end(ParallelVacuumState *pvs,
    > > IndexBulkDeleteResult **istats)
    > >   DestroyParallelContext(pvs->pcxt);
    > >   ExitParallelMode();
    > >
    > > + if (AmAutoVacuumWorkerProcess())
    > > + pv_shared_cost_params = NULL;
    > > +
    > >
    > > If an autovacuum worker raises an error during parallel vacuum, it
    > > doesn't pv_shared_cost_params. Then, if it doesn't use parallel vacuum
    > > on the next table to vacuum, it would end up with SEGV as it attempts
    > > to propagate the vacuum delay parameters.
    >
    > Ouch. Indeed, I did not foresee this.
    > Thank you for noticing it!
    >
    > I think we should add some cleanup for autovacuum near the ParallelContext
    > cleanup, since they are interconnected. I also want to return our tests that
    > are triggering ERROR/PANIC in the leader worker in order to check whether all
    > resources are released. I hope I will be able to get to that by tomorrow
    > evening.
    
    I think that the beginning of vacuum loop (in PG_TRY() block in
    vacuum()) seems better place as we're resetting vacuum delay
    parameters:
    
            in_vacuum = true;
            VacuumFailsafeActive = false;
            VacuumUpdateCosts();
            VacuumCostBalance = 0;
            VacuumCostBalanceLocal = 0;
            VacuumSharedCostBalance = NULL;
            VacuumActiveNWorkers = NULL;
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  106. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-02T23:30:42Z

    On Thu, Apr 2, 2026 at 4:02 AM Alexander Korotkov <aekorotkov@gmail.com> wrote:
    >
    > Hi!
    >
    > On Wed, Apr 1, 2026 at 9:55 PM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Mon, Mar 30, 2026 at 5:14 PM SATYANARAYANA NARLAPURAM
    > > <satyanarlapuram@gmail.com> wrote:
    > > >
    > > > Hi
    > > >
    > > > On Mon, Mar 30, 2026 at 1:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >>
    > > >> Hi,
    > > >>
    > > >> On Mon, Mar 30, 2026 at 7:17 AM SATYANARAYANA NARLAPURAM
    > > >> <satyanarlapuram@gmail.com> wrote:
    > > >> >
    > > >> > Thank you for working on this, very useful feature. Sharing a few thoughts:
    > > >> >
    > > >> > 1. Shouldn't we also cap by max_parallel_workers to avoid wasting DSM resources in parallel_vacuum_compute_workers?
    > > >>
    > > >> Actually, autovacuum_max_parallel_workers is already limited by
    > > >> max_parallel_workers. It is not clear for me why we allow setting this GUC
    > > >> higher than max_parallel_workers, but if this happens, I think it is a user's
    > > >> misconfiguration.
    > > >>
    > > >> > 2. Is it intentional that other autovacuum workers not yield cost limits to the parallel auto vacuum workers? Cost limits are distributed first equally to the autovacuum workers.
    > > >> > and then they share that. Therefore, parallel workers will be heavily throttled. IIUC, this problem doesn't exist with manual vacuum.
    > > >> >  If we don't fix this, at least we should document this.
    > > >>
    > > >> Parallel a/v workers inherit cost based parameters (including the
    > > >> vacuum_cost_limit) from the leader worker. Do you mean that this can be too
    > > >> low value for parallel operation? If so, user can manually increase the
    > > >> vacuum_cost_limit reloption for those tables, where parallel a/v sleeps too
    > > >> much (due to cost delay).
    > > >>
    > > >> BTW, describing the cost limit propagation to the parallel a/v workers is
    > > >> worth mentioning in the documentation. I'll add it in the next patch version.
    > > >>
    > > >> > 3. Additionally, is there a point where, based on the cost limits, launching additional workers becomes counterproductive compared to running fewer workers and preventing it?
    > > >>
    > > >> I don't think that we can possibly find a universal limit that will be
    > > >> appropriate for all possible configurations. By now we are using a pretty
    > > >> simple formula for parallel degree calculation. Since user have several ways
    > > >> to affect this formula, I guess that there will be no problems with it (except
    > > >> my concerns about opt-out style).
    > > >>
    > > >> > 4. Would it make sense to add a table level override to disable parallelism or set parallel worker count?
    > > >>
    > > >> We already have the "autovacuum_parallel_workers" reloption that is used as
    > > >> an additional limit for the number of parallel workers. In particular, this
    > > >> reloption can be used to disable parallelism at all.
    > > >>
    > > >> >
    > > >> > I ran some perf tests to show the improvements with parallel vacuum and shared below.
    > > >>
    > > >> Thank you very much!
    > > >>
    > > >> > Observations:
    > > >> >
    > > >> > 1. Parallel autovacuum provides consistent speedup. With cost_limit=200 and
    > > >> >    7 workers, vacuum completes 1.41x faster (71s -> 50s). With cost_limit=60,
    > > >> >    the speedup is 1.25x (194s -> 154s).
    > > >> > 2. I see the benefit comes from parallelizing index vacuum. With 8 indexes totaling
    > > >> >    ~530 MB, parallel workers scan indexes concurrently instead of the leader
    > > >> >    scanning them one by one. The leader's CPU user time drops from ~3s to
    > > >> >    ~0.8s as index work is offloaded
    > > >> >
    > > >>
    > > >> 1.41 speedup with 7 parallel workers may not seem like a great win, but it is
    > > >> a whole time of autovacuum operation (not only index bulkdel/cleanup) with
    > > >> pretty small indexes.
    > > >>
    > > >> May I ask you to run the same test with a higher table's size (several dozen
    > > >> gigabytes)? I think the results will be more "expressive".
    > > >
    > > >
    > > > I ran it with a Billion rows in a table with 8 indexes. The improvement with 7 workers is 1.8x.
    > > > Please note that there is a fixed overhead in other vacuum steps, for example heap scan.
    > > > In the environments where cost-based delay is used (the default), benefits will be modest
    > > > unless vacuum_cost_delay is set to sufficiently large value.
    > > >
    > > > Hardware:
    > > >   CPU:     Intel Xeon Platinum 8573C, 1 socket × 8 cores × 2 threads = 16 vCPUs
    > > >   RAM:     128 GB (131,900 MB)
    > > >   Swap:    None
    > > >
    > > > Workload Description
    > > >
    > > > Table Schema:
    > > >   CREATE TABLE avtest (
    > > >       id       bigint PRIMARY KEY,
    > > >       col1     int,           -- random()*1e9
    > > >       col2     int,           -- random()*1e9
    > > >       col3     int,           -- random()*1e9
    > > >       col4     int,           -- random()*1e9
    > > >       col5     int,           -- random()*1e9
    > > >       col6     text,          -- 'text_' || random()*1e6  (short text ~10 chars)
    > > >       col7     timestamp,     -- now() - random()*365 days
    > > >       padding  text           -- repeat('x', 50)
    > > >   ) WITH (fillfactor = 90);
    > > >
    > > > Indexes (8 total):
    > > >   avtest_pkey   — btree on (id)        bigint
    > > >   idx_av_col1   — btree on (col1)      int
    > > >   idx_av_col2   — btree on (col2)      int
    > > >   idx_av_col3   — btree on (col3)      int
    > > >   idx_av_col4   — btree on (col4)      int
    > > >   idx_av_col5   — btree on (col5)      int
    > > >   idx_av_col6   — btree on (col6)      text
    > > >   idx_av_col7   — btree on (col7)      timestamp
    > > >
    > > > Dead Tuple Generation:
    > > >   DELETE FROM avtest WHERE id % 5 IN (1, 2);
    > > >   This deletes exactly 40% of rows, uniformly distributed across all pages.
    > > >
    > > > Vacuum Trigger:
    > > >   Autovacuum is triggered naturally by lowering the threshold to 0 and setting
    > > >   scale_factor to a value that causes immediate launch after the DELETE.
    > > >
    > > > Worker Configurations Tested:
    > > >   0 workers  — leader-only vacuum (baseline, no parallelism)
    > > >   2 workers  — leader + 2 parallel workers (3 processes total)
    > > >   4 workers  — leader + 4 parallel workers (5 processes total)
    > > >   7 workers  — leader + 7 parallel workers (8 processes total, 1 per index)
    > > >
    > > > Dataset:
    > > >   Rows:         1,000,000,000
    > > >   Heap size:    139 GB
    > > >   Total size:   279 GB (heap + 8 indexes)
    > > >   Dead tuples:  400,000,000 (40%)
    > > >
    > > > Index Sizes:
    > > >   avtest_pkey    21 GB   (bigint)
    > > >   idx_av_col7    21 GB   (timestamp)
    > > >   idx_av_col1    18 GB   (int)
    > > >   idx_av_col2    18 GB   (int)
    > > >   idx_av_col3    18 GB   (int)
    > > >   idx_av_col4    18 GB   (int)
    > > >   idx_av_col5    18 GB   (int)
    > > >   idx_av_col6     7 GB   (text — shorter keys, smaller index)
    > > >   Total indexes: 139 GB
    > > >
    > > > Server Settings:
    > > >   shared_buffers                = 96GB
    > > >   maintenance_work_mem          = 1GB
    > > >   max_wal_size                  = 100GB
    > > >   checkpoint_timeout            = 1h
    > > >   autovacuum_vacuum_cost_delay  = 0ms (NO throttling)
    > > >   autovacuum_vacuum_cost_limit  = 1000
    > > >
    > > >
    > > > Summary:
    > > >
    > > > Workers  Avg(s)    Min(s)    Max(s)    Speedup   Time Saved
    > > > -------  ------    ------    ------    -------   ----------
    > > > 0        1645.93   1645.01   1646.84    1.00x          —
    > > > 2        1276.35   1275.64   1277.05    1.29x     369.58s (6.2 min)
    > > > 4        1052.62   1048.92   1056.32    1.56x     593.31s (9.9 min)
    > > > 7         892.23    886.59    897.86    1.84x     753.70s (12.6 min)
    > > >
    > >
    > > Thank you for sharing the performance test results!
    > >
    > > While the benchmark results look good to me, have you compared the
    > > performance differences between parallel vacuum in the VACUUM command
    > > (with the PARALLEL option) and parallel vacuum in autovacuum? Since
    > > parallel autovacuum introduces some logic to check for delay parameter
    > > updates, I thought it was worth verifying if this adds any overhead.
    > >
    > > BTW, in my view, the most challenging part of this patch is the
    > > propagation logic for vacuum delay parameters. This propagation is
    > > necessary because, unlike manual VACUUM, autovacuum workers can reload
    > > their configuration during operation. We must ensure that parallel
    > > workers stay synchronized with these updated parameters.
    > >
    > > The current patch implements this in vacuumparallel.c: the leader
    > > shares delay parameters in DSM and updates them (if any vacuum delay
    > > parameters are updated) after a config reload, while workers poll for
    > > updates at every vacuum_delay_point() call to refresh their local
    > > variables.
    > >
    > > Another possible approach would be an event-driven model where the
    > > leader notifies workers after updating shared parameters—for example,
    > > by adding a shm_mq between the leader (as the sender) and each worker
    > > (as the receiver).
    > >
    > > I've compared these two ideas and opted for the former (polling).
    > > While a polling approach could theoretically be costly, the current
    > > implementation is self-contained within the parallel vacuum logic and
    > > does not touch the core parallel query infrastructure. The
    > > notification approach might look more elegant, but I'm concerned it
    > > adds unnecessary complexity just for the autovacuum case. Since the
    > > polling is essentially just checking an atomic variable, the overhead
    > > should be negligible.
    > >
    > > To verify this, I conducted benchmarks comparing the whole execution
    > > time and index vacuuming duration.
    > >
    > > Setup:
    > >
    > > - Disabled (auto) vacuum delays and buffer usage limits.
    > > - Parallel autovacuum with 1 worker on a table with 2 indexes (approx.
    > > 4 GB each).
    > > - 5 runs.
    > >
    > > Case 1: The latest patch (with polling)
    > >
    > > Average: 3.95s (Index: 1.54s)
    > > Median: 3.62s (Index: 1.37s)
    > >
    > > Case 2: The latest patch without polling
    > >
    > > Average: 3.98s (Index: 1.56s)
    > > Median: 3.70s (Index: 1.40s)
    > >
    > > Note that in order to simulate the code that doesn't have the polling,
    > > I reverted the following change:
    > >
    > > -   if (InterruptPending ||
    > > -       (!VacuumCostActive && !ConfigReloadPending))
    > > +   if (InterruptPending)
    > > +       return;
    > > +
    > > +   if (IsParallelWorker())
    > > +   {
    > > +       /*
    > > +        * Update cost-based vacuum delay parameters for a parallel autovacuum
    > > +        * worker if any changes are detected.
    > > +        */
    > > +       parallel_vacuum_update_shared_delay_params();
    > > +   }
    > > +
    > > +   if (!VacuumCostActive && !ConfigReloadPending)
    > >
    > > The parallel vacuum workers don't check the shared vacuum delay
    > > parameter at all, which is still fine as I disabled vacuum delays.
    > >
    > > Overall, the results show no noticeable overhead from the polling approach.
    >
    > I would say this polling approach is very cheap.  When there are no
    > updates, it only has to check a single 32-bit value from shared
    > memory.  And that value doesn't get updated frequently; it's good for
    > caching.  No wonder we see no measurable overhead.
    
    Thank you for the comments!
    
    >
    > Regarding the event-driven approach, given that the parallel worker
    > process is busy with other jobs (doing actual vacuuming), it would
    > anyway have to poll for new events from time to time.  Thus, I don't
    > think it's possible to organize polling for new events any cheaper
    > than the current approach of polling for updates in shmem.
    
    What do you think about the idea of using proc signals like the patch
    I've sent recently[1]? With that approach, workers have to check the
    local variable. It seems slightly cheaper and can use the existing
    logic.
    
    [1] https://www.postgresql.org/message-id/CAD21AoBm0cxQjtWuY0f7%2BaT4UiRV%2B%2BaFKkzjj6vmERTj_UFnxA%40mail.gmail.com
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  107. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-03T11:43:33Z

    Hi,
    
    On Fri, Apr 3, 2026 at 6:31 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > What do you think about the idea of using proc signals like the patch
    > I've sent recently[1]? With that approach, workers have to check the
    > local variable. It seems slightly cheaper and can use the existing
    > logic.
    >
    
    Thank you for the patch!
    
    1) Maybe we should implement this logic within ParallelMessages? For example,
    I see this ParallelMessages use case in parallel a/v :
    /*
     * Call the parallel variant of pgstat_progress_incr_param so workers can
     * report progress of index vacuum to the leader.
     */
    pgstat_progress_parallel_incr_param(PROGRESS_VACUUM_INDEXES_PROCESSED, 1);
    
    I.e. parallel a/v workers already communicate with a leader via
    ParallelMessages, so it will be convenient to extend this protocol by a new
    message.
    
    2) I don't think that the difference between accessing atomic and local
    variable can be measured for parallel workers. But sending a signal to every
    parallel worker is surely slower than just incrementing an atomic variable.
    IIUC you created this patch in order to solve the task of using an existing
    infrastructure instead of creating a new utilitarian solution. However, I think
    that both the polling approach and signalling approach (in its current
    implementation) are basically equal. I mean that in both cases we have an
    autovacuum-specific mechanism to share particular parameters between particular
    workers.
    
    I will try to explain how I see the solution to this problem. :
    Your implementation can be made more abstract, so that it becomes a new
    internal mechanism that other modules can use in the future. E.g. we can create
    an interface that allows any parallel leader (not necessarily just an
    autovacuum leader) to inform its parallel workers that some config parameters
    have been changed. At the same time, parallel workers can use this interface in
    order to specify, which parameters (or groups of parameters) they want to
    consume from the leader. What do you think?
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  108. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-03T13:45:39Z

    Hi,
    
    On Fri, Apr 3, 2026 at 6:00 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > On Thu, Apr 2, 2026 at 8:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > I think we should add some cleanup for autovacuum near the ParallelContext
    > > cleanup, since they are interconnected. I also want to return our tests that
    > > are triggering ERROR/PANIC in the leader worker in order to check whether all
    > > resources are released. I hope I will be able to get to that by tomorrow
    > > evening.
    >
    > I think that the beginning of vacuum loop (in PG_TRY() block in
    > vacuum()) seems better place as we're resetting vacuum delay
    > parameters:
    >
    >         in_vacuum = true;
    >         VacuumFailsafeActive = false;
    >         VacuumUpdateCosts();
    >         VacuumCostBalance = 0;
    >         VacuumCostBalanceLocal = 0;
    >         VacuumSharedCostBalance = NULL;
    >         VacuumActiveNWorkers = NULL;
    >
    
    I am still thinking that this pointer is related to the ParallelContext, and it
    is a bit confusing that we can manipulate it outside all "parallel" logic.
    Since this variable points to the DSM it looks very natural for me if its
    lifetime will be similar to the DSM. Please, see attached patch, that resets
    this pointer during dsm detaching.
    
    --
    Best regards,
    Daniil Davydov
    
  109. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-04T01:11:58Z

    On Fri, Apr 3, 2026 at 6:45 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Fri, Apr 3, 2026 at 6:00 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > On Thu, Apr 2, 2026 at 8:10 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > > >
    > > > I think we should add some cleanup for autovacuum near the ParallelContext
    > > > cleanup, since they are interconnected. I also want to return our tests that
    > > > are triggering ERROR/PANIC in the leader worker in order to check whether all
    > > > resources are released. I hope I will be able to get to that by tomorrow
    > > > evening.
    > >
    > > I think that the beginning of vacuum loop (in PG_TRY() block in
    > > vacuum()) seems better place as we're resetting vacuum delay
    > > parameters:
    > >
    > >         in_vacuum = true;
    > >         VacuumFailsafeActive = false;
    > >         VacuumUpdateCosts();
    > >         VacuumCostBalance = 0;
    > >         VacuumCostBalanceLocal = 0;
    > >         VacuumSharedCostBalance = NULL;
    > >         VacuumActiveNWorkers = NULL;
    > >
    >
    > I am still thinking that this pointer is related to the ParallelContext, and it
    > is a bit confusing that we can manipulate it outside all "parallel" logic.
    > Since this variable points to the DSM it looks very natural for me if its
    > lifetime will be similar to the DSM. Please, see attached patch, that resets
    > this pointer during dsm detaching.
    
    Sounds a reasonable apporach.
    
    Regarding the regression tests, ISTM we no longer need
    'autovacuum-leader-before-indexes-processing' injection point since it
    currently tests that parallel workers update their delay parameters
    during the initialization (i.e., in parallel_vacuum_main()). In order
    to verify the behavior of workers updating their delay parameters
    while processing indexes, we would need another injection ponit to
    stop parallel workers, which seems overkill to me. So I removed it but
    the test still covers the propagation logic.
    
    Regarding the patch, I don't think it's a good idea to include
    bgworker_internals.h from reloptions.c:
    
    @@ -28,6 +28,7 @@
     #include "commands/defrem.h"
     #include "commands/tablespace.h"
     #include "nodes/makefuncs.h"
    +#include "postmaster/bgworker_internals.h"
     #include "storage/lock.h"
     #include "utils/array.h"
     #include "utils/attoptcache.h"
    @@ -236,6 +237,15 @@ static relopt_int intRelOpts[] =
            },
            SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100
        },
    +   {
    +       {
    +           "autovacuum_parallel_workers",
    +           "Maximum number of parallel autovacuum workers that can be
    used for processing this table.",
    +           RELOPT_KIND_HEAP,
    +           ShareUpdateExclusiveLock
    +       },
    +       -1, -1, MAX_PARALLEL_WORKER_LIMIT
    +   },
    
    I'd leave the maximum value as 1024.
    
    I've attached patch and please check it. I think it's a good shape and
    I'm going to push it next Monday barrying objections.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
  110. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-04T08:37:55Z

    Hi,
    
    On Sat, Apr 4, 2026 at 8:12 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > Regarding the regression tests, ISTM we no longer need
    > 'autovacuum-leader-before-indexes-processing' injection point since it
    > currently tests that parallel workers update their delay parameters
    > during the initialization (i.e., in parallel_vacuum_main()). In order
    > to verify the behavior of workers updating their delay parameters
    > while processing indexes, we would need another injection ponit to
    > stop parallel workers, which seems overkill to me. So I removed it but
    > the test still covers the propagation logic.
    >
    > Regarding the patch, I don't think it's a good idea to include
    > bgworker_internals.h from reloptions.c:
    >
    > I'd leave the maximum value as 1024.
    
    OK, let's leave it.
    
    >
    > I've attached patch and please check it. I think it's a good shape and
    > I'm going to push it next Monday barrying objections.
    >
    
    Thank you for updating the patch!
    All changes look good to me.
    
    BTW, what about the "opt-in vs. opt-out style" issue?
    As I wrote here [1], we can consider a new approach - allow the user to set the
    autovacuum_max_workers reloption even if GUC parameter is zero.
    I think it can satisfy all possible use cases.
    
    [1] https://www.postgresql.org/message-id/CAJDiXggvE%3De%3D0%2BHnZ1XjwUcXYTb0dw77pRUts5gqY997YaxVjQ%40mail.gmail.com
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  111. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-07T07:48:46Z

    On Wed, Apr 1, 2026 at 12:44 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    > >
    > > > He confirmed that as a rule there are *hundreds of thousands* of tables in the
    > > > system, the vast majority of which do not need to be vacuumed in parallel mode.
    > >
    > > I'm still struggling to see the technical justification; why would a
    > > user want to avoid parallel vacuuming on eligible tables if they have
    > > already explicitly allowed the system to use more resources by setting
    > > autovacuum_max_parallel_workers to >0?
    >
    > Here I am talking about "introductory data". I.e. the situation that the user
    > has before considering our parameter usage. Based on this situation, it seems
    > to me that not everyone will want to turn on parallel a/v (because of resource
    > shortage hazard).
    >
    > > If resource contention occurs,
    > > it is typically a sign that the global parameters need re-tuning. As I
    > > mentioned, the same contention can occur even with an opt-in style if
    > > multiple tables are manually configured.
    > >
    >
    > Yep, we already discussed it and I agree with you. I think that in the case of
    > opt-in style the resource contention will be much more controlled. But actually
    > the opt-in style in the form in which I originally proposed it, no longer seems
    > like a good idea to me. Classic opt-in style will deprive us of support for
    > half of the parallel a/v use cases. Anton's proposal seems to me like a good
    > balance between the two styles.
    >
    > > > He also suggested the following : let the reloption overlap the value of the
    > > > GUC parameter. I.e. even if av_max_parallel_workers parameters is 0 the user
    > > > still can set the av_parallel_workers to 10 for some table, and autovacuum
    > > > will process this table in parallel.
    > > >
    > > > I remember that you want to use the GUC parameter as a global switch, and this
    > > > approach will break this logic. But according to Anton's words, it is okay if
    > > > the GUC parameter cannot disable parallel a/v for all tables instantly. It will
    > > > become an administrator's responsibility to manually turn off parallel a/v for
    > > > several tables (again, it is completely OK). Thus, this feature can be handy
    > > > for all use cases.
    > >
    > > While some autovacuum parameters do override GUCs, those are typically
    > > local to the process (like cost delay). Parallel workers, however, are
    > > a shared system-wide resource. In a multi-tenant environment, allowing
    > > a single table's reloption to bypass the
    > > autovacuum_max_parallel_workers = 0 limit could lead to unexpected
    > > exhaustion of the worker pool.
    >
    > Will this exhaustion really be unexpected? If we describe such an ability in
    > the documentation, and the user uses it, then everything is fair. Even if
    > administrator forgets that he enabled av_parallel_workers reloption somewhere,
    > then he can :
    
    How can DBAs prevent parallel workers from being exhaustly used if
    users set a high value to the reloption?
    
    > 1)
    > Check the logfile (if log level is not too high) searching for logs like
    > "parallel workers: index vacuum: N planned, N launched in total".
    > 2)
    > Run a query that selects all tables which have av_parallel_workers > 0.
    
    Does that mean DBAs would need to run these queries periodically? I
    don't think that in a multi-tenant environment, DBAs can (or should)
    execute ALTER TABLE on user-owned tables just to fix resource issues.
    
    >
    > >I think that this GUC should act as a
    > > reliable global switch for resource management.
    >
    > I agree that the "global switch" is an attractive idea and we should strive
    > for it. But our parameter *can* play the role of the switch if users don't
    > manually touch the av_parallel_workers reloption. But if they do - well, it is
    > their responsibility to turn the reloption off.
    >
    > >
    > > > I hope it doesn't look like as an adapting to the needs of a specific user.
    > > > A lot of super-large productions are migrating to postgres now, and I believe
    > > > that we should ensure their comfort too.
    > >
    > > I'm not prioritizing one specific use case over another. I believe
    > > that there are also users who want to use parallel vacuum on hundreds
    > > of thousands of tables. We should consider a better solution while
    > > checking it from multiple perspectives such as the usability, the
    > > robustness and consistency with the existing features and behaviors
    > > etc.
    >
    > For those users who want to use parallel a/v for hundreds of thousands of
    > tables we have the default value "-1" which allows parallel a/v everywhere via
    > GUC parameter manipulation.
    >
    > For those users who want to parallel a/v on several specific tables we can
    > allow setting reloption that will override the GUC.
    >
    > I guess that the question is : "Is it normal if the GUC parameter will lose
    > ability to turn off parallel a/v everywhere after the user has manually raised
    > the value for the av_parallel_workers reloption on a few tables?". If the
    > answer is "Yes", I don't see any obstacles for us to allow overriding the GUC
    > parameter via reloption.
    
    I think the answer is no, particularly for this parameter. Since it
    controls a system-wide shared resource, it should be capped by a GUC
    to ensure centralized management, consistent with other
    parallel-query-related GUCs and reloptions.
    
    Regards,
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  112. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-07T07:49:57Z

    On Sat, Apr 4, 2026 at 1:38 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Sat, Apr 4, 2026 at 8:12 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > Regarding the regression tests, ISTM we no longer need
    > > 'autovacuum-leader-before-indexes-processing' injection point since it
    > > currently tests that parallel workers update their delay parameters
    > > during the initialization (i.e., in parallel_vacuum_main()). In order
    > > to verify the behavior of workers updating their delay parameters
    > > while processing indexes, we would need another injection ponit to
    > > stop parallel workers, which seems overkill to me. So I removed it but
    > > the test still covers the propagation logic.
    > >
    > > Regarding the patch, I don't think it's a good idea to include
    > > bgworker_internals.h from reloptions.c:
    > >
    > > I'd leave the maximum value as 1024.
    >
    > OK, let's leave it.
    >
    > >
    > > I've attached patch and please check it. I think it's a good shape and
    > > I'm going to push it next Monday barrying objections.
    > >
    >
    > Thank you for updating the patch!
    > All changes look good to me.
    
    Thank you! Pushed.
    
    > BTW, what about the "opt-in vs. opt-out style" issue?
    > As I wrote here [1], we can consider a new approach - allow the user to set the
    > autovacuum_max_workers reloption even if GUC parameter is zero.
    > I think it can satisfy all possible use cases.
    
    I've just replied to the email. Please check it[1].
    
    Regards,
    
    [1] https://www.postgresql.org/message-id/CAD21AoDEfe5-tYSqa%3DMGLP5TX5QH2irVZVyULCeTQj0J92Hp1A%40mail.gmail.com
    
    -- 
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com
    
    
    
    
  113. Re: POC: Parallel processing of indexes in autovacuum

    Daniil Davydov <3danissimo@gmail.com> — 2026-04-07T13:32:32Z

    Hi,
    
    On Tue, Apr 7, 2026 at 10:49 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    >
    > > >
    > > > While some autovacuum parameters do override GUCs, those are typically
    > > > local to the process (like cost delay). Parallel workers, however, are
    > > > a shared system-wide resource. In a multi-tenant environment, allowing
    > > > a single table's reloption to bypass the
    > > > autovacuum_max_parallel_workers = 0 limit could lead to unexpected
    > > > exhaustion of the worker pool.
    > >
    > > Will this exhaustion really be unexpected? If we describe such an ability in
    > > the documentation, and the user uses it, then everything is fair. Even if
    > > administrator forgets that he enabled av_parallel_workers reloption somewhere,
    > > then he can :
    >
    > How can DBAs prevent parallel workers from being exhaustly used if
    > users set a high value to the reloption?
    >
    
    Only manual control. Since DBA increased reloption manually, it is OK to ask
    him to manually decrease it.
    
    > > 1)
    > > Check the logfile (if log level is not too high) searching for logs like
    > > "parallel workers: index vacuum: N planned, N launched in total".
    > > 2)
    > > Run a query that selects all tables which have av_parallel_workers > 0.
    >
    > Does that mean DBAs would need to run these queries periodically?
    
    Not really. I say that even if DBA has lost control on the parallel a/v
    workers, it has an ability to find these bottlenecks.
    
    > I don't think that in a multi-tenant environment, DBAs can (or should)
    > execute ALTER TABLE on user-owned tables just to fix resource issues.
    >
    
    Well, the people I talked to had a different opinion which is based on clients
    feedback : what is acceptable and what is not. I don't think that we can
    convince each other, so let it be as it is :)
    
    But if you don't mind continuing to discuss this topic (perhaps with the
    involvement of other people), I would love to create a new thread for it.
    
    > > I guess that the question is : "Is it normal if the GUC parameter will lose
    > > ability to turn off parallel a/v everywhere after the user has manually raised
    > > the value for the av_parallel_workers reloption on a few tables?". If the
    > > answer is "Yes", I don't see any obstacles for us to allow overriding the GUC
    > > parameter via reloption.
    >
    > I think the answer is no, particularly for this parameter. Since it
    > controls a system-wide shared resource, it should be capped by a GUC
    > to ensure centralized management, consistent with other
    > parallel-query-related GUCs and reloptions.
    
    OK. I believe that "global switch" will also be pretty handy for many use cases.
    
    > Thank you! Pushed.
    
    Great news! Thank you very much for your help, Masahiko-san!
    
    --
    Best regards,
    Daniil Davydov
    
    
    
    
  114. Re: POC: Parallel processing of indexes in autovacuum

    Masahiko Sawada <sawada.mshk@gmail.com> — 2026-04-09T18:37:04Z

    On Tue, Apr 7, 2026 at 6:32 AM Daniil Davydov <3danissimo@gmail.com> wrote:
    >
    > Hi,
    >
    > On Tue, Apr 7, 2026 at 10:49 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
    > >
    > > > >
    > > > > While some autovacuum parameters do override GUCs, those are typically
    > > > > local to the process (like cost delay). Parallel workers, however, are
    > > > > a shared system-wide resource. In a multi-tenant environment, allowing
    > > > > a single table's reloption to bypass the
    > > > > autovacuum_max_parallel_workers = 0 limit could lead to unexpected
    > > > > exhaustion of the worker pool.
    > > >
    > > > Will this exhaustion really be unexpected? If we describe such an ability in
    > > > the documentation, and the user uses it, then everything is fair. Even if
    > > > administrator forgets that he enabled av_parallel_workers reloption somewhere,
    > > > then he can :
    > >
    > > How can DBAs prevent parallel workers from being exhaustly used if
    > > users set a high value to the reloption?
    > >
    >
    > Only manual control. Since DBA increased reloption manually, it is OK to ask
    > him to manually decrease it.
    
    In multi-tenant environments, the roles of table owners and DBAs are
    often separated. Tenants can freely set reloptions via ALTER TABLE,
    but a DBA cannot easily revert those settings on user-owned tables.
    Even if a DBA tries to use ALTER TABLE to fix a misconfigured
    reloption, it would cancel any currently running autovacuum on that
    table. Furthermore, if the table is undergoing an anti-wraparound
    vacuum, the ALTER TABLE command itself will be blocked, making it
    impossible to resolve a resource crisis quickly. If a single tenant
    could exhaust the entire parallel worker pool by setting a high
    reloption value, the DBA would have no effective way to prevent or
    mitigate it under an override model.
    
    While I understand the use case for enabling parallel vacuum only on
    specific tables, this is already achievable under the cap model (by
    setting a global GUC > 0 and using the reloption to disable it on
    others), even if the initial configuration is more tedious.
    
    Also, I'm concerned that the override behavior would be inconsistent
    with other parallel-query-related features. While some autovacuum
    reloptions (like autovacuum_vacuum_scale_factor) do override GUCs,
    those parameters only affect the local behavior of that specific
    process and do not impact shared system-wide resources. In contrast,
    autovacuum_max_parallel_workers takes workers from the
    max_parallel_workers pool. Allowing a single table to monopolize this
    shared pool by bypassing the GUC cap creates a significant risk that
    cannot be easily managed.
    
    While this isn't exclusively about multi-tenancy, I think that we
    cannot simply introduce a behavior that creates such a high risk for
    system-wide resource exhaustion.
    
    >
    > > > 1)
    > > > Check the logfile (if log level is not too high) searching for logs like
    > > > "parallel workers: index vacuum: N planned, N launched in total".
    > > > 2)
    > > > Run a query that selects all tables which have av_parallel_workers > 0.
    > >
    > > Does that mean DBAs would need to run these queries periodically?
    >
    > Not really. I say that even if DBA has lost control on the parallel a/v
    > workers, it has an ability to find these bottlenecks.
    >
    > > I don't think that in a multi-tenant environment, DBAs can (or should)
    > > execute ALTER TABLE on user-owned tables just to fix resource issues.
    > >
    >
    > Well, the people I talked to had a different opinion which is based on clients
    > feedback : what is acceptable and what is not. I don't think that we can
    > convince each other, so let it be as it is :)
    >
    > But if you don't mind continuing to discuss this topic (perhaps with the
    > involvement of other people), I would love to create a new thread for it.
    
    Okay.
    
    Regards,
    
    --
    Masahiko Sawada
    Amazon Web Services: https://aws.amazon.com