Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ubDKS-00F7q0-GC for pgsql-hackers@arkaria.postgresql.org; Mon, 14 Jul 2025 07:10:24 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ubDKQ-004KBt-CU for pgsql-hackers@arkaria.postgresql.org; Mon, 14 Jul 2025 07:10:23 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ubDKP-004KBk-Qd for pgsql-hackers@lists.postgresql.org; Mon, 14 Jul 2025 07:10:22 +0000 Received: from mail-lj1-x22b.google.com ([2a00:1450:4864:20::22b]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1ubDKN-007C4H-35 for pgsql-hackers@lists.postgresql.org; Mon, 14 Jul 2025 07:10:21 +0000 Received: by mail-lj1-x22b.google.com with SMTP id 38308e7fff4ca-32b435ef653so31193941fa.2 for ; Mon, 14 Jul 2025 00:10:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1752477017; x=1753081817; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=H+L3eyN8bkDdgblszfYHV54Ts+DoEXhKSSqqyXc3hr8=; b=dH/wJdRTa1hJHwe8nTuSUNTitzOriZrKdyCmq5QY74TxV5mdgd1Un3+GgNcHidlflj GNZXGUySDw/WuvB84YwzfN4tp5zPfzKMeIWor1iCPXHxF18IJv2AT5fmyyu6O+QmU3rX gqiMCd8p60ZWDvztizARfWU/jdX7sBiDLZcxnRWs7t/ClnJoAz+GcKeFmWf0kXG70oaG Vpert3qyFRo6LNROc1HVV0jG+niRLBJxWGISYpHOXwmMWMnkCUdoNCfSFqqrMz1CwM67 VzZGdUTnWU/9+bg2S2D2ywLSIMaOI5TY0p1TBMTzup3hyeC4CMoPfBtJshrxySX8AHve 1WQg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1752477017; x=1753081817; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=H+L3eyN8bkDdgblszfYHV54Ts+DoEXhKSSqqyXc3hr8=; b=CZW0iqkYlxrzqvnoegVJSrUV4FtF/2qEuKkCzJ7PMSLilmXJOF4pCbu6sJXSZzCUWh UFhfcbLMRbbX8dVn3/zjWr/1coULX3XiMT68t8BJRfgqBp0o8hm4YmLepR0vHYDjK2ur JGqieukh0t7Fs/UPKitQD1+YtFDov6VpuE7Ho4VhAz5FsW/gXLZufD1ILJNDl9VzH/Nd aFyOylnsylX/2AaQJ0VuDul2VzfhScGgaYjcihFl6fiM2+J6XDHkVAuBPnircARF01jG p5dasY5uIGc0ON2BjYwrzm4mQtfzJGP78UcrciVYGJ3PBI8/EyhnjkfXMwOMmQ6JPJDL ZBDg== X-Forwarded-Encrypted: i=1; AJvYcCVBlGpZ2GqJeSxOaBAhote5b1W65HoXRpumgOoDwYZCWrsSV0z7Df9kWMUeEOZQd2VEqvEfRABlobm71rTb@lists.postgresql.org X-Gm-Message-State: AOJu0Yyoe741UvVaSzkAkmntyzijF5JeQPmUpY3emAOgK6lImIh4krkw 4Djy7gc+IdI4Olo7nF8qJa3wRtq/cfhqVD2/rS3xp5DVndbhAyuWIw+L1bmwZt/aMWEf6HWZDwj 9YQMDXldu749hMpVubIgTU8tDPUGI5Ho= X-Gm-Gg: ASbGnctvrObD73sxv396oMwcfi5aStQcGWhE7NPBicr4gdo3gm4rFj/N0NBHX5RIlF/ xxvgYfVzNYTngZYnSnjwPeHbh4suftI3Px59ZvyM9MDZz4vanBJjHhlSVh0ojR1A2L3qJHviOCM oNtVIkJasw553hPlL2wSfDb6yguBaFLfyfSBVeORMy/EixS0Q1jBmx7qdqyWE5m+hMw3qDB6Kzs E7d5kn0glGdyhIvc77bdqlsd6AKhhfAX8icWekv X-Google-Smtp-Source: AGHT+IEIq5ip5FUchV//ZsjtQBo5hwNRLIIGIdYWXYOyR+0paVTxiGhelFt7vQLhf4h4eY7H1tzdSOIHXKn/gAfwGtY= X-Received: by 2002:a2e:834a:0:b0:32a:6aa0:217b with SMTP id 38308e7fff4ca-3305346b169mr32560861fa.25.1752477016136; Mon, 14 Jul 2025 00:10:16 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Masahiko Sawada Date: Mon, 14 Jul 2025 16:09:39 +0900 X-Gm-Features: Ac12FXx1t3bgeECTHcrml5YmqH1_29iavR4Rbo52K728KC6cEjIa34vRaLPg4Mw Message-ID: Subject: Re: POC: Parallel processing of indexes in autovacuum To: Daniil Davydov <3danissimo@gmail.com> Cc: Matheus Alcantara , Sami Imseih , Maxim Orlov , Postgres hackers Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Sun, Jul 6, 2025 at 5:00=E2=80=AFPM Daniil Davydov <3danissimo@gmail.com= > wrote: > > Hi, > > On Fri, Jul 4, 2025 at 9:21=E2=80=AFPM Matheus Alcantara > 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 autova= cuum 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 =3D 0 # disabled by default and lim= ited 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? Bu= t > > 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_paral= lel_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 r= unning at this > > + * moment, available workers number will not exeed limi= t 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 relo= ption 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 documentatio= n > > 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 =3D - (nindexes_mwm > 0) ? - maintenance_work_mem / Min(parallel_workers, nindexes_mwm) : - maintenance_work_mem; + + if (AmAutoVacuumWorkerProcess()) + shared->maintenance_work_mem_worker =3D + (nindexes_mwm > 0) ? + autovacuum_work_mem / Min(parallel_workers, nindexes_mwm) : + autovacuum_work_mem; + else + shared->maintenance_work_mem_worker =3D + (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 =3D AmAutoVacuumWorkerProcess() && autovacuum_work_mem !=3D -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 =3D pvs->pcxt->nworkers_launched; /* remember this va= lue */ 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 o= r * when parallelism is disabled. */ - if (!IsUnderPostmaster || max_parallel_maintenance_workers =3D=3D 0) + if (!IsUnderPostmaster || + (autovacuum_max_parallel_workers =3D=3D 0 && AmAutoVacuumWorkerProc= ess()) || + (max_parallel_maintenance_workers =3D=3D 0 && !AmAutoVacuumWorkerPr= ocess())) return 0; /* @@ -597,15 +617,17 @@ parallel_vacuum_compute_workers(Relation *indrels, int nindexes, int nrequested, parallel_workers =3D (nrequested > 0) ? Min(nrequested, nindexes_parallel) : nindexes_parallel; - /* Cap by max_parallel_maintenance_workers */ - parallel_workers =3D Min(parallel_workers, max_parallel_maintenance_wor= kers); + /* Cap by GUC variable */ + parallel_workers =3D 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 =3D ParallelAutoVacuumReserveWorkers(nworkers); + I think it's better to move this code to right after setting "nworkers =3D 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_launch= ed); + } 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 =3D 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 =3D AutoVacuumShmem->av_available_parallel_workers; + AutoVacuumShmem->av_available_parallel_workers =3D 0; + } + else + { + /* OK, we can provide all requested workers. */ + can_launch =3D nworkers; + AutoVacuumShmem->av_available_parallel_workers -=3D nworkers; + } Can we simplify this logic as follows? can_launch =3D Min(AutoVacuumShmem->av_available_parallel_workers, nworker= s); AutoVacuumShmem->av_available_parallel_workers -=3D can_launch; Regards, --=20 Masahiko Sawada Amazon Web Services: https://aws.amazon.com