Thread
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Allow autovacuum to use parallel vacuum workers.
- 1ff3180ca016 19 (unreleased) landed
-
Add parallel vacuum worker usage to VACUUM (VERBOSE) and autovacuum logs.
- adcdbe93860b 19 (unreleased) landed
-
doc: Put new options in consistent order on man pages
- bc35adee8d7a 18.0 cited
-
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.
-
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. > -
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
-
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)
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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 >
-
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
-
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
-
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
-
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)
-
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
-
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 -
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
-
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 -
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 -
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)
-
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 -
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
-
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
-
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 -
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 -
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 -
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 -
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 -
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
-
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 -
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 -
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 -
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 -
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) -
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 -
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 -
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 -
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 -
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
-
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 -
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
-
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 -
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
-
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 -
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) -
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 -
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 -
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 -
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 -
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 -
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
-
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
-
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
-
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 -
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 -
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 -
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 -
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)
-
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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
-
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
-
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
-
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
-
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
-
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 -
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 -
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 -
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 -
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
-
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
-
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
-
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
-
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 -
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 -
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 -
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
-
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
-
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 -
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
-
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 > >
-
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
-
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
-
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
-
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
-
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
-
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 -
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
-
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
-
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
-
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
-
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 -
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
-
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 -
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 -
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
-
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
-
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 -
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
-
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
-
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
-
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
-
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