Option to not use ringbuffer in VACUUM, using it in failsafe mode
Hi,
The use of the ringbuffer in VACUUM often causes very substantial slowdowns.
The primary reason for that is that often most/all the buffers in the
ringbuffer have been dirtied when they were processed, with an associated WAL
record. When we then reuse the buffer via the (quite small) ringbuffer, we
need to flush the WAL before reclaiming the buffer. A synchronous flush of
the WAL every few buffers ends up as a very significant bottleneck, unless you
have local low-latency durable storage (i.e. local storage with some sort of
power protection).
The slowdown caused by the frequent WAL flushes is very significant.
A secondary issue, when we end up doing multiple passes, we'll have to re-read
data into shared_buffers, when we just wrote it out and evicted it.
An example:
On a local SSD with decently fast fdatasync([1]according to pg_test_fsync: fdatasync 769.189 ops/sec 1300 usecs/op). Table size is 3322MB, with ~10%
updated, ~30% deleted tuples, and a single index. m_w_m is large enough to do
this in one pass. I used pg_prewarm() of another relation to ensure the
vacuumed table isn't in s_b (otherwise ringbuffers aren't doing anything).
s_b ringbuffer enabled time wal_syncs wal_sync_time
128MB 1 77797ms
128MB 0 13676ms 241 2538ms
8GB 1 72834ms 23976 51989ms
8GB 0 9544ms 150 1634ms
see [2]For the s_b 128MB case: for logs / stats of the 8GB run. All the data here is in the OS page
cache, so we don't even pay the real-price for reading the data multiple
times.
On cloud hardware with higher fsync latency I've seen > 15x time differences
between using the ringbuffers and avoiding them by using pg_prewarm.
Of course there's a good reason we have the ringbuffer - we don't want
maintenance operations to completely disturb the buffer pool and harm the
production workload. But if, e.g., the database isn't available due to
anti-wraparound measures, there's no other workload to protect, and the
ringbuffer substantially reduces availability. Initial data loads could
similarly benefit.
Therefore I'd like to add an option to the VACUUM command to use to disable
the use of the ringbuffer. Not sure about the name yet.
I think we should auto-enable that mode once we're using the failsafe mode,
similar to [auto]vacuum cost delays getting disabled
(c.f. lazy_check_wraparound_failsafe()). If things are bad enough that we're
soon going to shut down, we want to be aggressive.
Greetings,
Andres Freund
[1]: according to pg_test_fsync: fdatasync 769.189 ops/sec 1300 usecs/op
fdatasync 769.189 ops/sec 1300 usecs/op
[2]: For the s_b 128MB case:
For the s_b 128MB case:
ringbuffers enabled:
2023-01-11 10:24:58.726 PST [355353][client backend][2/19:0][psql] INFO: aggressively vacuuming "postgres.public.copytest_0"
2023-01-11 10:26:19.488 PST [355353][client backend][2/19:0][psql] INFO: finished vacuuming "postgres.public.copytest_0": index scans: 1
pages: 0 removed, 424975 remain, 424975 scanned (100.00% of total)
tuples: 4333300 removed, 6666700 remain, 0 are dead but not yet removable
removable cutoff: 2981, which was 0 XIDs old when operation ended
new relfrozenxid: 2981, which is 102 XIDs ahead of previous value
frozen: 424975 pages from table (100.00% of total) had 6666700 tuples frozen
index scan needed: 424975 pages from table (100.00% of total) had 4325101 dead item identifiers removed
index "copytest_0_id_idx": pages: 10032 in total, 0 newly deleted, 0 currently deleted, 0 reusable
I/O timings: read: 2284.292 ms, write: 4325.009 ms
avg read rate: 83.203 MB/s, avg write rate: 83.199 MB/s
buffer usage: 425044 hits, 860113 misses, 860074 dirtied
WAL usage: 1709902 records, 434990 full page images, 2273501683 bytes
system usage: CPU: user: 11.62 s, system: 11.86 s, elapsed: 80.76 s
┌─────────────┬─────────┬────────────┬──────────────────┬───────────┬──────────┬────────────────┬───────────────┬───────────────────────────────┐
│ wal_records │ wal_fpi │ wal_bytes │ wal_buffers_full │ wal_write │ wal_sync │ wal_write_time │ wal_sync_time │ stats_reset │
├─────────────┼─────────┼────────────┼──────────────────┼───────────┼──────────┼────────────────┼───────────────┼───────────────────────────────┤
│ 8092795 │ 443356 │ 2999358740 │ 1569 │ 28651 │ 27081 │ 1874.391 │ 59895.674 │ 2023-01-11 10:24:58.664859-08 │
└─────────────┴─────────┴────────────┴──────────────────┴───────────┴──────────┴────────────────┴───────────────┴───────────────────────────────┘
ringbuffers disabled:
2023-01-11 10:23:05.081 PST [355054][client backend][2/19:0][psql] INFO: aggressively vacuuming "postgres.public.copytest_0"
2023-01-11 10:23:18.755 PST [355054][client backend][2/19:0][psql] INFO: finished vacuuming "postgres.public.copytest_0": index scans: 1
pages: 0 removed, 424979 remain, 424979 scanned (100.00% of total)
tuples: 4333300 removed, 6666700 remain, 0 are dead but not yet removable
removable cutoff: 2879, which was 0 XIDs old when operation ended
new relfrozenxid: 2879, which is 102 XIDs ahead of previous value
frozen: 424979 pages from table (100.00% of total) had 6666700 tuples frozen
index scan needed: 424979 pages from table (100.00% of total) had 4325176 dead item identifiers removed
index "copytest_0_id_idx": pages: 10032 in total, 0 newly deleted, 0 currently deleted, 0 reusable
I/O timings: read: 1247.366 ms, write: 2888.756 ms
avg read rate: 491.485 MB/s, avg write rate: 491.395 MB/s
buffer usage: 424927 hits, 860242 misses, 860083 dirtied
WAL usage: 1709918 records, 434994 full page images, 2273503049 bytes
system usage: CPU: user: 5.42 s, system: 6.26 s, elapsed: 13.67 s
┌─────────────┬─────────┬────────────┬──────────────────┬───────────┬──────────┬────────────────┬───────────────┬───────────────────────────────┐
│ wal_records │ wal_fpi │ wal_bytes │ wal_buffers_full │ wal_write │ wal_sync │ wal_write_time │ wal_sync_time │ stats_reset │
├─────────────┼─────────┼────────────┼──────────────────┼───────────┼──────────┼────────────────┼───────────────┼───────────────────────────────┤
│ 8092963 │ 443362 │ 2999373996 │ 212190 │ 212333 │ 241 │ 1209.516 │ 2538.706 │ 2023-01-11 10:23:05.004783-08 │
└─────────────┴─────────┴────────────┴──────────────────┴───────────┴──────────┴────────────────┴───────────────┴───────────────────────────────┘
For the s_b 8GB case:
ringbuffers enabled:
2023-01-11 10:04:12.479 PST [352665][client backend][2/19:0][psql] INFO: aggressively vacuuming "postgres.public.copytest_0"
2023-01-11 10:05:25.312 PST [352665][client backend][2/19:0][psql] INFO: finished vacuuming "postgres.public.copytest_0": index scans: 1
pages: 0 removed, 424977 remain, 424977 scanned (100.00% of total)
tuples: 4333300 removed, 6666700 remain, 0 are dead but not yet removable
removable cutoff: 2675, which was 0 XIDs old when operation ended
new relfrozenxid: 2675, which is 102 XIDs ahead of previous value
frozen: 424977 pages from table (100.00% of total) had 6666700 tuples frozen
index scan needed: 424977 pages from table (100.00% of total) had 4325066 dead item identifiers removed
index "copytest_0_id_idx": pages: 10032 in total, 0 newly deleted, 0 currently deleted, 0 reusable
I/O timings: read: 2610.875 ms, write: 4177.842 ms
avg read rate: 81.688 MB/s, avg write rate: 87.515 MB/s
buffer usage: 523611 hits, 761552 misses, 815868 dirtied
WAL usage: 1709910 records, 434992 full page images, 2273502729 bytes
system usage: CPU: user: 11.00 s, system: 11.86 s, elapsed: 72.83 s
┌─────────────┬─────────┬────────────┬──────────────────┬───────────┬──────────┬────────────────┬───────────────┬───────────────────────────────┐
│ wal_records │ wal_fpi │ wal_bytes │ wal_buffers_full │ wal_write │ wal_sync │ wal_write_time │ wal_sync_time │ stats_reset │
├─────────────┼─────────┼────────────┼──────────────────┼───────────┼──────────┼────────────────┼───────────────┼───────────────────────────────┤
│ 8092707 │ 443358 │ 2999354090 │ 42259 │ 66227 │ 23976 │ 2050.963 │ 51989.099 │ 2023-01-11 10:04:12.404054-08 │
└─────────────┴─────────┴────────────┴──────────────────┴───────────┴──────────┴────────────────┴───────────────┴───────────────────────────────┘
ringbuffers disabled:
2023-01-11 10:08:48.414 PST [353287][client backend][3/19:0][psql] INFO: aggressively vacuuming "postgres.public.copytest_0"
2023-01-11 10:08:57.956 PST [353287][client backend][3/19:0][psql] INFO: finished vacuuming "postgres.public.copytest_0": index scans: 1
pages: 0 removed, 424977 remain, 424977 scanned (100.00% of total)
tuples: 4333300 removed, 6666700 remain, 0 are dead but not yet removable
removable cutoff: 2777, which was 0 XIDs old when operation ended
new relfrozenxid: 2777, which is 102 XIDs ahead of previous value
frozen: 424977 pages from table (100.00% of total) had 6666700 tuples frozen
index scan needed: 424976 pages from table (100.00% of total) had 4325153 dead item identifiers removed
index "copytest_0_id_idx": pages: 10032 in total, 0 newly deleted, 0 currently deleted, 0 reusable
I/O timings: read: 1040.230 ms, write: 0.000 ms
avg read rate: 312.016 MB/s, avg write rate: 356.242 MB/s
buffer usage: 904078 hits, 381084 misses, 435101 dirtied
WAL usage: 1709908 records, 434992 full page images, 2273499663 bytes
system usage: CPU: user: 5.57 s, system: 2.26 s, elapsed: 9.54 s
┌─────────────┬─────────┬────────────┬──────────────────┬───────────┬──────────┬────────────────┬───────────────┬───────────────────────────────┐
│ wal_records │ wal_fpi │ wal_bytes │ wal_buffers_full │ wal_write │ wal_sync │ wal_write_time │ wal_sync_time │ stats_reset │
├─────────────┼─────────┼────────────┼──────────────────┼───────────┼──────────┼────────────────┼───────────────┼───────────────────────────────┤
│ 8092933 │ 443358 │ 2999364596 │ 236354 │ 236398 │ 150 │ 1166.314 │ 1634.408 │ 2023-01-11 10:08:48.350328-08 │
└─────────────┴─────────┴────────────┴──────────────────┴───────────┴──────────┴────────────────┴───────────────┴───────────────────────────────┘
On Wed, Jan 11, 2023 at 10:27 AM Andres Freund <andres@anarazel.de> wrote:
Therefore I'd like to add an option to the VACUUM command to use to disable
the use of the ringbuffer. Not sure about the name yet.
Sounds like a good idea.
I think we should auto-enable that mode once we're using the failsafe mode,
similar to [auto]vacuum cost delays getting disabled
(c.f. lazy_check_wraparound_failsafe()). If things are bad enough that we're
soon going to shut down, we want to be aggressive.
+1
--
Peter Geoghegan
Hi,
On 2023-01-11 10:27:20 -0800, Andres Freund wrote:
On cloud hardware with higher fsync latency I've seen > 15x time differences
between using the ringbuffers and avoiding them by using pg_prewarm.
A slightly edited version of what I've in the past to defeat the ringbuffers
using pg_prewarm, as I think it might be useful for others:
WITH what_rel AS (
SELECT 'copytest_0'::regclass AS vacuum_me
),
what_to_prefetch AS (
SELECT vacuum_me, greatest(heap_blks_total - 1, 0) AS last_block,
CASE WHEN phase = 'scanning heap' THEN heap_blks_scanned ELSE heap_blks_vacuumed END AS current_pos
FROM what_rel, pg_stat_progress_vacuum
WHERE relid = vacuum_me AND phase IN ('scanning heap', 'vacuuming heap')
)
SELECT
vacuum_me, current_pos,
pg_prewarm(vacuum_me, 'buffer', 'main', current_pos, least(current_pos + 10000, last_block))
FROM what_to_prefetch
\watch 0.1
Having this running in the background brings the s_b=128MB, ringbuffer enabled
case down from 77797ms to 14838ms. Close to the version with the ringbuffer
disabled.
Unfortunately, afaik, that trick isn't currently possible for the index vacuum
phase, as we don't yet expose the current scan position. And not every index
might be as readily prefetchable as just prefetching the next 10k blocks from
the current position.
That's not too bad if your indexes are small, but unfortunately that's not
always the case...
Greetings,
Andres Freund
Hi,
On 2023-01-11 10:35:19 -0800, Peter Geoghegan wrote:
On Wed, Jan 11, 2023 at 10:27 AM Andres Freund <andres@anarazel.de> wrote:
Therefore I'd like to add an option to the VACUUM command to use to disable
the use of the ringbuffer. Not sure about the name yet.Sounds like a good idea.
Any idea about the name? The obvious thing is to reference ring buffers in the
option name, but that's more of an implementation detail...
Some ideas:
USE_RING_BUFFERS on|off
SCAN_PROTECTION on|off
REUSE_BUFFERS on|off
LIMIT_BUFFER_USAGE on|off
Regards,
Andres
On Wed, Jan 11, 2023 at 10:58 AM Andres Freund <andres@anarazel.de> wrote:
Any idea about the name? The obvious thing is to reference ring buffers in the
option name, but that's more of an implementation detail...
What are the chances that anybody using this feature via a manual
VACUUM command will also use INDEX_CLEANUP off? It's not really
supposed to be used routinely, at all. Right? It's just for
emergencies.
Perhaps it can be tied to INDEX_CLEANUP=off? That makes it hard to get
just the behavior you want when testing VACUUM, but maybe that doesn't
matter.
Realistically, most of the value here comes from changing the failsafe
behavior, which doesn't require the user to know anything about
VACUUM. I know that AWS has reduced the vacuum_failsafe_age default on
RDS to 1.2 billion (a decision made before I joined Amazon), so it is
already something AWS lean on quite a bit where available.
--
Peter Geoghegan
Hi,
On 2023-01-11 11:06:26 -0800, Peter Geoghegan wrote:
On Wed, Jan 11, 2023 at 10:58 AM Andres Freund <andres@anarazel.de> wrote:
Any idea about the name? The obvious thing is to reference ring buffers in the
option name, but that's more of an implementation detail...What are the chances that anybody using this feature via a manual
VACUUM command will also use INDEX_CLEANUP off? It's not really
supposed to be used routinely, at all. Right? It's just for
emergencies.
I think it's also quite useful for e.g. vacuuming after initial data loads or
if you need to do a first vacuum after a lot of bloat accumulated due to a
stuck transaction.
Perhaps it can be tied to INDEX_CLEANUP=off? That makes it hard to get
just the behavior you want when testing VACUUM, but maybe that doesn't
matter.
I don't like that - it's also quite useful to disable use of ringbuffers when
you actually need to clean up indexes. Especially when we have a lot of dead
tuples we'll rescan indexes over and over...
Greetings,
Andres Freund
On Wed, Jan 11, 2023 at 11:18 AM Andres Freund <andres@anarazel.de> wrote:
I don't like that - it's also quite useful to disable use of ringbuffers when
you actually need to clean up indexes. Especially when we have a lot of dead
tuples we'll rescan indexes over and over...
That's a fair point.
My vote goes to "REUSE_BUFFERS", then.
--
Peter Geoghegan
On Wed, Jan 11, 2023 at 10:58:54AM -0800, Andres Freund wrote:
Hi,
On 2023-01-11 10:35:19 -0800, Peter Geoghegan wrote:
On Wed, Jan 11, 2023 at 10:27 AM Andres Freund <andres@anarazel.de> wrote:
Therefore I'd like to add an option to the VACUUM command to use to disable
the use of the ringbuffer. Not sure about the name yet.Sounds like a good idea.
Any idea about the name? The obvious thing is to reference ring buffers in the
option name, but that's more of an implementation detail...Some ideas:
USE_RING_BUFFERS on|off
REUSE_BUFFERS on|off
+1 for either of these.
I don't think it's an issue to expose implementation details here.
Anyone who wants to change this will know about the implementation
details that they're changing, and it's better to refer to it by the
same/similar name and not by some other name that's hard to find.
BTW I can't see that the ring buffer is currently exposed in any
user-facing docs for COPY/ALTER/VACUUM/CREATE ?
--
Justin
Hi,
On 2023-01-11 14:38:34 -0600, Justin Pryzby wrote:
On Wed, Jan 11, 2023 at 10:58:54AM -0800, Andres Freund wrote:
Some ideas:
USE_RING_BUFFERS on|off
REUSE_BUFFERS on|off+1 for either of these.
Then I'd go for REUSE_BUFFERS. What made you prefer it over
LIMIT_BUFFER_USAGE?
USE_BUFFER_ACCESS_STRATEGY would be a name tied to the implementation that's
not awful, I think..
I don't think it's an issue to expose implementation details here.
Anyone who wants to change this will know about the implementation
details that they're changing, and it's better to refer to it by the
same/similar name and not by some other name that's hard to find.
A ringbuffer could refer to a lot of things other than something limiting
buffer usage, that's why I don't like it.
BTW I can't see that the ring buffer is currently exposed in any
user-facing docs for COPY/ALTER/VACUUM/CREATE ?
Yea, there's surprisingly little in the docs about it, given how much it
influences behaviour. It's mentioned in tablesample-method.sgml, but without
explanation - and it's a page documenting C API...
Greetings,
Andres Freund
Peter Geoghegan <pg@bowt.ie> writes:
On Wed, Jan 11, 2023 at 11:18 AM Andres Freund <andres@anarazel.de> wrote:
I don't like that - it's also quite useful to disable use of ringbuffers when
you actually need to clean up indexes. Especially when we have a lot of dead
tuples we'll rescan indexes over and over...
That's a fair point.
My vote goes to "REUSE_BUFFERS", then.
I wonder whether it could make sense to allow a larger ringbuffer size,
rather than just the limit cases of "on" and "off".
regards, tom lane
Hi,
On 2023-01-11 16:18:34 -0500, Tom Lane wrote:
Peter Geoghegan <pg@bowt.ie> writes:
On Wed, Jan 11, 2023 at 11:18 AM Andres Freund <andres@anarazel.de> wrote:
I don't like that - it's also quite useful to disable use of ringbuffers when
you actually need to clean up indexes. Especially when we have a lot of dead
tuples we'll rescan indexes over and over...That's a fair point.
My vote goes to "REUSE_BUFFERS", then.
I wonder whether it could make sense to allow a larger ringbuffer size,
rather than just the limit cases of "on" and "off".
I can see that making sense, particularly if we were to later extend this to
other users of ringbuffers. E.g. COPYs us of the ringbuffer makes loading of
data > 16MB but also << s_b vastly slower, but it can still be very important
to use if there's lots of parallel processes loading data.
Maybe BUFFER_USAGE_LIMIT, with a value from -1 to N, with -1 indicating the
default value, 0 preventing use of a buffer access strategy, and 1..N
indicating the size in blocks?
Would we want to set an upper limit lower than implied by the memory limit for
the BufferAccessStrategy allocation?
Greetings,
Andres Freund
On Wed, Jan 11, 2023 at 2:39 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-01-11 16:18:34 -0500, Tom Lane wrote:
Peter Geoghegan <pg@bowt.ie> writes:
On Wed, Jan 11, 2023 at 11:18 AM Andres Freund <andres@anarazel.de>
wrote:
I don't like that - it's also quite useful to disable use of
ringbuffers when
you actually need to clean up indexes. Especially when we have a lot
of dead
tuples we'll rescan indexes over and over...
That's a fair point.
My vote goes to "REUSE_BUFFERS", then.
I wonder whether it could make sense to allow a larger ringbuffer size,
rather than just the limit cases of "on" and "off".I can see that making sense, particularly if we were to later extend this
to
other users of ringbuffers. E.g. COPYs us of the ringbuffer makes loading
of
data > 16MB but also << s_b vastly slower, but it can still be very
important
to use if there's lots of parallel processes loading data.
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?
Then give VACUUM a (BUFFER_POOL=ring*|shared) option?
I think making DBAs aware of this dynamic and making the ring buffer usage
user-facing is beneficial in its own right (at least, the concept that
changes done by vacuum don't impact shared_buffers, regardless of how that
non-impact manifests). But I don't see much benefit trying to come up with
a different name.
David J.
Hi,
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?
The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.
Then give VACUUM a (BUFFER_POOL=ring*|shared) option?
That seems likely to mislead, because it'd still use shared buffers when the
blocks are already present. The ring buffers aren't a separate buffer pool,
they're a subset of the normal bufferpool. Lookup is done normally, only when
a page isn't found, the search for a victim buffer first tries to use a buffer
from the ring.
I think making DBAs aware of this dynamic and making the ring buffer usage
user-facing is beneficial in its own right (at least, the concept that
changes done by vacuum don't impact shared_buffers, regardless of how that
non-impact manifests).
VACUUM can end up dirtying all of shared buffers, even with the ring buffer in
use...
Greetings,
Andres Freund
Hi,
So, I attached a rough implementation of both the autovacuum failsafe
reverts to shared buffers and the vacuum option (no tests or docs or
anything).
The first three patches in the set are just for enabling use of shared
buffers in failsafe mode for autovacuum. I haven't actually ensured it
works (i.e. triggering failsafe mode and checking the stats for whether
or not shared buffers were used).
I was wondering about the status of the autovacuum wraparound failsafe
test suggested in [1]/messages/by-id/CAB8KJ=j1b3kscX8Cg5G=Q39ZQsv2x4URXsuTueJLz=fcvJ3eoQ@mail.gmail.com. I don't see it registered for the March's
commitfest. I'll probably review it since it will be useful for this
patchset.
The first patch in the set is to free the BufferAccessStrategy object
that is made in do_autovacuum() -- I don't see when the memory context
it is allocated in is destroyed, so it seems like it might be a leak?
The last patch in the set is a trial implementation of the VACUUM option
suggested -- BUFFER_USAGE_LIMIT. More on that below.
On Wed, Jan 11, 2023 at 4:39 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-01-11 16:18:34 -0500, Tom Lane wrote:
Peter Geoghegan <pg@bowt.ie> writes:
On Wed, Jan 11, 2023 at 11:18 AM Andres Freund <andres@anarazel.de>
wrote:
I don't like that - it's also quite useful to disable use of
ringbuffers when
you actually need to clean up indexes. Especially when we have a lot
of dead
tuples we'll rescan indexes over and over...
That's a fair point.
My vote goes to "REUSE_BUFFERS", then.
I wonder whether it could make sense to allow a larger ringbuffer size,
rather than just the limit cases of "on" and "off".I can see that making sense, particularly if we were to later extend this
to
other users of ringbuffers. E.g. COPYs us of the ringbuffer makes loading
of
data > 16MB but also << s_b vastly slower, but it can still be very
important
to use if there's lots of parallel processes loading data.Maybe BUFFER_USAGE_LIMIT, with a value from -1 to N, with -1 indicating the
default value, 0 preventing use of a buffer access strategy, and 1..N
indicating the size in blocks?
I have found the implementation you suggested very hard to use.
The attached fourth patch in the set implements it the way you suggest.
I had to figure out what number to set the BUFER_USAGE_LIMIT to -- and,
since I don't specify shared buffers in units of nbuffer, it's pretty
annoying to have to figure out a valid number.
I think that it would be better to have it be either a percentage of
shared buffers or a size in units of bytes/kb/mb like that of shared
buffers.
Using a fraction or percentage appeals to me because you don't need to
reference your shared buffers setting and calculate what size you want
to set it to. Also, parsing the size in different units sounds like more
work.
Unfortunately, the fraction doesn't really work if we cap the ring size
of a buffer access strategy to NBuffers / 8. Also, there are other
issues like what would 0% and 100% mean.
I have a list of other questions, issues, and TODOs related to the code
I wrote to implement BUFFER_USAGE_LIMIT, but I'm not sure those are
worth discussing until we shape up the interface.
Would we want to set an upper limit lower than implied by the memory limit
for
the BufferAccessStrategy allocation?
So, I was wondering what you thought about NBuffers / 8 (the current
limit). Does it make sense?
If we clamp the user-specified value to this, I think we definitely need
to inform them through some kind of logging or message. I am sure there
are lots of other gucs doing this -- do you know any off the top of your
head?
- Melanie
[1]: /messages/by-id/CAB8KJ=j1b3kscX8Cg5G=Q39ZQsv2x4URXsuTueJLz=fcvJ3eoQ@mail.gmail.com
/messages/by-id/CAB8KJ=j1b3kscX8Cg5G=Q39ZQsv2x4URXsuTueJLz=fcvJ3eoQ@mail.gmail.com
Attachments:
v1-0003-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v1-0003-use-shared-buffers-when-failsafe-active.patchDownload
From 313deb07f729924da650eb01d31cdb722950a5b1 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v1 3/4] use shared buffers when failsafe active
---
src/backend/access/heap/vacuumlazy.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..b319a244d5 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,11 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
v1-0001-dont-leak-strategy-object.patchtext/x-patch; charset=US-ASCII; name=v1-0001-dont-leak-strategy-object.patchDownload
From 49552b63a87365d9ea7c4b9d74f77007e08b54fc Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 11:59:53 -0500
Subject: [PATCH v1 1/4] dont leak strategy object
---
src/backend/postmaster/autovacuum.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index ff6149a179..673ff99767 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2607,6 +2607,8 @@ deleted:
if (did_vacuum || !found_concurrent_worker)
vac_update_datfrozenxid();
+ FreeAccessStrategy(bstrategy);
+
/* Finally close out the last transaction. */
CommitTransactionCommand();
}
--
2.37.2
v1-0002-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v1-0002-remove-global-variable-vac_strategy.patchDownload
From e93496939bbda8fabbd8bac9920d5ec18ff959c4 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v1 2/4] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index aa79d9de4d..78980cf19c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -74,7 +74,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -93,7 +92,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -333,7 +332,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -398,7 +397,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -503,7 +501,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -521,7 +519,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1832,7 +1830,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2068,7 +2066,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
@@ -2094,7 +2092,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
* totally unimportant for toast relations.
*/
if (toast_relid != InvalidOid)
- vacuum_rel(toast_relid, NULL, params, true);
+ vacuum_rel(toast_relid, NULL, params, true, bstrategy);
/*
* Now release the session-level lock on the main table.
--
2.37.2
v1-0004-add-vacuum-option-to-specify-nbuffers.patchtext/x-patch; charset=US-ASCII; name=v1-0004-add-vacuum-option-to-specify-nbuffers.patchDownload
From 61f3e0513efdc26625820a04ec5af185f3049398 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 15:28:34 -0500
Subject: [PATCH v1 4/4] add vacuum option to specify nbuffers
---
contrib/amcheck/verify_heapam.c | 2 +-
contrib/amcheck/verify_nbtree.c | 2 +-
contrib/bloom/blscan.c | 2 +-
contrib/pg_visibility/pg_visibility.c | 4 +-
contrib/pgstattuple/pgstatapprox.c | 2 +-
contrib/pgstattuple/pgstatindex.c | 4 +-
contrib/pgstattuple/pgstattuple.c | 2 +-
src/backend/access/heap/heapam.c | 4 +-
src/backend/commands/dbcommands.c | 2 +-
src/backend/commands/vacuum.c | 50 ++++++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 8 +++-
src/backend/postmaster/autovacuum.c | 5 ++-
src/backend/storage/buffer/bufmgr.c | 4 +-
src/backend/storage/buffer/freelist.c | 58 ++++++++++++++++++++++++---
src/include/commands/vacuum.h | 1 +
src/include/storage/bufmgr.h | 2 +-
16 files changed, 127 insertions(+), 25 deletions(-)
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 4fcfd6df72..e3aa89975d 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -330,7 +330,7 @@ verify_heapam(PG_FUNCTION_ARGS)
PG_RETURN_NULL();
}
- ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ ctx.bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
ctx.buffer = InvalidBuffer;
ctx.page = NULL;
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 257cff671b..b0552a94ef 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -547,7 +547,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace,
state->targetcontext = AllocSetContextCreate(CurrentMemoryContext,
"amcheck context",
ALLOCSET_DEFAULT_SIZES);
- state->checkstrategy = GetAccessStrategy(BAS_BULKREAD);
+ state->checkstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
/* Get true root block from meta-page */
metapage = palloc_btree_page(state, BTREE_METAPAGE);
diff --git a/contrib/bloom/blscan.c b/contrib/bloom/blscan.c
index 6cc7d07164..10e3086e39 100644
--- a/contrib/bloom/blscan.c
+++ b/contrib/bloom/blscan.c
@@ -119,7 +119,7 @@ blgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
* We're going to read the whole index. This is why we use appropriate
* buffer access strategy.
*/
- bas = GetAccessStrategy(BAS_BULKREAD);
+ bas = GetAccessStrategy(BAS_BULKREAD, -1);
npages = RelationGetNumberOfBlocks(scan->indexRelation);
for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index 2a4acfd1ee..47a113db67 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -476,7 +476,7 @@ collect_visibility_data(Oid relid, bool include_pd)
vbits *info;
BlockNumber blkno;
Buffer vmbuffer = InvalidBuffer;
- BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
rel = relation_open(relid, AccessShareLock);
@@ -554,7 +554,7 @@ collect_corrupt_items(Oid relid, bool all_visible, bool all_frozen)
corrupt_items *items;
BlockNumber blkno;
Buffer vmbuffer = InvalidBuffer;
- BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
TransactionId OldestXmin = InvalidTransactionId;
rel = relation_open(relid, AccessShareLock);
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index f601dc6121..e71a468988 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -72,7 +72,7 @@ statapprox_heap(Relation rel, output_type *stat)
TransactionId OldestXmin;
OldestXmin = GetOldestNonRemovableTransactionId(rel);
- bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
nblocks = RelationGetNumberOfBlocks(rel);
scanned = 0;
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index d69ac1c93d..53c6e99bfa 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -219,7 +219,7 @@ pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
BlockNumber nblocks;
BlockNumber blkno;
BTIndexStat indexStat;
- BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
if (!IS_INDEX(rel) || !IS_BTREE(rel))
ereport(ERROR,
@@ -612,7 +612,7 @@ pgstathashindex(PG_FUNCTION_ARGS)
nblocks = RelationGetNumberOfBlocks(rel);
/* prepare access strategy for this index */
- bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
/* Start from blkno 1 as 0th block is metapage */
for (blkno = 1; blkno < nblocks; blkno++)
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 93b7834b77..587d79bb38 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -518,7 +518,7 @@ pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
pgstattuple_type stat = {0};
/* prepare access strategy for this index */
- bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
blkno = start;
for (;;)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 7eb79cee58..6a3efe3d2d 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -280,7 +280,7 @@ initscan(HeapScanDesc scan, ScanKey key, bool keep_startblock)
{
/* During a rescan, keep the previous strategy object. */
if (scan->rs_strategy == NULL)
- scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD);
+ scan->rs_strategy = GetAccessStrategy(BAS_BULKREAD, -1);
}
else
{
@@ -1772,7 +1772,7 @@ GetBulkInsertState(void)
BulkInsertState bistate;
bistate = (BulkInsertState) palloc(sizeof(BulkInsertStateData));
- bistate->strategy = GetAccessStrategy(BAS_BULKWRITE);
+ bistate->strategy = GetAccessStrategy(BAS_BULKWRITE, -1);
bistate->current_buf = InvalidBuffer;
return bistate;
}
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index a0259cc593..51f3d64de7 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -280,7 +280,7 @@ ScanSourceDatabasePgClass(Oid tbid, Oid dbid, char *srcpath)
smgrclose(smgr);
/* Use a buffer access strategy since this is a bulk read operation. */
- bstrategy = GetAccessStrategy(BAS_BULKREAD);
+ bstrategy = GetAccessStrategy(BAS_BULKREAD, -1);
/*
* As explained in the function header comments, we need a snapshot that
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 78980cf19c..b3fded878b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -126,6 +126,10 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* default value of buffers buffer access strategy */
+ params.buffers = -1;
+
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -207,6 +211,45 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ // TODO: default_ring_size calculated here and GetAccessStrategy() == bad
+ int vac_buffers;
+ int default_ring_size = 256 * 1024 / BLCKSZ;
+ int max_ring_size = NBuffers / 8;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires an integer value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffers = defGetInt64(opt);
+
+ /* out-of-bounds cases */
+ if (vac_buffers < -1 || vac_buffers > max_ring_size)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ max_ring_size),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ if (vac_buffers == -1)
+ vac_buffers = default_ring_size;
+ else if (vac_buffers < default_ring_size)
+ {
+ elog(WARNING, "buffer_usage_limit %d is below the minimum buffer_usage_limit of %d. setting it to %d",
+ vac_buffers, default_ring_size, default_ring_size);
+
+ vac_buffers = default_ring_size;
+ }
+
+ params.buffers = vac_buffers;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -393,8 +436,7 @@ vacuum(List *relations, VacuumParams *params,
if (bstrategy == NULL)
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ bstrategy = GetAccessStrategy(BAS_VACUUM, params->buffers);
MemoryContextSwitchTo(old_context);
}
@@ -2066,7 +2108,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs,
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
+ {
+ if (params->buffers == 0)
+ bstrategy = NULL;
table_relation_vacuum(rel, params, bstrategy);
+ }
/* Roll back any GUC changes executed by index functions */
AtEOXact_GUC(false, save_nestlevel);
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..2c8380bd2e 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,8 +1012,12 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should this work differently even though they are only for indexes
+ */
+ pvs.bstrategy = GetAccessStrategy(BAS_VACUUM, -1);
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 673ff99767..3d88d72973 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,11 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ bstrategy = GetAccessStrategy(BAS_VACUUM, -1);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 98904a7c05..62ef088306 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -3818,8 +3818,8 @@ RelationCopyStorageUsingBuffer(RelFileLocator srclocator,
buf.data, true);
/* This is a bulk operation, so use buffer access strategies. */
- bstrategy_src = GetAccessStrategy(BAS_BULKREAD);
- bstrategy_dst = GetAccessStrategy(BAS_BULKWRITE);
+ bstrategy_src = GetAccessStrategy(BAS_BULKREAD, -1);
+ bstrategy_dst = GetAccessStrategy(BAS_BULKWRITE, -1);
/* Iterate over each block of the source relation file. */
for (blkno = 0; blkno < nblocks; blkno++)
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..24d21cc4f1 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -531,23 +531,61 @@ StrategyInitialize(bool init)
* ----------------------------------------------------------------
*/
+static const char *
+btype_get_name(BufferAccessStrategyType btype)
+{
+ switch (btype)
+ {
+ case BAS_NORMAL:
+ return "normal";
+ case BAS_BULKREAD:
+ return "bulkread";
+ case BAS_BULKWRITE:
+ return "bulkwrite";
+ case BAS_VACUUM:
+ return "vacuum";
+ }
+
+ elog(ERROR, "unrecognized BufferAccessStrategy: %d", btype);
+ pg_unreachable();
+}
+
+
/*
* GetAccessStrategy -- create a BufferAccessStrategy object
*
* The object is allocated in the current memory context.
*/
+// TODO: make a macro or something for nbuffers = -1
BufferAccessStrategy
-GetAccessStrategy(BufferAccessStrategyType btype)
+GetAccessStrategy(BufferAccessStrategyType btype, int buffers)
{
BufferAccessStrategy strategy;
int ring_size;
+ const char *strategy_name = btype_get_name(btype);
+
+ if (btype != BAS_VACUUM)
+ {
+ if (buffers == 0)
+ elog(ERROR, "Use of shared buffers unsupported for buffer access strategy: %s. nbuffers must be -1.",
+ strategy_name);
+
+ if (buffers > 0)
+ elog(ERROR, "Specification of ring size in buffers unsupported for buffer access strategy: %s. nbuffers must be -1.",
+ strategy_name);
+ }
+
+ // TODO: DEBUG logging message for dev?
+ if (buffers == 0)
+ btype = BAS_NORMAL;
/*
* Select ring size to use. See buffer/README for rationales.
*
* Note: if you change the ring size for BAS_BULKREAD, see also
* SYNC_SCAN_REPORT_INTERVAL in access/heap/syncscan.c.
+ * TODO: update README
*/
switch (btype)
{
@@ -562,16 +600,26 @@ GetAccessStrategy(BufferAccessStrategyType btype)
ring_size = 16 * 1024 * 1024 / BLCKSZ;
break;
case BAS_VACUUM:
- ring_size = 256 * 1024 / BLCKSZ;
- break;
+ {
+ int default_ring_size = 256 * 1024 / BLCKSZ;
+ if (buffers == -1)
+ ring_size = default_ring_size;
+ else
+ ring_size = Max(buffers, default_ring_size);
+ // TODO: log if we end up setting ring_size bigger than nbuffers because it would have been smaller than the default
+ break;
+ }
+ /* unreachable as long as we get the string representation of the type above */
default:
elog(ERROR, "unrecognized buffer access strategy: %d",
- (int) btype);
- return NULL; /* keep compiler quiet */
+ (int) btype);
+
+ pg_unreachable();
}
/* Make sure ring isn't an undue fraction of shared buffers */
+ // TODO: log message that tells you if your specified number of buffers is clamped
ring_size = Min(NBuffers / 8, ring_size);
/* Allocate the object and initialize all elements to zeroes */
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 689dbb7702..341f1e2525 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -235,6 +235,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int buffers;
} VacuumParams;
/*
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..2157200973 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -195,7 +195,7 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
-extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
Hi,
On 2023-02-22 16:32:53 -0500, Melanie Plageman wrote:
I was wondering about the status of the autovacuum wraparound failsafe
test suggested in [1]. I don't see it registered for the March's
commitfest. I'll probably review it since it will be useful for this
patchset.
It's pretty hard to make it work reliably. I was suggesting somewhere that we
ought to add a EMERGENCY parameter to manual VACUUMs to allow testing that
path a tad more easily.
The first patch in the set is to free the BufferAccessStrategy object
that is made in do_autovacuum() -- I don't see when the memory context
it is allocated in is destroyed, so it seems like it might be a leak?
The backend shuts down just after, so that's not a real issue. Not that it'd
hurt to fix it.
I can see that making sense, particularly if we were to later extend this
to
other users of ringbuffers. E.g. COPYs us of the ringbuffer makes loading
of
data > 16MB but also << s_b vastly slower, but it can still be very
important
to use if there's lots of parallel processes loading data.Maybe BUFFER_USAGE_LIMIT, with a value from -1 to N, with -1 indicating the
default value, 0 preventing use of a buffer access strategy, and 1..N
indicating the size in blocks?
I have found the implementation you suggested very hard to use.
The attached fourth patch in the set implements it the way you suggest.
I had to figure out what number to set the BUFER_USAGE_LIMIT to -- and,
since I don't specify shared buffers in units of nbuffer, it's pretty
annoying to have to figure out a valid number.
I think we should be able to parse it in a similar way to how we parse
shared_buffers. You could even implement this as a GUC that is then set by
VACUUM (similar to how VACUUM FREEZE is implemented).
I think that it would be better to have it be either a percentage of
shared buffers or a size in units of bytes/kb/mb like that of shared
buffers.
I don't think a percentage of shared buffers works particularly well - you
very quickly run into the ringbuffer becoming impractically big.
Would we want to set an upper limit lower than implied by the memory limit
for
the BufferAccessStrategy allocation?So, I was wondering what you thought about NBuffers / 8 (the current
limit). Does it make sense?
That seems *way* too big. Imagine how large allocations we'd end up with a
shared_buffers size of a few TB.
I'd probably make it a hard error at 1GB and a silent cap at NBuffers / 2 or
such.
@@ -547,7 +547,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace, state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ALLOCSET_DEFAULT_SIZES); - state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); + state->checkstrategy = GetAccessStrategy(BAS_BULKREAD, -1);/* Get true root block from meta-page */
metapage = palloc_btree_page(state, BTREE_METAPAGE);
Changing this everywhere seems pretty annoying, particularly because I suspect
a bunch of extensions also use GetAccessStrategy(). How about a
GetAccessStrategyExt(), GetAccessStrategyCustomSize() or such?
BufferAccessStrategy -GetAccessStrategy(BufferAccessStrategyType btype) +GetAccessStrategy(BufferAccessStrategyType btype, int buffers) { BufferAccessStrategy strategy; int ring_size; + const char *strategy_name = btype_get_name(btype);
Shouldn't be executed when we don't need it.
+ if (btype != BAS_VACUUM) + { + if (buffers == 0) + elog(ERROR, "Use of shared buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name); + + if (buffers > 0) + elog(ERROR, "Specification of ring size in buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name); + } + + // TODO: DEBUG logging message for dev? + if (buffers == 0) + btype = BAS_NORMAL;
GetAccessStrategy() often can be executed hundreds of thousands of times a
second, so I'm very sceptical that adding log messages to it useful.
Greetings,
Andres Freund
On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.
How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.
Although I'm not quite opposing the idea of statement level controls
(like the VACUUM one proposed here), it is better to make these ring
buffer sizes configurable across the system to help with the other
similar cases e.g., a CTAS or RMV can help subsequent reads from
shared buffers if ring buffer is skipped.
Thoughts?
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
On Thu, Feb 23, 2023 at 3:03 AM Melanie Plageman
<melanieplageman@gmail.com> wrote:
Hi,
So, I attached a rough implementation of both the autovacuum failsafe
reverts to shared buffers and the vacuum option (no tests or docs or
anything).
Thanks for the patches. I have some comments.
0001:
1. I don't quite understand the need for this 0001 patch. Firstly,
bstrategy allocated once per autovacuum worker in AutovacMemCxt which
goes away with the process. Secondly, the worker exits after
do_autovacuum() with which memory context is gone. I think this patch
is unnecessary unless I'm missing something.
0002:
1. Don't we need to remove vac_strategy for analyze.c as well? It's
pretty-meaningless there than vacuum.c as we're passing bstrategy to
all required functions.
0004:
1. I think no multiple sentences in a single error message. How about
"of %d, changing it to %d"?
+ elog(WARNING, "buffer_usage_limit %d is below the
minimum buffer_usage_limit of %d. setting it to %d",
2. Typically, postgres error messages start with lowercase letters,
hints and detail messages start with uppercase letters.
+ if (buffers == 0)
+ elog(ERROR, "Use of shared buffers unsupported for buffer
access strategy: %s. nbuffers must be -1.",
+ strategy_name);
+
+ if (buffers > 0)
+ elog(ERROR, "Specification of ring size in buffers
unsupported for buffer access strategy: %s. nbuffers must be -1.",
+ strategy_name);
3. A function for this seems unnecessary, especially when a static
array would do the needful, something like forkNames[].
+static const char *
+btype_get_name(BufferAccessStrategyType btype)
+{
+ switch (btype)
+ {
4. Why are these assumptions needed? Can't we simplify by doing
validations on the new buffers parameter only when the btype is
BAS_VACUUM?
+ if (buffers == 0)
+ elog(ERROR, "Use of shared buffers unsupported for buffer
access strategy: %s. nbuffers must be -1.",
+ strategy_name);
+ // TODO: DEBUG logging message for dev?
+ if (buffers == 0)
+ btype = BAS_NORMAL;
5. Is this change needed for this patch?
default:
elog(ERROR, "unrecognized buffer access strategy: %d",
- (int) btype);
- return NULL; /* keep compiler quiet */
+ (int) btype);
+
+ pg_unreachable();
--
Bharath Rupireddy
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
Thank you to all reviewers!
This email is in answer to the three reviews
done since my last version. Attached is v2 and inline below is replies
to all the review comments.
The main difference between this version and the previous version is
that I've added a guc, buffer_usage_limit and the VACUUM option
BUFFER_USAGE_LIMIT is now to be specified in size (like kB, MB, etc).
I currently only use the guc value for VACUUM, but it is meant to be
used for all buffer access strategies and is configurable at the session
level.
I would prefer that we had the option of resizing the buffer access
strategy object per table being autovacuumed. Since autovacuum reloads
the config file between tables, this would be quite possible.
I started implementing this, but stopped because the code is not really
in a good state for that.
In fact, I'm not very happy with my implementation at all because I
think given the current structure of vacuum() and vacuum_rel(), it will
potentially make the code more confusing.
I don't like how autovacuum and vacuum use vacuum_rel() and vacuum()
differently (autovacuum always calls vacuum() with a list containing a
single relation). And vacuum() takes buffer access strategy as a
parameter, supposedly so that autovacuum can change the buffer access
strategy object per call, but it doesn't do that. And then vacuum() and
vacuum_rel() go and access VacuumParams at various places with no rhyme
or reason -- seemingly just based on the random availability of other
objects whose state they would like to check on. So, IMO, in adding a
"buffers" parameter to VacuumParams, I am asking for confusion in
autovacuum code with table-level VacuumParams containing an value for
buffers that isn't used.
On Mon, Feb 27, 2023 at 4:21 PM Andres Freund <andres@anarazel.de> wrote:
On 2023-02-22 16:32:53 -0500, Melanie Plageman wrote:
The first patch in the set is to free the BufferAccessStrategy object
that is made in do_autovacuum() -- I don't see when the memory context
it is allocated in is destroyed, so it seems like it might be a leak?The backend shuts down just after, so that's not a real issue. Not that it'd
hurt to fix it.
I've dropped that patch from the set.
I can see that making sense, particularly if we were to later extend this
to
other users of ringbuffers. E.g. COPYs us of the ringbuffer makes loading
of
data > 16MB but also << s_b vastly slower, but it can still be very
important
to use if there's lots of parallel processes loading data.Maybe BUFFER_USAGE_LIMIT, with a value from -1 to N, with -1 indicating the
default value, 0 preventing use of a buffer access strategy, and 1..N
indicating the size in blocks?I have found the implementation you suggested very hard to use.
The attached fourth patch in the set implements it the way you suggest.
I had to figure out what number to set the BUFER_USAGE_LIMIT to -- and,
since I don't specify shared buffers in units of nbuffer, it's pretty
annoying to have to figure out a valid number.I think we should be able to parse it in a similar way to how we parse
shared_buffers. You could even implement this as a GUC that is then set by
VACUUM (similar to how VACUUM FREEZE is implemented).
in the attached v2, I've used parse_int() to do this.
I think that it would be better to have it be either a percentage of
shared buffers or a size in units of bytes/kb/mb like that of shared
buffers.I don't think a percentage of shared buffers works particularly well - you
very quickly run into the ringbuffer becoming impractically big.
It is now a size.
Would we want to set an upper limit lower than implied by the memory limit
for
the BufferAccessStrategy allocation?So, I was wondering what you thought about NBuffers / 8 (the current
limit). Does it make sense?That seems *way* too big. Imagine how large allocations we'd end up with a
shared_buffers size of a few TB.I'd probably make it a hard error at 1GB and a silent cap at NBuffers / 2 or
such.
Well, as I mentioned NBuffers / 8 is the current GetAccessStrategy()
cap.
In the attached patchset, I have introduced a hard cap of 16GB which is
enforced for the VACUUM option and for the buffer_usage_limit guc. I
kept the "silent cap" at NBuffers / 8 but am open to changing it to
NBuffers / 2 if we think it is okay for its silent cap to be different
than GetAccessStrategy()'s cap.
@@ -547,7 +547,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool heapkeyspace, state->targetcontext = AllocSetContextCreate(CurrentMemoryContext, "amcheck context", ALLOCSET_DEFAULT_SIZES); - state->checkstrategy = GetAccessStrategy(BAS_BULKREAD); + state->checkstrategy = GetAccessStrategy(BAS_BULKREAD, -1);/* Get true root block from meta-page */
metapage = palloc_btree_page(state, BTREE_METAPAGE);Changing this everywhere seems pretty annoying, particularly because I suspect
a bunch of extensions also use GetAccessStrategy(). How about a
GetAccessStrategyExt(), GetAccessStrategyCustomSize() or such?
Yes, I don't know what I was thinking. Changed it to
GetAccessStrategyExt() -- though now I am thinking I don't like Ext and
want to change it.
BufferAccessStrategy -GetAccessStrategy(BufferAccessStrategyType btype) +GetAccessStrategy(BufferAccessStrategyType btype, int buffers) { BufferAccessStrategy strategy; int ring_size; + const char *strategy_name = btype_get_name(btype);Shouldn't be executed when we don't need it.
I got rid of it for now.
+ if (btype != BAS_VACUUM) + { + if (buffers == 0) + elog(ERROR, "Use of shared buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name); + + if (buffers > 0) + elog(ERROR, "Specification of ring size in buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name); + } + + // TODO: DEBUG logging message for dev? + if (buffers == 0) + btype = BAS_NORMAL;GetAccessStrategy() often can be executed hundreds of thousands of times a
second, so I'm very sceptical that adding log messages to it useful.
So, in the case of vacuum and autovacuum, I don't see how
GetAccessStrategyExt() could be called hundreds of thousands of times a
second. It is not even called for each table being vacuumed -- it is
only called before vacuuming a list of tables.
On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.
So, I've rebelled a bit and implemented a single guc,
buffer_usage_limit, in the attached patchset. Users can set it at the
session or system level or they can specify BUFFER_USAGE_LIMIT to
vacuum. It is the same size for all operations. By default all of this
would be the same as it is now.
The attached patchset does not use the guc for any operations except
VACUUM, though. I will add on another patch if people still feel
strongly that we cannot have a single guc. If the other operations use
this guc, I think we could get much of the same flexibility as having
multiple gucs by just being able to set it at the session level (or
having command options).
Although I'm not quite opposing the idea of statement level controls
(like the VACUUM one proposed here), it is better to make these ring
buffer sizes configurable across the system to help with the other
similar cases e.g., a CTAS or RMV can help subsequent reads from
shared buffers if ring buffer is skipped.
Yes, I've done both.
On Tue, Feb 28, 2023 at 3:52 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:
On Thu, Feb 23, 2023 at 3:03 AM Melanie Plageman
0001:
1. I don't quite understand the need for this 0001 patch. Firstly,
bstrategy allocated once per autovacuum worker in AutovacMemCxt which
goes away with the process. Secondly, the worker exits after
do_autovacuum() with which memory context is gone. I think this patch
is unnecessary unless I'm missing something.
I've dropped this one.
0002:
1. Don't we need to remove vac_strategy for analyze.c as well? It's
pretty-meaningless there than vacuum.c as we're passing bstrategy to
all required functions.
So, it is a bit harder to remove it from analyze because acquire_func
func doesn't take the buffer access strategy as a parameter and
acquire_sample_rows uses the vac_context global variable to pass to
table_scan_analyze_next_block().
We could change acquire_func, but it looks like FDW uses it, so I'm not
sure. It would be more for consistency than as a performance win, as I
imagine analyze is less of a problem than vacuum (i.e. it is probably
reading fewer blocks and probably not dirtying them [unless it does
on-access pruning?]).
I haven't done this in the attached set.
0004:
1. I think no multiple sentences in a single error message. How about
"of %d, changing it to %d"?
+ elog(WARNING, "buffer_usage_limit %d is below the
minimum buffer_usage_limit of %d. setting it to %d",
I've removed this message, but if I put back a message about clamping, I
will remember this note.
2. Typically, postgres error messages start with lowercase letters, hints and detail messages start with uppercase letters. + if (buffers == 0) + elog(ERROR, "Use of shared buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name); + + if (buffers > 0) + elog(ERROR, "Specification of ring size in buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name);
Thanks! I've removed some of the error messages for now, but, for the
ones I kept, I tthink they are consistent now with this pattern.
3. A function for this seems unnecessary, especially when a static array would do the needful, something like forkNames[]. +static const char * +btype_get_name(BufferAccessStrategyType btype) +{ + switch (btype) + {
I've removed it for now.
4. Why are these assumptions needed? Can't we simplify by doing validations on the new buffers parameter only when the btype is BAS_VACUUM? + if (buffers == 0) + elog(ERROR, "Use of shared buffers unsupported for buffer access strategy: %s. nbuffers must be -1.", + strategy_name);+ // TODO: DEBUG logging message for dev? + if (buffers == 0) + btype = BAS_NORMAL;
So, I've moved validation to the vacuum option parsing for the vacuum
option and am using the guc infrastructure to check min and max for the
guc value.
5. Is this change needed for this patch? default: elog(ERROR, "unrecognized buffer access strategy: %d", - (int) btype); - return NULL; /* keep compiler quiet */ + (int) btype); + + pg_unreachable();
The pg_unreachable() is removed, as I've left GetAccessStrategy()
untouched.
- Melanie
Attachments:
v2-0003-add-vacuum-option-to-specify-nbuffers-and-guc.patchtext/x-patch; charset=US-ASCII; name=v2-0003-add-vacuum-option-to-specify-nbuffers-and-guc.patchDownload
From d9a0183b98601e4299b8b12a4ae06e9aaf5c1bfe Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 15:28:34 -0500
Subject: [PATCH v2 3/3] add vacuum option to specify nbuffers and guc
---
src/backend/commands/vacuum.c | 51 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 6 ++-
src/backend/postmaster/autovacuum.c | 15 +++++-
src/backend/storage/buffer/README | 2 +
src/backend/storage/buffer/freelist.c | 40 +++++++++++++++
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 3 ++
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 5 ++
11 files changed, 134 insertions(+), 3 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c62be52e3b..dc51d69821 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -127,6 +127,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -210,6 +213,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, NULL))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d. %s is invalid.",
+ MAX_BAS_RING_SIZE_KB, vac_buffer_size),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -399,7 +439,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyExt(BAS_VACUUM, buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyExt(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..16742f6612 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,7 +1012,11 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should this work differently even though they are only for indexes
+ */
pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
/* Setup error traceback support for ereport() */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c0e2e00a7e..ffd9308952 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,14 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyExt(BAS_VACUUM, buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2881,6 +2887,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ // TODO: should this be 0 so that we are sure that vacuum() never
+ // allocates a new bstrategy for us, even if we pass in NULL for that
+ // parameter? maybe could change how failsafe NULLs out bstrategy if
+ // so?
+ tab->at_params.ring_size = buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..bf166bbdf1 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -245,6 +245,8 @@ for WAL flushes. While it's okay for a background vacuum to be slowed by
doing its own WAL flushing, we'd prefer that COPY not be subject to that,
so we let it use up a bit more of the buffer arena.
+TODO: update with info about new option to control the size
+
Background Writer's Processing
------------------------------
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..a7e29f85ac 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -22,6 +22,7 @@
#include "storage/proc.h"
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
+#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)
/*
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+
+BufferAccessStrategy
+GetAccessStrategyExt(BufferAccessStrategyType btype, int ring_size)
+{
+ BufferAccessStrategy strategy;
+ int nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ // TODO: this being called ring_size and also nbuffers being called
+ // ring_size in GetAccessStrategy is confusing. Rename the member of
+ // BufferAccessStrategy
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size < MAX_BAS_RING_SIZE_KB);
+
+ // TODO: warn about clamping?
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ // TODO: make this smaller?
+ nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /* Allocate the object and initialize all elements to zeroes */
+ strategy = (BufferAccessStrategy)
+ palloc0(offsetof(BufferAccessStrategyData, buffers) +
+ nbuffers * sizeof(Buffer));
+
+ /* Set field that didn't start out zero */
+ strategy->btype = btype;
+ strategy->ring_size = nbuffers;
+
+ return strategy;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..5167ecddcc 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..55d4965d62 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2206,6 +2206,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for operations employing a buffer access strategy."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..d5a8d24621 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -156,6 +156,9 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#buffer_usage_limit = -1 # size of buffer access strategy ring. -1 to use default,
+ # 0 to disable buffer access strategy and use shared buffers
+ # > 0 to specify size
# - Disk -
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..06bf66f4f4 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..7d7d91b1a4 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..32148ba580 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -196,6 +199,8 @@ extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyExt(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
v2-0002-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v2-0002-use-shared-buffers-when-failsafe-active.patchDownload
From 615b2881dd1fe9c51acbdf1d1eb83f664205bf48 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v2 2/3] use shared buffers when failsafe active
---
src/backend/access/heap/vacuumlazy.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..b319a244d5 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,11 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
v2-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v2-0001-remove-global-variable-vac_strategy.patchDownload
From a4eb134bf003187898bba0fffcaddcb2d964f9af Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v2 1/3] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2e12baf8eb..c62be52e3b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -74,7 +74,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -93,7 +92,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -337,7 +336,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -403,7 +402,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -508,7 +506,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -526,7 +524,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1837,7 +1835,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2083,7 +2081,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2117,7 +2115,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.
In attached v3, I've changed the name of the guc from buffer_usage_limit
to vacuum_buffer_usage_limit, since it is only used for vacuum and
autovacuum.
I haven't added the other suggested strategy gucs, as those could easily
be done in a future patchset.
I've also changed GetAccessStrategyExt() to GetAccessStrategyWithSize()
-- similar to initArrayResultWithSize().
And I've added tab completion for BUFFER_USAGE_LIMIT so that it is
easier to try out my patch.
Most of the TODOs in the code are related to the question of how
autovacuum uses the guc vacuum_buffer_usage_limit. autovacuum creates
the buffer access strategy ring in do_autovacuum() before looping
through and vacuuming tables. It passes this strategy object on to
vacuum(). Since we reuse the same strategy object for all tables in a
given invocation of do_autovacuum(), only failsafe autovacuum would
change buffer access strategies. This is probably okay, but it does mean
that the table-level VacuumParams variable, ring_size, means something
different for autovacuum than vacuum. Autovacuum workers will always
have set it to -1. We won't ever reach code in vacuum() which relies on
VacuumParams->ring_size as long as autovacuum workers pass a non-NULL
BufferAccessStrategy object to vacuum(), though.
- Melanie
Attachments:
v3-0002-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v3-0002-use-shared-buffers-when-failsafe-active.patchDownload
From 6f40d87f4f462d48a67721260be7e30a7438520e Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v3 2/3] use shared buffers when failsafe active
---
src/backend/access/heap/vacuumlazy.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..b319a244d5 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,11 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
v3-0003-add-vacuum-option-to-specify-ring_size-and-guc.patchtext/x-patch; charset=US-ASCII; name=v3-0003-add-vacuum-option-to-specify-ring_size-and-guc.patchDownload
From 139e71241729d7304188dffb603e6f43db0bf67c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 15:28:34 -0500
Subject: [PATCH v3 3/3] add vacuum option to specify ring_size and guc
---
src/backend/commands/vacuum.c | 51 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 6 ++-
src/backend/postmaster/autovacuum.c | 15 +++++-
src/backend/storage/buffer/README | 2 +
src/backend/storage/buffer/freelist.c | 40 +++++++++++++++
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 4 ++
src/bin/psql/tab-complete.c | 2 +-
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 5 ++
12 files changed, 136 insertions(+), 4 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c62be52e3b..22fe42ee2e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -127,6 +127,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -210,6 +213,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, NULL))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d. %s is invalid.",
+ MAX_BAS_RING_SIZE_KB, vac_buffer_size),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -399,7 +439,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..16742f6612 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,7 +1012,11 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should this work differently even though they are only for indexes
+ */
pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
/* Setup error traceback support for ereport() */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c0e2e00a7e..7dbd3b8935 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,14 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2881,6 +2887,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ // TODO: should this be 0 so that we are sure that vacuum() never
+ // allocates a new bstrategy for us, even if we pass in NULL for that
+ // parameter? maybe could change how failsafe NULLs out bstrategy if
+ // so?
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..bf166bbdf1 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -245,6 +245,8 @@ for WAL flushes. While it's okay for a background vacuum to be slowed by
doing its own WAL flushing, we'd prefer that COPY not be subject to that,
so we let it use up a bit more of the buffer arena.
+TODO: update with info about new option to control the size
+
Background Writer's Processing
------------------------------
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..2fc5412e40 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -22,6 +22,7 @@
#include "storage/proc.h"
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
+#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)
/*
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ BufferAccessStrategy strategy;
+ int nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ // TODO: this being called ring_size and also nbuffers being called
+ // ring_size in GetAccessStrategy is confusing. Rename the member of
+ // BufferAccessStrategy
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size < MAX_BAS_RING_SIZE_KB);
+
+ // TODO: warn about clamping?
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ // TODO: make this smaller?
+ nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /* Allocate the object and initialize all elements to zeroes */
+ strategy = (BufferAccessStrategy)
+ palloc0(offsetof(BufferAccessStrategyData, buffers) +
+ nbuffers * sizeof(Buffer));
+
+ /* Set field that didn't start out zero */
+ strategy->btype = btype;
+ strategy->ring_size = nbuffers;
+
+ return strategy;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..44bd07d109 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2206,6 +2206,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for operations employing a buffer access strategy."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..36273aa47d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -156,6 +156,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy and use shared buffers
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8f12af799b..fab5b79c34 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..06bf66f4f4 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..338de38568 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -196,6 +199,8 @@ extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
v3-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v3-0001-remove-global-variable-vac_strategy.patchDownload
From 02e1c5a0d967a6e42138470c05e2071332934165 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v3 1/3] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2e12baf8eb..c62be52e3b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -74,7 +74,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -93,7 +92,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -337,7 +336,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -403,7 +402,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -508,7 +506,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -526,7 +524,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1837,7 +1835,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2083,7 +2081,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2117,7 +2115,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
On Sat, Mar 11, 2023 at 09:55:33AM -0500, Melanie Plageman wrote:
Subject: [PATCH v3 2/3] use shared buffers when failsafe active
+ /* + * Assume the caller who allocated the memory for the + * BufferAccessStrategy object will free it. + */ + vacrel->bstrategy = NULL;
This comment could use elaboration:
** VACUUM normally restricts itself to a small ring buffer; but in
failsafe mode, in order to process tables as quickly as possible, allow
the leaving behind large number of dirty buffers.
Subject: [PATCH v3 3/3] add vacuum option to specify ring_size and guc
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
+#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)
Macros are normally be capitalized
It's a good idea to write "(bufsize)", in case someone passes "a+b".
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype) +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
Maybe it would make sense for GetAccessStrategy() to call
GetAccessStrategyWithSize(). Or maybe not.
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the buffer pool size for operations employing a buffer access strategy."),
The description should mention vacuum, if that's the scope of the GUC's
behavior.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers + # > 0 to specify size
If I'm not wrong, there's still no documentation about "ring buffers" or
postgres' "strategy". Which seems important to do for this patch, along
with other documentation.
This patch should add support in vacuumdb.c. And maybe a comment about
adding support there, since it's annoying when it the release notes one
year say "support VACUUM (FOO)" and then one year later say "support
vacuumdb --foo".
--
Justin
On Sat, 11 Mar 2023 at 16:55, Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.In attached v3, I've changed the name of the guc from buffer_usage_limit
to vacuum_buffer_usage_limit, since it is only used for vacuum and
autovacuum.
Sorry for arriving late to this thread, but what about sizing the ring
dynamically? From what I gather the primary motivation for larger ring
size is avoiding WAL flushes due to dirty buffer writes. We already
catch that event with StrategyRejectBuffer(). So maybe a dynamic
sizing algorithm could be applied to the ringbuffer. Make the buffers
array in strategy capable of holding up to the limit of buffers, but
set ring size conservatively. If we have to flush WAL, double the ring
size (up to the limit). If we loop around the ring without flushing,
decrease the ring size by a small amount to let clock sweep reclaim
them for use by other backends.
--
Ants Aasma
Senior Database Engineer
www.cybertec-postgresql.com
Thanks for your interest in this patch!
On Mon, Mar 13, 2023 at 8:38 AM Ants Aasma <ants@cybertec.at> wrote:
On Sat, 11 Mar 2023 at 16:55, Melanie Plageman
<melanieplageman@gmail.com> wrote:On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.In attached v3, I've changed the name of the guc from buffer_usage_limit
to vacuum_buffer_usage_limit, since it is only used for vacuum and
autovacuum.Sorry for arriving late to this thread, but what about sizing the ring
dynamically? From what I gather the primary motivation for larger ring
size is avoiding WAL flushes due to dirty buffer writes. We already
catch that event with StrategyRejectBuffer(). So maybe a dynamic
sizing algorithm could be applied to the ringbuffer. Make the buffers
array in strategy capable of holding up to the limit of buffers, but
set ring size conservatively. If we have to flush WAL, double the ring
size (up to the limit). If we loop around the ring without flushing,
decrease the ring size by a small amount to let clock sweep reclaim
them for use by other backends.
So, the original motivation of this patch was to allow autovacuum in
failsafe mode to abandon use of a buffer access strategy, since, at that
point, there is no reason to hold back. The idea was expanded to be an
option to explicit vacuum, since users often must initiate an explicit
vacuum after a forced shutdown due to transaction ID wraparound.
As for routine vacuuming and the other buffer access strategies, I think
there is an argument for configurability based on operator knowledge --
perhaps your workload will use the data you are COPYing as soon as the
COPY finishes, so you might as well disable a buffer access strategy or
use a larger fraction of shared buffers. Also, the ring sizes were
selected sixteen years ago and average server memory and data set sizes
have changed.
StrategyRejectBuffer() will allow bulkreads to, as you say, use more
buffers than the original ring size, since it allows them to kick
dirty buffers out of the ring and claim new shared buffers.
Bulkwrites and vacuums, however, will inevitably dirty buffers and
require flushing the buffer (and thus flushing the associated WAL) when
reusing them. Bulkwrites and vacuum do not kick dirtied buffers out of
the ring, since dirtying buffers is their common case. A dynamic
resizing like the one you suggest would likely devolve to vacuum and
bulkwrite strategies always using the max size.
As for decreasing the ring size, buffers are only "added" to the ring
lazily and, technically, as it is now, buffers which have been added
added to the ring can always be reclaimed by the clocksweep (as long as
they are not pinned). The buffer access strategy is more of a
self-imposed restriction than it is a reservation. Since the ring is
small and the buffers are being frequently reused, odds are the usage
count will be 1 and we will be the one who set it to 1, but there is no
guarantee. If, when attempting to reuse the buffer, its usage count is
1 (or it is pinned), we also will kick it out of the ring and go look
for a replacement buffer.
I do think that it is a bit unreasonable to expect users to know how
large they would like to make their buffer access strategy ring. What we
want is some way of balancing different kinds of workloads and
maintenance tasks reasonably. If your database has no activity because
it is the middle of the night or it was shutdown because of transaction
id wraparound, there is no reason why vacuum should limit the number of
buffers it uses. I'm sure there are many other such examples.
- Melanie
Thanks for the review!
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Sat, Mar 11, 2023 at 09:55:33AM -0500, Melanie Plageman wrote:
Subject: [PATCH v3 2/3] use shared buffers when failsafe active
+ /* + * Assume the caller who allocated the memory for the + * BufferAccessStrategy object will free it. + */ + vacrel->bstrategy = NULL;This comment could use elaboration:
** VACUUM normally restricts itself to a small ring buffer; but in
failsafe mode, in order to process tables as quickly as possible, allow
the leaving behind large number of dirty buffers.
Agreed. It definitely needs a comment like this. I will update in the
next version along with addressing your other feedback (after sorting
out some of the other points in this mail on which I still have
questions).
Subject: [PATCH v3 3/3] add vacuum option to specify ring_size and guc
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
+#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)Macros are normally be capitalized
Yes, there doesn't seem to be a great amount of consistency around
this... See pgstat.c read_chunk_s and bufmgr.c BufHdrGetBlock and
friends. Though there are probably more capitalized than not. Since it
does a bit of math and returns a value, I wanted to convey that it was
more like a function. Also, since the name was long, I thought all-caps
would be hard to read. However, if you or others feel strongly, I am
attached neither to the capitalization nor to the name at all (what do
you think of the name?).
It's a good idea to write "(bufsize)", in case someone passes "a+b".
Ah yes, this is a good idea -- I always miss at least one set of
parentheses when writing a macro. In this case, I didn't think of it
since the current caller couldn't pass an expression.
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype) +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)Maybe it would make sense for GetAccessStrategy() to call
GetAccessStrategyWithSize(). Or maybe not.
You mean instead of having anyone call GetAccessStrategyWithSize()?
We would need to change the signature of GetAccessStrategy() then -- and
there are quite a few callers.
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the buffer pool size for operations employing a buffer access strategy."),The description should mention vacuum, if that's the scope of the GUC's
behavior.
Good catch. Will update in next version.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers + # > 0 to specify sizeIf I'm not wrong, there's still no documentation about "ring buffers" or
postgres' "strategy". Which seems important to do for this patch, along
with other documentation.
Yes, it is. I have been thinking about where in the docs to add it (the
docs about buffer access strategies). Any ideas?
This patch should add support in vacuumdb.c.
Oh, I had totally forgotten about vacuumdb.
And maybe a comment about adding support there, since it's annoying
when it the release notes one year say "support VACUUM (FOO)" and then
one year later say "support vacuumdb --foo".
I'm not sure I totally follow. Do you mean to add a comment to
ExecVacuum() saying to add support to vacuumdb when adding a new option
to vacuum?
In the past, did people forget to add support to vacuumdb for new vacuum
options or did they forget to document that they did that or did they
forgot to include that they did that in the release notes?
- Melanie
On Sat, Mar 11, 2023 at 11:55 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.In attached v3, I've changed the name of the guc from buffer_usage_limit
to vacuum_buffer_usage_limit, since it is only used for vacuum and
autovacuum.I haven't added the other suggested strategy gucs, as those could easily
be done in a future patchset.I've also changed GetAccessStrategyExt() to GetAccessStrategyWithSize()
-- similar to initArrayResultWithSize().And I've added tab completion for BUFFER_USAGE_LIMIT so that it is
easier to try out my patch.Most of the TODOs in the code are related to the question of how
autovacuum uses the guc vacuum_buffer_usage_limit. autovacuum creates
the buffer access strategy ring in do_autovacuum() before looping
through and vacuuming tables. It passes this strategy object on to
vacuum(). Since we reuse the same strategy object for all tables in a
given invocation of do_autovacuum(), only failsafe autovacuum would
change buffer access strategies. This is probably okay, but it does mean
that the table-level VacuumParams variable, ring_size, means something
different for autovacuum than vacuum. Autovacuum workers will always
have set it to -1. We won't ever reach code in vacuum() which relies on
VacuumParams->ring_size as long as autovacuum workers pass a non-NULL
BufferAccessStrategy object to vacuum(), though.
I've not reviewed the patchset in depth yet but I got assertion
failure and SEGV when using the buffer_usage_limit parameter.
postgres(1:471180)=# vacuum (buffer_usage_limit 10000000000) ;
2023-03-15 17:10:02.947 JST [471180] ERROR: buffer_usage_limit for a
vacuum must be between -1 and 16777216. 10000000000 is invalid. at
character 9
The message show the max value is 16777216, but when I set it, I got
an assertion failure:
postgres(1:470992)=# vacuum (buffer_usage_limit 16777216) ;
TRAP: failed Assert("ring_size < MAX_BAS_RING_SIZE_KB"), File:
"freelist.c", Line: 606, PID: 470992
Then when I used 1 byte lower value, 16777215, I got a SEGV:
postgres(1:471180)=# vacuum (buffer_usage_limit 16777215) ;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: 2023-03-15
17:10:59.404 JST [471159] LOG: server process (PID 471180) was
terminated by signal 11: Segmentation fault
Finally, when I used a more lower value, 16777100, I got a memory
allocation error:
postgres(1:471361)=# vacuum (buffer_usage_limit 16777100) ;
2023-03-15 17:12:17.853 JST [471361] ERROR: invalid memory alloc
request size 18446744073709551572
Probably vacuum_buffer_usage_limit also has the same issue.
Also, should we support a table option for vacuum_buffer_usage_limit as well?
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
On Wed, 15 Mar 2023 at 02:29, Melanie Plageman
<melanieplageman@gmail.com> wrote:
As for routine vacuuming and the other buffer access strategies, I think
there is an argument for configurability based on operator knowledge --
perhaps your workload will use the data you are COPYing as soon as the
COPY finishes, so you might as well disable a buffer access strategy or
use a larger fraction of shared buffers. Also, the ring sizes were
selected sixteen years ago and average server memory and data set sizes
have changed.
To be clear I'm not at all arguing against configurability. I was
thinking that dynamic use could make the configuration simpler by self
tuning to use no more buffers than is useful.
StrategyRejectBuffer() will allow bulkreads to, as you say, use more
buffers than the original ring size, since it allows them to kick
dirty buffers out of the ring and claim new shared buffers.Bulkwrites and vacuums, however, will inevitably dirty buffers and
require flushing the buffer (and thus flushing the associated WAL) when
reusing them. Bulkwrites and vacuum do not kick dirtied buffers out of
the ring, since dirtying buffers is their common case. A dynamic
resizing like the one you suggest would likely devolve to vacuum and
bulkwrite strategies always using the max size.
I think it should self stabilize around the point where the WAL is
either flushed by other commit activity, WAL writer or WAL buffers
filling up. Writing out their own dirtied buffers will still happen,
just the associated WAL flushes will be in larger chunks and possibly
done by other processes.
As for decreasing the ring size, buffers are only "added" to the ring
lazily and, technically, as it is now, buffers which have been added
added to the ring can always be reclaimed by the clocksweep (as long as
they are not pinned). The buffer access strategy is more of a
self-imposed restriction than it is a reservation. Since the ring is
small and the buffers are being frequently reused, odds are the usage
count will be 1 and we will be the one who set it to 1, but there is no
guarantee. If, when attempting to reuse the buffer, its usage count is1 (or it is pinned), we also will kick it out of the ring and go look
for a replacement buffer.
Right, but while the buffer is actively used by the ring it is
unlikely that clocksweep will find it at usage 0 as the ring buffer
should cycle more often than the clocksweep. Whereas if the ring stops
using a buffer, clocksweep will eventually come and reclaim it. And if
the ring shrinking decision turns out to be wrong before the
clocksweep gets around to reusing it, we can bring the same buffer
back into the ring.
I do think that it is a bit unreasonable to expect users to know how
large they would like to make their buffer access strategy ring. What we
want is some way of balancing different kinds of workloads and
maintenance tasks reasonably. If your database has no activity because
it is the middle of the night or it was shutdown because of transaction
id wraparound, there is no reason why vacuum should limit the number of
buffers it uses. I'm sure there are many other such examples.
Ideally yes, though I am not hopeful of finding a solution that does
this any time soon. Just to take your example, if a nightly
maintenance job wipes out the shared buffer contents slightly
optimizing its non time-critical work and then causes morning user
visible load to have big latency spikes due to cache misses, that's
not a good tradeoff either.
--
Ants Aasma
Senior Database Engineer
www.cybertec-postgresql.com
On Wed, 15 Mar 2023 at 02:57, Melanie Plageman
<melanieplageman@gmail.com> wrote:
Subject: [PATCH v3 3/3] add vacuum option to specify ring_size and guc
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
+#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)Macros are normally be capitalized
Yes, there doesn't seem to be a great amount of consistency around
this... See pgstat.c read_chunk_s and bufmgr.c BufHdrGetBlock and
friends. Though there are probably more capitalized than not. Since it
does a bit of math and returns a value, I wanted to convey that it was
more like a function. Also, since the name was long, I thought all-caps
would be hard to read. However, if you or others feel strongly, I am
attached neither to the capitalization nor to the name at all (what do
you think of the name?).
A static inline function seems like a less surprising and more type
safe solution for this.
--
Ants Aasma
Senior Database Engineer
www.cybertec-postgresql.com
On Tue, Mar 14, 2023 at 08:56:58PM -0400, Melanie Plageman wrote:
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype) +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)Maybe it would make sense for GetAccessStrategy() to call
GetAccessStrategyWithSize(). Or maybe not.You mean instead of having anyone call GetAccessStrategyWithSize()?
We would need to change the signature of GetAccessStrategy() then -- and
there are quite a few callers.
I mean to avoid code duplication, GetAccessStrategy() could "Select ring
size to use" and then call into GetAccessStrategyWithSize(). Maybe it's
counter to your intent or otherwise not worth it to save 8 LOC.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers + # > 0 to specify sizeIf I'm not wrong, there's still no documentation about "ring buffers" or
postgres' "strategy". Which seems important to do for this patch, along
with other documentation.Yes, it is. I have been thinking about where in the docs to add it (the
docs about buffer access strategies). Any ideas?
This patch could add something to the vacuum manpage and to the appendix.
And maybe references from the shared_buffers and pg_buffercache
manpages.
This patch should add support in vacuumdb.c.
Oh, I had totally forgotten about vacuumdb.
:)
And maybe a comment about adding support there, since it's annoying
when it the release notes one year say "support VACUUM (FOO)" and then
one year later say "support vacuumdb --foo".I'm not sure I totally follow. Do you mean to add a comment to
ExecVacuum() saying to add support to vacuumdb when adding a new option
to vacuum?
Yeah, like:
/* Options added here should also be added to vacuumdb.c */
In the past, did people forget to add support to vacuumdb for new vacuum
options or did they forget to document that they did that or did they
forgot to include that they did that in the release notes?
The first. Maybe not often, it's not important whether it's in the
original patch, but it's odd if the vacuumdb option isn't added until
the following release, which then shows up as a separate "feature".
--
Justin
Thanks for the reviews and for trying the patch!
On Wed, Mar 15, 2023 at 4:31 AM Masahiko Sawada <sawada.mshk@gmail.com> wrote:
On Sat, Mar 11, 2023 at 11:55 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:On Tue, Feb 28, 2023 at 3:16 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:On Thu, Jan 12, 2023 at 6:06 AM Andres Freund <andres@anarazel.de> wrote:
On 2023-01-11 17:26:19 -0700, David G. Johnston wrote:
Should we just add "ring_buffers" to the existing "shared_buffers" and
"temp_buffers" settings?The different types of ring buffers have different sizes, for good reasons. So
I don't see that working well. I also think it'd be more often useful to
control this on a statement basis - if you have a parallel import tool that
starts NCPU COPYs you'd want a smaller buffer than a single threaded COPY. Of
course each session can change the ring buffer settings, but still.How about having GUCs for each ring buffer (bulk_read_ring_buffers,
bulk_write_ring_buffers, vacuum_ring_buffers - ah, 3 more new GUCs)?
These options can help especially when statement level controls aren't
easy to add (COPY, CREATE TABLE AS/CTAS, REFRESH MAT VIEW/RMV)? If
needed users can also set them at the system level. For instance, one
can set bulk_write_ring_buffers to other than 16MB or -1 to disable
the ring buffer to use shared_buffers and run a bunch of bulk write
queries.In attached v3, I've changed the name of the guc from buffer_usage_limit
to vacuum_buffer_usage_limit, since it is only used for vacuum and
autovacuum.I haven't added the other suggested strategy gucs, as those could easily
be done in a future patchset.I've also changed GetAccessStrategyExt() to GetAccessStrategyWithSize()
-- similar to initArrayResultWithSize().And I've added tab completion for BUFFER_USAGE_LIMIT so that it is
easier to try out my patch.Most of the TODOs in the code are related to the question of how
autovacuum uses the guc vacuum_buffer_usage_limit. autovacuum creates
the buffer access strategy ring in do_autovacuum() before looping
through and vacuuming tables. It passes this strategy object on to
vacuum(). Since we reuse the same strategy object for all tables in a
given invocation of do_autovacuum(), only failsafe autovacuum would
change buffer access strategies. This is probably okay, but it does mean
that the table-level VacuumParams variable, ring_size, means something
different for autovacuum than vacuum. Autovacuum workers will always
have set it to -1. We won't ever reach code in vacuum() which relies on
VacuumParams->ring_size as long as autovacuum workers pass a non-NULL
BufferAccessStrategy object to vacuum(), though.I've not reviewed the patchset in depth yet but I got assertion
failure and SEGV when using the buffer_usage_limit parameter.postgres(1:471180)=# vacuum (buffer_usage_limit 10000000000) ;
2023-03-15 17:10:02.947 JST [471180] ERROR: buffer_usage_limit for a
vacuum must be between -1 and 16777216. 10000000000 is invalid. at
character 9The message show the max value is 16777216, but when I set it, I got
an assertion failure:postgres(1:470992)=# vacuum (buffer_usage_limit 16777216) ;
TRAP: failed Assert("ring_size < MAX_BAS_RING_SIZE_KB"), File:
"freelist.c", Line: 606, PID: 470992Then when I used 1 byte lower value, 16777215, I got a SEGV:
postgres(1:471180)=# vacuum (buffer_usage_limit 16777215) ;
server closed the connection unexpectedly
This probably means the server terminated abnormally
before or while processing the request.
The connection to the server was lost. Attempting reset: 2023-03-15
17:10:59.404 JST [471159] LOG: server process (PID 471180) was
terminated by signal 11: Segmentation faultFinally, when I used a more lower value, 16777100, I got a memory
allocation error:postgres(1:471361)=# vacuum (buffer_usage_limit 16777100) ;
2023-03-15 17:12:17.853 JST [471361] ERROR: invalid memory alloc
request size 18446744073709551572Probably vacuum_buffer_usage_limit also has the same issue.
Oh dear--it seems I had an integer overflow when calculating the number
of buffers using the specified buffer size in the macro:
#define bufsize_limit_to_nbuffers(bufsize) (bufsize * 1024 / BLCKSZ)
In the attached v4, I've changed that to:
static inline int
bufsize_limit_to_nbuffers(int bufsize_limit_kb)
{
int blcksz_kb = BLCKSZ / 1024;
Assert(blcksz_kb > 0);
return bufsize_limit_kb / blcksz_kb;
}
This should address Justin's suggestions and Ants' concern about the
macro as well.
Also, I was missing the = in the Assert(ring_size <= MAX_BAS_RING_SIZE)
I've fixed that as well, so it should work for you to specify up to 16777216.
Also, should we support a table option for vacuum_buffer_usage_limit as well?
Hmm. Since this is meant more for balancing resource usage globally, it
doesn't make as much sense as a table option to me. But, I could be
convinced.
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Sat, Mar 11, 2023 at 09:55:33AM -0500, Melanie Plageman wrote:
Subject: [PATCH v3 2/3] use shared buffers when failsafe active
+ /* + * Assume the caller who allocated the memory for the + * BufferAccessStrategy object will free it. + */ + vacrel->bstrategy = NULL;This comment could use elaboration:
** VACUUM normally restricts itself to a small ring buffer; but in
failsafe mode, in order to process tables as quickly as possible, allow
the leaving behind large number of dirty buffers.
I've added a comment in attached v4 which is a bit different than Justin's
suggestion but still more verbose than the previous comment.
Subject: [PATCH v3 3/3] add vacuum option to specify ring_size and guc + {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM, + gettext_noop("Sets the buffer pool size for operations employing a buffer access strategy."),The description should mention vacuum, if that's the scope of the GUC's
behavior.
I've updated this in v4.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers + # > 0 to specify sizeIf I'm not wrong, there's still no documentation about "ring buffers" or
postgres' "strategy". Which seems important to do for this patch, along
with other documentation.
So, on the topic of "other documentation", I have, at least, added docs
for the vacuum_buffer_usage_limit guc and the BUFFER_USAGE option to
VACUUM and the buffer-usage-limit parameter to vacuumdb.
This patch should add support in vacuumdb.c. And maybe a comment about
adding support there, since it's annoying when it the release notes one
year say "support VACUUM (FOO)" and then one year later say "support
vacuumdb --foo".
So, v4 adds support for buffer-usage-limit to vacuumdb. There are a few
issues. The main one is that no other vacuumdb option takes a size as a
parameter. I couldn't actually find any other client with a parameter
specified as a size.
My VACUUM option code is using the GUC size parsing code from
parse_int() -- including the unit flag GUC_UNIT_KB. Now that vacuumdb
also needs to parse sizes, I think we'll need to lift the parse_int()
code and the unit_conversion struct and
unit_conversion_memory_unit_conversion_table out of guc.c and put it
somewhere that it can be accessed for more than guc parsing (e.g. option
parsing).
For vacuumdb in this version, I just specified buffer-usage-limit is
only in kB and thus can only be specified as an int.
If we had something like pg_parse_size() in common, would this make
sense? It would be a little bit of work to figure out what to do about
the flags, etc.
Another issue is the server-side guc
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
I just redefined it in vacuumdb code. I'm not sure what the preferred
method for dealing with this is.
I know this validation would get done server-side if I just passed the
user-specified option through, but all of the other vacuumdb options
appear to be doing min/max boundary validation on the client side.
On Wed, Mar 15, 2023 at 8:14 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Mar 14, 2023 at 08:56:58PM -0400, Melanie Plageman wrote:
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype) +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)Maybe it would make sense for GetAccessStrategy() to call
GetAccessStrategyWithSize(). Or maybe not.You mean instead of having anyone call GetAccessStrategyWithSize()?
We would need to change the signature of GetAccessStrategy() then -- and
there are quite a few callers.I mean to avoid code duplication, GetAccessStrategy() could "Select ring
size to use" and then call into GetAccessStrategyWithSize(). Maybe it's
counter to your intent or otherwise not worth it to save 8 LOC.
Oh, that's a cool idea. I will think on it.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers + # > 0 to specify sizeIf I'm not wrong, there's still no documentation about "ring buffers" or
postgres' "strategy". Which seems important to do for this patch, along
with other documentation.Yes, it is. I have been thinking about where in the docs to add it (the
docs about buffer access strategies). Any ideas?This patch could add something to the vacuum manpage and to the appendix.
And maybe references from the shared_buffers and pg_buffercache
manpages.
So, I was thinking it would be good to have some documentation in
general about Buffer Access Strategies (i.e. not just for vacuum). It
would have been nice to have something to reference from the pg_stat_io
docs that describe what buffer access strategies are.
And maybe a comment about adding support there, since it's annoying
when it the release notes one year say "support VACUUM (FOO)" and then
one year later say "support vacuumdb --foo".I'm not sure I totally follow. Do you mean to add a comment to
ExecVacuum() saying to add support to vacuumdb when adding a new option
to vacuum?Yeah, like:
/* Options added here should also be added to vacuumdb.c */
I've added a little something to the comment above the VacuumParams
struct.
In the past, did people forget to add support to vacuumdb for new vacuum
options or did they forget to document that they did that or did they
forgot to include that they did that in the release notes?The first. Maybe not often, it's not important whether it's in the
original patch, but it's odd if the vacuumdb option isn't added until
the following release, which then shows up as a separate "feature".
I've squished in the code for adding the parameter to vacuumdb in a
single commit with the guc and vacuum option, but I will separate it out
after some of the basics get sorted.
- Melanie
Attachments:
v4-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v4-0001-remove-global-variable-vac_strategy.patchDownload
From 8e57765196e8af4f53914b813ae88c23cf88ddf8 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v4 1/3] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..a6aac30529 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -75,7 +75,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -94,7 +93,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -338,7 +337,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -404,7 +403,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -509,7 +507,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -527,7 +525,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1838,7 +1836,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2082,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2116,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
v4-0003-add-vacuum-db-option-to-specify-ring_size-and-guc.patchtext/x-patch; charset=US-ASCII; name=v4-0003-add-vacuum-db-option-to-specify-ring_size-and-guc.patchDownload
From d75a38fe364b5ddfab220cac34a81f069c7415a3 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 15 Mar 2023 15:47:22 -0400
Subject: [PATCH v4 3/3] add vacuum[db] option to specify ring_size and guc
---
doc/src/sgml/config.sgml | 24 +++++++++
doc/src/sgml/ref/vacuum.sgml | 21 ++++++++
doc/src/sgml/ref/vacuumdb.sgml | 19 +++++++
src/backend/commands/vacuum.c | 51 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 6 ++-
src/backend/postmaster/autovacuum.c | 15 +++++-
src/backend/storage/buffer/README | 2 +
src/backend/storage/buffer/freelist.c | 48 +++++++++++++++++
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 4 ++
src/bin/psql/tab-complete.c | 2 +-
src/bin/scripts/vacuumdb.c | 28 ++++++++++
src/include/commands/vacuum.h | 4 ++
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 5 ++
16 files changed, 239 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c41cc6c6..1a84b5715d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1981,6 +1981,30 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size to be used for a given invocation of
+ <command>VACUUM</command> or instance of autovacuum. This size is
+ converted to a number of shared buffers which will be reused as part of
+ a <literal>Buffer Access Strategy</literal>. <literal>0</literal> will
+ disable use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> will set the size to a default of <literal>256
+ kB</literal>. The maximum ring buffer size is <literal>16 GB</literal>.
+ Though you may set <varname>vacuum_buffer_usage_limit</varname> below
+ <literal>128 kB</literal>, it will be clamped to <literal>128
+ kB</literal> at runtime. The default value is <literal>-1</literal>.
+ This parameter can be set at any time.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..d0c8adaa61 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">integer</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,26 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate a number of shared buffers which will be reused as
+ part of a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that <command>VACUUM</command> should
+ fall back to the value specified by <xref
+ linkend="guc-vacuum-buffer-usage-limit"/>. The maximum value is
+ <literal>16 GB</literal>. Though you may specify a size smaller than
+ <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. This size applies to a single invocation of
+ <command>VACUUM</command>. The analyze stage and parallel vacuum workers
+ do not use this size.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..dfc57643a3 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,25 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size in kB of the ring buffer used for vacuuming. This size is used
+ to calculate a number of shared buffers which will be reused as part of
+ a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that the vacuum should fall back to the
+ value specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>. The
+ maximum value is <literal>16777216</literal> (16 GB). Though you may
+ specify a size smaller than <literal>128</literal>, the value will be
+ clamped to <literal>128 kB</literal> at runtime. This size applies to a
+ single invocation of <command>vacuumdb</command>. The analyze stage and
+ parallel vacuum workers do not use this size.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a6aac30529..8fa09b786a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -128,6 +128,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -211,6 +214,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, NULL))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d. %s is invalid.",
+ MAX_BAS_RING_SIZE_KB, vac_buffer_size),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -400,7 +440,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..16742f6612 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,7 +1012,11 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should this work differently even though they are only for indexes
+ */
pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
/* Setup error traceback support for ereport() */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c0e2e00a7e..7dbd3b8935 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,14 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2881,6 +2887,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ // TODO: should this be 0 so that we are sure that vacuum() never
+ // allocates a new bstrategy for us, even if we pass in NULL for that
+ // parameter? maybe could change how failsafe NULLs out bstrategy if
+ // so?
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..bf166bbdf1 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -245,6 +245,8 @@ for WAL flushes. While it's okay for a background vacuum to be slowed by
doing its own WAL flushing, we'd prefer that COPY not be subject to that,
so we let it use up a bit more of the buffer arena.
+TODO: update with info about new option to control the size
+
Background Writer's Processing
------------------------------
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..f12c9224d7 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -586,6 +586,54 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ BufferAccessStrategy strategy;
+ int nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ // TODO: this being called ring_size and also nbuffers being called
+ // ring_size in GetAccessStrategy is confusing. Rename the member of
+ // BufferAccessStrategy
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ // TODO: warn about clamping?
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ // TODO: make this smaller?
+ nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /* Allocate the object and initialize all elements to zeroes */
+ strategy = (BufferAccessStrategy)
+ palloc0(offsetof(BufferAccessStrategyData, buffers) +
+ nbuffers * sizeof(Buffer));
+
+ /* Set fields that don't start out zero */
+ strategy->btype = btype;
+ strategy->ring_size = nbuffers;
+
+ return strategy;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..883ee29d14 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2206,6 +2206,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..36273aa47d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -156,6 +156,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy and use shared buffers
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 42e87b9e49..6fd80dd3c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..74e50ac286 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -25,6 +25,7 @@
#include "fe_utils/simple_list.h"
#include "fe_utils/string_utils.h"
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
/* vacuum options controlled by user flags */
typedef struct vacuumingOptions
@@ -46,6 +47,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ int buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +125,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +150,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = -1;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +270,12 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ if (!option_parse_int(optarg, "--buffer-usage-limit",
+ -1, MAX_BAS_RING_SIZE_KB,
+ &vacopts.buffer_usage_limit))
+ exit(1);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -324,6 +334,9 @@ main(int argc, char *argv[])
if (!vacopts.process_toast)
pg_fatal("cannot use the \"%s\" option when performing only analyze",
"no-process-toast");
+ if (vacopts.buffer_usage_limit != -1)
+ pg_fatal("cannot use the \"%s\" option when performing only analyze",
+ "buffer-usage-limit");
/* allow 'and_analyze' with 'analyze_only' */
}
@@ -550,6 +563,13 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ // TODO: this is a problem: if the user specifies this option with -1 in a
+ // version before 16, it will not produce an error message. it also won't
+ // do anything, but that still doesn't seem right.
+ if (vacopts->buffer_usage_limit > -1 && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1068,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit > -1)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT %d", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1138,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE_KB size of ring buffer in kB used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..63755480cf 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
@@ -236,6 +239,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..338de38568 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -196,6 +199,8 @@ extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
v4-0002-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v4-0002-use-shared-buffers-when-failsafe-active.patchDownload
From 55a4cb0356951582acfe920112ed887f80670ce4 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v4 2/3] use shared buffers when failsafe active
---
src/backend/access/heap/vacuumlazy.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..3de7976cf6 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,13 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Abandon use of a buffer access strategy when entering failsafe mode,
+ * as completing the ongoing VACUUM is our top priority.
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
On Wed, Mar 15, 2023 at 9:03 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Sat, Mar 11, 2023 at 09:55:33AM -0500, Melanie Plageman wrote:
This patch should add support in vacuumdb.c. And maybe a comment about
adding support there, since it's annoying when it the release notes one
year say "support VACUUM (FOO)" and then one year later say "support
vacuumdb --foo".So, v4 adds support for buffer-usage-limit to vacuumdb. There are a few
issues. The main one is that no other vacuumdb option takes a size as a
parameter. I couldn't actually find any other client with a parameter
specified as a size.My VACUUM option code is using the GUC size parsing code from
parse_int() -- including the unit flag GUC_UNIT_KB. Now that vacuumdb
also needs to parse sizes, I think we'll need to lift the parse_int()
code and the unit_conversion struct and
unit_conversion_memory_unit_conversion_table out of guc.c and put it
somewhere that it can be accessed for more than guc parsing (e.g. option
parsing).For vacuumdb in this version, I just specified buffer-usage-limit is
only in kB and thus can only be specified as an int.If we had something like pg_parse_size() in common, would this make
sense? It would be a little bit of work to figure out what to do about
the flags, etc.Another issue is the server-side guc
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
I just redefined it in vacuumdb code. I'm not sure what the preferred
method for dealing with this is.I know this validation would get done server-side if I just passed the
user-specified option through, but all of the other vacuumdb options
appear to be doing min/max boundary validation on the client side.
So, after discussing vacuumdb client-side validation off-list with Jelte,
I realized that I was trying to do too much there.
Attached v5 passes the contents of the buffer-usage-limit option to
vacuumdb unvalidated into the VACUUM command string which vacuumdb
builds. This solves most of the problems.
I also improved the error messages coming from VACUUM
(buffer_usage_limit) handling.
- Melanie
Attachments:
v5-0002-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v5-0002-use-shared-buffers-when-failsafe-active.patchDownload
From 5ca6eed0c126ed050277960d0a74979d70031239 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v5 2/3] use shared buffers when failsafe active
---
src/backend/access/heap/vacuumlazy.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..3de7976cf6 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,13 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Abandon use of a buffer access strategy when entering failsafe mode,
+ * as completing the ongoing VACUUM is our top priority.
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
v5-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v5-0001-remove-global-variable-vac_strategy.patchDownload
From a3bf94e4044f14dcd7b5c062b8417a3e0b9fe686 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v5 1/3] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..a6aac30529 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -75,7 +75,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -94,7 +93,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -338,7 +337,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -404,7 +403,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -509,7 +507,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -527,7 +525,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1838,7 +1836,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2082,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2116,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
v5-0003-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchtext/x-patch; charset=US-ASCII; name=v5-0003-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchDownload
From a38fb38daf476cc4a572757d2634713bc2bd4f8a Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Thu, 16 Mar 2023 20:27:51 -0400
Subject: [PATCH v5 3/3] Add vacuum[db] buffer usage limit option and guc
---
doc/src/sgml/config.sgml | 25 +++++++++
doc/src/sgml/ref/vacuum.sgml | 22 ++++++++
doc/src/sgml/ref/vacuumdb.sgml | 20 ++++++++
src/backend/commands/vacuum.c | 51 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 6 ++-
src/backend/postmaster/autovacuum.c | 15 +++++-
src/backend/storage/buffer/README | 2 +
src/backend/storage/buffer/freelist.c | 48 +++++++++++++++++
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 4 ++
src/bin/psql/tab-complete.c | 2 +-
src/bin/scripts/vacuumdb.c | 21 ++++++++
src/include/commands/vacuum.h | 4 ++
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 5 ++
16 files changed, 235 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c41cc6c6..241aafddd9 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1981,6 +1981,31 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size to be used for a given invocation of
+ <command>VACUUM</command> or instance of autovacuum. This size is
+ converted to a number of shared buffers which will be reused as part of
+ a <literal>Buffer Access Strategy</literal>. <literal>0</literal> will
+ disable use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> will set the size to a default of <literal>256
+ kB</literal>. The maximum ring buffer size is <literal>16 GB</literal>.
+ Though you may set <varname>vacuum_buffer_usage_limit</varname> below
+ <literal>128 kB</literal>, it will be clamped to <literal>128
+ kB</literal> at runtime. The default value is <literal>-1</literal>.
+ Without units, the size is taken as <literal>kB</literal>. This
+ parameter can be set at any time.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..216c09c3d7 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate a number of shared buffers which will be reused as
+ part of a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that <command>VACUUM</command> should
+ fall back to the value specified by <xref
+ linkend="guc-vacuum-buffer-usage-limit"/>. The maximum value is
+ <literal>16 GB</literal>. Though you may specify a size smaller than
+ <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. Without units, the size is taken as
+ <literal>kB</literal>. This size applies to a single invocation of
+ <command>VACUUM</command>. The analyze stage and parallel vacuum workers
+ do not use this size.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..5e8a6c167d 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,26 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size of the ring buffer used for vacuuming. This size is used to
+ calculate a number of shared buffers which will be reused as part of a
+ <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that the vacuum should fall back to the
+ value specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>. The
+ maximum value is <literal>16 GB</literal>. Though you may specify a
+ size smaller than <literal>128 kB</literal>, the value will be clamped
+ to <literal>128 kB</literal> at runtime. Without units, the value is
+ taken as <literal>kB</literal>. This size applies to a single
+ invocation of <command>vacuumdb</command>. The analyze stage and
+ parallel vacuum workers do not use this size.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a6aac30529..f434f07dd1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -128,6 +128,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -211,6 +214,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+ const char *hintmsg;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -400,7 +440,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..16742f6612 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,7 +1012,11 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should this work differently even though they are only for indexes
+ */
pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
/* Setup error traceback support for ereport() */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c0e2e00a7e..7dbd3b8935 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,14 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2881,6 +2887,13 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ // TODO: should this be 0 so that we are sure that vacuum() never
+ // allocates a new bstrategy for us, even if we pass in NULL for that
+ // parameter? maybe could change how failsafe NULLs out bstrategy if
+ // so?
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..bf166bbdf1 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -245,6 +245,8 @@ for WAL flushes. While it's okay for a background vacuum to be slowed by
doing its own WAL flushing, we'd prefer that COPY not be subject to that,
so we let it use up a bit more of the buffer arena.
+TODO: update with info about new option to control the size
+
Background Writer's Processing
------------------------------
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..97ca3f344f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -586,6 +586,54 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ BufferAccessStrategy strategy;
+ int nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ // TODO: this being called ring_size and also nbuffers being called
+ // ring_size in GetAccessStrategy is confusing. Rename the member of
+ // BufferAccessStrategy
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ // TODO: warn about clamping?
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ // TODO: make this smaller?
+ nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /* Allocate the object and initialize all elements to zeroes */
+ strategy = (BufferAccessStrategy)
+ palloc0(offsetof(BufferAccessStrategyData, buffers) +
+ nbuffers * sizeof(Buffer));
+
+ /* Set fields that don't start out zero */
+ strategy->btype = btype;
+ strategy->ring_size = nbuffers;
+
+ return strategy;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..883ee29d14 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2206,6 +2206,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..36273aa47d 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -156,6 +156,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy and use shared buffers
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 42e87b9e49..6fd80dd3c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..5b2a5d2858 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -324,6 +330,9 @@ main(int argc, char *argv[])
if (!vacopts.process_toast)
pg_fatal("cannot use the \"%s\" option when performing only analyze",
"no-process-toast");
+ if (vacopts.buffer_usage_limit)
+ pg_fatal("cannot use the \"%s\" option when performing only analyze",
+ "buffer-usage-limit");
/* allow 'and_analyze' with 'analyze_only' */
}
@@ -550,6 +559,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1061,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1131,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..d8fba51d82 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
@@ -236,6 +239,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..338de38568 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -196,6 +199,8 @@ extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
Subject: [PATCH v4 3/3] add vacuum[db] option to specify ring_size and guc
+ Specifies the ring buffer size to be used for a given invocation of + <command>VACUUM</command> or instance of autovacuum. This size is + converted to a number of shared buffers which will be reused as part of
I'd say "specifies the size of shared_buffers to be reused as .."
+ a <literal>Buffer Access Strategy</literal>. <literal>0</literal> will + disable use of a <literal>Buffer Access Strategy</literal>. + <literal>-1</literal> will set the size to a default of <literal>256 + kB</literal>. The maximum ring buffer size is <literal>16 GB</literal>. + Though you may set <varname>vacuum_buffer_usage_limit</varname> below + <literal>128 kB</literal>, it will be clamped to <literal>128 + kB</literal> at runtime. The default value is <literal>-1</literal>. + This parameter can be set at any time.
GUC docs usually also say something like
"If this value is specified without units, it is taken as .."
+ is used to calculate a number of shared buffers which will be reused as
*the* number?
+ <command>VACUUM</command>. The analyze stage and parallel vacuum workers + do not use this size.
I think what you mean is that vacuum's heap scan stage uses the
strategy, but the index scan/cleanup phases doesn't?
+ The size in kB of the ring buffer used for vacuuming. This size is used + to calculate a number of shared buffers which will be reused as part of
*the* number
+++ b/doc/src/sgml/ref/vacuumdb.sgml
The docs here duplicate the sql-vacuum docs. It seems better to refer
to the vacuum page for details, like --parallel does.
Unrelated: it would be nice if the client-side options were documented
separately from the server-side options. Especially due to --jobs and
--parallel.
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, NULL)) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("buffer_usage_limit for a vacuum must be between -1 and %d. %s is invalid.", + MAX_BAS_RING_SIZE_KB, vac_buffer_size), + parser_errposition(pstate, opt->location))); + } + + /* check for out-of-bounds */ + if (result < -1 || result > MAX_BAS_RING_SIZE_KB) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("buffer_usage_limit for a vacuum must be between -1 and %d", + MAX_BAS_RING_SIZE_KB), + parser_errposition(pstate, opt->location))); + }
I think these checks could be collapsed into a single ereport().
if !parse_int() || (result < -1 || result > MAX_BAS_RINGSIZE_KB):
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("buffer_usage_limit for a vacuum must be an integer between -1 and %d",
MAX_BAS_RING_SIZE_KB),
There was a recent, similar, and unrelated suggestion here:
/messages/by-id/20230314.135859.260879647537075548.horikyota.ntt@gmail.com
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffers
I think it's confusing to say "and use shared buffers", since
"strategies" also use shared_buffers. It seems better to remove those 4
words.
@@ -550,6 +563,13 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");+ // TODO: this is a problem: if the user specifies this option with -1 in a + // version before 16, it will not produce an error message. it also won't + // do anything, but that still doesn't seem right.
Actually, that seems fine to me. If someone installs v16 vacuumdb, they
can run it against old servers and specify the option as -1 without it
failing with an error. I don't know if anyone will find that useful,
but it doesn't seem unreasonable.
I still think adding something to the glossary would be good.
Buffer Access Strategy:
A circular/ring buffer used for reading or writing data pages from/to
the operating system. Ring buffers are used for sequential scans of
large tables, VACUUM, COPY FROM, CREATE TABLE AS SELECT, ALTER TABLE,
and CLUSTER. By using only a limited portion of >shared_buffers<, the
ring buffer avoids avoids evicting large amounts of data whenever a
backend performs bulk I/O operations. Use of a ring buffer also forces
the backend to write out its own dirty pages, rather than leaving them
behind to be cleaned up by other backends.
If there's a larger section added than a glossary entry, the text could
be promoted from src/backend/storage/buffer/README to doc/.
--
Justin
Thanks for the review!
Attached is an updated v6.
v6 has some updates and corrections. It has two remaining TODOs in the
code: one is around what value to initialize the ring_size to in
VacuumParams, the other is around whether or not parallel vacuum index
workers should in fact stick with the default buffer access strategy
sizes.
I also separated vacuumdb into its own commit.
I also have addressed Justin's review feedback.
On Sat, Mar 18, 2023 at 2:30 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
Subject: [PATCH v4 3/3] add vacuum[db] option to specify ring_size and guc
+ Specifies the ring buffer size to be used for a given invocation of + <command>VACUUM</command> or instance of autovacuum. This size is + converted to a number of shared buffers which will be reused as part ofI'd say "specifies the size of shared_buffers to be reused as .."
I've included "shared_buffers" in the description.
+ a <literal>Buffer Access Strategy</literal>. <literal>0</literal> will + disable use of a <literal>Buffer Access Strategy</literal>. + <literal>-1</literal> will set the size to a default of <literal>256 + kB</literal>. The maximum ring buffer size is <literal>16 GB</literal>. + Though you may set <varname>vacuum_buffer_usage_limit</varname> below + <literal>128 kB</literal>, it will be clamped to <literal>128 + kB</literal> at runtime. The default value is <literal>-1</literal>. + This parameter can be set at any time.GUC docs usually also say something like
"If this value is specified without units, it is taken as .."
I had updated this in v5 with slightly different wording, but I now am
using the wording you suggested (which does appear standard in the rest
of the docs).
+ is used to calculate a number of shared buffers which will be reused as
*the* number?
updated.
+ <command>VACUUM</command>. The analyze stage and parallel vacuum workers + do not use this size.I think what you mean is that vacuum's heap scan stage uses the
strategy, but the index scan/cleanup phases doesn't?
Yes, non-parallel index vacuum and cleanup will use whatever value you
specify but parallel workers make their own buffer access strategy
object. I've updated the docs to indicate that they will use the default
size for this.
+ The size in kB of the ring buffer used for vacuuming. This size is used + to calculate a number of shared buffers which will be reused as part of*the* number
fixed.
+++ b/doc/src/sgml/ref/vacuumdb.sgmlThe docs here duplicate the sql-vacuum docs. It seems better to refer
to the vacuum page for details, like --parallel does.
Good idea.
Unrelated: it would be nice if the client-side options were documented
separately from the server-side options. Especially due to --jobs and
--parallel.
Yes, that would be helpful.
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, NULL)) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("buffer_usage_limit for a vacuum must be between -1 and %d. %s is invalid.", + MAX_BAS_RING_SIZE_KB, vac_buffer_size), + parser_errposition(pstate, opt->location))); + } + + /* check for out-of-bounds */ + if (result < -1 || result > MAX_BAS_RING_SIZE_KB) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("buffer_usage_limit for a vacuum must be between -1 and %d", + MAX_BAS_RING_SIZE_KB), + parser_errposition(pstate, opt->location))); + }I think these checks could be collapsed into a single ereport().
if !parse_int() || (result < -1 || result > MAX_BAS_RINGSIZE_KB):
ereport(ERROR,
errcode(ERRCODE_SYNTAX_ERROR),
errmsg("buffer_usage_limit for a vacuum must be an integer between -1 and %d",
MAX_BAS_RING_SIZE_KB),There was a recent, similar, and unrelated suggestion here:
/messages/by-id/20230314.135859.260879647537075548.horikyota.ntt@gmail.com
So, these have been updated/improved in v5. I still didn't combine them.
I see what you are saying about combining them (and I checked the link
you shared), but in this case, having them separate allows me to provide
info using the hintmsg passed to parse_int() about why it failed during
parse_int -- which could be something not related to range. So, I think
it makes sense to keep them separate.
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring. + # -1 to use default, + # 0 to disable vacuum buffer access strategy and use shared buffersI think it's confusing to say "and use shared buffers", since
"strategies" also use shared_buffers. It seems better to remove those 4
words.
Got it. I've gone ahead and done that.
@@ -550,6 +563,13 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");+ // TODO: this is a problem: if the user specifies this option with -1 in a + // version before 16, it will not produce an error message. it also won't + // do anything, but that still doesn't seem right.Actually, that seems fine to me. If someone installs v16 vacuumdb, they
can run it against old servers and specify the option as -1 without it
failing with an error. I don't know if anyone will find that useful,
but it doesn't seem unreasonable.
I sort of skirted around this by removing any validation from vacuumdb
(present in v5 and still the case in v6). Now, the parameter is a string
and I check if it is non-NULL when the version is < 16. However, this
will no longer have the property that someone can use v16 vacuumdb and
pass buffer-usage-limit and have it not fail. I think that is okay,
though, since they might be confused thinking it was doing something.
I still think adding something to the glossary would be good.
Buffer Access Strategy:
A circular/ring buffer used for reading or writing data pages from/to
the operating system. Ring buffers are used for sequential scans of
large tables, VACUUM, COPY FROM, CREATE TABLE AS SELECT, ALTER TABLE,
and CLUSTER. By using only a limited portion of >shared_buffers<, the
ring buffer avoids avoids evicting large amounts of data whenever a
backend performs bulk I/O operations. Use of a ring buffer also forces
the backend to write out its own dirty pages, rather than leaving them
behind to be cleaned up by other backends.
Yes, I have taken some ideas from here and added a separate commit
before all the others adding Buffer Access Strategy to the
documentation.
If there's a larger section added than a glossary entry, the text could
be promoted from src/backend/storage/buffer/README to doc/.
This is a good idea. I think we provided enough information in the
glossary (as far as users would care) if it weren't for the new
buffer_usage_limit guc, which probably merits more explanation about how
it interacts with buffer access strategies. Since it is only used for
vacuum now, do you think such a thing would belong in VACUUM-related
documentation? Like somewhere in [1]https://www.postgresql.org/docs/devel/routine-vacuuming.html?
On Wed, Mar 15, 2023 at 9:03 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Wed, Mar 15, 2023 at 8:14 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Tue, Mar 14, 2023 at 08:56:58PM -0400, Melanie Plageman wrote:
On Sat, Mar 11, 2023 at 2:16 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
@@ -586,6 +587,45 @@ GetAccessStrategy(BufferAccessStrategyType btype) +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)Maybe it would make sense for GetAccessStrategy() to call
GetAccessStrategyWithSize(). Or maybe not.You mean instead of having anyone call GetAccessStrategyWithSize()?
We would need to change the signature of GetAccessStrategy() then -- and
there are quite a few callers.I mean to avoid code duplication, GetAccessStrategy() could "Select ring
size to use" and then call into GetAccessStrategyWithSize(). Maybe it's
counter to your intent or otherwise not worth it to save 8 LOC.Oh, that's a cool idea. I will think on it.
So, I thought about doing a version of this by adding a helper which did
the allocation of the BufferAccessStrategy object given a number of
buffers that could be called by both GetAccessStrategy() and
GetAccessStrategyWithSize(). I decided not to because I wanted to emit a
debug message if the size of the ring was clamped lower or higher than
the user would expect -- but only do this in GetAccessStrategyWithSize()
since there is no user expectation in the use of GetAccessStrategy().
- Melanie
[1]: https://www.postgresql.org/docs/devel/routine-vacuuming.html
Attachments:
v6-0006-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v6-0006-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 1be30138fa37bb62980fa68fbbff29ef6bc9bfa4 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v6 6/6] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 12 ++++++++++++
src/bin/scripts/vacuumdb.c | 21 +++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 36 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..0b4c29f685 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size of the ring buffer used for vacuuming. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ a <literal>Buffer Access Strategy</literal>. See <xref
+ linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..5b2a5d2858 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -324,6 +330,9 @@ main(int argc, char *argv[])
if (!vacopts.process_toast)
pg_fatal("cannot use the \"%s\" option when performing only analyze",
"no-process-toast");
+ if (vacopts.buffer_usage_limit)
+ pg_fatal("cannot use the \"%s\" option when performing only analyze",
+ "buffer-usage-limit");
/* allow 'and_analyze' with 'analyze_only' */
}
@@ -550,6 +559,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1061,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1131,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5f2a58b2c3..d8fba51d82 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v6-0004-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchtext/x-patch; charset=US-ASCII; name=v6-0004-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchDownload
From a2a59567ad38dae1c3013da5a4b359e76f3b75fc Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 13:15:24 -0400
Subject: [PATCH v6 4/6] Rename Buffer Access Strategy->ring_size nbuffers
ring_size sounds like it is in units of bytes when it is actually a
number of buffers. Rename it to nbuffers to make future commit related
to ring size less confusing.
---
src/backend/storage/buffer/freelist.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..f122709fbe 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -74,7 +74,7 @@ typedef struct BufferAccessStrategyData
/* Overall strategy type */
BufferAccessStrategyType btype;
/* Number of elements in buffers[] array */
- int ring_size;
+ int nbuffers;
/*
* Index of the "current" slot in the ring, ie, the one most recently
@@ -541,7 +541,7 @@ BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
BufferAccessStrategy strategy;
- int ring_size;
+ int nbuffers;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +556,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
case BAS_BULKWRITE:
- ring_size = 16 * 1024 * 1024 / BLCKSZ;
+ nbuffers = 16 * 1024 * 1024 / BLCKSZ;
break;
case BAS_VACUUM:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
default:
@@ -572,16 +572,16 @@ GetAccessStrategy(BufferAccessStrategyType btype)
}
/* Make sure ring isn't an undue fraction of shared buffers */
- ring_size = Min(NBuffers / 8, ring_size);
+ nbuffers = Min(NBuffers / 8, nbuffers);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
- ring_size * sizeof(Buffer));
+ nbuffers * sizeof(Buffer));
/* Set fields that don't start out zero */
strategy->btype = btype;
- strategy->ring_size = ring_size;
+ strategy->nbuffers = nbuffers;
return strategy;
}
@@ -615,7 +615,7 @@ GetBufferFromRing(BufferAccessStrategy strategy, uint32 *buf_state)
/* Advance to next ring slot */
- if (++strategy->current >= strategy->ring_size)
+ if (++strategy->current >= strategy->nbuffers)
strategy->current = 0;
/*
--
2.37.2
v6-0005-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchtext/x-patch; charset=US-ASCII; name=v6-0005-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchDownload
From ea1fbd78532c7ee898dd4d84c197f8422debaf43 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:08 -0400
Subject: [PATCH v6 5/6] Add vacuum[db] buffer usage limit option and guc
---
doc/src/sgml/config.sgml | 26 ++++++++
doc/src/sgml/ref/vacuum.sgml | 23 ++++++++
src/backend/commands/vacuum.c | 51 +++++++++++++++-
src/backend/commands/vacuumparallel.c | 8 ++-
src/backend/postmaster/autovacuum.c | 17 +++++-
src/backend/storage/buffer/README | 21 +++++--
src/backend/storage/buffer/freelist.c | 59 +++++++++++++++++++
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 4 ++
src/bin/psql/tab-complete.c | 2 +-
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 5 ++
14 files changed, 221 insertions(+), 10 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fca38a4514..1bf0050d8d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1981,6 +1981,32 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for a given invocation of <command>VACUUM</command> or instance of
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a <literal>Buffer Access
+ Strategy</literal>. <literal>0</literal> will disable use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal> will
+ set the size to a default of <literal>256 kB</literal>. The maximum
+ ring buffer size is <literal>16 GB</literal>. Though you may set
+ <varname>vacuum_buffer_usage_limit</varname> below <literal>128
+ kB</literal>, it will be clamped to <literal>128 kB</literal> at
+ runtime. The default value is <literal>-1</literal>. If this value is
+ specified without units, it is taken as kilobytes. This parameter can
+ be set at any time.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..8ab89cfa3c 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,28 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate the number of shared buffers which will be reused as
+ part of a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that <command>VACUUM</command> should
+ fall back to the value specified by <xref
+ linkend="guc-vacuum-buffer-usage-limit"/>. The maximum value is
+ <literal>16 GB</literal>. Though you may specify a size smaller than
+ <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. If this value is specified without units, it is
+ taken as kilobytes. This size applies to a single invocation of
+ <command>VACUUM</command>. Parallel VACUUM workers use the default Buffer
+ Access Strategy ring size during index scan and index cleanup phases of
+ VACUUM.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a6aac30529..f434f07dd1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -128,6 +128,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -211,6 +214,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+ const char *hintmsg;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -400,7 +440,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..4c19fc095e 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -1012,7 +1012,13 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
+ /*
+ * Each parallel VACUUM worker gets its own access strategy
+ * For now, use the default buffer access strategy ring size.
+ * TODO: should each parallel VACUUM worker doing index vacuum get a ring
+ * of the full custom size if we are doing that? Or should we split it
+ * amongst them? Or should we just use the default?
+ */
pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
/* Setup error traceback support for ereport() */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c0e2e00a7e..a8657b0b32 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,8 +2291,14 @@ do_autovacuum(void)
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
* point is for VACUUM not to blow out the shared cache.
+ * If we later enter failsafe mode, we will cease use of the
+ * BufferAccessStrategy. Either way, we clean up the BufferAccessStrategy
+ * object at the end of this function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2881,6 +2887,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * TODO: should this be 0 so that we are sure that vacuum() never
+ * allocates a new bstrategy for us, even if we pass in NULL for that
+ * parameter? maybe could change how failsafe NULLs out bstrategy if
+ * so?
+ */
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d1be1ca5b7 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,21 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM's default Buffer Access Strategy uses a 256KB ring like sequential
+scans, but dirty pages are not removed from the ring. Instead, WAL is flushed
+if needed to allow reuse of the buffers. Before introducing the buffer ring
+strategy in 8.3, VACUUM's buffers were sent to the freelist, which was
+effectively a buffer ring of 1 buffer, resulting in excessive WAL flushing.
+Allowing VACUUM to update 256KB between WAL flushes should be more efficient.
+
+As an alternative, VACUUM can use a user-specified ring size. The VACUUM
+parameter "BUFFER_USAGE_LIMIT" and GUC vacuum_buffer_usage_limit can be used to
+specify the amount of shared memory to be used during vacuuming. This size is
+used to calculate the number of buffers in the ring when it is created. A value
+of 0 for vacuum_buffer_usage_limit will disable use of the Buffer Access
+Strategy and allow vacuuming to use shared buffers as normal.
+In failsafe mode, autovacuum will always abandon use of a Buffer Access
+Strategy.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..f0a1b59e2c 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -586,6 +586,65 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ BufferAccessStrategy strategy;
+ int nbuffers;
+ int clamped_nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ {
+ ereport(DEBUG1,
+ (errmsg_internal("Buffer Access Strategy ring_size %d kB has been clamped to minimum %d kB",
+ ring_size,
+ MIN_BAS_RING_SIZE_KB)));
+
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ }
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ clamped_nbuffers = Min(NBuffers / 8, nbuffers);
+
+ if (clamped_nbuffers < nbuffers)
+ ereport(DEBUG1,
+ (errmsg_internal("active Buffer Access Strategy may use a maximum of %d buffers. %d has been clamped",
+ NBuffers / 8,
+ nbuffers)));
+
+ nbuffers = clamped_nbuffers;
+
+ /* Allocate the object and initialize all elements to zeroes */
+ strategy = (BufferAccessStrategy)
+ palloc0(offsetof(BufferAccessStrategyData, buffers) +
+ nbuffers * sizeof(Buffer));
+
+ /* Set fields that don't start out zero */
+ strategy->btype = btype;
+ strategy->nbuffers = nbuffers;
+
+ return strategy;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..883ee29d14 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2206,6 +2206,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..e55a1008f1 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -156,6 +156,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 42e87b9e49..6fd80dd3c3 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..5f2a58b2c3 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index b8a18b8081..338de38568 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -196,6 +199,8 @@ extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
v6-0003-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v6-0003-use-shared-buffers-when-failsafe-active.patchDownload
From 52c802a00572b382ee0203b0038dbe509e4ecb72 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v6 3/6] use shared buffers when failsafe active
---
doc/src/sgml/config.sgml | 8 +++++---
src/backend/access/heap/vacuumlazy.c | 7 +++++++
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c41cc6c6..fca38a4514 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9291,9 +9291,11 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
the failsafe to trigger during any <command>VACUUM</command>.
</para>
<para>
- When the failsafe is triggered, any cost-based delay that is
- in effect will no longer be applied, and further non-essential
- maintenance tasks (such as index vacuuming) are bypassed.
+ When the failsafe is triggered, any cost-based delay that is in effect
+ will no longer be applied, further non-essential maintenance tasks
+ (such as index vacuuming) are bypassed, and any Buffer Access Strategy
+ in use will be abandoned and the vacuum will be free to use as many
+ shared buffers as it needs to finish vacuuming the table.
</para>
<para>
The default is 1.6 billion transactions. Although users can
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..3de7976cf6 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2622,6 +2622,13 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
if (unlikely(vacuum_xid_failsafe_check(&vacrel->cutoffs)))
{
vacrel->failsafe_active = true;
+ /*
+ * Abandon use of a buffer access strategy when entering failsafe mode,
+ * as completing the ongoing VACUUM is our top priority.
+ * Assume the caller who allocated the memory for the
+ * BufferAccessStrategy object will free it.
+ */
+ vacrel->bstrategy = NULL;
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
--
2.37.2
v6-0002-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v6-0002-remove-global-variable-vac_strategy.patchDownload
From 9ee2f4f68fc9314d42244b16d5268324fe7aff53 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v6 2/6] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..a6aac30529 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -75,7 +75,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -94,7 +93,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -338,7 +337,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -404,7 +403,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -509,7 +507,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -527,7 +525,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1838,7 +1836,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2082,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2116,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
v6-0001-Add-Buffer-Access-Strategy-to-glossary.patchtext/x-patch; charset=US-ASCII; name=v6-0001-Add-Buffer-Access-Strategy-to-glossary.patchDownload
From 66a4555afa4bcad41fc6da241f28336e314b8328 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 17:40:11 -0400
Subject: [PATCH v6 1/6] Add Buffer Access Strategy to glossary
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/ZBYDTrD1kyGg%2BHkS%40telsasoft.com
---
doc/src/sgml/glossary.sgml | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index 7c01a541fe..d2168f26b1 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -252,6 +252,28 @@
</glossdef>
</glossentry>
+ <glossentry id="glossary-buffer-access-strategy">
+ <glossterm>Buffer Access Strategy</glossterm>
+ <glossdef>
+ <para>
+ Some operations will access a large number of pages at a time. In order to
+ avoid using all of <varname>shared_buffers</varname> and evicting blocks
+ from buffers in use by other workloads, some operations employ a
+ <varname>Buffer Access Strategy</varname>. A <varname>Buffer Access
+ Strategy</varname> sets up references to a limited number of
+ <varname>shared_buffers</varname> and reuses them circularly. If the
+ operation dirties a buffer that it then must reuse, it must write out that
+ buffer and any associated WAL itself.
+ </para>
+ <para>
+ <varname>Buffer Access Strategies</varname> are used for sequential scans
+ of large tables, VACUUM, COPY operations, CREATE TABLE AS SELECT, ALTER
+ TABLE, CREATE DATABASE, CREATE INDEX, and CLUSTER, amongst other
+ operations.
+ </para>
+ </glossdef>
+ </glossentry>
+
<glossentry id="glossary-cast">
<glossterm>Cast</glossterm>
<glossdef>
--
2.37.2
On Wed, Mar 15, 2023 at 6:46 AM Ants Aasma <ants@cybertec.at> wrote:
On Wed, 15 Mar 2023 at 02:29, Melanie Plageman
<melanieplageman@gmail.com> wrote:As for routine vacuuming and the other buffer access strategies, I think
there is an argument for configurability based on operator knowledge --
perhaps your workload will use the data you are COPYing as soon as the
COPY finishes, so you might as well disable a buffer access strategy or
use a larger fraction of shared buffers. Also, the ring sizes were
selected sixteen years ago and average server memory and data set sizes
have changed.To be clear I'm not at all arguing against configurability. I was
thinking that dynamic use could make the configuration simpler by self
tuning to use no more buffers than is useful.
Yes, but I am struggling with how we would define "useful".
StrategyRejectBuffer() will allow bulkreads to, as you say, use more
buffers than the original ring size, since it allows them to kick
dirty buffers out of the ring and claim new shared buffers.Bulkwrites and vacuums, however, will inevitably dirty buffers and
require flushing the buffer (and thus flushing the associated WAL) when
reusing them. Bulkwrites and vacuum do not kick dirtied buffers out of
the ring, since dirtying buffers is their common case. A dynamic
resizing like the one you suggest would likely devolve to vacuum and
bulkwrite strategies always using the max size.I think it should self stabilize around the point where the WAL is
either flushed by other commit activity, WAL writer or WAL buffers
filling up. Writing out their own dirtied buffers will still happen,
just the associated WAL flushes will be in larger chunks and possibly
done by other processes.
They will have to write out any WAL associated with modifications to the
dirty buffer before flushing it, so I'm not sure I understand how this
would work.
As for decreasing the ring size, buffers are only "added" to the ring
lazily and, technically, as it is now, buffers which have been added
added to the ring can always be reclaimed by the clocksweep (as long as
they are not pinned). The buffer access strategy is more of a
self-imposed restriction than it is a reservation. Since the ring is
small and the buffers are being frequently reused, odds are the usage
count will be 1 and we will be the one who set it to 1, but there is no
guarantee. If, when attempting to reuse the buffer, its usage count is1 (or it is pinned), we also will kick it out of the ring and go look
for a replacement buffer.
Right, but while the buffer is actively used by the ring it is
unlikely that clocksweep will find it at usage 0 as the ring buffer
should cycle more often than the clocksweep. Whereas if the ring stops
using a buffer, clocksweep will eventually come and reclaim it. And if
the ring shrinking decision turns out to be wrong before the
clocksweep gets around to reusing it, we can bring the same buffer
back into the ring.
I can see what you mean about excluding a buffer from the ring being a
more effective way of allowing it to be reclaimed. However, I'm not sure
I understand the use case. If the operation, say vacuum, is actively
using the buffer and keeping its usage count at one, then what would be
the criteria for it to decide to stop using it?
Also, if vacuum used the buffer once and then didn't reuse it but, for
some reason, the vacuum isn't over, it isn't any different at that point
than some other buffer with a usage count of one. It isn't any harder
for it to be reclaimed by the clocksweep.
The argument I could see for decreasing the size even when the buffers
are being used by the operation employing the strategy is if there is
pressure from other workloads to use those buffers. But, designing a
system that would reclaim buffers when needed by other workloads is more
complicated than what is being proposed here.
I do think that it is a bit unreasonable to expect users to know how
large they would like to make their buffer access strategy ring. What we
want is some way of balancing different kinds of workloads and
maintenance tasks reasonably. If your database has no activity because
it is the middle of the night or it was shutdown because of transaction
id wraparound, there is no reason why vacuum should limit the number of
buffers it uses. I'm sure there are many other such examples.Ideally yes, though I am not hopeful of finding a solution that does
this any time soon. Just to take your example, if a nightly
maintenance job wipes out the shared buffer contents slightly
optimizing its non time-critical work and then causes morning user
visible load to have big latency spikes due to cache misses, that's
not a good tradeoff either.
Yes, that is a valid concern.
- Melanie
On Mon, 20 Mar 2023 at 00:59, Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Wed, Mar 15, 2023 at 6:46 AM Ants Aasma <ants@cybertec.at> wrote:
On Wed, 15 Mar 2023 at 02:29, Melanie Plageman
<melanieplageman@gmail.com> wrote:As for routine vacuuming and the other buffer access strategies, I think
there is an argument for configurability based on operator knowledge --
perhaps your workload will use the data you are COPYing as soon as the
COPY finishes, so you might as well disable a buffer access strategy or
use a larger fraction of shared buffers. Also, the ring sizes were
selected sixteen years ago and average server memory and data set sizes
have changed.To be clear I'm not at all arguing against configurability. I was
thinking that dynamic use could make the configuration simpler by self
tuning to use no more buffers than is useful.Yes, but I am struggling with how we would define "useful".
For copy and vacuum, the only reason I can see for keeping visited
buffers around is to avoid flushing WAL or at least doing it in larger
batches. Once the ring is big enough that WAL doesn't need to be
flushed on eviction, making it bigger only wastes space that could be
used by something that is not going to be evicted soon.
StrategyRejectBuffer() will allow bulkreads to, as you say, use more
buffers than the original ring size, since it allows them to kick
dirty buffers out of the ring and claim new shared buffers.Bulkwrites and vacuums, however, will inevitably dirty buffers and
require flushing the buffer (and thus flushing the associated WAL) when
reusing them. Bulkwrites and vacuum do not kick dirtied buffers out of
the ring, since dirtying buffers is their common case. A dynamic
resizing like the one you suggest would likely devolve to vacuum and
bulkwrite strategies always using the max size.I think it should self stabilize around the point where the WAL is
either flushed by other commit activity, WAL writer or WAL buffers
filling up. Writing out their own dirtied buffers will still happen,
just the associated WAL flushes will be in larger chunks and possibly
done by other processes.They will have to write out any WAL associated with modifications to the
dirty buffer before flushing it, so I'm not sure I understand how this
would work.
By the time the dirty buffer needs eviction the WAL associated with it
can already be written out by concurrent commits, WAL writer or by WAL
buffers filling up. The bigger the ring is, the higher the chance that
one of these will happen before we loop around.
As for decreasing the ring size, buffers are only "added" to the ring
lazily and, technically, as it is now, buffers which have been added
added to the ring can always be reclaimed by the clocksweep (as long as
they are not pinned). The buffer access strategy is more of a
self-imposed restriction than it is a reservation. Since the ring is
small and the buffers are being frequently reused, odds are the usage
count will be 1 and we will be the one who set it to 1, but there is no
guarantee. If, when attempting to reuse the buffer, its usage count is1 (or it is pinned), we also will kick it out of the ring and go look
for a replacement buffer.
Right, but while the buffer is actively used by the ring it is
unlikely that clocksweep will find it at usage 0 as the ring buffer
should cycle more often than the clocksweep. Whereas if the ring stops
using a buffer, clocksweep will eventually come and reclaim it. And if
the ring shrinking decision turns out to be wrong before the
clocksweep gets around to reusing it, we can bring the same buffer
back into the ring.I can see what you mean about excluding a buffer from the ring being a
more effective way of allowing it to be reclaimed. However, I'm not sure
I understand the use case. If the operation, say vacuum, is actively
using the buffer and keeping its usage count at one, then what would be
the criteria for it to decide to stop using it?
The criteria for reducing ring size could be that we have cycled the
ring buffer n times without having to do any WAL flushes.
Also, if vacuum used the buffer once and then didn't reuse it but, for
some reason, the vacuum isn't over, it isn't any different at that point
than some other buffer with a usage count of one. It isn't any harder
for it to be reclaimed by the clocksweep.The argument I could see for decreasing the size even when the buffers
are being used by the operation employing the strategy is if there is
pressure from other workloads to use those buffers. But, designing a
system that would reclaim buffers when needed by other workloads is more
complicated than what is being proposed here.
I don't think any specific reclaim is needed, if the ring stops using
a buffer *and* there is pressure from other workloads the buffer will
get used for other stuff by the normal clocksweep. If the ring keeps
using it then the normal clocksweep is highly unlikely to find it with
usage count 0. If there is no concurrent allocation pressure, the ring
can start using it again if that turns out to be necessary (probably
should still check that it hasn't been reused by someone else).
--
Ants Aasma
Senior Database Engineer
www.cybertec-postgresql.com
On Tue, Mar 21, 2023 at 6:03 AM Ants Aasma <ants@cybertec.at> wrote:
On Mon, 20 Mar 2023 at 00:59, Melanie Plageman
<melanieplageman@gmail.com> wrote:On Wed, Mar 15, 2023 at 6:46 AM Ants Aasma <ants@cybertec.at> wrote:
On Wed, 15 Mar 2023 at 02:29, Melanie Plageman
<melanieplageman@gmail.com> wrote:As for routine vacuuming and the other buffer access strategies, I think
there is an argument for configurability based on operator knowledge --
perhaps your workload will use the data you are COPYing as soon as the
COPY finishes, so you might as well disable a buffer access strategy or
use a larger fraction of shared buffers. Also, the ring sizes were
selected sixteen years ago and average server memory and data set sizes
have changed.To be clear I'm not at all arguing against configurability. I was
thinking that dynamic use could make the configuration simpler by self
tuning to use no more buffers than is useful.Yes, but I am struggling with how we would define "useful".
For copy and vacuum, the only reason I can see for keeping visited
buffers around is to avoid flushing WAL or at least doing it in larger
batches. Once the ring is big enough that WAL doesn't need to be
flushed on eviction, making it bigger only wastes space that could be
used by something that is not going to be evicted soon.
Well, I think if you know you will use the data you are COPYing right
away in your normal workload, it could be useful to have the ring be
large or to disable use of the ring. And, for vacuum, if you need to get
it done as quickly as possible, again, it could be useful to have the
ring be large or disable use of the ring.
StrategyRejectBuffer() will allow bulkreads to, as you say, use more
buffers than the original ring size, since it allows them to kick
dirty buffers out of the ring and claim new shared buffers.Bulkwrites and vacuums, however, will inevitably dirty buffers and
require flushing the buffer (and thus flushing the associated WAL) when
reusing them. Bulkwrites and vacuum do not kick dirtied buffers out of
the ring, since dirtying buffers is their common case. A dynamic
resizing like the one you suggest would likely devolve to vacuum and
bulkwrite strategies always using the max size.I think it should self stabilize around the point where the WAL is
either flushed by other commit activity, WAL writer or WAL buffers
filling up. Writing out their own dirtied buffers will still happen,
just the associated WAL flushes will be in larger chunks and possibly
done by other processes.They will have to write out any WAL associated with modifications to the
dirty buffer before flushing it, so I'm not sure I understand how this
would work.By the time the dirty buffer needs eviction the WAL associated with it
can already be written out by concurrent commits, WAL writer or by WAL
buffers filling up. The bigger the ring is, the higher the chance that
one of these will happen before we loop around.
Ah, I think I understand the idea now. So, I think it is an interesting
idea to try and find the goldilocks size for the ring buffer. It is
especially interesting to me in the case in which we are enlarging the
ring.
However, given that concurrent workload variability, machine I/O latency
fluctuations, etc, we will definitely have to set a max value of some
kind anyway for the ring size. So, this seems more like a complimentary
feature to vacuum_buffer_usage_limit. If we added some kind of adaptive
sizing in a later version, we could emphasize in the guidance for
setting vacuum_buffer_usage_limit that it is the *maximum* size you
would like to allow vacuum to use. And, of course, there are the other
operations which use buffer access strategies.
As for decreasing the ring size, buffers are only "added" to the ring
lazily and, technically, as it is now, buffers which have been added
added to the ring can always be reclaimed by the clocksweep (as long as
they are not pinned). The buffer access strategy is more of a
self-imposed restriction than it is a reservation. Since the ring is
small and the buffers are being frequently reused, odds are the usage
count will be 1 and we will be the one who set it to 1, but there is no
guarantee. If, when attempting to reuse the buffer, its usage count is1 (or it is pinned), we also will kick it out of the ring and go look
for a replacement buffer.
Right, but while the buffer is actively used by the ring it is
unlikely that clocksweep will find it at usage 0 as the ring buffer
should cycle more often than the clocksweep. Whereas if the ring stops
using a buffer, clocksweep will eventually come and reclaim it. And if
the ring shrinking decision turns out to be wrong before the
clocksweep gets around to reusing it, we can bring the same buffer
back into the ring.I can see what you mean about excluding a buffer from the ring being a
more effective way of allowing it to be reclaimed. However, I'm not sure
I understand the use case. If the operation, say vacuum, is actively
using the buffer and keeping its usage count at one, then what would be
the criteria for it to decide to stop using it?The criteria for reducing ring size could be that we have cycled the
ring buffer n times without having to do any WAL flushes.Also, if vacuum used the buffer once and then didn't reuse it but, for
some reason, the vacuum isn't over, it isn't any different at that point
than some other buffer with a usage count of one. It isn't any harder
for it to be reclaimed by the clocksweep.The argument I could see for decreasing the size even when the buffers
are being used by the operation employing the strategy is if there is
pressure from other workloads to use those buffers. But, designing a
system that would reclaim buffers when needed by other workloads is more
complicated than what is being proposed here.I don't think any specific reclaim is needed, if the ring stops using
a buffer *and* there is pressure from other workloads the buffer will
get used for other stuff by the normal clocksweep. If the ring keeps
using it then the normal clocksweep is highly unlikely to find it with
usage count 0. If there is no concurrent allocation pressure, the ring
can start using it again if that turns out to be necessary (probably
should still check that it hasn't been reused by someone else).
Yes, you don't need a specific reclaim mechanism. But you would want to
be quite conservative about decreasing the ring size (given workload
variation and machine variations such as bursting in the cloud) and
probably not do so simply because the operation using the strategy
doesn't absolutely need the buffer but also because other concurrent
workloads really need the buffer. And, it seems complicated to determine
if other workloads do need the buffer.
- Melanie
On Mon, 20 Mar 2023 at 11:50, Melanie Plageman
<melanieplageman@gmail.com> wrote:
Attached is an updated v6.
I had a look over the v6-0001 patch. There are a few things I think
could be done better:
"Some operations will access a large number of pages at a time", does
this really need "at a time"? I think it's more relevant that the
operation uses a large number of pages.
Missing <firstterm> around Buffer Access Strategy.
Various things could be linked to other sections of the glossary, e.g.
pages could link to glossary-data-page, shared buffers could link to
glossary-shared-memory and WAL could link to glossary-wal.
The final paragraph should have <command> tags around the various
commands that you list.
I have adjusted those and slightly reworded a few other things. See
the attached .diff which can be applied atop of v6-0001.
David
Attachments:
v6-0001-adjustments.diffapplication/octet-stream; name=v6-0001-adjustments.diffDownload
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index d2168f26b1..eb837d4622 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -256,20 +256,26 @@
<glossterm>Buffer Access Strategy</glossterm>
<glossdef>
<para>
- Some operations will access a large number of pages at a time. In order to
- avoid using all of <varname>shared_buffers</varname> and evicting blocks
- from buffers in use by other workloads, some operations employ a
- <varname>Buffer Access Strategy</varname>. A <varname>Buffer Access
- Strategy</varname> sets up references to a limited number of
- <varname>shared_buffers</varname> and reuses them circularly. If the
- operation dirties a buffer that it then must reuse, it must write out that
- buffer and any associated WAL itself.
+ Some operations will access a large number of
+ <glossterm linkend="glossary-data-page">pages</glossterm>. A
+ <firstterm>Buffer Access Strategy</firstterm> helps to prevent these
+ these operations from evicting too many pages from
+ <glossterm linkend="glossary-shared-memory">shared buffers</glossterm>.
+ </para>
+ <para>
+ A Buffer Access Strategy sets up references to a limited number of
+ <glossterm linkend="glossary-shared-memory">shared buffers</glossterm>
+ and reuses them circularly. When the operation requires a new page, a
+ victim buffer is chosen from the buffers in the strategy, which may
+ require dirty buffers and possibly also require
+ <glossterm linkend="glossary-wal">WAL</glossterm> to be flushed to disk.
</para>
<para>
- <varname>Buffer Access Strategies</varname> are used for sequential scans
- of large tables, VACUUM, COPY operations, CREATE TABLE AS SELECT, ALTER
- TABLE, CREATE DATABASE, CREATE INDEX, and CLUSTER, amongst other
- operations.
+ Buffer Access Strategies are used for various operations such as;
+ sequential scans of large tables, <command>VACUUM</command>,
+ <command>COPY</command>, <command>CREATE TABLE AS SELECT</command>,
+ <command>ALTER TABLE</command>, <command>CREATE DATABASE</command>,
+ <command>CREATE INDEX</command>, and <command>CLUSTER</cluster>.
</para>
</glossdef>
</glossentry>
On Thu, Mar 30, 2023 at 11:54 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Mon, 20 Mar 2023 at 11:50, Melanie Plageman
<melanieplageman@gmail.com> wrote:Attached is an updated v6.
I had a look over the v6-0001 patch. There are a few things I think
could be done better:"Some operations will access a large number of pages at a time", does
this really need "at a time"? I think it's more relevant that the
operation uses a large number of pages.Missing <firstterm> around Buffer Access Strategy.
Various things could be linked to other sections of the glossary, e.g.
pages could link to glossary-data-page, shared buffers could link to
glossary-shared-memory and WAL could link to glossary-wal.The final paragraph should have <command> tags around the various
commands that you list.I have adjusted those and slightly reworded a few other things. See
the attached .diff which can be applied atop of v6-0001.
There was one small typo keeping this from compiling. Also a repeated
word. I've fixed these. I also edited a bit of indentation and tweaked
some wording. Diff attached (to be applied on top of your diff).
- Melanie
Attachments:
v6-0001-more-adjustments.patchtext/x-patch; charset=US-ASCII; name=v6-0001-more-adjustments.patchDownload
From 7ca6e4c47656d0e3fe5984f9e67aa51f1d22c73c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Fri, 31 Mar 2023 09:50:45 -0400
Subject: [PATCH v6] more adjustments
---
doc/src/sgml/glossary.sgml | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml
index eb837d4622..8bf5e2b92d 100644
--- a/doc/src/sgml/glossary.sgml
+++ b/doc/src/sgml/glossary.sgml
@@ -256,26 +256,26 @@
<glossterm>Buffer Access Strategy</glossterm>
<glossdef>
<para>
- Some operations will access a large number of
- <glossterm linkend="glossary-data-page">pages</glossterm>. A
- <firstterm>Buffer Access Strategy</firstterm> helps to prevent these
- these operations from evicting too many pages from
- <glossterm linkend="glossary-shared-memory">shared buffers</glossterm>.
+ Some operations will access a large number of <glossterm
+ linkend="glossary-data-page">pages</glossterm>. A <firstterm>Buffer
+ Access Strategy</firstterm> helps to prevent these operations from
+ evicting too many pages from <glossterm
+ linkend="glossary-shared-memory">shared buffers</glossterm>.
</para>
<para>
A Buffer Access Strategy sets up references to a limited number of
- <glossterm linkend="glossary-shared-memory">shared buffers</glossterm>
- and reuses them circularly. When the operation requires a new page, a
- victim buffer is chosen from the buffers in the strategy, which may
- require dirty buffers and possibly also require
- <glossterm linkend="glossary-wal">WAL</glossterm> to be flushed to disk.
- </para>
- <para>
- Buffer Access Strategies are used for various operations such as;
+ <glossterm linkend="glossary-shared-memory">shared buffers</glossterm> and
+ reuses them circularly. When the operation requires a new page, a victim
+ buffer is chosen from the buffers in the strategy ring, which may require
+ flushing the page's dirty data and possibly also associated <glossterm
+ linkend="glossary-wal">WAL</glossterm> to permanent storage.
+ </para>
+ <para>
+ Buffer Access Strategies are used for various operations such as:
sequential scans of large tables, <command>VACUUM</command>,
- <command>COPY</command>, <command>CREATE TABLE AS SELECT</command>,
+ <command>COPY</command>, <command>CREATE TABLE AS SELECT</command> (CTAS),
<command>ALTER TABLE</command>, <command>CREATE DATABASE</command>,
- <command>CREATE INDEX</command>, and <command>CLUSTER</cluster>.
+ <command>CREATE INDEX</command>, and <command>CLUSTER</command>.
</para>
</glossdef>
</glossentry>
--
2.37.2
On Sat, 1 Apr 2023 at 02:52, Melanie Plageman <melanieplageman@gmail.com> wrote:
There was one small typo keeping this from compiling. Also a repeated
word. I've fixed these. I also edited a bit of indentation and tweaked
some wording. Diff attached (to be applied on top of your diff).
Thanks for fixing that mistake.
For reference, I had changed things to end lines early so that the
glossterm tags could be on a line of their own without breaking to a
new line. The rest of the file seems to be done that way, so I thought
we'd better stick to it.
I swapped out "associated WAL" for "unflushed WAL". I didn't agree
that the WAL that would be flushed would have any particular
association with the to-be-written page.
I dropped CTAS since I didn't see any other mention in the docs about
that. I could maybe see the sense in making reference to the
abbreviated form if we were going to mention it again and didn't want
to spell the whole thing out each time, but that's not the case here.
I pushed the result.
David
On Fri, Mar 31, 2023 at 5:47 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Sat, 1 Apr 2023 at 02:52, Melanie Plageman <melanieplageman@gmail.com> wrote:
There was one small typo keeping this from compiling. Also a repeated
word. I've fixed these. I also edited a bit of indentation and tweaked
some wording. Diff attached (to be applied on top of your diff).Thanks for fixing that mistake.
For reference, I had changed things to end lines early so that the
glossterm tags could be on a line of their own without breaking to a
new line. The rest of the file seems to be done that way, so I thought
we'd better stick to it.I swapped out "associated WAL" for "unflushed WAL". I didn't agree
that the WAL that would be flushed would have any particular
association with the to-be-written page.I dropped CTAS since I didn't see any other mention in the docs about
that. I could maybe see the sense in making reference to the
abbreviated form if we were going to mention it again and didn't want
to spell the whole thing out each time, but that's not the case here.I pushed the result.
Cool!
I've attached v7 with that commit dropped and with support for parallel
vacuum workers to use the same number of buffers in their own Buffer
Access Strategy ring as the main vacuum phase did. I also updated the
docs to indicate that vacuum_buffer_usage_limit is per backend (not per
instance of VACUUM).
- Melanie
Attachments:
v7-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v7-0001-remove-global-variable-vac_strategy.patchDownload
From 9357d88d14c1ba0a0cffc4bd734e8f3ec0e2dd9f Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v7 1/5] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..a6aac30529 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -75,7 +75,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -94,7 +93,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -338,7 +337,7 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
+ * Due to static variable vac_context
* vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
* calls a hostile index expression that itself calls ANALYZE.
*/
@@ -404,7 +403,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -509,7 +507,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -527,7 +525,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1838,7 +1836,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2082,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2116,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
v7-0002-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v7-0002-use-shared-buffers-when-failsafe-active.patchDownload
From ffa2524260aa3c05b6ee0c272980dcfecb108646 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v7 2/5] use shared buffers when failsafe active
---
doc/src/sgml/config.sgml | 8 +++++---
src/backend/access/heap/vacuumlazy.c | 8 ++++++++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fcb53c6997..7b8cf624dc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9319,9 +9319,11 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
the failsafe to trigger during any <command>VACUUM</command>.
</para>
<para>
- When the failsafe is triggered, any cost-based delay that is
- in effect will no longer be applied, and further non-essential
- maintenance tasks (such as index vacuuming) are bypassed.
+ When the failsafe is triggered, any cost-based delay that is in effect
+ will no longer be applied, further non-essential maintenance tasks
+ (such as index vacuuming) are bypassed, and any Buffer Access Strategy
+ in use will be abandoned and the vacuum will be free to use as many
+ shared buffers as it needs to finish vacuuming the table.
</para>
<para>
The default is 1.6 billion transactions. Although users can
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 8f14cf85f3..29c57f1b61 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2623,6 +2623,14 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
{
vacrel->failsafe_active = true;
+ /*
+ * Abandon use of a buffer access strategy when entering failsafe
+ * mode, as completing the ongoing VACUUM is our top priority. Assume
+ * the caller who allocated the memory for the BufferAccessStrategy
+ * object will free it.
+ */
+ vacrel->bstrategy = NULL;
+
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
vacrel->do_index_cleanup = false;
--
2.37.2
v7-0004-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchtext/x-patch; charset=US-ASCII; name=v7-0004-Add-vacuum-db-buffer-usage-limit-option-and-guc.patchDownload
From 11706c4735c4948a99974a364c3df2ca0e2eab76 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:08 -0400
Subject: [PATCH v7 4/5] Add vacuum[db] buffer usage limit option and guc
---
doc/src/sgml/config.sgml | 26 ++++++++
doc/src/sgml/ref/vacuum.sgml | 21 ++++++
src/backend/commands/vacuum.c | 51 ++++++++++++++-
src/backend/commands/vacuumparallel.c | 13 +++-
src/backend/postmaster/autovacuum.c | 19 +++++-
src/backend/storage/buffer/README | 21 ++++--
src/backend/storage/buffer/freelist.c | 65 ++++++++++++++++++-
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 ++++
src/backend/utils/misc/postgresql.conf.sample | 4 ++
src/bin/psql/tab-complete.c | 2 +-
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 8 +++
14 files changed, 232 insertions(+), 13 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7b8cf624dc..dcae06ff05 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,32 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or instance of autovacuum. This size is
+ converted to the number of shared buffers which will be reused as part
+ of a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ will disable use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> will set the size to a default of <literal>256
+ kB</literal>. The maximum ring buffer size is <literal>16 GB</literal>.
+ Though you may set <varname>vacuum_buffer_usage_limit</varname> below
+ <literal>128 kB</literal>, it will be clamped to <literal>128
+ kB</literal> at runtime. The default value is <literal>-1</literal>. If
+ this value is specified without units, it is taken as kilobytes. This
+ parameter can be set at any time.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..a8c169e11e 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,26 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate the number of shared buffers which will be reused as
+ part of a <literal>Buffer Access Strategy</literal>. <literal>0</literal>
+ disables use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> indicates that <command>VACUUM</command> should
+ fall back to the value specified by <xref
+ linkend="guc-vacuum-buffer-usage-limit"/>. The maximum value is
+ <literal>16 GB</literal>. Though you may specify a size smaller than
+ <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. If this value is specified without units, it is
+ taken as kilobytes. This size applies to a backend participating in a
+ single invocation of <command>VACUUM</command>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a6aac30529..f434f07dd1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -128,6 +128,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -211,6 +214,43 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+ const char *hintmsg;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -400,7 +440,16 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index bcd40c80a1..7ff4421596 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -361,6 +367,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = bas_nbuffers(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1012,8 +1021,8 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithNBuffers(BAS_VACUUM, shared->ring_nbuffers);
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..ce54535692 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,15 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2884,6 +2890,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * TODO: should this be 0 so that we are sure that vacuum() never
+ * allocates a new bstrategy for us, even if we pass in NULL for that
+ * parameter? maybe could change how failsafe NULLs out bstrategy if
+ * so?
+ */
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d1be1ca5b7 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,21 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM's default Buffer Access Strategy uses a 256KB ring like sequential
+scans, but dirty pages are not removed from the ring. Instead, WAL is flushed
+if needed to allow reuse of the buffers. Before introducing the buffer ring
+strategy in 8.3, VACUUM's buffers were sent to the freelist, which was
+effectively a buffer ring of 1 buffer, resulting in excessive WAL flushing.
+Allowing VACUUM to update 256KB between WAL flushes should be more efficient.
+
+As an alternative, VACUUM can use a user-specified ring size. The VACUUM
+parameter "BUFFER_USAGE_LIMIT" and GUC vacuum_buffer_usage_limit can be used to
+specify the amount of shared memory to be used during vacuuming. This size is
+used to calculate the number of buffers in the ring when it is created. A value
+of 0 for vacuum_buffer_usage_limit will disable use of the Buffer Access
+Strategy and allow vacuuming to use shared buffers as normal.
+In failsafe mode, autovacuum will always abandon use of a Buffer Access
+Strategy.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..8514da0c65 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -531,6 +531,22 @@ StrategyInitialize(bool init)
* ----------------------------------------------------------------
*/
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+int
+bas_nbuffers(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* GetAccessStrategy -- create a BufferAccessStrategy object
@@ -540,7 +556,6 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
int nbuffers;
/*
@@ -574,6 +589,14 @@ GetAccessStrategy(BufferAccessStrategyType btype)
/* Make sure ring isn't an undue fraction of shared buffers */
nbuffers = Min(NBuffers / 8, nbuffers);
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+BufferAccessStrategy
+GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers)
+{
+ BufferAccessStrategy strategy;
+
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
@@ -586,6 +609,46 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ int nbuffers;
+ int clamped_nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size != -1);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ {
+ ereport(DEBUG1,
+ (errmsg_internal("Buffer Access Strategy ring_size %d kB has been clamped to minimum %d kB",
+ ring_size,
+ MIN_BAS_RING_SIZE_KB)));
+
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ }
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ clamped_nbuffers = Min(NBuffers / 8, nbuffers);
+
+ if (clamped_nbuffers < nbuffers)
+ ereport(DEBUG1,
+ (errmsg_internal("active Buffer Access Strategy may use a maximum of %d buffers. %d has been clamped",
+ NBuffers / 8,
+ nbuffers)));
+
+ nbuffers = clamped_nbuffers;
+
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5476c0f4aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..91599a4975 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..26947f7928 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..5f2a58b2c3 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..c5d67ad3b9 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -194,7 +197,12 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+extern int bas_nbuffers(BufferAccessStrategy strategy);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
+
+extern BufferAccessStrategy GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
--
2.37.2
v7-0003-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchtext/x-patch; charset=US-ASCII; name=v7-0003-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchDownload
From 53834130b9ccd7b7d36307d158f27728a583f651 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 13:15:24 -0400
Subject: [PATCH v7 3/5] Rename Buffer Access Strategy->ring_size nbuffers
ring_size sounds like it is in units of bytes when it is actually a
number of buffers. Rename it to nbuffers to make future commit related
to ring size less confusing.
---
src/backend/storage/buffer/freelist.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..f122709fbe 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -74,7 +74,7 @@ typedef struct BufferAccessStrategyData
/* Overall strategy type */
BufferAccessStrategyType btype;
/* Number of elements in buffers[] array */
- int ring_size;
+ int nbuffers;
/*
* Index of the "current" slot in the ring, ie, the one most recently
@@ -541,7 +541,7 @@ BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
BufferAccessStrategy strategy;
- int ring_size;
+ int nbuffers;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +556,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
case BAS_BULKWRITE:
- ring_size = 16 * 1024 * 1024 / BLCKSZ;
+ nbuffers = 16 * 1024 * 1024 / BLCKSZ;
break;
case BAS_VACUUM:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
default:
@@ -572,16 +572,16 @@ GetAccessStrategy(BufferAccessStrategyType btype)
}
/* Make sure ring isn't an undue fraction of shared buffers */
- ring_size = Min(NBuffers / 8, ring_size);
+ nbuffers = Min(NBuffers / 8, nbuffers);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
- ring_size * sizeof(Buffer));
+ nbuffers * sizeof(Buffer));
/* Set fields that don't start out zero */
strategy->btype = btype;
- strategy->ring_size = ring_size;
+ strategy->nbuffers = nbuffers;
return strategy;
}
@@ -615,7 +615,7 @@ GetBufferFromRing(BufferAccessStrategy strategy, uint32 *buf_state)
/* Advance to next ring slot */
- if (++strategy->current >= strategy->ring_size)
+ if (++strategy->current >= strategy->nbuffers)
strategy->current = 0;
/*
--
2.37.2
v7-0005-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v7-0005-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 89485bbe9f1f62ff78070da23bfccdec74df7330 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v7 5/5] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 12 ++++++++++++
src/bin/scripts/vacuumdb.c | 21 +++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 36 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..0b4c29f685 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size of the ring buffer used for vacuuming. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ a <literal>Buffer Access Strategy</literal>. See <xref
+ linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..5b2a5d2858 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -324,6 +330,9 @@ main(int argc, char *argv[])
if (!vacopts.process_toast)
pg_fatal("cannot use the \"%s\" option when performing only analyze",
"no-process-toast");
+ if (vacopts.buffer_usage_limit)
+ pg_fatal("cannot use the \"%s\" option when performing only analyze",
+ "buffer-usage-limit");
/* allow 'and_analyze' with 'analyze_only' */
}
@@ -550,6 +559,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1061,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1131,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5f2a58b2c3..d8fba51d82 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
On Sat, 1 Apr 2023 at 12:57, Melanie Plageman <melanieplageman@gmail.com> wrote:
I've attached v7 with that commit dropped and with support for parallel
vacuum workers to use the same number of buffers in their own Buffer
Access Strategy ring as the main vacuum phase did. I also updated the
docs to indicate that vacuum_buffer_usage_limit is per backend (not per
instance of VACUUM).
(was just replying about v6-0002 when this came in. Replying here instead)
For v7-0001, can we just get rid of both of those static globals? I'm
gobsmacked by the existing "A few variables that don't seem worth
passing around as parameters" comment. Not wanting to pass parameters
around is a horrible excuse for adding global variables, even static
ones.
Attached is what I propose in .diff form so that the CFbot can run on
your v7 patches without picking this up.
I considered if we could switch memory contexts before calling
expand_vacuum_rel() and get_all_vacuum_rels(), but I see, at least in
the case of expand_vacuum_rel() that we'd probably want to list_free()
the output of find_all_inheritors() to save that from leaking into the
vac_context. It seems safe just to switch into the vac_context only
when we really want to keep that memory around. (I do think switching
in each iteration of the foreach(part_lc, part_oids) loop is
excessive, however. Just not enough for me to want to change it)
David
Attachments:
get_rid_of_a_few_globals_from_vacuum.c.diffapplication/octet-stream; name=get_rid_of_a_few_globals_from_vacuum.c.diffDownload
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..ca9d55e5bf 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -72,12 +72,6 @@ int vacuum_multixact_freeze_table_age;
int vacuum_failsafe_age;
int vacuum_multixact_failsafe_age;
-
-/* A few variables that don't seem worth passing around as parameters */
-static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
-
-
/*
* Variables for cost-based parallel vacuum. See comments atop
* compute_parallel_delay to understand how it works.
@@ -87,14 +81,15 @@ pg_atomic_uint32 *VacuumActiveNWorkers = NULL;
int VacuumCostBalanceLocal = 0;
/* non-export function prototypes */
-static List *expand_vacuum_rel(VacuumRelation *vrel, int options);
-static List *get_all_vacuum_rels(int options);
+static List *expand_vacuum_rel(MemoryContext vac_context,
+ VacuumRelation *vrel, int options);
+static List *get_all_vacuum_rels(MemoryContext vac_context, int options);
static void vac_truncate_clog(TransactionId frozenXID,
MultiXactId minMulti,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -313,6 +308,7 @@ vacuum(List *relations, VacuumParams *params,
{
static bool in_vacuum = false;
+ MemoryContext vac_context;
const char *stmttype;
volatile bool in_outer_xact,
use_own_xacts;
@@ -338,9 +334,9 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
- * vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
- * calls a hostile index expression that itself calls ANALYZE.
+ * Check for and disallow recursive calls. This could happen when VACUUM
+ * FULL or ANALYZE calls a hostile index expression that itself calls
+ * ANALYZE.
*/
if (in_vacuum)
ereport(ERROR,
@@ -404,7 +400,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -426,7 +421,7 @@ vacuum(List *relations, VacuumParams *params,
List *sublist;
MemoryContext old_context;
- sublist = expand_vacuum_rel(vrel, params->options);
+ sublist = expand_vacuum_rel(vac_context, vrel, params->options);
old_context = MemoryContextSwitchTo(vac_context);
newrels = list_concat(newrels, sublist);
MemoryContextSwitchTo(old_context);
@@ -434,7 +429,7 @@ vacuum(List *relations, VacuumParams *params,
relations = newrels;
}
else
- relations = get_all_vacuum_rels(params->options);
+ relations = get_all_vacuum_rels(vac_context, params->options);
/*
* Decide whether we need to start/commit our own transactions.
@@ -509,7 +504,8 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false,
+ bstrategy))
continue;
}
@@ -527,7 +523,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -582,7 +578,6 @@ vacuum(List *relations, VacuumParams *params,
* context!
*/
MemoryContextDelete(vac_context);
- vac_context = NULL;
}
/*
@@ -760,7 +755,8 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options,
* are made in vac_context.
*/
static List *
-expand_vacuum_rel(VacuumRelation *vrel, int options)
+expand_vacuum_rel(MemoryContext vac_context, VacuumRelation *vrel,
+ int options)
{
List *vacrels = NIL;
MemoryContext oldcontext;
@@ -899,7 +895,7 @@ expand_vacuum_rel(VacuumRelation *vrel, int options)
* the current database. The list is built in vac_context.
*/
static List *
-get_all_vacuum_rels(int options)
+get_all_vacuum_rels(MemoryContext vac_context, int options)
{
List *vacrels = NIL;
Relation pgclass;
@@ -1838,7 +1834,8 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
+ bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2081,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2115,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
On Sat, Apr 01, 2023 at 01:05:19PM +1300, David Rowley wrote:
Attached is what I propose in .diff form so that the CFbot can run on
your v7 patches without picking this up.
But it processes .diff, too
https://wiki.postgresql.org/wiki/Cfbot#Which_attachments_are_considered_to_be_patches.3F
On Fri, Mar 31, 2023 at 8:05 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Sat, 1 Apr 2023 at 12:57, Melanie Plageman <melanieplageman@gmail.com> wrote:
I've attached v7 with that commit dropped and with support for parallel
vacuum workers to use the same number of buffers in their own Buffer
Access Strategy ring as the main vacuum phase did. I also updated the
docs to indicate that vacuum_buffer_usage_limit is per backend (not per
instance of VACUUM).(was just replying about v6-0002 when this came in. Replying here instead)
For v7-0001, can we just get rid of both of those static globals? I'm
gobsmacked by the existing "A few variables that don't seem worth
passing around as parameters" comment. Not wanting to pass parameters
around is a horrible excuse for adding global variables, even static
ones.
Makes sense to me.
Attached is what I propose in .diff form so that the CFbot can run on
your v7 patches without picking this up.
Your diff LGTM.
Earlier upthread in [1]/messages/by-id/CALj2ACXKgAQpKsCPi6ox+K5JLDB9TAxeObyVOfrmgTjqmc0aAA@mail.gmail.com, Bharath had mentioned in a review comment about
removing the global variables that he would have expected the analogous
global in analyze.c to also be removed (vac_strategy [and analyze.c also
has anl_context]).
I looked into doing this, and this is what I found out (see full
rationale in [2]/messages/by-id/CAAKRu_brtqmd4e7kwEeKjySP22y4ywF32M7pvpi+x5txgF0+ig@mail.gmail.com):
it is a bit harder to remove it from analyze because acquire_func
doesn't take the buffer access strategy as a parameter and
acquire_sample_rows uses the vac_context global variable to pass to
table_scan_analyze_next_block().
I don't know if this is worth mentioning in the commit removing the
other globals? Maybe it will just make it more confusing...
I considered if we could switch memory contexts before calling
expand_vacuum_rel() and get_all_vacuum_rels(), but I see, at least in
the case of expand_vacuum_rel() that we'd probably want to list_free()
the output of find_all_inheritors() to save that from leaking into the
vac_context. It seems safe just to switch into the vac_context only
when we really want to keep that memory around. (I do think switching
in each iteration of the foreach(part_lc, part_oids) loop is
excessive, however. Just not enough for me to want to change it)
Yes, I see what you mean. Your decision makes sense to me.
- Melanie
[1]: /messages/by-id/CALj2ACXKgAQpKsCPi6ox+K5JLDB9TAxeObyVOfrmgTjqmc0aAA@mail.gmail.com
[2]: /messages/by-id/CAAKRu_brtqmd4e7kwEeKjySP22y4ywF32M7pvpi+x5txgF0+ig@mail.gmail.com
Hi,
I was just doing some cleanup on the main patch in this set and realized
that it was missing a few things. One of which is forbidding the
BUFFER_USAGE_LIMIT with VACUUM FULL since VACUUM FULL does not use a
BAS_VACUUM strategy.
VACUUM FULL technically uses a bulkread buffer access strategy for
reading the original relation if its number of blocks is > number of
shared buffers / 4 (see initscan()). The new rel writing is done using
smgrextend/write directly and doesn't go through shared buffers. I
think it is a stretch to try and use the size passed in to VACUUM by
BUFFER_USAGE_LIMIT for the bulkread strategy ring.
As for forbidding the combination, I noticed that when VACUUM FULL is
specified with INDEX_CLEANUP OFF, there is no syntax error but the
INDEX_CLEANUP option is simply ignored. This is documented behavior.
I somehow feel like VACUUM (FULL, BUFFER_USAGE_LIMIT 'x') should error
out instead of silently not using the buffer usage limit, though.
I am looking for others' opinions.
- Melanie
On Sat, Apr 01, 2023 at 01:29:13PM -0400, Melanie Plageman wrote:
Hi,
I was just doing some cleanup on the main patch in this set and realized
that it was missing a few things. One of which is forbidding the
BUFFER_USAGE_LIMIT with VACUUM FULL since VACUUM FULL does not use a
BAS_VACUUM strategy.VACUUM FULL technically uses a bulkread buffer access strategy for
reading the original relation if its number of blocks is > number of
shared buffers / 4 (see initscan()). The new rel writing is done using
smgrextend/write directly and doesn't go through shared buffers. I
think it is a stretch to try and use the size passed in to VACUUM by
BUFFER_USAGE_LIMIT for the bulkread strategy ring.
When you say that it's a stretch, do you mean that it'd be a pain to add
arguments to handful of functions to pass down the setting ? Or that
it's unclear if doing so would be the desirable/needed/intended/expected
behavior ?
I think if VACUUM FULL were going to allow a configurable strategy size,
then so should CLUSTER. But it seems fine if they don't.
I wonder if maybe strategy should be configurable in some more generic
way, like a GUC. At one point I had a patch to allow INSERT to use
strategy buffers (not just INSERT SELECT). And that's still pretty
desirable. Also COPY. I've seen load spikes caused by pg_dumping
tables which are just below 25% of shared_buffers. Which is exacerbated
because pg_dump deliberately orders tables by size, so those tables are
dumped one after another, each causing eviction of ~20% of shared
buffers. And exacerbated some more because TOAST don't seem to use a
ring buffer in that case.
I somehow feel like VACUUM (FULL, BUFFER_USAGE_LIMIT 'x') should error
out instead of silently not using the buffer usage limit, though.I am looking for others' opinions.
Sorry, no opinion here :)
One thing is that it's fine to take something that previously throw an
error and change it to not throw an error anymore. But it's undesirable
to do the opposite. For that reason, there's may be a tendency to add
errors for cases like this.
--
Justin
On Sat, Apr 1, 2023 at 1:57 PM Justin Pryzby <pryzby@telsasoft.com> wrote:
On Sat, Apr 01, 2023 at 01:29:13PM -0400, Melanie Plageman wrote:
Hi,
I was just doing some cleanup on the main patch in this set and realized
that it was missing a few things. One of which is forbidding the
BUFFER_USAGE_LIMIT with VACUUM FULL since VACUUM FULL does not use a
BAS_VACUUM strategy.VACUUM FULL technically uses a bulkread buffer access strategy for
reading the original relation if its number of blocks is > number of
shared buffers / 4 (see initscan()). The new rel writing is done using
smgrextend/write directly and doesn't go through shared buffers. I
think it is a stretch to try and use the size passed in to VACUUM by
BUFFER_USAGE_LIMIT for the bulkread strategy ring.When you say that it's a stretch, do you mean that it'd be a pain to add
arguments to handful of functions to pass down the setting ? Or that
it's unclear if doing so would be the desirable/needed/intended/expected
behavior ?
More that I don't think it makes sense. VACUUM FULL only uses a buffer
access strategy (BAS_BULKREAD) for reading the original relation in and
not for writing the new one. It has different concerns because its
behavior is totally different from regular vacuum. It is not modifying
the original buffers (AFAIK) and the amount of WAL it is generating is
different. Also, no matter what, the new relation won't be in shared
buffers because of VACUUM FULL using the smgr functions directly. So, I
think that allowing the two options together is confusing for the user
because it seems to imply we can give them some benefit that we cannot.
I wonder if maybe strategy should be configurable in some more generic
way, like a GUC. At one point I had a patch to allow INSERT to use
strategy buffers (not just INSERT SELECT). And that's still pretty
desirable. Also COPY. I've seen load spikes caused by pg_dumping
tables which are just below 25% of shared_buffers. Which is exacerbated
because pg_dump deliberately orders tables by size, so those tables are
dumped one after another, each causing eviction of ~20% of shared
buffers. And exacerbated some more because TOAST don't seem to use a
ring buffer in that case.
Yes, it is probably worth exploring how configurable or dynamic Buffer
Access Strategies should be for other users (e.g. not just VACUUM).
However, since the ring sizes wouldn't be the same for all the different
operations, it is probably easier to start with a single kind of
operation and go from there.
I somehow feel like VACUUM (FULL, BUFFER_USAGE_LIMIT 'x') should error
out instead of silently not using the buffer usage limit, though.I am looking for others' opinions.
Sorry, no opinion here :)
One thing is that it's fine to take something that previously throw an
error and change it to not throw an error anymore. But it's undesirable
to do the opposite. For that reason, there's may be a tendency to add
errors for cases like this.
So, I have made it error out when you specify BUFFER_USAGE_LIMIT with
VACUUM FULL or VACUUM ONLY_DATABASE_STATS. However, if you specify
buffer_usage_limit -1 with either of these options, it will not error
out. I don't love this, but I noticed that VACUUM (FULL, PARALLEL 0)
does not error out, while VACUUM (FULL, PARALLEL X) where X > 0 does.
If I want to error out when BUFFER_USAGE_LIMIT specified at all but
still do so at the bottom of ExecVacuum() with the rest of the vacuum
option sanity checking, I will probably need to add a flag bit for
VacuumParams->options.
I was wondering why some "sanity checking" of vacuum options is done in
ExecVacuum() and some in vacuum() (it isn't just split by what is
applicable to autovacuum and what isn't).
I noticed that even in cases where we don't use the strategy object we
still made it, which I thought seemed like a bit of a waste and easy to
fix. I've added a commit which does not make the BufferAccessStrategy
object when VACUUM FULL or VACUUM ONLY_DATABASE_STATS are specified. I
noticed that we also don't use the strategy for VACUUM (PROCESS_MAIN
false, PROCESS_TOAST false), but it didn't seem worth handling this very
specific case, so I didn't.
v8 attached has the prohibitions specified above (including for
vacuumdb, as relevant) as well as some cleanup, added test cases, and
updated documentation.
0001 is essentially unmodified (i.e. I didn't do anything with the other
global variable David mentioned).
I still have a few open questions:
- what the initial value of ring_size for autovacuum should be (see the
one remaining TODO in the code)
- should ANALYZE allow specifying BUFFER_USAGE_LIMIT since it uses the guc
value when that is set?
- should INDEX_CLEANUP off cause VACUUM to use shared buffers and
disable use of a strategy (like failsafe vacuum)
- should we add anything to VACUUM VERBOSE output about the number of
reuses of strategy buffers?
- Should we make BufferAccessStrategyData non-opaque so that we don't
have to add a getter for nbuffers. I could have implemented this in
another way, but I don't really see why BufferAccessStrategyData
should be opaque
- Melanie
Attachments:
v8-0001-remove-global-variable-vac_strategy.patchtext/x-patch; charset=US-ASCII; name=v8-0001-remove-global-variable-vac_strategy.patchDownload
From f1398dd6e4649424e754bbe1a8c00b3ea8755cde Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:06:41 -0500
Subject: [PATCH v8 1/6] remove global variable vac_strategy
---
src/backend/commands/vacuum.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c54360a6a0..4f7c132c2f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -75,7 +75,6 @@ int vacuum_multixact_failsafe_age;
/* A few variables that don't seem worth passing around as parameters */
static MemoryContext vac_context = NULL;
-static BufferAccessStrategy vac_strategy;
/*
@@ -94,7 +93,7 @@ static void vac_truncate_clog(TransactionId frozenXID,
TransactionId lastSaneFrozenXid,
MultiXactId lastSaneMinMulti);
static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
- bool skip_privs);
+ bool skip_privs, BufferAccessStrategy bstrategy);
static double compute_parallel_delay(void);
static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
@@ -338,9 +337,9 @@ vacuum(List *relations, VacuumParams *params,
in_outer_xact = IsInTransactionBlock(isTopLevel);
/*
- * Due to static variables vac_context, anl_context and vac_strategy,
- * vacuum() is not reentrant. This matters when VACUUM FULL or ANALYZE
- * calls a hostile index expression that itself calls ANALYZE.
+ * Due to static variable vac_context vacuum() is not reentrant. This
+ * matters when VACUUM FULL or ANALYZE calls a hostile index expression
+ * that itself calls ANALYZE.
*/
if (in_vacuum)
ereport(ERROR,
@@ -404,7 +403,6 @@ vacuum(List *relations, VacuumParams *params,
bstrategy = GetAccessStrategy(BAS_VACUUM);
MemoryContextSwitchTo(old_context);
}
- vac_strategy = bstrategy;
/*
* Build list of relation(s) to process, putting any new data in
@@ -509,7 +507,7 @@ vacuum(List *relations, VacuumParams *params,
if (params->options & VACOPT_VACUUM)
{
- if (!vacuum_rel(vrel->oid, vrel->relation, params, false))
+ if (!vacuum_rel(vrel->oid, vrel->relation, params, false, bstrategy))
continue;
}
@@ -527,7 +525,7 @@ vacuum(List *relations, VacuumParams *params,
}
analyze_rel(vrel->oid, vrel->relation, params,
- vrel->va_cols, in_outer_xact, vac_strategy);
+ vrel->va_cols, in_outer_xact, bstrategy);
if (use_own_xacts)
{
@@ -1838,7 +1836,7 @@ vac_truncate_clog(TransactionId frozenXID,
* At entry and exit, we are not inside a transaction.
*/
static bool
-vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
+vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs, BufferAccessStrategy bstrategy)
{
LOCKMODE lmode;
Relation rel;
@@ -2084,7 +2082,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
cluster_rel(relid, InvalidOid, &cluster_params);
}
else
- table_relation_vacuum(rel, params, vac_strategy);
+ table_relation_vacuum(rel, params, bstrategy);
}
/* Roll back any GUC changes executed by index functions */
@@ -2118,7 +2116,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, bool skip_privs)
memcpy(&toast_vacuum_params, params, sizeof(VacuumParams));
toast_vacuum_params.options |= VACOPT_PROCESS_MAIN;
- vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true);
+ vacuum_rel(toast_relid, NULL, &toast_vacuum_params, true, bstrategy);
}
/*
--
2.37.2
v8-0003-use-shared-buffers-when-failsafe-active.patchtext/x-patch; charset=US-ASCII; name=v8-0003-use-shared-buffers-when-failsafe-active.patchDownload
From 3383a88c0b817244bf19c05ab346291ce0339e0c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 22 Feb 2023 12:26:01 -0500
Subject: [PATCH v8 3/6] use shared buffers when failsafe active
---
doc/src/sgml/config.sgml | 8 +++++---
src/backend/access/heap/vacuumlazy.c | 8 ++++++++
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index fcb53c6997..7b8cf624dc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -9319,9 +9319,11 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
the failsafe to trigger during any <command>VACUUM</command>.
</para>
<para>
- When the failsafe is triggered, any cost-based delay that is
- in effect will no longer be applied, and further non-essential
- maintenance tasks (such as index vacuuming) are bypassed.
+ When the failsafe is triggered, any cost-based delay that is in effect
+ will no longer be applied, further non-essential maintenance tasks
+ (such as index vacuuming) are bypassed, and any Buffer Access Strategy
+ in use will be abandoned and the vacuum will be free to use as many
+ shared buffers as it needs to finish vacuuming the table.
</para>
<para>
The default is 1.6 billion transactions. Although users can
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index ae628d747d..0be41640be 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -2623,6 +2623,14 @@ lazy_check_wraparound_failsafe(LVRelState *vacrel)
{
vacrel->failsafe_active = true;
+ /*
+ * Abandon use of a buffer access strategy when entering failsafe
+ * mode, as completing the ongoing VACUUM is our top priority. Assume
+ * the caller who allocated the memory for the BufferAccessStrategy
+ * object will free it.
+ */
+ vacrel->bstrategy = NULL;
+
/* Disable index vacuuming, index cleanup, and heap rel truncation */
vacrel->do_index_vacuuming = false;
vacrel->do_index_cleanup = false;
--
2.37.2
v8-0005-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v8-0005-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From d087f002cd8ee34391ec2e7d635e9cfde893c849 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:08 -0400
Subject: [PATCH v8 5/6] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++++
doc/src/sgml/ref/analyze.sgml | 10 ++-
doc/src/sgml/ref/vacuum.sgml | 26 +++++++
src/backend/commands/vacuum.c | 62 ++++++++++++++-
src/backend/commands/vacuumparallel.c | 13 +++-
src/backend/postmaster/autovacuum.c | 19 ++++-
src/backend/storage/buffer/README | 21 ++++--
src/backend/storage/buffer/freelist.c | 75 ++++++++++++++++++-
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 2 +-
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 8 ++
src/test/regress/expected/vacuum.out | 17 +++++
src/test/regress/sql/vacuum.sql | 13 ++++
17 files changed, 299 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7b8cf624dc..4974e4eaac 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a <glossterm
+ linkend="glossary-buffer-access-strategy">Buffer Access
+ Strategy</glossterm>. <literal>0</literal> will disable use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal> will
+ set the size to a default of <literal>256 kB</literal>. The maximum
+ ring buffer size is <literal>16 GB</literal>. Though you may set
+ <varname>vacuum_buffer_usage_limit</varname> below <literal>128
+ kB</literal>, it will be clamped to <literal>128 kB</literal> at
+ runtime. The default value is <literal>-1</literal>. If this value is
+ specified without units, it is taken as kilobytes. This parameter can
+ be set at any time. It can be overridden for <link
+ linkend="sql-vacuum"><command>VACUUM</command></link> when passing the
+ <command>BUFFER_USAGE_LIMIT</command> parameter.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..019d34423a 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -51,9 +51,13 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
Without a <replaceable class="parameter">table_and_columns</replaceable>
list, <command>ANALYZE</command> processes every table and materialized view
in the current database that the current user has permission to analyze.
- With a list, <command>ANALYZE</command> processes only those table(s).
- It is further possible to give a list of column names for a table,
- in which case only the statistics for those columns are collected.
+ With a list, <command>ANALYZE</command> processes only those table(s). It is
+ further possible to give a list of column names for a table, in which case
+ only the statistics for those columns are collected.
+ <command>ANALYZE</command> uses a <glossterm
+ linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ when reading in the sample data. The number of buffers consumed for this can
+ be controlled by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
</para>
<para>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..df21a35ef5 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,31 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate the number of shared buffers which will be reused as
+ part of a <glossterm linkend="glossary-buffer-access-strategy">Buffer
+ Access Strategy</glossterm>. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>. The maximum
+ value is <literal>16 GB</literal>. Though you may specify a size smaller
+ than <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. If this value is specified without units, it is
+ taken as kilobytes. This size applies to a backend participating in a
+ single invocation of <command>VACUUM</command>. This option can't be used
+ with the <literal>FULL</literal> option or
+ <literal>ONLY_DATABASE_STATS</literal> option. If
+ <literal>ANALYZE</literal> is also specified, the
+ <literal>BUFFER_USAGE_LIMIT</literal> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 0cc9c31523..6780ea0a70 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -128,6 +128,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -211,6 +214,42 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+ const char *hintmsg;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -242,6 +281,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -402,7 +451,18 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(params->ring_size >= -1);
+
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 2cdbd182b6..10f54377d4 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = bas_nbuffers(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,8 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithNBuffers(BAS_VACUUM, shared->ring_nbuffers);
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..ce54535692 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,15 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2884,6 +2890,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * TODO: should this be 0 so that we are sure that vacuum() never
+ * allocates a new bstrategy for us, even if we pass in NULL for that
+ * parameter? maybe could change how failsafe NULLs out bstrategy if
+ * so?
+ */
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d1be1ca5b7 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,21 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM's default Buffer Access Strategy uses a 256KB ring like sequential
+scans, but dirty pages are not removed from the ring. Instead, WAL is flushed
+if needed to allow reuse of the buffers. Before introducing the buffer ring
+strategy in 8.3, VACUUM's buffers were sent to the freelist, which was
+effectively a buffer ring of 1 buffer, resulting in excessive WAL flushing.
+Allowing VACUUM to update 256KB between WAL flushes should be more efficient.
+
+As an alternative, VACUUM can use a user-specified ring size. The VACUUM
+parameter "BUFFER_USAGE_LIMIT" and GUC vacuum_buffer_usage_limit can be used to
+specify the amount of shared memory to be used during vacuuming. This size is
+used to calculate the number of buffers in the ring when it is created. A value
+of 0 for vacuum_buffer_usage_limit will disable use of the Buffer Access
+Strategy and allow vacuuming to use shared buffers as normal.
+In failsafe mode, autovacuum will always abandon use of a Buffer Access
+Strategy.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..a387c8b9c6 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -531,6 +531,23 @@ StrategyInitialize(bool init)
* ----------------------------------------------------------------
*/
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+
+int
+bas_nbuffers(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* GetAccessStrategy -- create a BufferAccessStrategy object
@@ -540,7 +557,6 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
int nbuffers;
/*
@@ -574,6 +590,17 @@ GetAccessStrategy(BufferAccessStrategyType btype)
/* Make sure ring isn't an undue fraction of shared buffers */
nbuffers = Min(NBuffers / 8, nbuffers);
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+
+BufferAccessStrategy
+GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers)
+{
+ BufferAccessStrategy strategy;
+
+ Assert(nbuffers > 0);
+
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
@@ -586,6 +613,52 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ int nbuffers;
+ int clamped_nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size >= 0);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ {
+ ereport(DEBUG1,
+ (errmsg_internal("Buffer Access Strategy ring_size %d kB has been clamped to minimum %d kB",
+ ring_size,
+ MIN_BAS_RING_SIZE_KB)));
+
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ }
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ clamped_nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /*
+ * Though default GetAccessStrategy() may also clamp the number of
+ * buffers, only bother warning the user when the input size was
+ * user-specified.
+ */
+ if (clamped_nbuffers < nbuffers)
+ ereport(DEBUG1,
+ (errmsg_internal("active Buffer Access Strategy may use a maximum of %d buffers. %d has been clamped",
+ NBuffers / 8,
+ nbuffers)));
+
+ nbuffers = clamped_nbuffers;
+
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5476c0f4aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..91599a4975 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..26947f7928 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..5f2a58b2c3 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..c5d67ad3b9 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -194,7 +197,12 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+extern int bas_nbuffers(BufferAccessStrategy strategy);
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
+
+extern BufferAccessStrategy GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers);
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..7542129f52 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,23 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- works with PARALLEL option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', PARALLEL 2) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
+-- value exceeds ring size max error
+VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
+ERROR: buffer_usage_limit for a vacuum must be between -1 and 16777216
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
+-- incompatible with VACUUM ONLY_DATABASE_STATS error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..a3a99ff56c 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,19 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- works with PARALLEL option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', PARALLEL 2) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+-- value exceeds ring size max error
+VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+-- incompatible with VACUUM ONLY_DATABASE_STATS error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
v8-0002-Don-t-make-vacuum-strategy-ring-when-unused.patchtext/x-patch; charset=US-ASCII; name=v8-0002-Don-t-make-vacuum-strategy-ring-when-unused.patchDownload
From 8f0af2e3b7f2db10f8d5c4013da7dcf71b6a3d0d Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 2 Apr 2023 10:28:09 -0400
Subject: [PATCH v8 2/6] Don't make vacuum strategy ring when unused
VACUUM FULL and VACUUM ONLY_DATABASE_STATS will not use the vacuum
strategy ring created in vacuum(), so don't waste time and memory making
it. It is worth noting that VACUUM (PROCESS_MAIN false, PROCESS_TOAST
false) will also not use it, but that doesn't seem worth handling
specifically.
---
src/backend/commands/vacuum.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 4f7c132c2f..0cc9c31523 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -396,7 +396,9 @@ vacuum(List *relations, VacuumParams *params,
* If caller didn't give us a buffer strategy object, make one in the
* cross-transaction memory context.
*/
- if (bstrategy == NULL)
+ if (bstrategy == NULL &&
+ !(params->options & VACOPT_ONLY_DATABASE_STATS ||
+ params->options & VACOPT_FULL))
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
--
2.37.2
v8-0004-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchtext/x-patch; charset=US-ASCII; name=v8-0004-Rename-Buffer-Access-Strategy-ring_size-nbuffers.patchDownload
From e266f181f4b7665cb663a2b4a1e6872d45526f39 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 13:15:24 -0400
Subject: [PATCH v8 4/6] Rename Buffer Access Strategy->ring_size nbuffers
ring_size sounds like it is in units of bytes when it is actually a
number of buffers. Rename it to nbuffers to make future commit related
to ring size less confusing.
---
src/backend/storage/buffer/freelist.c | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index c690d5f15f..f122709fbe 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -74,7 +74,7 @@ typedef struct BufferAccessStrategyData
/* Overall strategy type */
BufferAccessStrategyType btype;
/* Number of elements in buffers[] array */
- int ring_size;
+ int nbuffers;
/*
* Index of the "current" slot in the ring, ie, the one most recently
@@ -541,7 +541,7 @@ BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
BufferAccessStrategy strategy;
- int ring_size;
+ int nbuffers;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +556,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
case BAS_BULKWRITE:
- ring_size = 16 * 1024 * 1024 / BLCKSZ;
+ nbuffers = 16 * 1024 * 1024 / BLCKSZ;
break;
case BAS_VACUUM:
- ring_size = 256 * 1024 / BLCKSZ;
+ nbuffers = 256 * 1024 / BLCKSZ;
break;
default:
@@ -572,16 +572,16 @@ GetAccessStrategy(BufferAccessStrategyType btype)
}
/* Make sure ring isn't an undue fraction of shared buffers */
- ring_size = Min(NBuffers / 8, ring_size);
+ nbuffers = Min(NBuffers / 8, nbuffers);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
- ring_size * sizeof(Buffer));
+ nbuffers * sizeof(Buffer));
/* Set fields that don't start out zero */
strategy->btype = btype;
- strategy->ring_size = ring_size;
+ strategy->nbuffers = nbuffers;
return strategy;
}
@@ -615,7 +615,7 @@ GetBufferFromRing(BufferAccessStrategy strategy, uint32 *buf_state)
/* Advance to next ring slot */
- if (++strategy->current >= strategy->ring_size)
+ if (++strategy->current >= strategy->nbuffers)
strategy->current = 0;
/*
--
2.37.2
v8-0006-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v8-0006-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From f59309c221f4ff776d0a6ebee5e1152300a884a1 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v8 6/6] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 12 ++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 38 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8cedef8d79 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size of the ring buffer used for vacuuming. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ a <glossterm linkend="glossary-buffer-access-strategy">Buffer Access
+ Strategy</glossterm>. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5f2a58b2c3..d8fba51d82 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
On Sat, 1 Apr 2023 at 13:24, Melanie Plageman <melanieplageman@gmail.com> wrote:
Your diff LGTM.
Earlier upthread in [1], Bharath had mentioned in a review comment about
removing the global variables that he would have expected the analogous
global in analyze.c to also be removed (vac_strategy [and analyze.c also
has anl_context]).I looked into doing this, and this is what I found out (see full
rationale in [2]):it is a bit harder to remove it from analyze because acquire_func
doesn't take the buffer access strategy as a parameter and
acquire_sample_rows uses the vac_context global variable to pass to
table_scan_analyze_next_block().I don't know if this is worth mentioning in the commit removing the
other globals? Maybe it will just make it more confusing...
I did look at that, but it seems a little tricky to make work unless
the AcquireSampleRowsFunc signature was changed. To me, it just does
not seem worth doing that to get rid of the two globals in analyze.c.
I pushed the patch with just the vacuum.c changes.
David
I've now pushed up v8-0004. Can rebase the remaining 2 patches on top
of master again and resend?
On Mon, 3 Apr 2023 at 08:11, Melanie Plageman <melanieplageman@gmail.com> wrote:
I still have a few open questions:
- what the initial value of ring_size for autovacuum should be (see the
one remaining TODO in the code)
I assume you're talking about the 256KB BAS_VACUUM one set in
GetAccessStrategy()? I don't think this patch should be doing anything
to change those defaults. Anything that does that should likely have
a new thread and come with analysis or reasoning about why the newly
proposed defaults are better than the old ones.
- should ANALYZE allow specifying BUFFER_USAGE_LIMIT since it uses the guc
value when that is set?
That's a good question...
- should INDEX_CLEANUP off cause VACUUM to use shared buffers and
disable use of a strategy (like failsafe vacuum)
I don't see why it should. It seems strange to have one option
magically make changes to some other option.
- should we add anything to VACUUM VERBOSE output about the number of
reuses of strategy buffers?
Sounds like this would require an extra array of counter variables in
BufferAccessStrategyData? I think it might be a bit late to start
experimenting with this.
- Should we make BufferAccessStrategyData non-opaque so that we don't
have to add a getter for nbuffers. I could have implemented this in
another way, but I don't really see why BufferAccessStrategyData
should be opaque
If nothing outside of the .c file requires access then there's little
need to make the members known outside of the file. Same as you'd want
to make classes private rather than public when possible in OOP.
If you do come up with a reason to be able to determine the size of
the BufferAccessStrategy from outside freelist.c, I'd say an accessor
method is the best way.
David
David
On Mon, Apr 3, 2023 at 1:09 AM David Rowley <dgrowleyml@gmail.com> wrote:
On Sat, 1 Apr 2023 at 13:24, Melanie Plageman <melanieplageman@gmail.com> wrote:
Your diff LGTM.
Earlier upthread in [1], Bharath had mentioned in a review comment about
removing the global variables that he would have expected the analogous
global in analyze.c to also be removed (vac_strategy [and analyze.c also
has anl_context]).I looked into doing this, and this is what I found out (see full
rationale in [2]):it is a bit harder to remove it from analyze because acquire_func
doesn't take the buffer access strategy as a parameter and
acquire_sample_rows uses the vac_context global variable to pass to
table_scan_analyze_next_block().I don't know if this is worth mentioning in the commit removing the
other globals? Maybe it will just make it more confusing...I did look at that, but it seems a little tricky to make work unless
the AcquireSampleRowsFunc signature was changed. To me, it just does
not seem worth doing that to get rid of the two globals in analyze.c.
Yes, I came to basically the same conclusion.
On Mon, Apr 3, 2023 at 7:57 AM David Rowley <dgrowleyml@gmail.com> wrote:
I've now pushed up v8-0004. Can rebase the remaining 2 patches on top
of master again and resend?
v9 attached.
On Mon, 3 Apr 2023 at 08:11, Melanie Plageman <melanieplageman@gmail.com> wrote:
I still have a few open questions:
- what the initial value of ring_size for autovacuum should be (see the
one remaining TODO in the code)I assume you're talking about the 256KB BAS_VACUUM one set in
GetAccessStrategy()? I don't think this patch should be doing anything
to change those defaults. Anything that does that should likely have
a new thread and come with analysis or reasoning about why the newly
proposed defaults are better than the old ones.
I actually was talking about something much more trivial but a little
more confusing.
In table_recheck_autovac(), I initialize the
autovac_table->at_params.ring_size to the value of the
vacuum_buffer_usage_limit guc. However, autovacuum makes its own
BufferAccessStrategy object (instead of relying on vacuum() to do it)
and passes that in to vacuum(). So, if we wanted autovacuum to disable
use of a strategy (and use as many shared buffers as it likes), it would
pass in NULL to vacuum(). If vauum_buffer_usage_limit is not 0, then we
would end up making and using a BufferAccessStrategy in vacuum().
If we instead initialized autovac_table->at_params.ring_size to 0, even
if the passed in BufferAccessStrategy is NULL, we wouldn't make a ring
for autovacuum. Right now, we don't disable the strategy for autovacuum
except in failsafe mode. And it is unclear when or why we would want to.
I also thought it might be weird to have the value of the ring_size be
initialized to something other than the value of
vacuum_buffer_usage_limit for autovacuum, since it is supposed to use
that guc value.
In fact, right now, we don't use the autovac_table->at_params.ring_size
set in table_recheck_autovac() when making the ring in do_autovacuum()
but instead use the guc directly.
I actually don't really like how vacuum() relies on the
BufferAccessStrategy parameter being NULL for autovacuum and feel like
there is a more intuitive way to handle all this. But, I didn't want to
make major changes at this point.
Anyway, the above is quite a bit more analysis than the issue is really
worth. We should pick something and then document it in a comment.
- should ANALYZE allow specifying BUFFER_USAGE_LIMIT since it uses the guc
value when that is set?That's a good question...
I kinda think we should just skip it. It adds to the surface area of the
feature.
- should INDEX_CLEANUP off cause VACUUM to use shared buffers and
disable use of a strategy (like failsafe vacuum)I don't see why it should. It seems strange to have one option
magically make changes to some other option.
Sure, sounds good.
- should we add anything to VACUUM VERBOSE output about the number of
reuses of strategy buffers?Sounds like this would require an extra array of counter variables in
BufferAccessStrategyData? I think it might be a bit late to start
experimenting with this.
Makes sense. I hadn't thought through the implementation. We count reuses in
pg_stat_io data structures but that is global and not per
BufferAccessStrategyData instance, so I agree to scrapping this idea.
- Should we make BufferAccessStrategyData non-opaque so that we don't
have to add a getter for nbuffers. I could have implemented this in
another way, but I don't really see why BufferAccessStrategyData
should be opaqueIf nothing outside of the .c file requires access then there's little
need to make the members known outside of the file. Same as you'd want
to make classes private rather than public when possible in OOP.If you do come up with a reason to be able to determine the size of
the BufferAccessStrategy from outside freelist.c, I'd say an accessor
method is the best way.
In the main patch, I wanted access to the number of buffers so that
parallel vacuum workers could make their own rings the same size. I
added an accessor, but it looked a bit silly so I thought I would ask if
we needed to keep the data structure opaque. It isn't called frequently
enough to worry about the function call overhead. Though the accessor
could use a better name than the one I chose.
- Melanie
Attachments:
v9-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v9-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From 2339872e09b60ec599f171065b4e4597fe8f0f61 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:08 -0400
Subject: [PATCH v9 1/2] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 +++++++
doc/src/sgml/ref/analyze.sgml | 10 ++-
doc/src/sgml/ref/vacuum.sgml | 26 ++++++
src/backend/commands/vacuum.c | 62 ++++++++++++-
src/backend/commands/vacuumparallel.c | 13 ++-
src/backend/postmaster/autovacuum.c | 19 +++-
src/backend/storage/buffer/README | 21 +++--
src/backend/storage/buffer/freelist.c | 90 ++++++++++++++++++-
src/backend/utils/init/globals.c | 2 +
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 2 +-
src/include/commands/vacuum.h | 1 +
src/include/miscadmin.h | 1 +
src/include/storage/bufmgr.h | 8 ++
src/test/regress/expected/vacuum.out | 17 ++++
src/test/regress/sql/vacuum.sql | 13 +++
17 files changed, 314 insertions(+), 16 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 5bad13d24a..bae2486bcc 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a <glossterm
+ linkend="glossary-buffer-access-strategy">Buffer Access
+ Strategy</glossterm>. <literal>0</literal> will disable use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal> will
+ set the size to a default of <literal>256 kB</literal>. The maximum
+ ring buffer size is <literal>16 GB</literal>. Though you may set
+ <varname>vacuum_buffer_usage_limit</varname> below <literal>128
+ kB</literal>, it will be clamped to <literal>128 kB</literal> at
+ runtime. The default value is <literal>-1</literal>. If this value is
+ specified without units, it is taken as kilobytes. This parameter can
+ be set at any time. It can be overridden for <link
+ linkend="sql-vacuum"><command>VACUUM</command></link> when passing the
+ <command>BUFFER_USAGE_LIMIT</command> parameter.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..019d34423a 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -51,9 +51,13 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
Without a <replaceable class="parameter">table_and_columns</replaceable>
list, <command>ANALYZE</command> processes every table and materialized view
in the current database that the current user has permission to analyze.
- With a list, <command>ANALYZE</command> processes only those table(s).
- It is further possible to give a list of column names for a table,
- in which case only the statistics for those columns are collected.
+ With a list, <command>ANALYZE</command> processes only those table(s). It is
+ further possible to give a list of column names for a table, in which case
+ only the statistics for those columns are collected.
+ <command>ANALYZE</command> uses a <glossterm
+ linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ when reading in the sample data. The number of buffers consumed for this can
+ be controlled by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
</para>
<para>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..df21a35ef5 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,31 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the ring buffer size for <command>VACUUM</command>. This size
+ is used to calculate the number of shared buffers which will be reused as
+ part of a <glossterm linkend="glossary-buffer-access-strategy">Buffer
+ Access Strategy</glossterm>. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>. The maximum
+ value is <literal>16 GB</literal>. Though you may specify a size smaller
+ than <literal>128</literal>, the value will be clamped to <literal>128
+ kB</literal> at runtime. If this value is specified without units, it is
+ taken as kilobytes. This size applies to a backend participating in a
+ single invocation of <command>VACUUM</command>. This option can't be used
+ with the <literal>FULL</literal> option or
+ <literal>ONLY_DATABASE_STATS</literal> option. If
+ <literal>ANALYZE</literal> is also specified, the
+ <literal>BUFFER_USAGE_LIMIT</literal> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da85330ef4..d060c6e288 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -124,6 +124,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -207,6 +210,42 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
skip_database_stats = defGetBoolean(opt);
else if (strcmp(opt->defname, "only_database_stats") == 0)
only_database_stats = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ char *vac_buffer_size;
+ int result;
+ const char *hintmsg;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* check for out-of-bounds */
+ if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between -1 and %d",
+ MAX_BAS_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+ }
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -238,6 +277,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -401,7 +450,18 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(params->ring_size >= -1);
+
+ if (params->ring_size == -1)
+ {
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 2cdbd182b6..10f54377d4 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = bas_nbuffers(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,8 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithNBuffers(BAS_VACUUM, shared->ring_nbuffers);
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..ce54535692 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,15 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (vacuum_buffer_usage_limit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, vacuum_buffer_usage_limit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2884,6 +2890,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * TODO: should this be 0 so that we are sure that vacuum() never
+ * allocates a new bstrategy for us, even if we pass in NULL for that
+ * parameter? maybe could change how failsafe NULLs out bstrategy if
+ * so?
+ */
+ tab->at_params.ring_size = vacuum_buffer_usage_limit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d1be1ca5b7 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,21 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM's default Buffer Access Strategy uses a 256KB ring like sequential
+scans, but dirty pages are not removed from the ring. Instead, WAL is flushed
+if needed to allow reuse of the buffers. Before introducing the buffer ring
+strategy in 8.3, VACUUM's buffers were sent to the freelist, which was
+effectively a buffer ring of 1 buffer, resulting in excessive WAL flushing.
+Allowing VACUUM to update 256KB between WAL flushes should be more efficient.
+
+As an alternative, VACUUM can use a user-specified ring size. The VACUUM
+parameter "BUFFER_USAGE_LIMIT" and GUC vacuum_buffer_usage_limit can be used to
+specify the amount of shared memory to be used during vacuuming. This size is
+used to calculate the number of buffers in the ring when it is created. A value
+of 0 for vacuum_buffer_usage_limit will disable use of the Buffer Access
+Strategy and allow vacuuming to use shared buffers as normal.
+In failsafe mode, autovacuum will always abandon use of a Buffer Access
+Strategy.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..7a0637c4e2 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -163,6 +163,15 @@ ClockSweepTick(void)
return victim;
}
+/*
+ * bas_nbuffers -- an accessory for the number of buffers in the ring
+ */
+int
+bas_nbuffers(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* have_free_buffer -- a lockless check to see if there is a free buffer in
* buffer pool.
@@ -531,6 +540,19 @@ StrategyInitialize(bool init)
* ----------------------------------------------------------------
*/
+/*
+ * Helper to convert a size to a number of buffers.
+ */
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
/*
* GetAccessStrategy -- create a BufferAccessStrategy object
@@ -540,7 +562,6 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
int nbuffers;
/*
@@ -574,6 +595,23 @@ GetAccessStrategy(BufferAccessStrategyType btype)
/* Make sure ring isn't an undue fraction of shared buffers */
nbuffers = Min(NBuffers / 8, nbuffers);
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+
+/*
+ * GetAccessStrategyWithNBuffers -- create a BufferAccessStrategy object with
+ * space for the passed-in number of buffers
+ *
+ * Also used as a helper for the other GetAccessStrategy* methods.
+ */
+BufferAccessStrategy
+GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers)
+{
+ BufferAccessStrategy strategy;
+
+ Assert(nbuffers > 0);
+
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
@@ -586,6 +624,56 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size)
+{
+ int nbuffers;
+ int clamped_nbuffers;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size >= 0);
+
+ if (ring_size == 0)
+ return NULL;
+
+ Assert(ring_size <= MAX_BAS_RING_SIZE_KB);
+
+ if (ring_size < MIN_BAS_RING_SIZE_KB)
+ {
+ ereport(DEBUG1,
+ (errmsg_internal("Buffer Access Strategy ring_size %d kB has been clamped to minimum %d kB",
+ ring_size,
+ MIN_BAS_RING_SIZE_KB)));
+
+ nbuffers = bufsize_limit_to_nbuffers(MIN_BAS_RING_SIZE_KB);
+ }
+ else
+ nbuffers = bufsize_limit_to_nbuffers(ring_size);
+
+ clamped_nbuffers = Min(NBuffers / 8, nbuffers);
+
+ /*
+ * Though default GetAccessStrategy() may also clamp the number of
+ * buffers, only bother warning the user when the input size was
+ * user-specified.
+ */
+ if (clamped_nbuffers < nbuffers)
+ ereport(DEBUG1,
+ (errmsg_internal("active Buffer Access Strategy may use a maximum of %d buffers. %d has been clamped",
+ NBuffers / 8,
+ nbuffers)));
+
+ nbuffers = clamped_nbuffers;
+
+ return GetAccessStrategyWithNBuffers(btype, nbuffers);
+}
+
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..6eca3371bd 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,6 +139,8 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
+int vacuum_buffer_usage_limit = -1;
+
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5476c0f4aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &vacuum_buffer_usage_limit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ NULL, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..91599a4975 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..26947f7928 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..5f2a58b2c3 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,7 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..b572dfcc6c 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -262,6 +262,7 @@ extern PGDLLIMPORT int work_mem;
extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..41329bf416 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,9 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -194,7 +197,12 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+extern int bas_nbuffers(BufferAccessStrategy strategy);
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int nbuffers);
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype, int nbuffers);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..7542129f52 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,23 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- works with PARALLEL option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', PARALLEL 2) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
+-- value exceeds ring size max error
+VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
+ERROR: buffer_usage_limit for a vacuum must be between -1 and 16777216
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
+-- incompatible with VACUUM ONLY_DATABASE_STATS error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..a3a99ff56c 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,19 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- works with PARALLEL option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', PARALLEL 2) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+-- value exceeds ring size max error
+VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+-- incompatible with VACUUM ONLY_DATABASE_STATS error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
v9-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v9-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 46df3fa693ac9aabb8f3d4999aeeb08170dd049e Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v9 2/2] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 12 ++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 38 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8cedef8d79 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ The size of the ring buffer used for vacuuming. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ a <glossterm linkend="glossary-buffer-access-strategy">Buffer Access
+ Strategy</glossterm>. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 5f2a58b2c3..d8fba51d82 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
On Tue, 4 Apr 2023 at 02:49, Melanie Plageman <melanieplageman@gmail.com> wrote:
v9 attached.
I've made a pass on the v9-0001 patch only. Here's what I noted down:
v9-0001:
1. In the documentation and comments, generally we always double-space
after a period. I see quite often you're not following this.
2. Doc: We could generally seem to break tags within paragraphs into
multiple lines. You're doing that quite a bit, e.g:
linkend="glossary-buffer-access-strategy">Buffer Access
Strategy</glossterm>. <literal>0</literal> will disable use of a
2. This is not a command
<command>BUFFER_USAGE_LIMIT</command> parameter.
<option> is probably what you want.
3. I'm not sure I agree that it's a good idea to refer to the strategy
with multiple different names. Here you've called it a "ring buffer",
but in the next sentence, you're calling it a Buffer Access Strategy.
Specifies the ring buffer size for <command>VACUUM</command>. This size
is used to calculate the number of shared buffers which will be reused as
part of a <glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>. <literal>0</literal> disables use of a
4. Can you explain your choice in not just making < 128 a hard error
rather than clamping?
I guess it means checks like this are made more simple, but that does
not seem like a good enough reason:
/* check for out-of-bounds */
if (result < -1 || result > MAX_BAS_RING_SIZE_KB)
postgres=# vacuum (parallel -1) pg_class;
ERROR: parallel workers for vacuum must be between 0 and 1024
Maybe the above is a good guide to follow.
To allow you to get rid of the clamping code, you'd likely need an
assign hook function for vacuum_buffer_usage_limit.
5. I see vacuum.sgml is full of inconsistencies around the use of
<literal> vs <option>. I was going to complain about your:
<literal>ONLY_DATABASE_STATS</literal> option. If
<literal>ANALYZE</literal> is also specified, the
<literal>BUFFER_USAGE_LIMIT</literal> value is used for both the vacuum
but I see you've likely just copied what's nearby.
There are also plenty of usages of <option> in that file. I'd rather
see you use <option>. Maybe there can be some other patch that sweeps
the entire docs to look for <literal>OPTION_NAME</literal> and
replaces them to use <option>.
6. I was surprised to see you've added both
GetAccessStrategyWithSize() and GetAccessStrategyWithNBuffers(). I
think the former is suitable for both. GetAccessStrategyWithNBuffers()
seems to be just used once outside of freelist.c
7. I don't think bas_nbuffers() is a good name for an external
function. StrategyGetBufferCount() seems better.
8. I don't quite follow this comment:
/*
* TODO: should this be 0 so that we are sure that vacuum() never
* allocates a new bstrategy for us, even if we pass in NULL for that
* parameter? maybe could change how failsafe NULLs out bstrategy if
* so?
*/
Can you explain under what circumstances would vacuum() allocate a
bstrategy when do_autovacuum() would not? Is this a case of a config
reload where someone changes vacuum_buffer_usage_limit from 0 to
something non-zero? If so, perhaps do_autovacuum() needs to detect
this and allocate a strategy rather than having vacuum() do it once
per table (wastefully).
9. buffer/README. I think it might be overkill to document details
about how the new vacuum option works in a section talking about
Buffer Ring Replacement Strategy. Perhaps it just worth something
like:
"In v16, the 256KB ring was made configurable by way of the
vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT VACUUM
option."
10. I think if you do #4 then you can get rid of all the range checks
and DEBUG1 elogs in GetAccessStrategyWithSize().
11. This seems a bit badly done:
int vacuum_buffer_usage_limit = -1;
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
I'd class vacuum_buffer_usage_limit as a "GUC parameters for vacuum"
too. Probably the CamelCase naming should be followed too.
12. ANALYZE too?
{"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
13. VacuumParams.ring_size has no comments explaining what it is.
14. vacuum_buffer_usage_limit seems to be lumped in with unrelated GUCs
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
15. No comment explaining what these are:
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
#define MIN_BAS_RING_SIZE_KB 128
16. Parameter names in function declaration and definition don't match in:
extern BufferAccessStrategy
GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int
nbuffers);
extern BufferAccessStrategy
GetAccessStrategyWithSize(BufferAccessStrategyType btype, int
nbuffers);
Also, line wraps at 79 chars. (80 including line feed)
17. If you want to test the 16GB upper limit, maybe going 1KB (or
8KB?) rather than 1GB over 16GB is better? 2097153kB, I think.
VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
David
On Mon, Apr 3, 2023 at 8:37 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Tue, 4 Apr 2023 at 02:49, Melanie Plageman <melanieplageman@gmail.com> wrote:
v9 attached.
I've made a pass on the v9-0001 patch only. Here's what I noted down:
Thanks for the review!
Attached v10 addresses the review feedback below.
Remaining TODOs:
- tests
- do something about config reload changing GUC
v9-0001:
1. In the documentation and comments, generally we always double-space
after a period. I see quite often you're not following this.
I've gone through and done this. I noticed after building the docs that
it doesn't seem to affect how many spaces are after a period in the
rendered docs, but I suppose it affects readability when editing the
sgml files.
2. Doc: We could generally seem to break tags within paragraphs into
multiple lines. You're doing that quite a bit, e.g:linkend="glossary-buffer-access-strategy">Buffer Access
Strategy</glossterm>. <literal>0</literal> will disable use of a
I've updated all of the ones I could find that I did this with.
2. This is not a command
<command>BUFFER_USAGE_LIMIT</command> parameter.
<option> is probably what you want.
I have gone through and attempted to correct all
option/command/application tag usages.
3. I'm not sure I agree that it's a good idea to refer to the strategy
with multiple different names. Here you've called it a "ring buffer",
but in the next sentence, you're calling it a Buffer Access Strategy.Specifies the ring buffer size for <command>VACUUM</command>. This size
is used to calculate the number of shared buffers which will be reused as
part of a <glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>. <literal>0</literal> disables use of a
I've updated this to always prefix any use of ring with "Buffer Access
Strategy". I don't know how you'll feel about it. It felt awkward in
some places to use Buffer Access Strategy as a complete stand-in for
ring buffer.
4. Can you explain your choice in not just making < 128 a hard error
rather than clamping?I guess it means checks like this are made more simple, but that does
not seem like a good enough reason:/* check for out-of-bounds */
if (result < -1 || result > MAX_BAS_RING_SIZE_KB)postgres=# vacuum (parallel -1) pg_class;
ERROR: parallel workers for vacuum must be between 0 and 1024Maybe the above is a good guide to follow.
To allow you to get rid of the clamping code, you'd likely need an
assign hook function for vacuum_buffer_usage_limit.
I've added a check hook and replicated the same restrictions in
ExecVacuum() where it parses the limit. I have included enforcement of
the conditional limit that the ring cannot occupy more than 1/8 of
shared buffers. The immediate consequence of this was that my tests were
no longer stable (except for the integer overflow one).
I have removed them for now until I can come up with a better testing
strategy.
On the topic of testing, I also thought we should remove the
VACUUM(BUFFER_USAGE_LIMIT X, PARALLEL X) test. Though the parallel
workers do make their own strategy rings and such a test would be
covering some code, I am hesitant to write a test that would never
really fail. The observable behavior of not using a strategy will be
either 1) basically nothing or 2) the same for parallel and
non-parallel. What do you think?
5. I see vacuum.sgml is full of inconsistencies around the use of
<literal> vs <option>. I was going to complain about your:<literal>ONLY_DATABASE_STATS</literal> option. If
<literal>ANALYZE</literal> is also specified, the
<literal>BUFFER_USAGE_LIMIT</literal> value is used for both the vacuumbut I see you've likely just copied what's nearby.
There are also plenty of usages of <option> in that file. I'd rather
see you use <option>. Maybe there can be some other patch that sweeps
the entire docs to look for <literal>OPTION_NAME</literal> and
replaces them to use <option>.
I haven't done the separate sweep patch, but I have updated my own
usages in this set.
6. I was surprised to see you've added both
GetAccessStrategyWithSize() and GetAccessStrategyWithNBuffers(). I
think the former is suitable for both. GetAccessStrategyWithNBuffers()
seems to be just used once outside of freelist.c
This has been updated and reorganized.
7. I don't think bas_nbuffers() is a good name for an external
function. StrategyGetBufferCount() seems better.
I've used this name.
8. I don't quite follow this comment:
/*
* TODO: should this be 0 so that we are sure that vacuum() never
* allocates a new bstrategy for us, even if we pass in NULL for that
* parameter? maybe could change how failsafe NULLs out bstrategy if
* so?
*/Can you explain under what circumstances would vacuum() allocate a
bstrategy when do_autovacuum() would not? Is this a case of a config
reload where someone changes vacuum_buffer_usage_limit from 0 to
something non-zero? If so, perhaps do_autovacuum() needs to detect
this and allocate a strategy rather than having vacuum() do it once
per table (wastefully).
Hmm. Yes, I started hacking on this, but I think it might be a bit
tricky to get right. I think it would make sense to check if
vacuum_buffer_usage_limit goes from 0 to not 0 or from not 0 to 0 and
allow disabling and enabling the buffer access strategy, however, I'm
not sure we want to allow changing the size during an autovacuum
worker's run. I started writing code to just allow enabling and
disabling, but I'm a little concerned that the distinction will be
difficult to understand for the user with no obvious indication of what
is happening. That is, you change the size and it silently does nothing,
but you set it to/from 0 and it silently does something?
One alternative for now is to save the ring size before looping through
the relations in do_autovacuum() and always restore that value in
tab->at_params.ring_size in table_recheck_autovac().
I'm not sure what to do.
9. buffer/README. I think it might be overkill to document details
about how the new vacuum option works in a section talking about
Buffer Ring Replacement Strategy. Perhaps it just worth something
like:"In v16, the 256KB ring was made configurable by way of the
vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT VACUUM
option."
I've made the change you suggested.
10. I think if you do #4 then you can get rid of all the range checks
and DEBUG1 elogs in GetAccessStrategyWithSize().
Done.
11. This seems a bit badly done:
int vacuum_buffer_usage_limit = -1;
int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;I'd class vacuum_buffer_usage_limit as a "GUC parameters for vacuum"
too. Probably the CamelCase naming should be followed too.
I've made this change.
12. ANALYZE too?
{"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
gettext_noop("Sets the buffer pool size for VACUUM and autovacuum."),
I've mentioned this here and also added the option for ANALYZE.
13. VacuumParams.ring_size has no comments explaining what it is.
I've added one.
14. vacuum_buffer_usage_limit seems to be lumped in with unrelated GUCs
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int vacuum_buffer_usage_limit;extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
I've moved it down a line.
15. No comment explaining what these are:
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
#define MIN_BAS_RING_SIZE_KB 128
I've added one.
16. Parameter names in function declaration and definition don't match in:
extern BufferAccessStrategy
GetAccessStrategyWithNBuffers(BufferAccessStrategyType btype, int
nbuffers);
extern BufferAccessStrategy
GetAccessStrategyWithSize(BufferAccessStrategyType btype, int
nbuffers);
I've fixed this.
Also, line wraps at 79 chars. (80 including line feed)
I've fixed that function prototype instance of it.
In general line wrap limit + pgindent can be quite challenging. I often
break something onto multiple lines to appease the line limit and then
pgindent will add an absurd number of tabs to align the second line in a
way that looks truly awful. I try to make local variables when this is a
problem, but it is often quite annoying to do that. I wish there was
some way to make pgindent do something different in these cases.
17. If you want to test the 16GB upper limit, maybe going 1KB (or
8KB?) rather than 1GB over 16GB is better? 2097153kB, I think.VACUUM (BUFFER_USAGE_LIMIT '17 GB') vac_option_tab;
I've removed this test for now until I figure out a way to actually hit
this reliably with different-sized shared buffers.
- Melanie
Attachments:
v10-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v10-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From bacae5fac52f64a903f2d35758d443112a8e30cd Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v10 2/2] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 13 +++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 39 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8280cf0fb0 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2ee5147e97..f027d3ed71 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v10-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v10-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From bab9269bde542fdfe82bce0171a5031a6c5afa83 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:08 -0400
Subject: [PATCH v10 1/2] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 26 ++++-
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 85 +++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 19 +++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 98 +++++++++++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/commands/vacuum.h | 6 ++
src/include/miscadmin.h | 2 +
src/include/storage/bufmgr.h | 35 +++++++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 4 +
src/test/regress/sql/vacuum.sql | 3 +
18 files changed, 357 insertions(+), 18 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..7e6af71af3 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ <literal>0</literal> will disable use of a <literal>Buffer Access Strategy</literal>.
+ <literal>-1</literal> will set the size to a default of
+ <literal>256 KB</literal>. The maximum size is
+ <literal>16 GB</literal>. The minimum size is the lesser
+ of 1/8 the size of shared buffers and <literal>128 KB</literal>. The
+ default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <link linkend="sql-vacuum"><command>VACUUM</command></link> and
+ <link linkend="sql-analyze"><command>ANALYZE</command></link>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..5b1fe1d860 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -51,9 +52,14 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
Without a <replaceable class="parameter">table_and_columns</replaceable>
list, <command>ANALYZE</command> processes every table and materialized view
in the current database that the current user has permission to analyze.
- With a list, <command>ANALYZE</command> processes only those table(s).
- It is further possible to give a list of column names for a table,
- in which case only the statistics for those columns are collected.
+ With a list, <command>ANALYZE</command> processes only those table(s). It is
+ further possible to give a list of column names for a table, in which case
+ only the statistics for those columns are collected.
+ <command>ANALYZE</command> uses a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ when reading in the sample data. The number of buffers consumed for this can
+ be controlled by <xref linkend="guc-vacuum-buffer-usage-limit"/> or by using
+ the <option>BUFFER_USAGE_LIMIT</option> option.
</para>
<para>
@@ -95,6 +101,20 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. See the
+ <link linkend="sql-vacuum"><command>VACUUM</command></link> option with
+ the same name.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..7ebac56498 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da85330ef4..22365ee473 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -124,6 +124,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -134,6 +137,65 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ int sb_limit_kb;
+ char *vac_buffer_size;
+ int blcksz_kb = BLCKSZ / 1024;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /* 0 and -1 are okay */
+ if (result != -1 && result != 0)
+ {
+ sb_limit_kb = StrategyGetClampedBufsize(result);
+
+ /*
+ * Check that specified size does not exceed allowed fraction
+ * of shared buffers.
+ */
+ if (result != sb_limit_kb)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for vacuum cannot exceed %d KB because shared buffers is %d KB",
+ sb_limit_kb, NBuffers * blcksz_kb)));
+ }
+
+ /*
+ * Check that specified size falls within the hard upper and
+ * lower limits.
+ */
+ if (result < MIN_BAS_RING_SIZE_KB || result > MAX_BAS_RING_SIZE_KB)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be between %d KB and %d KB",
+ MIN_BAS_RING_SIZE_KB, MAX_BAS_RING_SIZE_KB)));
+ }
+ }
+
+ params.ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -238,6 +300,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -401,7 +473,18 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(params->ring_size >= -1);
+
+ if (params->ring_size == -1)
+ {
+ if (VacuumBufferUsageLimit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ }
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..9b5ee32cb0 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,15 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit == -1)
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2884,6 +2890,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * TODO: should this be 0 so that we are sure that vacuum() never
+ * allocates a new bstrategy for us, even if we pass in NULL for that
+ * parameter? maybe could change how failsafe NULLs out bstrategy if
+ * so?
+ */
+ tab->at_params.ring_size = VacuumBufferUsageLimit;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..ae777e9cd5 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -20,6 +20,7 @@
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/proc.h"
+#include "utils/guc_hooks.h"
#define INT_ACCESS_ONCE(var) ((int)(*((volatile int *)&(var))))
@@ -531,6 +532,42 @@ StrategyInitialize(bool init)
* ----------------------------------------------------------------
*/
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ int sb_limit_kb;
+
+ int blcksz_kb = BLCKSZ / 1024;
+
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Make sure ring isn't an undue fraction of shared buffers */
+ sb_limit_kb = StrategyGetClampedBufsize(*newval);
+
+ if (*newval != sb_limit_kb)
+ {
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" value cannot exceed %d KB because shared_buffers is %d KB",
+ sb_limit_kb, NBuffers * blcksz_kb);
+ return false;
+ }
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_RING_SIZE_KB && *newval <= MAX_BAS_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be between %d KB and %d KB",
+ MIN_BAS_RING_SIZE_KB, MAX_BAS_RING_SIZE_KB);
+
+ return false;
+}
/*
* GetAccessStrategy -- create a BufferAccessStrategy object
@@ -540,8 +577,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +592,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +607,45 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
- /* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+
+ return GetAccessStrategyWithSize(btype,
+ StrategyGetClampedBufsize(ring_size_kb));
+}
+
+
+/*
+ * Helper to convert a size to a number of buffers.
+ */
+static inline int
+bufsize_limit_to_nbuffers(int bufsize_limit_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ return bufsize_limit_kb / blcksz_kb;
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int nbuffers;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ nbuffers = bufsize_limit_to_nbuffers(ring_size_kb);
+
+ Assert(nbuffers <= NBuffers / 8 &&
+ nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +659,17 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..75e67cd74c 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..b962f500dc 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..2ee5147e97 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,12 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+
+ /*
+ * The size in KB of the Buffer Access Strategy ring to be used for VACUUM
+ * and ANALYZE.
+ */
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..03f4786fb2 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,8 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..f3a12b9277 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -101,6 +101,14 @@ extern PGDLLIMPORT int32 *LocalRefCount;
/* upper limit for effective_io_concurrency */
#define MAX_IO_CONCURRENCY 1000
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_RING_SIZE_KB 128
+
/* special block number for ReadBuffer() */
#define P_NEW InvalidBlockNumber /* grow the file to get a new page */
@@ -194,7 +202,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
@@ -286,6 +300,27 @@ BufferGetPage(Buffer buffer)
return (Page) BufferGetBlock(buffer);
}
+/*
+ * StrategyGetClampedBufsize
+ * Returns a clamped buffer size in KB
+ *
+ * Buffer Access Strategies should not consume more than the 1/8 of shared
+ * buffers. Given a size in KB, this helper calculates an acceptable clamped
+ * value, given the current size of shared buffers.
+ */
+static inline int
+StrategyGetClampedBufsize(int bufsize_kb)
+{
+ int sb_limit_kb;
+ int blcksz_kb = BLCKSZ / 1024;
+
+ Assert(blcksz_kb > 0);
+
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+
+ return Min(sb_limit_kb, bufsize_kb);
+}
+
/*
* Check whether the given snapshot is too old to have safely read the given
* page from the given table. If so, throw a "snapshot too old" error.
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..fe675c5a2e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,10 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..fc068b7705 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,9 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
On Wed, 5 Apr 2023 at 05:53, Melanie Plageman <melanieplageman@gmail.com> wrote:
Attached v10 addresses the review feedback below.
Thanks. Here's another round on v10-0001:
1. The following documentation fragment does not seem to be aligned
with the code:
<literal>16 GB</literal>. The minimum size is the lesser
of 1/8 the size of shared buffers and <literal>128 KB</literal>. The
default value is <literal>-1</literal>. If this value is specified
The relevant code is:
static inline int
StrategyGetClampedBufsize(int bufsize_kb)
{
int sb_limit_kb;
int blcksz_kb = BLCKSZ / 1024;
Assert(blcksz_kb > 0);
sb_limit_kb = NBuffers / 8 * blcksz_kb;
return Min(sb_limit_kb, bufsize_kb);
}
The code seems to mean that the *maximum* is the lesser of 16GB and
shared_buffers / 8. You're saying it's the minimum.
2. I think you could get rid of the double "Buffer Access Stategy" in:
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>.
<literal>0</literal> will disable use of a <literal>Buffer
Access Strategy</literal>.
<literal>-1</literal> will set the size to a default of
<literal>256 KB</literal>. The maximum size is
how about:
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>.
A setting of <literal>0</literal> will allow the operation to use any
number of <varname>shared_buffers</varname>, whereas
<literal>-1</literal> will set the size to a default of
<literal>256 KB</literal>. The maximum size is
3. In the following snippet you can use <xref linkend="sql-vacuum"/>
or just <command>VACUUM</command>. There are examples of both in that
file. I don't have a preference as it which, but I think what you've
got isn't great.
<link linkend="sql-vacuum"><command>VACUUM</command></link> and
<link linkend="sql-analyze"><command>ANALYZE</command></link>
4. I wonder if there's a reason this needs to be written in the
overview of ANALYZE.
<command>ANALYZE</command> uses a
<glossterm linkend="glossary-buffer-access-strategy">Buffer Access
Strategy</glossterm>
when reading in the sample data. The number of buffers consumed for this can
be controlled by <xref linkend="guc-vacuum-buffer-usage-limit"/> or by using
the <option>BUFFER_USAGE_LIMIT</option> option.
I think it's fine just to mention it under BUFFER_USAGE_LIMIT. It just
does not seem fundamental enough to be worth being upfront about it.
The other things mentioned in that section don't seem related to
parameters, so there might be no better place for those to go. That's
not the case for what you're adding here.
5. I think I'd rather see the details spelt out here rather than
telling the readers to look at what VACUUM does:
Specifies the
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>
ring buffer size for <command>ANALYZE</command>. See the
<link linkend="sql-vacuum"><command>VACUUM</command></link> option with
the same name.
6. When I asked about restricting the valid values of
vacuum_buffer_usage_limit to -1 / 0 or 128 KB to 16GB, I didn't expect
you to code in the NBuffers / 8 check. We shouldn't chain
dependencies between GUCs like that. Imagine someone editing their
postgresql.conf after realising shared_buffers is too large for their
RAM, they reduce it and restart. The database fails to start because
vacuum_buffer_usage_limit is too large! Angry DBA?
Take what's already written about vacuum_failsafe_age as your guidance on this:
"The default is 1.6 billion transactions. Although users can set this
value anywhere from zero to 2.1 billion, VACUUM will silently adjust
the effective value to no less than 105% of
autovacuum_freeze_max_age."
Here we just document the silent capping. You can still claim the
valid range is 128KB to 16GB in the docs. You can mention the 1/8th of
shared buffers cap same as what's mentioned about "105%" above.
When I mentioned #4 and #10 in my review of the v9-0001 patch, I just
wanted to not surprise users who do vacuum_buffer_usage_limit = 64 and
magically get 128.
7. In ExecVacuum(), similar to #6 from above, it's also not great that
you're raising an ERROR based on if StrategyGetClampedBufsize() clamps
or not. If someone has a script that does:
VACUUM (BUFFER_USAGE_LIMIT '1 GB'); it might be annoying that it stops
working when someone adjusts shared buffers from 10GB to 6GB.
I really think the NBuffers / 8 clamping just should be done inside
GetAccessStrategyWithSize().
8. I think this ERROR in vacuum.c should mention that 0 is a valid value.
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("buffer_usage_limit for a vacuum must be between %d KB and %d KB",
MIN_BAS_RING_SIZE_KB, MAX_BAS_RING_SIZE_KB)));
I doubt there's a need to mention -1 as that's the same as not
specifying BUFFER_USAGE_LIMIT.
9. The following might be worthy of a comment explaining the order of
precedence of how we choose the size:
if (params->ring_size == -1)
{
if (VacuumBufferUsageLimit == -1)
bstrategy = GetAccessStrategy(BAS_VACUUM);
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
}
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
10. I wonder if you need to keep bufsize_limit_to_nbuffers(). It's
just used once and seems trivial enough just to write that code inside
GetAccessStrategyWithSize().
11. It's probably worth putting the valid range in the sample config:
#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer
access strategy ring.
# -1 to use default,
# 0 to disable vacuum buffer access strategy
# > 0 to specify size <-- here
12. Is bufmgr.h the right place for these?
/*
* Upper and lower hard limits for the Buffer Access Strategy ring size
* specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
* to VACUUM and ANALYZE.
*/
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
#define MIN_BAS_RING_SIZE_KB 128
Your comment implies they're VACUUM / ANALYZE limits. If we want to
impose these limits to all access strategies then these seem like good
names and location, otherwise, I imagine miscadmin.h is the correct
place. If so, they'll likely want to be renamed to something more
VACUUM specific. I don't particularly have a preference. 128 -
1677216 seem like reasonable limits for any buffer access strategy.
13. I think check_vacuum_buffer_usage_limit() does not belong in
freelist.c. Maybe vacuum.c?
14. Not related to this patch, but why do we have half the vacuum
related GUCs in vacuum.c and the other half in globals.c? I see
vacuum_freeze_table_age is defined in vacuum.c but is also needed in
autovacuum.c, so that rules out the globals.c ones being for vacuum.c
and autovacuum.c. It seems a bit messy. I'm not really sure where
VacuumBufferUsageLimit should go now.
Remaining TODOs:
- tests
- do something about config reload changing GUC
Shouldn't table_recheck_autovac() pfree/palloc a new strategy if the
size changes?
I'm not sure what the implications are with that and the other patch
you're working on to allow vacuum config changes mid-vacuum. We'll
need to be careful and not immediately break that if that gets
committed then this does or vice-versa.
David
On Tue, Apr 4, 2023 at 8:14 PM David Rowley <dgrowleyml@gmail.com> wrote:
14. Not related to this patch, but why do we have half the vacuum
related GUCs in vacuum.c and the other half in globals.c? I see
vacuum_freeze_table_age is defined in vacuum.c but is also needed in
autovacuum.c, so that rules out the globals.c ones being for vacuum.c
and autovacuum.c. It seems a bit messy. I'm not really sure where
VacuumBufferUsageLimit should go now.
vacuum_freeze_table_age is an abomination, even compared to the rest
of these GUCs. It was added around the time the visibility map first
went in, and so is quite a bit more recent than
autovacuum_freeze_max_age.
Before the introduction of the visibility map, we only had
autovacuum_freeze_max_age, and it was used to schedule antiwraparound
autovacuums -- there was no such thing as aggressive VACUUMs (just
antiwraparound autovacuums), and no need for autovacuum_freeze_max_age
at all. So autovacuum_freeze_max_age was just for autovacuum.c code.
There was only one type of VACUUM, and they always advanced
relfrozenxid to the same degree.
With the introduction of the visibility map, we needed to have
autovacuum_freeze_max_age in vacuum.c for the first time, to deal with
interpreting the then-new vacuum_freeze_table_age GUC correctly. We
silently truncate the vacuum_freeze_table_age setting so that it never
exceeds 95% of autovacuum_freeze_max_age (the
105%-of-autovacuum_freeze_max_age vacuum_failsafe_age thing that
you're discussing is symmetric). So after 2009
autovacuum_freeze_max_age actually plays an important role in VACUUM,
the command, and not just autovacuum.
This is related to the problem of the autovacuum_freeze_max_age
reloption being completely broken [1]/messages/by-id/CAH2-Wz=DJAokY_GhKJchgpa8k9t_H_OVOvfPEn97jGNr9W=deg@mail.gmail.com -- Peter Geoghegan. If autovacuum_freeze_max_age
was still purely just an autovacuum.c scheduling thing, then there
would be no problem with having a separate reloption of the same name.
There are big problems precisely because vacuum.c doesn't do anything
with the autovacuum_freeze_max_age reloption. Though it does okay with
the autovacuum_freeze_table_age reloption, which gets passed in. (Yes,
it's called autovacuum_freeze_table_age in reloption form -- not
vacuum_freeze_table_age, like the GUC).
Note that the decision to ignore the reloption version of
autovacuum_freeze_max_age in the failsafe's
105%-of-autovacuum_freeze_max_age thing was a deliberate one. The
autovacuum_freeze_max_age GUC is authoritative in the sense that it
cannot be overridden locally, except in the direction of making
aggressive VACUUMs happen more frequently for a given table (so they
can't be less frequent via reloption configuration). I suppose you'd
have to untangle that mess if you wanted to fix the
autovacuum_freeze_max_age reloption issue I go into in [1]/messages/by-id/CAH2-Wz=DJAokY_GhKJchgpa8k9t_H_OVOvfPEn97jGNr9W=deg@mail.gmail.com -- Peter Geoghegan.
[1]: /messages/by-id/CAH2-Wz=DJAokY_GhKJchgpa8k9t_H_OVOvfPEn97jGNr9W=deg@mail.gmail.com -- Peter Geoghegan
--
Peter Geoghegan
Hi,
On 2023-04-04 13:53:15 -0400, Melanie Plageman wrote:
8. I don't quite follow this comment:
/*
* TODO: should this be 0 so that we are sure that vacuum() never
* allocates a new bstrategy for us, even if we pass in NULL for that
* parameter? maybe could change how failsafe NULLs out bstrategy if
* so?
*/Can you explain under what circumstances would vacuum() allocate a
bstrategy when do_autovacuum() would not? Is this a case of a config
reload where someone changes vacuum_buffer_usage_limit from 0 to
something non-zero? If so, perhaps do_autovacuum() needs to detect
this and allocate a strategy rather than having vacuum() do it once
per table (wastefully).
Hm. I don't much like that we use a single strategy for multiple tables
today. That way even tiny tables never end up in shared_buffers. But that's
really a discussion for a different thread. However, if were to use a
per-table bstrategy, it'd be a lot easier to react to changes of the config.
I doubt it's worth adding complications to the code for changing the size of
the ringbuffer during an ongoing vacuum scan, at least for 16. Reacting to
enabling/disbling the ringbuffer alltogether seems a bit more important, but
still not crucial compared to making it configurable at all.
I think it'd be OK to add a comment saying something like "XXX: In the future
we might want to react to configuration changes of the ring buffer size during
a vacuum" or such.
WRT to the TODO specifically: Yes, passing in 0 seems to make sense. I don't
see a reason not to do so? But perhaps there's a better solution:
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?
Random note while looking at the code:
ISTM that adding handling of -1 in GetAccessStrategyWithSize() would make the
code more readable. Instead of
if (params->ring_size == -1)
{
if (VacuumBufferUsageLimit == -1)
bstrategy = GetAccessStrategy(BAS_VACUUM);
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
}
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
you could just have something like:
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
params->ring_size != -1 ? params->ring_size : VacuumBufferUsageLimit);
by falling back to the default values from GetAccessStrategy().
Or even more "extremely", you could entirely remove references to
VacuumBufferUsageLimit and handle that in freelist.c
Greetings,
Andres Freund
v11 attached with updates detailed below.
On Tue, Apr 4, 2023 at 11:14 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Wed, 5 Apr 2023 at 05:53, Melanie Plageman <melanieplageman@gmail.com> wrote:
Attached v10 addresses the review feedback below.
Thanks. Here's another round on v10-0001:
1. The following documentation fragment does not seem to be aligned
with the code:<literal>16 GB</literal>. The minimum size is the lesser
of 1/8 the size of shared buffers and <literal>128 KB</literal>. The
default value is <literal>-1</literal>. If this value is specifiedThe relevant code is:
static inline int
StrategyGetClampedBufsize(int bufsize_kb)
{
int sb_limit_kb;
int blcksz_kb = BLCKSZ / 1024;Assert(blcksz_kb > 0);
sb_limit_kb = NBuffers / 8 * blcksz_kb;
return Min(sb_limit_kb, bufsize_kb);
}The code seems to mean that the *maximum* is the lesser of 16GB and
shared_buffers / 8. You're saying it's the minimum.
Good catch. Fixed.
2. I think you could get rid of the double "Buffer Access Stategy" in:
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>.
<literal>0</literal> will disable use of a <literal>Buffer
Access Strategy</literal>.
<literal>-1</literal> will set the size to a default of
<literal>256 KB</literal>. The maximum size ishow about:
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>.
A setting of <literal>0</literal> will allow the operation to use any
number of <varname>shared_buffers</varname>, whereas
<literal>-1</literal> will set the size to a default of
<literal>256 KB</literal>. The maximum size is
I've made these changes.
3. In the following snippet you can use <xref linkend="sql-vacuum"/>
or just <command>VACUUM</command>. There are examples of both in that
file. I don't have a preference as it which, but I think what you've
got isn't great.<link linkend="sql-vacuum"><command>VACUUM</command></link> and
<link linkend="sql-analyze"><command>ANALYZE</command></link>
I've updated it to use the link. I thought it would be nice to have the
link in case the reader wants to look at the BUFFER_USAGE_LIMIT option
docs there.
4. I wonder if there's a reason this needs to be written in the
overview of ANALYZE.<command>ANALYZE</command> uses a
<glossterm linkend="glossary-buffer-access-strategy">Buffer Access
Strategy</glossterm>
when reading in the sample data. The number of buffers consumed for this can
be controlled by <xref linkend="guc-vacuum-buffer-usage-limit"/> or by using
the <option>BUFFER_USAGE_LIMIT</option> option.I think it's fine just to mention it under BUFFER_USAGE_LIMIT. It just
does not seem fundamental enough to be worth being upfront about it.
The other things mentioned in that section don't seem related to
parameters, so there might be no better place for those to go. That's
not the case for what you're adding here.
I updated this.
5. I think I'd rather see the details spelt out here rather than
telling the readers to look at what VACUUM does:Specifies the
<glossterm linkend="glossary-buffer-access-strategy">Buffer
Access Strategy</glossterm>
ring buffer size for <command>ANALYZE</command>. See the
<link linkend="sql-vacuum"><command>VACUUM</command></link> option with
the same name.
I've updated it to contain the same text, as relevant, as the VACUUM
option contains. Note that both rely on the vacuum_buffer_usage_limit
GUC documentation for a description of upper and lower bounds.
6. When I asked about restricting the valid values of
vacuum_buffer_usage_limit to -1 / 0 or 128 KB to 16GB, I didn't expect
you to code in the NBuffers / 8 check. We shouldn't chain
dependencies between GUCs like that. Imagine someone editing their
postgresql.conf after realising shared_buffers is too large for their
RAM, they reduce it and restart. The database fails to start because
vacuum_buffer_usage_limit is too large! Angry DBA?Take what's already written about vacuum_failsafe_age as your guidance on this:
"The default is 1.6 billion transactions. Although users can set this
value anywhere from zero to 2.1 billion, VACUUM will silently adjust
the effective value to no less than 105% of
autovacuum_freeze_max_age."Here we just document the silent capping. You can still claim the
valid range is 128KB to 16GB in the docs. You can mention the 1/8th of
shared buffers cap same as what's mentioned about "105%" above.When I mentioned #4 and #10 in my review of the v9-0001 patch, I just
wanted to not surprise users who do vacuum_buffer_usage_limit = 64 and
magically get 128.
7. In ExecVacuum(), similar to #6 from above, it's also not great that
you're raising an ERROR based on if StrategyGetClampedBufsize() clamps
or not. If someone has a script that does:VACUUM (BUFFER_USAGE_LIMIT '1 GB'); it might be annoying that it stops
working when someone adjusts shared buffers from 10GB to 6GB.I really think the NBuffers / 8 clamping just should be done inside
GetAccessStrategyWithSize().
Got it. I've done what you suggested.
I had some logic issues as well that I fixed and reorderd the code.
8. I think this ERROR in vacuum.c should mention that 0 is a valid value.
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("buffer_usage_limit for a vacuum must be between %d KB and %d KB",
MIN_BAS_RING_SIZE_KB, MAX_BAS_RING_SIZE_KB)));I doubt there's a need to mention -1 as that's the same as not
specifying BUFFER_USAGE_LIMIT.
I've done this. I didn't say that 0 meant disabling the strategy. Do you
think that would be useful?
9. The following might be worthy of a comment explaining the order of
precedence of how we choose the size:if (params->ring_size == -1)
{
if (VacuumBufferUsageLimit == -1)
bstrategy = GetAccessStrategy(BAS_VACUUM);
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
}
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
I've updated this. Also, after doing so, I realized the if/else logic
here and in ExecVacuum() could be better and updated the ordering to
more closely mirror the human readable logic.
10. I wonder if you need to keep bufsize_limit_to_nbuffers(). It's
just used once and seems trivial enough just to write that code inside
GetAccessStrategyWithSize().
I've gotten rid of it.
11. It's probably worth putting the valid range in the sample config:
#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer
access strategy ring.
# -1 to use default,
# 0 to disable vacuum buffer access strategy
# > 0 to specify size <-- here
Done.
12. Is bufmgr.h the right place for these?
/*
* Upper and lower hard limits for the Buffer Access Strategy ring size
* specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
* to VACUUM and ANALYZE.
*/
#define MAX_BAS_RING_SIZE_KB (16 * 1024 * 1024)
#define MIN_BAS_RING_SIZE_KB 128Your comment implies they're VACUUM / ANALYZE limits. If we want to
impose these limits to all access strategies then these seem like good
names and location, otherwise, I imagine miscadmin.h is the correct
place. If so, they'll likely want to be renamed to something more
VACUUM specific. I don't particularly have a preference. 128 -
1677216 seem like reasonable limits for any buffer access strategy.
I don't assert on these limits in GetAccessStrategyWithSize(), and,
since the rest of the code is mainly only concerned with vacuum, I think
it is better to make these limits vacuum-specific. If we decide to make
other access strategies configurable, we can generalize these macros
then. As such, I have moved them into miscadmin.h.
13. I think check_vacuum_buffer_usage_limit() does not belong in
freelist.c. Maybe vacuum.c?
I've moved it to vacuum.c. I put it above ExecVacuum() since that would
be correct alphabetically, but I'm not sure if it would be better to
move it down since ExecVacuum() is the "main entry point".
14. Not related to this patch, but why do we have half the vacuum
related GUCs in vacuum.c and the other half in globals.c? I see
vacuum_freeze_table_age is defined in vacuum.c but is also needed in
autovacuum.c, so that rules out the globals.c ones being for vacuum.c
and autovacuum.c. It seems a bit messy. I'm not really sure where
VacuumBufferUsageLimit should go now.
I've left it where it is and added a (helpful?) comment.
Remaining TODOs:
- tests
- do something about config reload changing GUCShouldn't table_recheck_autovac() pfree/palloc a new strategy if the
size changes?
See thoughts about that below in response to Andres' mail.
I'm not sure what the implications are with that and the other patch
you're working on to allow vacuum config changes mid-vacuum. We'll
need to be careful and not immediately break that if that gets
committed then this does or vice-versa.
We can think hard about this. If we go with adding a TODO for the size,
and keeping the same ring, it won't be a problem.
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
On 2023-04-04 13:53:15 -0400, Melanie Plageman wrote:
8. I don't quite follow this comment:
/*
* TODO: should this be 0 so that we are sure that vacuum() never
* allocates a new bstrategy for us, even if we pass in NULL for that
* parameter? maybe could change how failsafe NULLs out bstrategy if
* so?
*/Can you explain under what circumstances would vacuum() allocate a
bstrategy when do_autovacuum() would not? Is this a case of a config
reload where someone changes vacuum_buffer_usage_limit from 0 to
something non-zero? If so, perhaps do_autovacuum() needs to detect
this and allocate a strategy rather than having vacuum() do it once
per table (wastefully).Hm. I don't much like that we use a single strategy for multiple tables
today. That way even tiny tables never end up in shared_buffers. But that's
really a discussion for a different thread. However, if were to use a
per-table bstrategy, it'd be a lot easier to react to changes of the config.
Agreed. I was wondering if it is okay to do the palloc()/pfree() for
every table given that some may be small.
I doubt it's worth adding complications to the code for changing the size of
the ringbuffer during an ongoing vacuum scan, at least for 16. Reacting to
enabling/disbling the ringbuffer alltogether seems a bit more important, but
still not crucial compared to making it configurable at all.I think it'd be OK to add a comment saying something like "XXX: In the future
we might want to react to configuration changes of the ring buffer size during
a vacuum" or such.
I've added the XXX to the autovacuum code. I think you mean it also
could be considered for VACUUM, but I've refrained from mentioning that
for now.
WRT to the TODO specifically: Yes, passing in 0 seems to make sense. I don't
see a reason not to do so? But perhaps there's a better solution:
I've done that (passed in a 0), I was concerned that future code may
reference this vacuum param and expect it to be aligned with the Buffer
Access Strategy in use. Really only vacuum() should be referencing the
params, so, perhaps it is not an issue...
Okay, now I've convinced myself that it is better to allocate the
strategy in ExecVacuum(). Then we can get rid of the
VacuumParams->ring_size altogether.
I haven't done that in this version because of the below concern (re: it
being appropriate to allocate the strategy in ExecVacuum() given its
current concern/focus).
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?
Thought about this -- I did think it might be a bit weird since
ExecVacuum() mainly does option handling and sanity checking. Doing
Buffer Access Strategy allocation seemed a bit out of place. I've left
it as is, but would be happy to change it if the consensus is that this
is better.
Random note while looking at the code:
ISTM that adding handling of -1 in GetAccessStrategyWithSize() would make the
code more readable. Instead ofif (params->ring_size == -1)
{
if (VacuumBufferUsageLimit == -1)
bstrategy = GetAccessStrategy(BAS_VACUUM);
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
}
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);you could just have something like:
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
params->ring_size != -1 ? params->ring_size : VacuumBufferUsageLimit);by falling back to the default values from GetAccessStrategy().
Or even more "extremely", you could entirely remove references to
VacuumBufferUsageLimit and handle that in freelist.c
Hmm. I see what you mean.
I've updated it to this, which is a bit better.
if (params->ring_size > -1)
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
params->ring_size);
else if (VacuumBufferUsageLimit > -1)
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
VacuumBufferUsageLimit);
else
bstrategy = GetAccessStrategy(BAS_VACUUM);
Not referencing VacuumBufferUsageLimit except in freelist.c is more
challenging because I think it would be weird to have
GetAccessStrategyWithSize() call GetAccessStrategy() which then calls
GetAccessStrategyWithSize().
- Melanie
Attachments:
v11-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v11-0002-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 974800478ff6044ea654847fea42413bfd7cea25 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v11 2/2] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 13 +++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 39 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8280cf0fb0 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 2ee5147e97..f027d3ed71 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v11-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v11-0001-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From b658f5bed15e02bff41fb89ac7b270bd187412da Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 15:18:50 -0400
Subject: [PATCH v11 1/2] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in the
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value -- unless it is specified as -1.
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 18 ++++
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 96 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 22 ++++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 48 ++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/commands/vacuum.h | 6 ++
src/include/miscadmin.h | 13 +++
src/include/storage/bufmgr.h | 6 ++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 4 +
src/test/regress/sql/vacuum.sql | 3 +
18 files changed, 299 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..1d75ec62df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>, whereas
+ <literal>-1</literal> will set the size to a default of <literal>256 KB</literal>.
+ The maximum size is <literal>16 GB</literal> and the minimum size is <literal>128 KB</literal>.
+ If the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, it is silently capped.
+ The default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..16cde8668e 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>ANALYZE</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..c1ddf88cf8 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da85330ef4..1027af095b 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,29 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -124,6 +148,9 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /* by default use buffer access strategy with default size */
+ params.ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -134,6 +161,46 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not -1 or 0.
+ */
+ if (result != -1 && result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ params.ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -238,6 +305,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && params.ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -401,7 +478,24 @@ vacuum(List *relations, VacuumParams *params,
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(params->ring_size >= -1);
+
+ /*
+ * If explicit VACUUM or ANALYZE specified a non-default
+ * BUFFER_USAGE_LIMIT, that overrides the value of GUC
+ * VacuumBufferUsageLimit. If VacuumBufferUsageLimit is set to
+ * something other than the default, use its value. Otherwise, use the
+ * default sizes for a Buffer Access Strategy. Note that autovacuum
+ * will have set VacuumParams->ring_size to the value it derived from
+ * VacuumBufferUsageLimit.
+ */
+ if (params->ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, params->ring_size);
+ else if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..729a4c144a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,18 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
+ *
+ * XXX: In the future we may want to react to changes in
+ * VacuumBufferUsageLimit while vacuuming.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
/*
* create a memory context to act as fake PortalContext, so that the
@@ -2884,6 +2893,15 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map,
tab->at_params.multixact_freeze_table_age = multixact_freeze_table_age;
tab->at_params.is_wraparound = wraparound;
tab->at_params.log_min_duration = log_min_duration;
+
+ /*
+ * Since we currently do not change the size of the Buffer Access
+ * Strategy while the autovacuum worker is running, always set this to
+ * 0 to avoid changes in VacuumBufferUsageLimit affecting allocation
+ * of the Buffer Usage Strategy.
+ */
+ tab->at_params.ring_size = 0;
+
tab->at_vacuum_cost_limit = vac_cost_limit;
tab->at_vacuum_cost_delay = vac_cost_delay;
tab->at_relname = NULL;
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..0c1ce8b93f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +570,36 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+ int nbuffers;
+ int sb_limit_kb;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ Assert(blcksz_kb > 0);
+
/* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+ ring_size_kb = Min(sb_limit_kb, ring_size_kb);
+
+ nbuffers = ring_size_kb / blcksz_kb;
+
+ Assert(nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +613,15 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5bad2b540f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..cdbcb95a07 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size. range 128-16777216
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..2ee5147e97 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -236,6 +236,12 @@ typedef struct VacuumParams
* disabled.
*/
int nworkers;
+
+ /*
+ * The size in KB of the Buffer Access Strategy ring to be used for VACUUM
+ * and ANALYZE.
+ */
+ int ring_size;
} VacuumParams;
/*
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..2d550f26e0 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,19 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..c174cb2c04 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -194,7 +194,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..fe675c5a2e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,10 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..fc068b7705 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,9 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-04-04 13:53:15 -0400, Melanie Plageman wrote:
8. I don't quite follow this comment:
/*
* TODO: should this be 0 so that we are sure that vacuum() never
* allocates a new bstrategy for us, even if we pass in NULL for that
* parameter? maybe could change how failsafe NULLs out bstrategy if
* so?
*/Can you explain under what circumstances would vacuum() allocate a
bstrategy when do_autovacuum() would not? Is this a case of a config
reload where someone changes vacuum_buffer_usage_limit from 0 to
something non-zero? If so, perhaps do_autovacuum() needs to detect
this and allocate a strategy rather than having vacuum() do it once
per table (wastefully).Hm. I don't much like that we use a single strategy for multiple tables
today. That way even tiny tables never end up in shared_buffers. But that's
really a discussion for a different thread. However, if were to use a
per-table bstrategy, it'd be a lot easier to react to changes of the config.I doubt it's worth adding complications to the code for changing the size of
the ringbuffer during an ongoing vacuum scan, at least for 16. Reacting to
enabling/disbling the ringbuffer alltogether seems a bit more important, but
still not crucial compared to making it configurable at all.I think it'd be OK to add a comment saying something like "XXX: In the future
we might want to react to configuration changes of the ring buffer size during
a vacuum" or such.WRT to the TODO specifically: Yes, passing in 0 seems to make sense. I don't
see a reason not to do so? But perhaps there's a better solution:Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?
So, I started looking into allocating the bstrategy in ExecVacuum().
While doing so, I was trying to understand if the "sanity checking" in
vacuum() could possibly apply to autovacuum, and I don't really see how.
AFAICT, autovacuum does not ever set VACOPT_DISABLE_PAGE_SKIPPING or
VACOPT_FULL or VACOPT_ONLY_DATABASE_STATS.
We could move those sanity checks up into ExecVacuum().
I also noticed that we make the vac_context in vacuum() which says it is
for "cross-transaction storage". We use it for the buffer access
strategy and for the newrels relation list created in vacuum(). Then we
delete it at the end of vacuum().
Autovacuum workers already make a similar kind of memory context called
AutovacMemCxt in do_autovacuum() which the comment says is for the list
of relations to vacuum/analyze across transactions.
What if we made ExecVacuum() make its own memory context and both it and
do_autovacuum() pass that memory context (along with the buffer access
strategy they make) to vacuum(), which then uses the memory context in
the same way it does now?
It simplifies the buffer usage limit patchset and also seems a bit more
clear than what is there now?
- Melanie
Hi,
On 2023-04-05 16:17:20 -0400, Melanie Plageman wrote:
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?So, I started looking into allocating the bstrategy in ExecVacuum().
While doing so, I was trying to understand if the "sanity checking" in
vacuum() could possibly apply to autovacuum, and I don't really see how.AFAICT, autovacuum does not ever set VACOPT_DISABLE_PAGE_SKIPPING or
VACOPT_FULL or VACOPT_ONLY_DATABASE_STATS.We could move those sanity checks up into ExecVacuum().
Would make sense.
ISTM that eventually most of what currently happens in vacuum() should be in
ExecVacuum(). There's a lot of stuff that can't happen for autovacuum. So it
just seems to make more sense to move those parts to ExecVacuum().
I also noticed that we make the vac_context in vacuum() which says it is
for "cross-transaction storage". We use it for the buffer access
strategy and for the newrels relation list created in vacuum(). Then we
delete it at the end of vacuum().
Autovacuum workers already make a similar kind of memory context called
AutovacMemCxt in do_autovacuum() which the comment says is for the list
of relations to vacuum/analyze across transactions.
AutovacMemCxt seems to be a bit longer lived / cover more than the context
created in vacuum(). It's where all the hash tables etc live that
do_autovacuum() uses to determine what to vacuum.
Note that do_autovacuum() also creates:
/*
* create a memory context to act as fake PortalContext, so that the
* contexts created in the vacuum code are cleaned up for each table.
*/
PortalContext = AllocSetContextCreate(AutovacMemCxt,
"Autovacuum Portal",
ALLOCSET_DEFAULT_SIZES);
which is then what vacuum() creates the "Vacuum" context in.
What if we made ExecVacuum() make its own memory context and both it and
do_autovacuum() pass that memory context (along with the buffer access
strategy they make) to vacuum(), which then uses the memory context in
the same way it does now?
Maybe? It's not clear to me why it'd be a win.
It simplifies the buffer usage limit patchset and also seems a bit more
clear than what is there now?
I don't really see what it'd make simpler? The context in vacuum() is used for
just that vacuum - we couldn't just use AutovacMemCxt, as that'd live much
longer (for all the tables a autovac worker processes).
Greetings,
Andres Freund
On Wed, 5 Apr 2023 at 16:37, Peter Geoghegan <pg@bowt.ie> wrote:
On Tue, Apr 4, 2023 at 8:14 PM David Rowley <dgrowleyml@gmail.com> wrote:
14. Not related to this patch, but why do we have half the vacuum
related GUCs in vacuum.c and the other half in globals.c? I see
vacuum_freeze_table_age is defined in vacuum.c but is also needed in
autovacuum.c, so that rules out the globals.c ones being for vacuum.c
and autovacuum.c. It seems a bit messy. I'm not really sure where
VacuumBufferUsageLimit should go now.vacuum_freeze_table_age is an abomination, even compared to the rest
of these GUCs. It was added around the time the visibility map first
went in, and so is quite a bit more recent than
autovacuum_freeze_max_age.Before the introduction of the visibility map, we only had
autovacuum_freeze_max_age, and it was used to schedule antiwraparound
autovacuums -- there was no such thing as aggressive VACUUMs (just
antiwraparound autovacuums), and no need for autovacuum_freeze_max_age
at all. So autovacuum_freeze_max_age was just for autovacuum.c code.
There was only one type of VACUUM, and they always advanced
relfrozenxid to the same degree.With the introduction of the visibility map, we needed to have
autovacuum_freeze_max_age in vacuum.c for the first time, to deal with
interpreting the then-new vacuum_freeze_table_age GUC correctly. We
silently truncate the vacuum_freeze_table_age setting so that it never
exceeds 95% of autovacuum_freeze_max_age (the
105%-of-autovacuum_freeze_max_age vacuum_failsafe_age thing that
you're discussing is symmetric). So after 2009
autovacuum_freeze_max_age actually plays an important role in VACUUM,
the command, and not just autovacuum.This is related to the problem of the autovacuum_freeze_max_age
reloption being completely broken [1]. If autovacuum_freeze_max_age
was still purely just an autovacuum.c scheduling thing, then there
would be no problem with having a separate reloption of the same name.
There are big problems precisely because vacuum.c doesn't do anything
with the autovacuum_freeze_max_age reloption. Though it does okay with
the autovacuum_freeze_table_age reloption, which gets passed in. (Yes,
it's called autovacuum_freeze_table_age in reloption form -- not
vacuum_freeze_table_age, like the GUC).Note that the decision to ignore the reloption version of
autovacuum_freeze_max_age in the failsafe's
105%-of-autovacuum_freeze_max_age thing was a deliberate one. The
autovacuum_freeze_max_age GUC is authoritative in the sense that it
cannot be overridden locally, except in the direction of making
aggressive VACUUMs happen more frequently for a given table (so they
can't be less frequent via reloption configuration). I suppose you'd
have to untangle that mess if you wanted to fix the
autovacuum_freeze_max_age reloption issue I go into in [1].[1] /messages/by-id/CAH2-Wz=DJAokY_GhKJchgpa8k9t_H_OVOvfPEn97jGNr9W=deg@mail.gmail.com
I read this twice yesterday and again this morning. It looks like
you're taking an opportunity to complain/vent about
vacuum_freeze_table_age and didn't really answer my query about why
all the vacuum GUCs aren't defined in the one file. I'd just picked
vacuum_freeze_table_age as a random one from vacuum.c to raise the
point about the inconsistency about the GUC locations.
I don't think this is the place to raise concerns with existing GUCs,
but if you did have a point about the GUCs locations, then you might
need to phase it differently as I didn't catch it.
David
On Wed, Apr 5, 2023 at 2:33 PM David Rowley <dgrowleyml@gmail.com> wrote:
I read this twice yesterday and again this morning. It looks like
you're taking an opportunity to complain/vent about
vacuum_freeze_table_age and didn't really answer my query about why
all the vacuum GUCs aren't defined in the one file. I'd just picked
vacuum_freeze_table_age as a random one from vacuum.c to raise the
point about the inconsistency about the GUC locations.
I thought that the point was obvious. Which is: the current situation
with the locations of these GUCs came about because the division
between autovacuum and VACUUM used to be a lot clearer, but that
changed. Without the locations of the GUCs also changing. More
generally, the current structure has lots of problems. And so it seems
to me that you're probably not wrong to suspect that it just doesn't
make much sense to keep them in different files now.
--
Peter Geoghegan
On Wed, Apr 5, 2023 at 5:15 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-04-05 16:17:20 -0400, Melanie Plageman wrote:
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?So, I started looking into allocating the bstrategy in ExecVacuum().
While doing so, I was trying to understand if the "sanity checking" in
vacuum() could possibly apply to autovacuum, and I don't really see how.AFAICT, autovacuum does not ever set VACOPT_DISABLE_PAGE_SKIPPING or
VACOPT_FULL or VACOPT_ONLY_DATABASE_STATS.We could move those sanity checks up into ExecVacuum().
Would make sense.
ISTM that eventually most of what currently happens in vacuum() should be in
ExecVacuum(). There's a lot of stuff that can't happen for autovacuum. So it
just seems to make more sense to move those parts to ExecVacuum().
I've done that in the attached wip patch. It is perhaps too much of a
change, I dunno.
I also noticed that we make the vac_context in vacuum() which says it is
for "cross-transaction storage". We use it for the buffer access
strategy and for the newrels relation list created in vacuum(). Then we
delete it at the end of vacuum().Autovacuum workers already make a similar kind of memory context called
AutovacMemCxt in do_autovacuum() which the comment says is for the list
of relations to vacuum/analyze across transactions.AutovacMemCxt seems to be a bit longer lived / cover more than the context
created in vacuum(). It's where all the hash tables etc live that
do_autovacuum() uses to determine what to vacuum.Note that do_autovacuum() also creates:
/*
* create a memory context to act as fake PortalContext, so that the
* contexts created in the vacuum code are cleaned up for each table.
*/
PortalContext = AllocSetContextCreate(AutovacMemCxt,
"Autovacuum Portal",
ALLOCSET_DEFAULT_SIZES);which is then what vacuum() creates the "Vacuum" context in.
Yea, I realized that when writing the patch.
What if we made ExecVacuum() make its own memory context and both it and
do_autovacuum() pass that memory context (along with the buffer access
strategy they make) to vacuum(), which then uses the memory context in
the same way it does now?Maybe? It's not clear to me why it'd be a win.
Less that it is a win and more that we need access to that memory
context when allocating the buffer access strategy, so we would have had
to make it in ExecVacuum(). And if we have already made it, we would
need to pass it in to vacuum() for it to use.
It simplifies the buffer usage limit patchset and also seems a bit more
clear than what is there now?I don't really see what it'd make simpler? The context in vacuum() is used for
just that vacuum - we couldn't just use AutovacMemCxt, as that'd live much
longer (for all the tables a autovac worker processes).
Autovacuum already made the BufferAccessStrategy in the AutovacMemCxt,
so this is the same behavior. I simply made autovacuum_do_vac_analyze()
make the per table vacuum memory context and pass that to vacuum(). So
we have the same amount of memory context granularity as before.
Attached patchset has some kind of isolation test failure due to a hard
deadlock that I haven't figured out yet. I thought it was something with
the "in_vacuum" static variable and having VACUUM or ANALYZE called when
already in VACUUM or ANALYZE, but that variable is the same as in
master.
I've mostly shared it because I want to know if this approach is worth
pursuing or not.
Also, while working on it, I noticed that I made a mistake in the code
that was committed in 4830f102 and didn't remember that we should still
make a Buffer Access Strategy in the case of VACUUM (FULL, ANALYZE).
Changing this:
if (params->options & (VACOPT_ONLY_DATABASE_STATS | VACOPT_FULL)) == 0)
to this:
if ((params.options & VACOPT_ONLY_DATABASE_STATS) == 0 ||
(params.options & VACOPT_FULL && (params.options & VACOPT_ANALYZE) == 0)
should fix it.
- Melanie
Attachments:
0002-add-BUFFER_USAGE_LIMIT-and-vacuum_buffer_usage_li.patchtext/x-patch; charset=US-ASCII; name=0002-add-BUFFER_USAGE_LIMIT-and-vacuum_buffer_usage_li.patchDownload
From 9cdadabe9205cdd57e51a9c1ff0601c745cc308e Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 17:22:02 -0400
Subject: [PATCH v1 2/2] add BUFFER_USAGE_LIMIT and vacuum_buffer_usage_limit
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 18 ++++
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 95 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 13 ++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 48 ++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/miscadmin.h | 13 +++
src/include/storage/bufmgr.h | 6 ++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 4 +
src/test/regress/sql/vacuum.sql | 3 +
17 files changed, 283 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..1d75ec62df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>, whereas
+ <literal>-1</literal> will set the size to a default of <literal>256 KB</literal>.
+ The maximum size is <literal>16 GB</literal> and the minimum size is <literal>128 KB</literal>.
+ If the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, it is silently capped.
+ The default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..16cde8668e 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>ANALYZE</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..c1ddf88cf8 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index c6eb1f2fad..7145ca38a4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,29 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +138,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ /* by default use buffer access strategy with default size */
+ int ring_size = -1;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -140,6 +166,46 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not -1 or 0.
+ */
+ if (result != -1 && result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -244,6 +310,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -360,7 +436,24 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If explicit VACUUM or ANALYZE specified a non-default
+ * BUFFER_USAGE_LIMIT, that overrides the value of GUC
+ * VacuumBufferUsageLimit. If VacuumBufferUsageLimit is set to
+ * something other than the default, use its value. Otherwise, use the
+ * default sizes for a Buffer Access Strategy. Note that autovacuum
+ * will have set VacuumParams->ring_size to the value it derived from
+ * VacuumBufferUsageLimit.
+ */
+ if (ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 8016a2dd47..7097aea1dc 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,9 +2291,18 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
+ *
+ * XXX: In the future we may want to react to changes in
+ * VacuumBufferUsageLimit while vacuuming.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..0c1ce8b93f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +570,36 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+ int nbuffers;
+ int sb_limit_kb;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ Assert(blcksz_kb > 0);
+
/* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+ ring_size_kb = Min(sb_limit_kb, ring_size_kb);
+
+ nbuffers = ring_size_kb / blcksz_kb;
+
+ Assert(nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +613,15 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5bad2b540f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..cdbcb95a07 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size. range 128-16777216
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..2d550f26e0 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,19 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..c174cb2c04 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -194,7 +194,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..fe675c5a2e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,10 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..fc068b7705 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,9 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
0001-wip-vacuum-refactor.patchtext/x-patch; charset=US-ASCII; name=0001-wip-vacuum-refactor.patchDownload
From f46fa37268b2873b4f98118ce8561fd87ef7297a Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 17:20:41 -0400
Subject: [PATCH v1 1/2] wip vacuum refactor
---
src/backend/commands/vacuum.c | 243 ++++++++++++++--------------
src/backend/postmaster/autovacuum.c | 17 +-
src/include/commands/vacuum.h | 3 +-
3 files changed, 139 insertions(+), 124 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da85330ef4..c6eb1f2fad 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -105,6 +105,7 @@ void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
+ BufferAccessStrategy bstrategy = NULL;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
@@ -115,8 +116,13 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool process_toast = true;
bool skip_database_stats = false;
bool only_database_stats = false;
+ MemoryContext vac_context;
ListCell *lc;
+ const char *stmttype;
+ volatile bool in_outer_xact,
+ use_own_xacts;
+
/* index_cleanup and truncate values unspecified for now */
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
params.truncate = VACOPTVALUE_UNSPECIFIED;
@@ -254,6 +260,61 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
}
}
+
+ stmttype = (params.options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
+
+ /*
+ * We cannot run VACUUM inside a user transaction block; if we were inside
+ * a transaction, then our commit- and start-transaction-command calls
+ * would not have the intended effect! There are numerous other subtle
+ * dependencies on this, too.
+ *
+ * ANALYZE (without VACUUM) can run either way.
+ */
+ if (params.options & VACOPT_VACUUM)
+ {
+ PreventInTransactionBlock(isTopLevel, stmttype);
+ in_outer_xact = false;
+ }
+ else
+ in_outer_xact = IsInTransactionBlock(isTopLevel);
+
+ /*
+ * Sanity check DISABLE_PAGE_SKIPPING option.
+ */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
+
+ /* sanity check for PROCESS_TOAST */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_PROCESS_TOAST) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROCESS_TOAST required with VACUUM FULL")));
+
+ /* sanity check for ONLY_DATABASE_STATS */
+ if (params.options & VACOPT_ONLY_DATABASE_STATS)
+ {
+ Assert(params.options & VACOPT_VACUUM);
+ if (vacstmt->rels != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
+ /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
+ if (params.options & ~(VACOPT_VACUUM |
+ VACOPT_VERBOSE |
+ VACOPT_PROCESS_MAIN |
+ VACOPT_PROCESS_TOAST |
+ VACOPT_ONLY_DATABASE_STATS))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
+ }
+
+
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@@ -279,8 +340,67 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */
params.log_min_duration = -1;
+ /*
+ * Create special memory context for cross-transaction storage.
+ *
+ * Since it is a child of PortalContext, it will go away eventually even
+ * if we suffer an error; there's no need for special abort cleanup logic.
+ */
+ vac_context = AllocSetContextCreate(PortalContext,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /*
+ * We needn't bother making a Buffer Access Strategy for VACUUM (FULL)
+ * (unless ANALYZE is also specified) or VACUUM (ONLY_DATABASE_STATS) as
+ * they'll not make use of it.
+ */
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) == 0 ||
+ (params.options & VACOPT_FULL && (params.options & VACOPT_ANALYZE) == 0))
+ {
+ MemoryContext old_context = MemoryContextSwitchTo(vac_context);
+
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ MemoryContextSwitchTo(old_context);
+ }
+
+ /*
+ * Decide whether we need to start/commit our own transactions.
+ *
+ * For VACUUM (with or without ANALYZE): always do so, so that we can
+ * release locks as soon as possible. (We could possibly use the outer
+ * transaction for a one-table VACUUM, but handling TOAST tables would be
+ * problematic.)
+ *
+ * For ANALYZE (no VACUUM): if inside a transaction block, we cannot
+ * start/commit our own transactions. Also, there's no need to do so if
+ * only processing one relation. For multiple relations when not within a
+ * transaction block, and also in an autovacuum worker, use own
+ * transactions so we can release locks sooner.
+ */
+ if (params.options & VACOPT_VACUUM)
+ use_own_xacts = true;
+ else
+ {
+ Assert(params.options & VACOPT_ANALYZE);
+ if (in_outer_xact)
+ use_own_xacts = false;
+ else if (list_length(vacstmt->rels) > 1)
+ use_own_xacts = true;
+ else
+ use_own_xacts = false;
+ }
+
/* Now go through the common routine */
- vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel);
+ vacuum(vacstmt->rels, ¶ms, bstrategy, vac_context, isTopLevel,
+ use_own_xacts, in_outer_xact);
+
+ /*
+ * Clean up working storage --- note we must do this after
+ * StartTransactionCommand, else we might be trying to delete the active
+ * context!
+ */
+ MemoryContextDelete(vac_context);
}
/*
@@ -304,35 +424,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
*/
void
vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel)
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool use_own_xacts, bool in_outer_xact)
{
- static bool in_vacuum = false;
- MemoryContext vac_context;
const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
-
+ static bool in_vacuum = false;
Assert(params != NULL);
stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
- /*
- * We cannot run VACUUM inside a user transaction block; if we were inside
- * a transaction, then our commit- and start-transaction-command calls
- * would not have the intended effect! There are numerous other subtle
- * dependencies on this, too.
- *
- * ANALYZE (without VACUUM) can run either way.
- */
- if (params->options & VACOPT_VACUUM)
- {
- PreventInTransactionBlock(isTopLevel, stmttype);
- in_outer_xact = false;
- }
- else
- in_outer_xact = IsInTransactionBlock(isTopLevel);
-
/*
* Check for and disallow recursive calls. This could happen when VACUUM
* FULL or ANALYZE calls a hostile index expression that itself calls
@@ -344,67 +445,6 @@ vacuum(List *relations, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype)));
- /*
- * Sanity check DISABLE_PAGE_SKIPPING option.
- */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
-
- /* sanity check for PROCESS_TOAST */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_PROCESS_TOAST) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PROCESS_TOAST required with VACUUM FULL")));
-
- /* sanity check for ONLY_DATABASE_STATS */
- if (params->options & VACOPT_ONLY_DATABASE_STATS)
- {
- Assert(params->options & VACOPT_VACUUM);
- if (relations != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
- /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
- if (params->options & ~(VACOPT_VACUUM |
- VACOPT_VERBOSE |
- VACOPT_PROCESS_MAIN |
- VACOPT_PROCESS_TOAST |
- VACOPT_ONLY_DATABASE_STATS))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
- }
-
- /*
- * Create special memory context for cross-transaction storage.
- *
- * Since it is a child of PortalContext, it will go away eventually even
- * if we suffer an error; there's no need for special abort cleanup logic.
- */
- vac_context = AllocSetContextCreate(PortalContext,
- "Vacuum",
- ALLOCSET_DEFAULT_SIZES);
-
- /*
- * If caller didn't give us a buffer strategy object, make one in the
- * cross-transaction memory context. We needn't bother making this for
- * VACUUM (FULL) or VACUUM (ONLY_DATABASE_STATS) as they'll not make use
- * of it.
- */
- if (bstrategy == NULL &&
- (params->options & (VACOPT_ONLY_DATABASE_STATS |
- VACOPT_FULL)) == 0)
- {
- MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
- bstrategy = GetAccessStrategy(BAS_VACUUM);
- MemoryContextSwitchTo(old_context);
- }
-
/*
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
@@ -435,35 +475,6 @@ vacuum(List *relations, VacuumParams *params,
else
relations = get_all_vacuum_rels(vac_context, params->options);
- /*
- * Decide whether we need to start/commit our own transactions.
- *
- * For VACUUM (with or without ANALYZE): always do so, so that we can
- * release locks as soon as possible. (We could possibly use the outer
- * transaction for a one-table VACUUM, but handling TOAST tables would be
- * problematic.)
- *
- * For ANALYZE (no VACUUM): if inside a transaction block, we cannot
- * start/commit our own transactions. Also, there's no need to do so if
- * only processing one relation. For multiple relations when not within a
- * transaction block, and also in an autovacuum worker, use own
- * transactions so we can release locks sooner.
- */
- if (params->options & VACOPT_VACUUM)
- use_own_xacts = true;
- else
- {
- Assert(params->options & VACOPT_ANALYZE);
- if (IsAutoVacuumWorkerProcess())
- use_own_xacts = true;
- else if (in_outer_xact)
- use_own_xacts = false;
- else if (list_length(relations) > 1)
- use_own_xacts = true;
- else
- use_own_xacts = false;
- }
-
/*
* vacuum_rel expects to be entered with no transaction active; it will
* start and commit its own transaction. But we are called by an SQL
@@ -576,12 +587,6 @@ vacuum(List *relations, VacuumParams *params,
vac_update_datfrozenxid();
}
- /*
- * Clean up working storage --- note we must do this after
- * StartTransactionCommand, else we might be trying to delete the active
- * context!
- */
- MemoryContextDelete(vac_context);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..8016a2dd47 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -338,7 +338,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy,
+ MemoryContext portal_context);
static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
@@ -2470,7 +2471,7 @@ do_autovacuum(void)
MemoryContextSwitchTo(PortalContext);
/* have at it */
- autovacuum_do_vac_analyze(tab, bstrategy);
+ autovacuum_do_vac_analyze(tab, bstrategy, PortalContext);
/*
* Clear a possible query-cancel signal, to avoid a late reaction
@@ -3143,11 +3144,13 @@ relation_needs_vacanalyze(Oid relid,
* Vacuum and/or analyze the specified table
*/
static void
-autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
+autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy,
+ MemoryContext portal_context)
{
RangeVar *rangevar;
VacuumRelation *rel;
List *rel_list;
+ MemoryContext vac_context;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
@@ -3157,7 +3160,13 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
rel_list = list_make1(rel);
- vacuum(rel_list, &tab->at_params, bstrategy, true);
+ vac_context = AllocSetContextCreate(portal_context,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true, true, false);
+
+ MemoryContextDelete(vac_context);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..a4c928a877 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -310,7 +310,8 @@ extern PGDLLIMPORT int VacuumCostBalanceLocal;
/* in commands/vacuum.c */
extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel);
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool use_own_xacts, bool in_outer_xact);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
--
2.37.2
On Wed, Apr 5, 2023 at 6:55 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Wed, Apr 5, 2023 at 5:15 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-04-05 16:17:20 -0400, Melanie Plageman wrote:
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?So, I started looking into allocating the bstrategy in ExecVacuum().
While doing so, I was trying to understand if the "sanity checking" in
vacuum() could possibly apply to autovacuum, and I don't really see how.AFAICT, autovacuum does not ever set VACOPT_DISABLE_PAGE_SKIPPING or
VACOPT_FULL or VACOPT_ONLY_DATABASE_STATS.We could move those sanity checks up into ExecVacuum().
Would make sense.
ISTM that eventually most of what currently happens in vacuum() should be in
ExecVacuum(). There's a lot of stuff that can't happen for autovacuum. So it
just seems to make more sense to move those parts to ExecVacuum().I've done that in the attached wip patch. It is perhaps too much of a
change, I dunno.I also noticed that we make the vac_context in vacuum() which says it is
for "cross-transaction storage". We use it for the buffer access
strategy and for the newrels relation list created in vacuum(). Then we
delete it at the end of vacuum().Autovacuum workers already make a similar kind of memory context called
AutovacMemCxt in do_autovacuum() which the comment says is for the list
of relations to vacuum/analyze across transactions.AutovacMemCxt seems to be a bit longer lived / cover more than the context
created in vacuum(). It's where all the hash tables etc live that
do_autovacuum() uses to determine what to vacuum.Note that do_autovacuum() also creates:
/*
* create a memory context to act as fake PortalContext, so that the
* contexts created in the vacuum code are cleaned up for each table.
*/
PortalContext = AllocSetContextCreate(AutovacMemCxt,
"Autovacuum Portal",
ALLOCSET_DEFAULT_SIZES);which is then what vacuum() creates the "Vacuum" context in.
Yea, I realized that when writing the patch.
What if we made ExecVacuum() make its own memory context and both it and
do_autovacuum() pass that memory context (along with the buffer access
strategy they make) to vacuum(), which then uses the memory context in
the same way it does now?Maybe? It's not clear to me why it'd be a win.
Less that it is a win and more that we need access to that memory
context when allocating the buffer access strategy, so we would have had
to make it in ExecVacuum(). And if we have already made it, we would
need to pass it in to vacuum() for it to use.It simplifies the buffer usage limit patchset and also seems a bit more
clear than what is there now?I don't really see what it'd make simpler? The context in vacuum() is used for
just that vacuum - we couldn't just use AutovacMemCxt, as that'd live much
longer (for all the tables a autovac worker processes).Autovacuum already made the BufferAccessStrategy in the AutovacMemCxt,
so this is the same behavior. I simply made autovacuum_do_vac_analyze()
make the per table vacuum memory context and pass that to vacuum(). So
we have the same amount of memory context granularity as before.Attached patchset has some kind of isolation test failure due to a hard
deadlock that I haven't figured out yet. I thought it was something with
the "in_vacuum" static variable and having VACUUM or ANALYZE called when
already in VACUUM or ANALYZE, but that variable is the same as in
master.I've mostly shared it because I want to know if this approach is worth
pursuing or not.
Figured out how to fix the issue, though I'm not sure I understand how
the issue can occur.
use_own_xacts seems like it will always be true for autovacuum when it
calls vacuum() and ExecVacuum() only calls vacuum() once, so I thought
that I could make use_own_xacts a parameter to vacuum() and push up its
calculation for VACUUM and ANALYZE into ExecVacuum().
This caused a deadlock, so there must be a way that in_vacuum is false
but vacuum() is called in a nested context.
Anyway, recalculating it every time in vacuum() fixes it.
Attached is a v12 of the whole vacuum_buffer_usage_limit patch set which
includes a commit to fix the bug in master and a commit to move relevant
code from vacuum() up into ExecVacuum().
The logic I suggested earlier for fixing the bug was...not right.
Attached fix should be right?
- Melanie
Attachments:
v12-0002-Push-vacuum-setup-code-up-into-ExecVacuum.patchtext/x-patch; charset=US-ASCII; name=v12-0002-Push-vacuum-setup-code-up-into-ExecVacuum.patchDownload
From 98aa041bd421fb1a9817bf72480f6e910a128b71 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 20:05:25 -0400
Subject: [PATCH v12 2/4] Push vacuum() setup code up into ExecVacuum()
Much of the sanity checking and setup done in vacuum() code was specific
to explicit VACUUM and ANALYZE. Move that code up into ExecVacuum().
While we are at it, allocate VACUUM/ANALYZE's BufferAccessStrategy in
ExecVacuum() and pass it into vacuum() instead of expecting vacuum() to
make it. This mimics autovacuum's behavior. To do this, create the
relevant memory context in ExecVacuum() and pass it in as a new
parameter to vacuum().
---
src/backend/commands/vacuum.c | 197 +++++++++++++---------------
src/backend/postmaster/autovacuum.c | 17 ++-
src/include/commands/vacuum.h | 3 +-
3 files changed, 109 insertions(+), 108 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 438e94410e..9b3f5c03ca 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -105,6 +105,7 @@ void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
+ BufferAccessStrategy bstrategy = NULL;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
@@ -115,8 +116,12 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool process_toast = true;
bool skip_database_stats = false;
bool only_database_stats = false;
+ MemoryContext vac_context;
ListCell *lc;
+ const char *stmttype;
+ volatile bool in_outer_xact;
+
/* index_cleanup and truncate values unspecified for now */
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
params.truncate = VACOPTVALUE_UNSPECIFIED;
@@ -254,6 +259,41 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
}
}
+
+ /*
+ * Sanity check DISABLE_PAGE_SKIPPING option.
+ */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
+
+ /* sanity check for PROCESS_TOAST */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_PROCESS_TOAST) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROCESS_TOAST required with VACUUM FULL")));
+
+ /* sanity check for ONLY_DATABASE_STATS */
+ if (params.options & VACOPT_ONLY_DATABASE_STATS)
+ {
+ Assert(params.options & VACOPT_VACUUM);
+ if (vacstmt->rels != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
+ /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
+ if (params.options & ~(VACOPT_VACUUM |
+ VACOPT_VERBOSE |
+ VACOPT_PROCESS_MAIN |
+ VACOPT_PROCESS_TOAST |
+ VACOPT_ONLY_DATABASE_STATS))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
+ }
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@@ -279,8 +319,57 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */
params.log_min_duration = -1;
+ stmttype = (params.options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
+
+ /*
+ * We cannot run VACUUM inside a user transaction block; if we were inside
+ * a transaction, then our commit- and start-transaction-command calls
+ * would not have the intended effect! There are numerous other subtle
+ * dependencies on this, too.
+ *
+ * ANALYZE (without VACUUM) can run either way.
+ */
+ if (params.options & VACOPT_VACUUM)
+ {
+ PreventInTransactionBlock(isTopLevel, stmttype);
+ in_outer_xact = false;
+ }
+ else
+ in_outer_xact = IsInTransactionBlock(isTopLevel);
+
+ /*
+ * Create special memory context for cross-transaction storage.
+ *
+ * Since it is a child of PortalContext, it will go away eventually even
+ * if we suffer an error; there's no need for special abort cleanup logic.
+ */
+ vac_context = AllocSetContextCreate(PortalContext,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /*
+ * We needn't bother making this for VACUUM (ONLY_DATABASE_STATS) or VACUUM
+ * (FULL) without the ANALYZE option, as they'll not make use of it.
+ */
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) == 0 &&
+ ((params.options & VACOPT_FULL) == 0 || (params.options & VACOPT_ANALYZE)))
+ {
+ MemoryContext old_context = MemoryContextSwitchTo(vac_context);
+
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ MemoryContextSwitchTo(old_context);
+ }
+
/* Now go through the common routine */
- vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel);
+ vacuum(vacstmt->rels, ¶ms, bstrategy, vac_context, isTopLevel,
+ in_outer_xact);
+
+ /*
+ * Clean up working storage --- note we must do this after
+ * StartTransactionCommand, else we might be trying to delete the active
+ * context!
+ */
+ MemoryContextDelete(vac_context);
}
/*
@@ -304,35 +393,18 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
*/
void
vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel)
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact)
{
- static bool in_vacuum = false;
- MemoryContext vac_context;
const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
+ static bool in_vacuum = false;
+ volatile bool use_own_xacts;
Assert(params != NULL);
stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
- /*
- * We cannot run VACUUM inside a user transaction block; if we were inside
- * a transaction, then our commit- and start-transaction-command calls
- * would not have the intended effect! There are numerous other subtle
- * dependencies on this, too.
- *
- * ANALYZE (without VACUUM) can run either way.
- */
- if (params->options & VACOPT_VACUUM)
- {
- PreventInTransactionBlock(isTopLevel, stmttype);
- in_outer_xact = false;
- }
- else
- in_outer_xact = IsInTransactionBlock(isTopLevel);
-
/*
* Check for and disallow recursive calls. This could happen when VACUUM
* FULL or ANALYZE calls a hostile index expression that itself calls
@@ -344,67 +416,6 @@ vacuum(List *relations, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype)));
- /*
- * Sanity check DISABLE_PAGE_SKIPPING option.
- */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
-
- /* sanity check for PROCESS_TOAST */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_PROCESS_TOAST) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PROCESS_TOAST required with VACUUM FULL")));
-
- /* sanity check for ONLY_DATABASE_STATS */
- if (params->options & VACOPT_ONLY_DATABASE_STATS)
- {
- Assert(params->options & VACOPT_VACUUM);
- if (relations != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
- /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
- if (params->options & ~(VACOPT_VACUUM |
- VACOPT_VERBOSE |
- VACOPT_PROCESS_MAIN |
- VACOPT_PROCESS_TOAST |
- VACOPT_ONLY_DATABASE_STATS))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
- }
-
- /*
- * Create special memory context for cross-transaction storage.
- *
- * Since it is a child of PortalContext, it will go away eventually even
- * if we suffer an error; there's no need for special abort cleanup logic.
- */
- vac_context = AllocSetContextCreate(PortalContext,
- "Vacuum",
- ALLOCSET_DEFAULT_SIZES);
-
- /*
- * If caller didn't give us a buffer strategy object, make one in the
- * cross-transaction memory context. We needn't bother making this for
- * VACUUM (ONLY_DATABASE_STATS) or VACUUM (FULL) without the ANALYZE
- * option, as they'll not make use of it.
- */
- if (bstrategy == NULL &&
- ((params->options & VACOPT_ONLY_DATABASE_STATS) == 0 &&
- ((params->options & VACOPT_FULL) == 0 || (params->options & VACOPT_ANALYZE))))
- {
-
- MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
- bstrategy = GetAccessStrategy(BAS_VACUUM);
- MemoryContextSwitchTo(old_context);
- }
/*
* Build list of relation(s) to process, putting any new data in
@@ -436,20 +447,6 @@ vacuum(List *relations, VacuumParams *params,
else
relations = get_all_vacuum_rels(vac_context, params->options);
- /*
- * Decide whether we need to start/commit our own transactions.
- *
- * For VACUUM (with or without ANALYZE): always do so, so that we can
- * release locks as soon as possible. (We could possibly use the outer
- * transaction for a one-table VACUUM, but handling TOAST tables would be
- * problematic.)
- *
- * For ANALYZE (no VACUUM): if inside a transaction block, we cannot
- * start/commit our own transactions. Also, there's no need to do so if
- * only processing one relation. For multiple relations when not within a
- * transaction block, and also in an autovacuum worker, use own
- * transactions so we can release locks sooner.
- */
if (params->options & VACOPT_VACUUM)
use_own_xacts = true;
else
@@ -577,12 +574,6 @@ vacuum(List *relations, VacuumParams *params,
vac_update_datfrozenxid();
}
- /*
- * Clean up working storage --- note we must do this after
- * StartTransactionCommand, else we might be trying to delete the active
- * context!
- */
- MemoryContextDelete(vac_context);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 585d28148c..76fac1fea7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -338,7 +338,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy,
+ MemoryContext portal_context);
static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
@@ -2470,7 +2471,7 @@ do_autovacuum(void)
MemoryContextSwitchTo(PortalContext);
/* have at it */
- autovacuum_do_vac_analyze(tab, bstrategy);
+ autovacuum_do_vac_analyze(tab, bstrategy, PortalContext);
/*
* Clear a possible query-cancel signal, to avoid a late reaction
@@ -3143,11 +3144,13 @@ relation_needs_vacanalyze(Oid relid,
* Vacuum and/or analyze the specified table
*/
static void
-autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
+autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy,
+ MemoryContext portal_context)
{
RangeVar *rangevar;
VacuumRelation *rel;
List *rel_list;
+ MemoryContext vac_context;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
@@ -3157,7 +3160,13 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
rel_list = list_make1(rel);
- vacuum(rel_list, &tab->at_params, bstrategy, true);
+ vac_context = AllocSetContextCreate(portal_context,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true, false);
+
+ MemoryContextDelete(vac_context);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..410eb9dece 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -310,7 +310,8 @@ extern PGDLLIMPORT int VacuumCostBalanceLocal;
/* in commands/vacuum.c */
extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel);
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
--
2.37.2
v12-0004-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v12-0004-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From bd86036f1d924f55d17ac6dbcf18a72bc9f43792 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v12 4/4] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 13 +++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 39 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8280cf0fb0 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 410eb9dece..34de657e50 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v12-0001-VACUUM-FULL-ANALYZE-needs-BufferAccessStrategy.patchtext/x-patch; charset=US-ASCII; name=v12-0001-VACUUM-FULL-ANALYZE-needs-BufferAccessStrategy.patchDownload
From e35af8098a81a80b82d25bee38ea7804eec2a9d0 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 20:02:10 -0400
Subject: [PATCH v12 1/4] VACUUM (FULL, ANALYZE) needs BufferAccessStrategy
4830f102 avoided making a BufferAccessStrategy when it would not be
used, however we overlooked that the ANALYZE stage of VACUUM (FULL,
ANALYZE) would still need the BufferAccessStrategy object. It didn't
break because ReadBuffer_common() accepts NULL BufferAccessStrategy
objects, but this was not the intended behavior.
---
src/backend/commands/vacuum.c | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da85330ef4..438e94410e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -392,13 +392,14 @@ vacuum(List *relations, VacuumParams *params,
/*
* If caller didn't give us a buffer strategy object, make one in the
* cross-transaction memory context. We needn't bother making this for
- * VACUUM (FULL) or VACUUM (ONLY_DATABASE_STATS) as they'll not make use
- * of it.
+ * VACUUM (ONLY_DATABASE_STATS) or VACUUM (FULL) without the ANALYZE
+ * option, as they'll not make use of it.
*/
if (bstrategy == NULL &&
- (params->options & (VACOPT_ONLY_DATABASE_STATS |
- VACOPT_FULL)) == 0)
+ ((params->options & VACOPT_ONLY_DATABASE_STATS) == 0 &&
+ ((params->options & VACOPT_FULL) == 0 || (params->options & VACOPT_ANALYZE))))
{
+
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
bstrategy = GetAccessStrategy(BAS_VACUUM);
--
2.37.2
v12-0003-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v12-0003-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From 241dbe346c409bd544dd102ec57d7377b8fc31ae Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 17:22:02 -0400
Subject: [PATCH v12 3/4] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 18 ++++
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 95 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 13 ++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 48 ++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/miscadmin.h | 13 +++
src/include/storage/bufmgr.h | 6 ++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 4 +
src/test/regress/sql/vacuum.sql | 3 +
17 files changed, 283 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..1d75ec62df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>, whereas
+ <literal>-1</literal> will set the size to a default of <literal>256 KB</literal>.
+ The maximum size is <literal>16 GB</literal> and the minimum size is <literal>128 KB</literal>.
+ If the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, it is silently capped.
+ The default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..16cde8668e 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>ANALYZE</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..c1ddf88cf8 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 9b3f5c03ca..72697756a8 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,29 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +138,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ /* by default use buffer access strategy with default size */
+ int ring_size = -1;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -139,6 +165,46 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not -1 or 0.
+ */
+ if (result != -1 && result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -243,6 +309,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -356,7 +432,24 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If explicit VACUUM or ANALYZE specified a non-default
+ * BUFFER_USAGE_LIMIT, that overrides the value of GUC
+ * VacuumBufferUsageLimit. If VacuumBufferUsageLimit is set to
+ * something other than the default, use its value. Otherwise, use the
+ * default sizes for a Buffer Access Strategy. Note that autovacuum
+ * will have set VacuumParams->ring_size to the value it derived from
+ * VacuumBufferUsageLimit.
+ */
+ if (ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 76fac1fea7..b094440443 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2291,9 +2291,18 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
+ *
+ * XXX: In the future we may want to react to changes in
+ * VacuumBufferUsageLimit while vacuuming.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..0c1ce8b93f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +570,36 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+ int nbuffers;
+ int sb_limit_kb;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ Assert(blcksz_kb > 0);
+
/* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+ ring_size_kb = Min(sb_limit_kb, ring_size_kb);
+
+ nbuffers = ring_size_kb / blcksz_kb;
+
+ Assert(nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +613,15 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5bad2b540f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..cdbcb95a07 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size. range 128-16777216
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..2d550f26e0 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,19 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 73762cb1ec..c174cb2c04 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -194,7 +194,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..fe675c5a2e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,10 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..fc068b7705 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,9 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
On Thu, 6 Apr 2023 at 12:42, Melanie Plageman <melanieplageman@gmail.com> wrote:
Attached is a v12 of the whole vacuum_buffer_usage_limit patch set which
includes a commit to fix the bug in master and a commit to move relevant
code from vacuum() up into ExecVacuum().
I'm still playing catch up to the moving of the pre-checks from
vacuum() to ExecVacuum(). I'm now wondering...
Is it intended that VACUUM t1,t2; now share the same strategy?
Currently, in master, we'll allocate a new strategy for t2 after
vacuuming t1. Does this not mean we'll now leave fewer t1 pages in
shared_buffers because the reuse of the strategy will force them out
with t2 pages? I understand there's nothing particularly invalid
about that, but it is a change in behaviour that the patch seems to be
making with very little consideration as to if it's better or worse.
David
On Wed, Apr 5, 2023 at 9:15 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Thu, 6 Apr 2023 at 12:42, Melanie Plageman <melanieplageman@gmail.com> wrote:
Attached is a v12 of the whole vacuum_buffer_usage_limit patch set which
includes a commit to fix the bug in master and a commit to move relevant
code from vacuum() up into ExecVacuum().I'm still playing catch up to the moving of the pre-checks from
vacuum() to ExecVacuum(). I'm now wondering...Is it intended that VACUUM t1,t2; now share the same strategy?
Currently, in master, we'll allocate a new strategy for t2 after
vacuuming t1. Does this not mean we'll now leave fewer t1 pages in
shared_buffers because the reuse of the strategy will force them out
with t2 pages? I understand there's nothing particularly invalid
about that, but it is a change in behaviour that the patch seems to be
making with very little consideration as to if it's better or worse.
I'm pretty sure that in master we also reuse the strategy since we make
it above this loop in vacuum() (and pass it in)
/*
* Loop to process each selected relation.
*/
foreach(cur, relations)
{
VacuumRelation *vrel = lfirst_node(VacuumRelation, cur);
if (params->options & VACOPT_VACUUM)
{
if (!vacuum_rel(vrel->oid, vrel->relation, params,
false, bstrategy))
continue;
}
On Wed, Apr 5, 2023 at 8:41 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Wed, Apr 5, 2023 at 6:55 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:On Wed, Apr 5, 2023 at 5:15 PM Andres Freund <andres@anarazel.de> wrote:
Hi,
On 2023-04-05 16:17:20 -0400, Melanie Plageman wrote:
On Wed, Apr 5, 2023 at 1:05 PM Andres Freund <andres@anarazel.de> wrote:
Perhaps the best solution for autovac vs interactive vacuum issue would be to
move the allocation of the bstrategy to ExecVacuum()?So, I started looking into allocating the bstrategy in ExecVacuum().
While doing so, I was trying to understand if the "sanity checking" in
vacuum() could possibly apply to autovacuum, and I don't really see how.AFAICT, autovacuum does not ever set VACOPT_DISABLE_PAGE_SKIPPING or
VACOPT_FULL or VACOPT_ONLY_DATABASE_STATS.We could move those sanity checks up into ExecVacuum().
Would make sense.
ISTM that eventually most of what currently happens in vacuum() should be in
ExecVacuum(). There's a lot of stuff that can't happen for autovacuum. So it
just seems to make more sense to move those parts to ExecVacuum().I've done that in the attached wip patch. It is perhaps too much of a
change, I dunno.I also noticed that we make the vac_context in vacuum() which says it is
for "cross-transaction storage". We use it for the buffer access
strategy and for the newrels relation list created in vacuum(). Then we
delete it at the end of vacuum().Autovacuum workers already make a similar kind of memory context called
AutovacMemCxt in do_autovacuum() which the comment says is for the list
of relations to vacuum/analyze across transactions.AutovacMemCxt seems to be a bit longer lived / cover more than the context
created in vacuum(). It's where all the hash tables etc live that
do_autovacuum() uses to determine what to vacuum.Note that do_autovacuum() also creates:
/*
* create a memory context to act as fake PortalContext, so that the
* contexts created in the vacuum code are cleaned up for each table.
*/
PortalContext = AllocSetContextCreate(AutovacMemCxt,
"Autovacuum Portal",
ALLOCSET_DEFAULT_SIZES);which is then what vacuum() creates the "Vacuum" context in.
Yea, I realized that when writing the patch.
What if we made ExecVacuum() make its own memory context and both it and
do_autovacuum() pass that memory context (along with the buffer access
strategy they make) to vacuum(), which then uses the memory context in
the same way it does now?Maybe? It's not clear to me why it'd be a win.
Less that it is a win and more that we need access to that memory
context when allocating the buffer access strategy, so we would have had
to make it in ExecVacuum(). And if we have already made it, we would
need to pass it in to vacuum() for it to use.It simplifies the buffer usage limit patchset and also seems a bit more
clear than what is there now?I don't really see what it'd make simpler? The context in vacuum() is used for
just that vacuum - we couldn't just use AutovacMemCxt, as that'd live much
longer (for all the tables a autovac worker processes).Autovacuum already made the BufferAccessStrategy in the AutovacMemCxt,
so this is the same behavior. I simply made autovacuum_do_vac_analyze()
make the per table vacuum memory context and pass that to vacuum(). So
we have the same amount of memory context granularity as before.Attached patchset has some kind of isolation test failure due to a hard
deadlock that I haven't figured out yet. I thought it was something with
the "in_vacuum" static variable and having VACUUM or ANALYZE called when
already in VACUUM or ANALYZE, but that variable is the same as in
master.I've mostly shared it because I want to know if this approach is worth
pursuing or not.Figured out how to fix the issue, though I'm not sure I understand how
the issue can occur.
use_own_xacts seems like it will always be true for autovacuum when it
calls vacuum() and ExecVacuum() only calls vacuum() once, so I thought
that I could make use_own_xacts a parameter to vacuum() and push up its
calculation for VACUUM and ANALYZE into ExecVacuum().
This caused a deadlock, so there must be a way that in_vacuum is false
but vacuum() is called in a nested context.
Anyway, recalculating it every time in vacuum() fixes it.Attached is a v12 of the whole vacuum_buffer_usage_limit patch set which
includes a commit to fix the bug in master and a commit to move relevant
code from vacuum() up into ExecVacuum().The logic I suggested earlier for fixing the bug was...not right.
Attached fix should be right?
David had already pushed a fix, so the patchset had merge conflicts.
Attached v13 should work.
- Melanie
Attachments:
v13-0001-Push-vacuum-setup-code-up-into-ExecVacuum.patchtext/x-patch; charset=US-ASCII; name=v13-0001-Push-vacuum-setup-code-up-into-ExecVacuum.patchDownload
From 5831e5de0b9c9af278ec58d2c9b96410151d0c42 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 20:05:25 -0400
Subject: [PATCH v13 1/3] Push vacuum() setup code up into ExecVacuum()
Much of the sanity checking and setup done in vacuum() code was specific
to explicit VACUUM and ANALYZE. Move that code up into ExecVacuum().
While we are at it, allocate VACUUM/ANALYZE's BufferAccessStrategy in
ExecVacuum() and pass it into vacuum() instead of expecting vacuum() to
make it. This mimics autovacuum's behavior. To do this, create the
relevant memory context in ExecVacuum() and pass it in as a new
parameter to vacuum().
---
src/backend/commands/vacuum.c | 191 +++++++++++++++-------------
src/backend/postmaster/autovacuum.c | 17 ++-
src/include/commands/vacuum.h | 3 +-
3 files changed, 115 insertions(+), 96 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2c31745fbc..cde5ee41a5 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -105,6 +105,7 @@ void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
+ BufferAccessStrategy bstrategy = NULL;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
@@ -115,8 +116,12 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool process_toast = true;
bool skip_database_stats = false;
bool only_database_stats = false;
+ MemoryContext vac_context;
ListCell *lc;
+ const char *stmttype;
+ volatile bool in_outer_xact;
+
/* index_cleanup and truncate values unspecified for now */
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
params.truncate = VACOPTVALUE_UNSPECIFIED;
@@ -254,6 +259,42 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
}
}
+
+ /*
+ * Sanity check DISABLE_PAGE_SKIPPING option.
+ */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
+
+ /* sanity check for PROCESS_TOAST */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_PROCESS_TOAST) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROCESS_TOAST required with VACUUM FULL")));
+
+ /* sanity check for ONLY_DATABASE_STATS */
+ if (params.options & VACOPT_ONLY_DATABASE_STATS)
+ {
+ Assert(params.options & VACOPT_VACUUM);
+ if (vacstmt->rels != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
+ /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
+ if (params.options & ~(VACOPT_VACUUM |
+ VACOPT_VERBOSE |
+ VACOPT_PROCESS_MAIN |
+ VACOPT_PROCESS_TOAST |
+ VACOPT_ONLY_DATABASE_STATS))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
+ }
+
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@@ -279,8 +320,62 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */
params.log_min_duration = -1;
+ stmttype = (params.options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
+
+ /*
+ * We cannot run VACUUM inside a user transaction block; if we were inside
+ * a transaction, then our commit- and start-transaction-command calls
+ * would not have the intended effect! There are numerous other subtle
+ * dependencies on this, too.
+ *
+ * ANALYZE (without VACUUM) can run either way.
+ */
+ if (params.options & VACOPT_VACUUM)
+ {
+ PreventInTransactionBlock(isTopLevel, stmttype);
+ in_outer_xact = false;
+ }
+ else
+ in_outer_xact = IsInTransactionBlock(isTopLevel);
+
+ /*
+ * Create special memory context for cross-transaction storage.
+ *
+ * Since it is a child of PortalContext, it will go away eventually even
+ * if we suffer an error; there's no need for special abort cleanup logic.
+ */
+ vac_context = AllocSetContextCreate(PortalContext,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /*
+ * Make a buffer strategy object in the cross-transaction memory context.
+ * We needn't bother making this for VACUUM (FULL) or VACUUM
+ * (ONLY_DATABASE_STATS) as they'll not make use of it. VACUUM (FULL,
+ * ANALYZE) is possible, so we'd better ensure that we make a strategy when
+ * we see ANALYZE.
+ */
+ if ((params.options & (VACOPT_ONLY_DATABASE_STATS |
+ VACOPT_FULL)) == 0 ||
+ (params.options & VACOPT_ANALYZE) != 0)
+ {
+
+ MemoryContext old_context = MemoryContextSwitchTo(vac_context);
+
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ MemoryContextSwitchTo(old_context);
+ }
+
/* Now go through the common routine */
- vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel);
+ vacuum(vacstmt->rels, ¶ms, bstrategy, vac_context, isTopLevel,
+ in_outer_xact);
+
+ /*
+ * Clean up working storage --- note we must do this after
+ * StartTransactionCommand, else we might be trying to delete the active
+ * context!
+ */
+ MemoryContextDelete(vac_context);
}
/*
@@ -304,35 +399,18 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
*/
void
vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel)
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact)
{
- static bool in_vacuum = false;
- MemoryContext vac_context;
const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
+ static bool in_vacuum = false;
+ volatile bool use_own_xacts;
Assert(params != NULL);
stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
- /*
- * We cannot run VACUUM inside a user transaction block; if we were inside
- * a transaction, then our commit- and start-transaction-command calls
- * would not have the intended effect! There are numerous other subtle
- * dependencies on this, too.
- *
- * ANALYZE (without VACUUM) can run either way.
- */
- if (params->options & VACOPT_VACUUM)
- {
- PreventInTransactionBlock(isTopLevel, stmttype);
- in_outer_xact = false;
- }
- else
- in_outer_xact = IsInTransactionBlock(isTopLevel);
-
/*
* Check for and disallow recursive calls. This could happen when VACUUM
* FULL or ANALYZE calls a hostile index expression that itself calls
@@ -344,69 +422,6 @@ vacuum(List *relations, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype)));
- /*
- * Sanity check DISABLE_PAGE_SKIPPING option.
- */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
-
- /* sanity check for PROCESS_TOAST */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_PROCESS_TOAST) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PROCESS_TOAST required with VACUUM FULL")));
-
- /* sanity check for ONLY_DATABASE_STATS */
- if (params->options & VACOPT_ONLY_DATABASE_STATS)
- {
- Assert(params->options & VACOPT_VACUUM);
- if (relations != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
- /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
- if (params->options & ~(VACOPT_VACUUM |
- VACOPT_VERBOSE |
- VACOPT_PROCESS_MAIN |
- VACOPT_PROCESS_TOAST |
- VACOPT_ONLY_DATABASE_STATS))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
- }
-
- /*
- * Create special memory context for cross-transaction storage.
- *
- * Since it is a child of PortalContext, it will go away eventually even
- * if we suffer an error; there's no need for special abort cleanup logic.
- */
- vac_context = AllocSetContextCreate(PortalContext,
- "Vacuum",
- ALLOCSET_DEFAULT_SIZES);
-
- /*
- * If caller didn't give us a buffer strategy object, make one in the
- * cross-transaction memory context. We needn't bother making this for
- * VACUUM (FULL) or VACUUM (ONLY_DATABASE_STATS) as they'll not make use
- * of it. VACUUM (FULL, ANALYZE) is possible, so we'd better ensure that
- * we make a strategy when we see ANALYZE.
- */
- if (bstrategy == NULL &&
- ((params->options & (VACOPT_ONLY_DATABASE_STATS |
- VACOPT_FULL)) == 0 ||
- (params->options & VACOPT_ANALYZE) != 0))
- {
- MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
- bstrategy = GetAccessStrategy(BAS_VACUUM);
- MemoryContextSwitchTo(old_context);
- }
-
/*
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
@@ -578,12 +593,6 @@ vacuum(List *relations, VacuumParams *params,
vac_update_datfrozenxid();
}
- /*
- * Clean up working storage --- note we must do this after
- * StartTransactionCommand, else we might be trying to delete the active
- * context!
- */
- MemoryContextDelete(vac_context);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e9ba0dc17c..d2b6f41ead 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -338,7 +338,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy,
+ MemoryContext portal_context);
static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
@@ -2469,7 +2470,7 @@ do_autovacuum(void)
MemoryContextSwitchTo(PortalContext);
/* have at it */
- autovacuum_do_vac_analyze(tab, bstrategy);
+ autovacuum_do_vac_analyze(tab, bstrategy, PortalContext);
/*
* Clear a possible query-cancel signal, to avoid a late reaction
@@ -3142,11 +3143,13 @@ relation_needs_vacanalyze(Oid relid,
* Vacuum and/or analyze the specified table
*/
static void
-autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
+autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy,
+ MemoryContext portal_context)
{
RangeVar *rangevar;
VacuumRelation *rel;
List *rel_list;
+ MemoryContext vac_context;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
@@ -3156,7 +3159,13 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
rel_list = list_make1(rel);
- vacuum(rel_list, &tab->at_params, bstrategy, true);
+ vac_context = AllocSetContextCreate(portal_context,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true, false);
+
+ MemoryContextDelete(vac_context);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..410eb9dece 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -310,7 +310,8 @@ extern PGDLLIMPORT int VacuumCostBalanceLocal;
/* in commands/vacuum.c */
extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel);
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
--
2.37.2
v13-0003-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v13-0003-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 53d068f0ec3f91666bfa62c9a0e7f2e8ca1b21af Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v13 3/3] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 13 +++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 39 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8280cf0fb0 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 410eb9dece..34de657e50 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v13-0002-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v13-0002-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From 399192400ab1e8f8bb13b4cbb17141beba503339 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 17:22:02 -0400
Subject: [PATCH v13 2/3] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 18 ++++
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 95 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 13 ++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 48 ++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/miscadmin.h | 13 +++
src/include/storage/bufmgr.h | 6 ++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 4 +
src/test/regress/sql/vacuum.sql | 3 +
17 files changed, 283 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..1d75ec62df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>, whereas
+ <literal>-1</literal> will set the size to a default of <literal>256 KB</literal>.
+ The maximum size is <literal>16 GB</literal> and the minimum size is <literal>128 KB</literal>.
+ If the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, it is silently capped.
+ The default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..16cde8668e 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>ANALYZE</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..c1ddf88cf8 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index cde5ee41a5..5427e2dedc 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,29 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +138,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ /* by default use buffer access strategy with default size */
+ int ring_size = -1;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -139,6 +165,46 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not -1 or 0.
+ */
+ if (result != -1 && result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -243,6 +309,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -362,7 +438,24 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If explicit VACUUM or ANALYZE specified a non-default
+ * BUFFER_USAGE_LIMIT, that overrides the value of GUC
+ * VacuumBufferUsageLimit. If VacuumBufferUsageLimit is set to
+ * something other than the default, use its value. Otherwise, use the
+ * default sizes for a Buffer Access Strategy. Note that autovacuum
+ * will have set VacuumParams->ring_size to the value it derived from
+ * VacuumBufferUsageLimit.
+ */
+ if (ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d2b6f41ead..421adc7ad7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,18 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
+ *
+ * XXX: In the future we may want to react to changes in
+ * VacuumBufferUsageLimit while vacuuming.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..0c1ce8b93f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +570,36 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+ int nbuffers;
+ int sb_limit_kb;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ Assert(blcksz_kb > 0);
+
/* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+ ring_size_kb = Min(sb_limit_kb, ring_size_kb);
+
+ nbuffers = ring_size_kb / blcksz_kb;
+
+ Assert(nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +613,15 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5bad2b540f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..cdbcb95a07 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size. range 128-16777216
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..2d550f26e0 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,19 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 788aa279ba..30daa11f7b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -260,7 +260,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..fe675c5a2e 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,10 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..fc068b7705 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,9 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
On Thu, 6 Apr 2023 at 13:14, David Rowley <dgrowleyml@gmail.com> wrote:
Is it intended that VACUUM t1,t2; now share the same strategy?
Currently, in master, we'll allocate a new strategy for t2 after
vacuuming t1. Does this not mean we'll now leave fewer t1 pages in
shared_buffers because the reuse of the strategy will force them out
with t2 pages? I understand there's nothing particularly invalid
about that, but it is a change in behaviour that the patch seems to be
making with very little consideration as to if it's better or worse.
Actually, never mind that. I'm wrong. The same strategy is used for
both tables before and after this change.
I stumbled on thinking vacuum() was being called in a loop from
ExecVacuum() rather than it just passing all of the relations to
vacuum() and the loop being done inside vacuum(), which is does.
David
Attached is v14 which adds back in tests for the BUFFER_USAGE_LIMIT
option. I haven't included a test for VACUUM (BUFFER_USAGE_LIMIT x,
PARALLEL x) for the reason I mentioned upthread -- even if we force it
to actually do the parallel vacuuming, we are adding exercising the code
where parallel vacuum workers make their own buffer access strategy
rings but not really adding a test that will fail usefully. If something
is wrong with the configurability of the buffer access strategy object,
I don't see how it will break differently in parallel vacuum workers vs
regular vacuum.
- Melanie
Attachments:
v14-0003-Add-buffer-usage-limit-option-to-vacuumdb.patchtext/x-patch; charset=US-ASCII; name=v14-0003-Add-buffer-usage-limit-option-to-vacuumdb.patchDownload
From 7d06a4deb75fe4dfe33e41f8261b43532ab29c3f Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Sun, 19 Mar 2023 18:00:28 -0400
Subject: [PATCH v14 3/3] Add buffer-usage-limit option to vacuumdb
---
doc/src/sgml/ref/vacuumdb.sgml | 13 +++++++++++++
src/bin/scripts/vacuumdb.c | 23 +++++++++++++++++++++++
src/include/commands/vacuum.h | 3 +++
3 files changed, 39 insertions(+)
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 74bac2d4ba..8280cf0fb0 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -278,6 +278,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 39be265b5b..941eac7727 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -46,6 +46,7 @@ typedef struct vacuumingOptions
bool process_main;
bool process_toast;
bool skip_database_stats;
+ char *buffer_usage_limit;
} vacuumingOptions;
/* object filter options */
@@ -123,6 +124,7 @@ main(int argc, char *argv[])
{"no-truncate", no_argument, NULL, 10},
{"no-process-toast", no_argument, NULL, 11},
{"no-process-main", no_argument, NULL, 12},
+ {"buffer-usage-limit", required_argument, NULL, 13},
{NULL, 0, NULL, 0}
};
@@ -147,6 +149,7 @@ main(int argc, char *argv[])
/* initialize options */
memset(&vacopts, 0, sizeof(vacopts));
vacopts.parallel_workers = -1;
+ vacopts.buffer_usage_limit = NULL;
vacopts.no_index_cleanup = false;
vacopts.force_index_cleanup = false;
vacopts.do_truncate = true;
@@ -266,6 +269,9 @@ main(int argc, char *argv[])
case 12:
vacopts.process_main = false;
break;
+ case 13:
+ vacopts.buffer_usage_limit = pg_strdup(optarg);
+ break;
default:
/* getopt_long already emitted a complaint */
pg_log_error_hint("Try \"%s --help\" for more information.", progname);
@@ -343,6 +349,11 @@ main(int argc, char *argv[])
pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
"no-index-cleanup", "force-index-cleanup");
+ /* buffer-usage-limit doesn't make sense with VACUUM FULL */
+ if (vacopts.buffer_usage_limit && vacopts.full)
+ pg_fatal("cannot use the \"%s\" option with the \"%s\" option",
+ "buffer-usage-limit", "full");
+
/* fill cparams except for dbname, which is set below */
cparams.pghost = host;
cparams.pgport = port;
@@ -550,6 +561,10 @@ vacuum_one_database(ConnParams *cparams,
pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
"--parallel", "13");
+ if (vacopts->buffer_usage_limit && PQserverVersion(conn) < 160000)
+ pg_fatal("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
+ "--buffer-usage-limit", "16");
+
/* skip_database_stats is used automatically if server supports it */
vacopts->skip_database_stats = (PQserverVersion(conn) >= 160000);
@@ -1048,6 +1063,13 @@ prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
vacopts->parallel_workers);
sep = comma;
}
+ if (vacopts->buffer_usage_limit)
+ {
+ Assert(serverVersion >= 160000);
+ appendPQExpBuffer(sql, "%sBUFFER_USAGE_LIMIT '%s'", sep,
+ vacopts->buffer_usage_limit);
+ sep = comma;
+ }
if (sep != paren)
appendPQExpBufferChar(sql, ')');
}
@@ -1111,6 +1133,7 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
+ printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 410eb9dece..34de657e50 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -213,6 +213,9 @@ typedef enum VacOptValue
*
* Note that at least one of VACOPT_VACUUM and VACOPT_ANALYZE must be set
* in options.
+ *
+ * When adding a new VacuumParam member, consider adding it to vacuumdb as
+ * well.
*/
typedef struct VacuumParams
{
--
2.37.2
v14-0001-Push-vacuum-setup-code-up-into-ExecVacuum.patchtext/x-patch; charset=US-ASCII; name=v14-0001-Push-vacuum-setup-code-up-into-ExecVacuum.patchDownload
From 045a6e94dbc1fa6e53cf88ee39b56abbc18a0d0f Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 20:05:25 -0400
Subject: [PATCH v14 1/3] Push vacuum() setup code up into ExecVacuum()
Much of the sanity checking and setup done in vacuum() code was specific
to explicit VACUUM and ANALYZE. Move that code up into ExecVacuum().
While we are at it, allocate VACUUM/ANALYZE's BufferAccessStrategy in
ExecVacuum() and pass it into vacuum() instead of expecting vacuum() to
make it. This mimics autovacuum's behavior. To do this, create the
relevant memory context in ExecVacuum() and pass it in as a new
parameter to vacuum().
---
src/backend/commands/vacuum.c | 186 ++++++++++++++--------------
src/backend/postmaster/autovacuum.c | 17 ++-
src/include/commands/vacuum.h | 3 +-
3 files changed, 110 insertions(+), 96 deletions(-)
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 2c31745fbc..bb2787411c 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -105,6 +105,7 @@ void
ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
{
VacuumParams params;
+ BufferAccessStrategy bstrategy = NULL;
bool verbose = false;
bool skip_locked = false;
bool analyze = false;
@@ -115,8 +116,12 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool process_toast = true;
bool skip_database_stats = false;
bool only_database_stats = false;
+ MemoryContext vac_context;
ListCell *lc;
+ const char *stmttype;
+ volatile bool in_outer_xact;
+
/* index_cleanup and truncate values unspecified for now */
params.index_cleanup = VACOPTVALUE_UNSPECIFIED;
params.truncate = VACOPTVALUE_UNSPECIFIED;
@@ -254,6 +259,42 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
}
}
+
+ /*
+ * Sanity check DISABLE_PAGE_SKIPPING option.
+ */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
+
+ /* sanity check for PROCESS_TOAST */
+ if ((params.options & VACOPT_FULL) != 0 &&
+ (params.options & VACOPT_PROCESS_TOAST) == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PROCESS_TOAST required with VACUUM FULL")));
+
+ /* sanity check for ONLY_DATABASE_STATS */
+ if (params.options & VACOPT_ONLY_DATABASE_STATS)
+ {
+ Assert(params.options & VACOPT_VACUUM);
+ if (vacstmt->rels != NIL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
+ /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
+ if (params.options & ~(VACOPT_VACUUM |
+ VACOPT_VERBOSE |
+ VACOPT_PROCESS_MAIN |
+ VACOPT_PROCESS_TOAST |
+ VACOPT_ONLY_DATABASE_STATS))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
+ }
+
/*
* All freeze ages are zero if the FREEZE option is given; otherwise pass
* them as -1 which means to use the default values.
@@ -279,8 +320,57 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* user-invoked vacuum uses VACOPT_VERBOSE instead of log_min_duration */
params.log_min_duration = -1;
+ stmttype = (params.options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
+
+ /*
+ * We cannot run VACUUM inside a user transaction block; if we were inside
+ * a transaction, then our commit- and start-transaction-command calls
+ * would not have the intended effect! There are numerous other subtle
+ * dependencies on this, too.
+ *
+ * ANALYZE (without VACUUM) can run either way.
+ */
+ if (params.options & VACOPT_VACUUM)
+ {
+ PreventInTransactionBlock(isTopLevel, stmttype);
+ in_outer_xact = false;
+ }
+ else
+ in_outer_xact = IsInTransactionBlock(isTopLevel);
+
+ /*
+ * Create special memory context for cross-transaction storage.
+ *
+ * Since it is a child of PortalContext, it will go away eventually even
+ * if we suffer an error; there's no need for special abort cleanup logic.
+ */
+ vac_context = AllocSetContextCreate(PortalContext,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /*
+ * Make a buffer strategy object in the cross-transaction memory context.
+ * We needn't bother making this for VACUUM (FULL) or VACUUM
+ * (ONLY_DATABASE_STATS) as they'll not make use of it. VACUUM (FULL,
+ * ANALYZE) is possible, so we'd better ensure that we make a strategy when
+ * we see ANALYZE.
+ */
+ if ((params.options & (VACOPT_ONLY_DATABASE_STATS |
+ VACOPT_FULL)) == 0 ||
+ (params.options & VACOPT_ANALYZE) != 0)
+ {
+
+ MemoryContext old_context = MemoryContextSwitchTo(vac_context);
+
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+ MemoryContextSwitchTo(old_context);
+ }
+
/* Now go through the common routine */
- vacuum(vacstmt->rels, ¶ms, NULL, isTopLevel);
+ vacuum(vacstmt->rels, ¶ms, bstrategy, vac_context, isTopLevel,
+ in_outer_xact);
+
+ MemoryContextDelete(vac_context);
}
/*
@@ -304,35 +394,18 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
*/
void
vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel)
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact)
{
- static bool in_vacuum = false;
- MemoryContext vac_context;
const char *stmttype;
- volatile bool in_outer_xact,
- use_own_xacts;
+ static bool in_vacuum = false;
+ volatile bool use_own_xacts;
Assert(params != NULL);
stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE";
- /*
- * We cannot run VACUUM inside a user transaction block; if we were inside
- * a transaction, then our commit- and start-transaction-command calls
- * would not have the intended effect! There are numerous other subtle
- * dependencies on this, too.
- *
- * ANALYZE (without VACUUM) can run either way.
- */
- if (params->options & VACOPT_VACUUM)
- {
- PreventInTransactionBlock(isTopLevel, stmttype);
- in_outer_xact = false;
- }
- else
- in_outer_xact = IsInTransactionBlock(isTopLevel);
-
/*
* Check for and disallow recursive calls. This could happen when VACUUM
* FULL or ANALYZE calls a hostile index expression that itself calls
@@ -344,69 +417,6 @@ vacuum(List *relations, VacuumParams *params,
errmsg("%s cannot be executed from VACUUM or ANALYZE",
stmttype)));
- /*
- * Sanity check DISABLE_PAGE_SKIPPING option.
- */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_DISABLE_PAGE_SKIPPING) != 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL")));
-
- /* sanity check for PROCESS_TOAST */
- if ((params->options & VACOPT_FULL) != 0 &&
- (params->options & VACOPT_PROCESS_TOAST) == 0)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("PROCESS_TOAST required with VACUUM FULL")));
-
- /* sanity check for ONLY_DATABASE_STATS */
- if (params->options & VACOPT_ONLY_DATABASE_STATS)
- {
- Assert(params->options & VACOPT_VACUUM);
- if (relations != NIL)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with a list of tables")));
- /* don't require people to turn off PROCESS_TOAST/MAIN explicitly */
- if (params->options & ~(VACOPT_VACUUM |
- VACOPT_VERBOSE |
- VACOPT_PROCESS_MAIN |
- VACOPT_PROCESS_TOAST |
- VACOPT_ONLY_DATABASE_STATS))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("ONLY_DATABASE_STATS cannot be specified with other VACUUM options")));
- }
-
- /*
- * Create special memory context for cross-transaction storage.
- *
- * Since it is a child of PortalContext, it will go away eventually even
- * if we suffer an error; there's no need for special abort cleanup logic.
- */
- vac_context = AllocSetContextCreate(PortalContext,
- "Vacuum",
- ALLOCSET_DEFAULT_SIZES);
-
- /*
- * If caller didn't give us a buffer strategy object, make one in the
- * cross-transaction memory context. We needn't bother making this for
- * VACUUM (FULL) or VACUUM (ONLY_DATABASE_STATS) as they'll not make use
- * of it. VACUUM (FULL, ANALYZE) is possible, so we'd better ensure that
- * we make a strategy when we see ANALYZE.
- */
- if (bstrategy == NULL &&
- ((params->options & (VACOPT_ONLY_DATABASE_STATS |
- VACOPT_FULL)) == 0 ||
- (params->options & VACOPT_ANALYZE) != 0))
- {
- MemoryContext old_context = MemoryContextSwitchTo(vac_context);
-
- bstrategy = GetAccessStrategy(BAS_VACUUM);
- MemoryContextSwitchTo(old_context);
- }
-
/*
* Build list of relation(s) to process, putting any new data in
* vac_context for safekeeping.
@@ -578,12 +588,6 @@ vacuum(List *relations, VacuumParams *params,
vac_update_datfrozenxid();
}
- /*
- * Clean up working storage --- note we must do this after
- * StartTransactionCommand, else we might be trying to delete the active
- * context!
- */
- MemoryContextDelete(vac_context);
}
/*
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e9ba0dc17c..d2b6f41ead 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -338,7 +338,8 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts,
bool *dovacuum, bool *doanalyze, bool *wraparound);
static void autovacuum_do_vac_analyze(autovac_table *tab,
- BufferAccessStrategy bstrategy);
+ BufferAccessStrategy bstrategy,
+ MemoryContext portal_context);
static AutoVacOpts *extract_autovac_opts(HeapTuple tup,
TupleDesc pg_class_desc);
static void perform_work_item(AutoVacuumWorkItem *workitem);
@@ -2469,7 +2470,7 @@ do_autovacuum(void)
MemoryContextSwitchTo(PortalContext);
/* have at it */
- autovacuum_do_vac_analyze(tab, bstrategy);
+ autovacuum_do_vac_analyze(tab, bstrategy, PortalContext);
/*
* Clear a possible query-cancel signal, to avoid a late reaction
@@ -3142,11 +3143,13 @@ relation_needs_vacanalyze(Oid relid,
* Vacuum and/or analyze the specified table
*/
static void
-autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
+autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy,
+ MemoryContext portal_context)
{
RangeVar *rangevar;
VacuumRelation *rel;
List *rel_list;
+ MemoryContext vac_context;
/* Let pgstat know what we're doing */
autovac_report_activity(tab);
@@ -3156,7 +3159,13 @@ autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy)
rel = makeVacuumRelation(rangevar, tab->at_relid, NIL);
rel_list = list_make1(rel);
- vacuum(rel_list, &tab->at_params, bstrategy, true);
+ vac_context = AllocSetContextCreate(portal_context,
+ "Vacuum",
+ ALLOCSET_DEFAULT_SIZES);
+
+ vacuum(rel_list, &tab->at_params, bstrategy, vac_context, true, false);
+
+ MemoryContextDelete(vac_context);
}
/*
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index bdfd96cfec..410eb9dece 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -310,7 +310,8 @@ extern PGDLLIMPORT int VacuumCostBalanceLocal;
/* in commands/vacuum.c */
extern void ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel);
extern void vacuum(List *relations, VacuumParams *params,
- BufferAccessStrategy bstrategy, bool isTopLevel);
+ BufferAccessStrategy bstrategy, MemoryContext vac_context,
+ bool isTopLevel, bool in_outer_xact);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
--
2.37.2
v14-0002-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchtext/x-patch; charset=US-ASCII; name=v14-0002-Add-VACUUM-BUFFER_USAGE_LIMIT-option-and-GUC.patchDownload
From 8173a4a915b9c2c27d2f6958737c556e89e44f38 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 5 Apr 2023 17:22:02 -0400
Subject: [PATCH v14 2/3] Add VACUUM BUFFER_USAGE_LIMIT option and GUC
Add GUC, vacuum_buffer_usage_limit, and VACUUM option,
BUFFER_USAGE_LIMIT, through which the user can specify the maximum size
to use for buffers for VACUUM, ANALYZE, and autovacuum. The size is
converted into a number of shared buffers which are tracked in a
BufferAccessStrategyData object. The explicit VACUUM option, when
specified, overrides the GUC value, unless it is specified as -1.
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://www.postgresql.org/message-id/flat/20230111182720.ejifsclfwymw2reb%40awork3.anarazel.de
---
doc/src/sgml/config.sgml | 30 ++++++
doc/src/sgml/ref/analyze.sgml | 18 ++++
doc/src/sgml/ref/vacuum.sgml | 22 +++++
src/backend/commands/vacuum.c | 95 ++++++++++++++++++-
src/backend/commands/vacuumparallel.c | 14 ++-
src/backend/postmaster/autovacuum.c | 13 ++-
src/backend/storage/buffer/README | 4 +
src/backend/storage/buffer/freelist.c | 48 ++++++++--
src/backend/utils/init/globals.c | 5 +-
src/backend/utils/misc/guc_tables.c | 11 +++
src/backend/utils/misc/postgresql.conf.sample | 4 +
src/bin/psql/tab-complete.c | 4 +-
src/include/miscadmin.h | 13 +++
src/include/storage/bufmgr.h | 6 ++
src/include/utils/guc_hooks.h | 3 +
src/test/regress/expected/vacuum.out | 19 ++++
src/test/regress/sql/vacuum.sql | 14 +++
17 files changed, 309 insertions(+), 14 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..1d75ec62df 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,36 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>, whereas
+ <literal>-1</literal> will set the size to a default of <literal>256 KB</literal>.
+ The maximum size is <literal>16 GB</literal> and the minimum size is <literal>128 KB</literal>.
+ If the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, it is silently capped.
+ The default value is <literal>-1</literal>. If this value is specified
+ without units, it is taken as kilobytes. This parameter can be set at
+ any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..16cde8668e 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>ANALYZE</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..c1ddf88cf8 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,27 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. <literal>-1</literal>
+ indicates that <command>VACUUM</command> should fall back to the value
+ specified by <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ This option can't be used with the <option>FULL</option> option or
+ <option>ONLY_DATABASE_STATS</option> option. If
+ <option>ANALYZE</option> is also specified, the
+ <option>BUFFER_USAGE_LIMIT</option> value is used for both the vacuum
+ and analyze stages.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bb2787411c..6f9255f18e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,29 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range. This should match the checking in ExecVacuum().
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +138,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ /* by default use buffer access strategy with default size */
+ int ring_size = -1;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -139,6 +165,46 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not -1 or 0.
+ */
+ if (result != -1 && result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit for a vacuum must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -243,6 +309,16 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ if ((params.options & VACOPT_FULL) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
+ if ((params.options & VACOPT_ONLY_DATABASE_STATS) && ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -362,7 +438,24 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If explicit VACUUM or ANALYZE specified a non-default
+ * BUFFER_USAGE_LIMIT, that overrides the value of GUC
+ * VacuumBufferUsageLimit. If VacuumBufferUsageLimit is set to
+ * something other than the default, use its value. Otherwise, use the
+ * default sizes for a Buffer Access Strategy. Note that autovacuum
+ * will have set VacuumParams->ring_size to the value it derived from
+ * VacuumBufferUsageLimit.
+ */
+ if (ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..614a35779a 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = StrategyGetBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index d2b6f41ead..421adc7ad7 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2290,9 +2290,18 @@ do_autovacuum(void)
/*
* Create a buffer access strategy object for VACUUM to use. We want to
* use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * point is for VACUUM not to blow out the shared cache. If we later enter
+ * failsafe mode, we will cease use of the BufferAccessStrategy. Either
+ * way, we clean up the BufferAccessStrategy object at the end of this
+ * function.
+ *
+ * XXX: In the future we may want to react to changes in
+ * VacuumBufferUsageLimit while vacuuming.
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..d7c9d99af2 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -236,6 +236,10 @@ buffers were sent to the freelist, which was effectively a buffer ring of 1
buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
256KB between WAL flushes should be more efficient.
+In v16, the 256KB ring was made configurable by way of the
+vacuum_buffer_usage_limit GUC and the BUFFER_USAGE_LIMIT option to VACUUM and
+ANALYZE.
+
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
seqscan UPDATE and DELETE use the bulkwrite strategy?) For bulk writes
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..0c1ce8b93f 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,8 +570,36 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int blcksz_kb = BLCKSZ / 1024;
+ int nbuffers;
+ int sb_limit_kb;
+ BufferAccessStrategy strategy;
+
+ /* Default nbuffers should have resulted in calling GetAccessStrategy() */
+ Assert(ring_size_kb >= 0);
+
+ if (ring_size_kb == 0)
+ return NULL;
+
+ Assert(blcksz_kb > 0);
+
/* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ sb_limit_kb = NBuffers / 8 * blcksz_kb;
+ ring_size_kb = Min(sb_limit_kb, ring_size_kb);
+
+ nbuffers = ring_size_kb / blcksz_kb;
+
+ Assert(nbuffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
@@ -586,6 +613,15 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return strategy;
}
+/*
+ * StrategyGetBufferCount -- an accessor for the number of buffers in the ring
+ */
+int
+StrategyGetBufferCount(BufferAccessStrategy strategy)
+{
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..059a097bef 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = -1;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..5bad2b540f 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ -1, -1, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..cdbcb95a07 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,10 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = -1 # size of vacuum and analyze buffer access strategy ring.
+ # -1 to use default,
+ # 0 to disable vacuum buffer access strategy
+ # > 0 to specify size. range 128-16777216
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..2d550f26e0 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,19 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
+
+/*
+ * Upper and lower hard limits for the Buffer Access Strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 788aa279ba..30daa11f7b 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -260,7 +260,13 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(
+ BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int StrategyGetBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..1893292634 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,9 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..1e94cf0435 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,25 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+ERROR: buffer_usage_limit for a vacuum must be 0 or between 128 KB and 16777216 KB
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+ERROR: buffer_usage_limit for a vacuum must be 0 or between 128 KB and 16777216 KB
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
+-- incompatible with VACUUM (ONLY_DATABASE_STATS) error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM ONLY_DATABASE_STATS
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..9b742ff6d4 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,20 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+-- incompatible with VACUUM (ONLY_DATABASE_STATS) error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', ONLY_DATABASE_STATS) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
--
2.37.2
second On Thu, 6 Apr 2023 at 14:14, Melanie Plageman
<melanieplageman@gmail.com> wrote:
Attached is v14 which adds back in tests for the BUFFER_USAGE_LIMIT
option.
I've spent quite a bit of time looking at this since you sent it. I've
also made quite a few changes, mostly cosmetic ones, but there are a
few things below which are more fundamental.
1. I don't really think we need to support VACUUM (BUFFER_USAGE_LIMIT
-1); It's just the same as VACUUM; Removing that makes the
documentation more simple.
2. I don't think we really need to allow vacuum_buffer_usage_limit =
-1. I think we can just set this to 256 and leave it. If we allow -1
then we need to document what -1 means. The more I think about it, the
more strange it seems to allow -1. I can't quite imagine work_mem = -1
means 4MB. Why 4MB? Changing this means we don't really need to do
anything special in:
+ if (VacuumBufferUsageLimit > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+ else
+ bstrategy = GetAccessStrategy(BAS_VACUUM);
That simply becomes:
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
The code inside GetAccessStrategyWithSize() handles returning NULL
when the GUC is zero.
The equivalent in ExecVacuum() becomes:
if (ring_size > -1)
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
else
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
instead of:
if (ring_size > -1)
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
else if (VacuumBufferUsageLimit > -1)
bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
else
bstrategy = GetAccessStrategy(BAS_VACUUM);
3. There was a bug in GetAccessStrategyBufferCount() (I renamed it to
that from StrategyGetBufferCount()) that didn't handle NULL input. The
problem was that if you set vacuum_buffer_usage_limit = 0 then did a
parallel vacuum that GetAccessStrategyWithSize() would return NULL due
to the 0 buffer input, but GetAccessStrategyBufferCount() couldn't
handle NULL. I've adjusted GetAccessStrategyBufferCount() just to
return 0 on NULL input.
Most of the rest is cosmetic. GetAccessStrategyWithSize() ended up
looking quite different. I didn't see the sense in converting the
shared_buffer size into kilobytes to compare when we could just
convert ring_size_kb to buffers slightly sooner and then just do:
/* Cap to 1/8th of shared_buffers */
ring_buffers = Min(NBuffers / 8, ring_buffers);
I renamed nbuffers to ring_buffers as it was a little too confusing to
have nbuffers (for ring size) and NBuffers (for shared_buffers).
A few other changes like getting rid of the regression test and code
check for VACUUM (ONLY_DATABASE_STATS, BUFFER_USAGE_LIMIT 0); There is
already an if check and ERROR that looks for ONLY_DATABASE_STATS with
any other option slightly later in the function. I also got rid of
the documentation that mentioned that wasn't supported as there's
already a mention in the ONLY_DATABASE_STATS which says it's not
supported with anything else. No other option seemed to care enough to
mention it, so I don't think BUFFER_USAGE_LIMIT is an exception.
I've attached v15. I've only glanced at the vacuumdb patch so far.
I'm not expecting it to be too controversial.
I'm fairly happy with v15 now but would welcome anyone who wants to
have a look in the next 8 hours or so, else I plan to push it.
David
Attachments:
v15_buffer_usage_limit.patchapplication/octet-stream; name=v15_buffer_usage_limit.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..c421da348d 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,35 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>. The maximum size is
+ <literal>16 GB</literal> and the minimum size is
+ <literal>128 KB</literal>. If the specified size would exceed 1/8 the
+ size of <varname>shared_buffers</varname>, it is silently capped to
+ that value. The default value is <literal>-1</literal>. If this
+ value is specified without units, it is taken as kilobytes. This
+ parameter can be set at any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..c844524911 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,21 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..0b02d9faef 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,24 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. If <option>ANALYZE</option>
+ is also specified, the <option>BUFFER_USAGE_LIMIT</option> value is used
+ for both the vacuum and analyze stages. This option can't be used with
+ the <option>FULL</option> option except if <option>ANALYZE</option> is
+ also specified.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ea1d8960f4..e92738c7f0 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,30 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range.
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Allow specifying the default or disabling Buffer Access Strategy */
+ if (*newval == -1 || *newval == 0)
+ return true;
+
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval >= MIN_BAS_VAC_RING_SIZE_KB &&
+ *newval <= MAX_BAS_VAC_RING_SIZE_KB)
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be -1, 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +139,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ /* by default use buffer access strategy with default size */
+ int ring_size = -1;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -136,6 +163,48 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not 0. We explicitly disallow -1 since
+ * that behavior can be obtained by not specifying
+ * BUFFER_USAGE_LIMIT.
+ */
+ if (result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit option must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -240,6 +309,17 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /*
+ * BUFFER_USAGE_LIMIT does nothing for VACUUM (FULL) so just raise an
+ * ERROR for that case. VACUUM (FULL, ANALYZE) does make use of it, so
+ * we'll permit that.
+ */
+ if ((params.options & VACOPT_FULL) && !(params.options & VACOPT_ANALYZE) &&
+ ring_size > -1)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -341,7 +421,20 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If BUFFER_USAGE_LIMIT was specified by the VACUUM command, that
+ * overrides the value of VacuumBufferUsageLimit. Otherwise, use
+ * VacuumBufferUsageLimit to define the size, which might be 0. We
+ * expliot that calling GetAccessStrategyWithSize with a zero size
+ * returns NULL.
+ */
+ if (ring_size > -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..f4b09488d0 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = GetAccessStrategyBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1e911b1b3..1b5f779384 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2287,11 +2287,21 @@ do_autovacuum(void)
}
/*
- * Create a buffer access strategy object for VACUUM to use. We want to
- * use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * Optionally, create a buffer access strategy object for VACUUM to use.
+ * When creating one, we want the same one across all tables being
+ * vacuumed this helps prevent autovacuum from blowing out shared buffers.
+ *
+ * VacuumBufferUsageLimit being set to 0 results in
+ * GetAccessStrategyWithSize returning NULL, effectively meaning we can
+ * use up to all of shared buffers.
+ *
+ * If we later enter failsafe mode on any of the tables being vacuumed, we
+ * will cease use of the BufferAccessStrategy only for that table.
+ *
+ * XXX should we consider adding code to adjust the size of this if
+ * VacuumBufferUsageLimit changes?
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..b635d13839 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,12 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM uses a ring like sequential scans, however, the size this ring
+controlled by the vacuum_buffer_usage_limit GUC. Dirty pages are not removed
+from the ring. Instead, WAL is flushed if needed to allow reuse of the
+buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's buffers
+were sent to the freelist, which was effectively a buffer ring of 1 buffer,
+resulting in excessive WAL flushing.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..710b05cbc5 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,21 +570,65 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
- /* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size.
+ *
+ * If the given ring size is 0, no BufferAccessStrategy will be created and
+ * the function will return NULL. The ring size may not be negative.
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int ring_buffers;
+ BufferAccessStrategy strategy;
+
+ Assert(ring_size_kb >= 0);
+
+ /* Figure out how many buffers ring_size_kb is */
+ ring_buffers = ring_size_kb / (BLCKSZ / 1024);
+
+ /* 0 means unlimited, so no BufferAccessStrategy required */
+ if (ring_buffers == 0)
+ return NULL;
+
+ /* Cap to 1/8th of shared_buffers */
+ ring_buffers = Min(NBuffers / 8, ring_buffers);
+
+ /* NBuffers should never be less than 16, so this shouldn't happen */
+ Assert(ring_buffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
- nbuffers * sizeof(Buffer));
+ ring_buffers * sizeof(Buffer));
/* Set fields that don't start out zero */
strategy->btype = btype;
- strategy->nbuffers = nbuffers;
+ strategy->nbuffers = ring_buffers;
return strategy;
}
+/*
+ * GetAccessStrategyBufferCount -- an accessor for the number of buffers in
+ * the ring
+ *
+ * Returns 0 on NULL input to match behavior of GetAccessStrategyWithSize()
+ * returning NULL with 0 size.
+ */
+int
+GetAccessStrategyBufferCount(BufferAccessStrategy strategy)
+{
+ if (strategy == NULL)
+ return 0;
+
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..011ec18015 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = 256;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..e8e8245e91 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ 256, 0, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..e715aff3b8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,9 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = 256 # size of vacuum and analyze buffer access strategy ring.
+ # 0 to disable vacuum buffer access strategy
+ # range 128kB to 16GB
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..d4f9ff8077 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,18 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Upper and lower hard limits for the buffer access strategy ring size
+ * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT
+ * option to VACUUM and ANALYZE.
+ */
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 788aa279ba..6ab00daa2e 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -260,7 +260,12 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int GetAccessStrategyBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..f722fb250a 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,8 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..3299232bb0 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,22 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+ERROR: buffer_usage_limit option must be 0 or between 128 KB and 16777216 KB
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+ERROR: buffer_usage_limit option must be 0 or between 128 KB and 16777216 KB
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..d23e1a8ced 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,18 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
On Thu, Apr 06, 2023 at 11:34:44PM +1200, David Rowley wrote:
second On Thu, 6 Apr 2023 at 14:14, Melanie Plageman
<melanieplageman@gmail.com> wrote:Attached is v14 which adds back in tests for the BUFFER_USAGE_LIMIT
option.I've spent quite a bit of time looking at this since you sent it. I've
also made quite a few changes, mostly cosmetic ones, but there are a
few things below which are more fundamental.1. I don't really think we need to support VACUUM (BUFFER_USAGE_LIMIT
-1); It's just the same as VACUUM; Removing that makes the
documentation more simple.
Agreed.
2. I don't think we really need to allow vacuum_buffer_usage_limit =
-1. I think we can just set this to 256 and leave it. If we allow -1
then we need to document what -1 means. The more I think about it, the
more strange it seems to allow -1. I can't quite imagine work_mem = -1
means 4MB. Why 4MB?
Agreed.
3. There was a bug in GetAccessStrategyBufferCount() (I renamed it to
that from StrategyGetBufferCount()) that didn't handle NULL input. The
problem was that if you set vacuum_buffer_usage_limit = 0 then did a
parallel vacuum that GetAccessStrategyWithSize() would return NULL due
to the 0 buffer input, but GetAccessStrategyBufferCount() couldn't
handle NULL. I've adjusted GetAccessStrategyBufferCount() just to
return 0 on NULL input.
Good catch.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index bcc49aec45..c421da348d 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -2001,6 +2001,35 @@ include_dir 'conf.d' </listitem> </varlistentry>+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit"> + <term> + <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>) + <indexterm> + <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Specifies the size of <varname>shared_buffers</varname> to be reused + for each backend participating in a given invocation of + <command>VACUUM</command> or <command>ANALYZE</command> or in + autovacuum. This size is converted to the number of shared buffers + which will be reused as part of a + <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>. + A setting of <literal>0</literal> will allow the operation to use any + number of <varname>shared_buffers</varname>. The maximum size is + <literal>16 GB</literal> and the minimum size is + <literal>128 KB</literal>. If the specified size would exceed 1/8 the + size of <varname>shared_buffers</varname>, it is silently capped to + that value. The default value is <literal>-1</literal>. If this
This still says that the default value is -1.
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index b6d30b5764..0b02d9faef 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -345,6 +346,24 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet </listitem> </varlistentry>+ <varlistentry> + <term><literal>BUFFER_USAGE_LIMIT</literal></term> + <listitem> + <para> + Specifies the + <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm> + ring buffer size for <command>VACUUM</command>. This size is used to + calculate the number of shared buffers which will be reused as part of + this strategy. <literal>0</literal> disables use of a + <literal>Buffer Access Strategy</literal>. If <option>ANALYZE</option> + is also specified, the <option>BUFFER_USAGE_LIMIT</option> value is used + for both the vacuum and analyze stages. This option can't be used with + the <option>FULL</option> option except if <option>ANALYZE</option> is + also specified. + </para> + </listitem> + </varlistentry>
I noticed you seemed to have removed the reference to the GUC
vacuum_buffer_usage_limit here. Was that intentional?
We may not need to mention "falling back" as I did before, however, the
GUC docs mention max/min values and such, which might be useful.
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index ea1d8960f4..e92738c7f0 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -56,6 +56,7 @@ #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/guc_hooks.h" #include "utils/memutils.h" #include "utils/pg_rusage.h" #include "utils/snapmgr.h" @@ -95,6 +96,30 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def); static bool vac_tid_reaped(ItemPointer itemptr, void *state); static int vac_cmp_itemptr(const void *left, const void *right);+/* + * GUC check function to ensure GUC value specified is within the allowable + * range. + */ +bool +check_vacuum_buffer_usage_limit(int *newval, void **extra, + GucSource source) +{ + /* Allow specifying the default or disabling Buffer Access Strategy */ + if (*newval == -1 || *newval == 0) + return true;
This should not check for -1 since that isn't the default anymore.
It should only need to check for 0 I think?
+ /* Value upper and lower hard limits are inclusive */ + if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && + *newval <= MAX_BAS_VAC_RING_SIZE_KB) + return true; + + /* Value does not fall within any allowable range */ + GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be -1, 0 or between %d KB and %d KB", + MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
Also remove -1 here.
* Primary entry point for manual VACUUM and ANALYZE commands * @@ -114,6 +139,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) bool disable_page_skipping = false; bool process_main = true; bool process_toast = true; + /* by default use buffer access strategy with default size */ + int ring_size = -1;
We need to update this comment to something like, "use an invalid value
for ring_size" so it is clear whether or not the BUFFER_USAGE_LIMIT was
specified when making the access strategy later". Also, I think just
removing the comment would be okay, because this is the normal behavior
for initializing values, I think.
@@ -240,6 +309,17 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));+ /* + * BUFFER_USAGE_LIMIT does nothing for VACUUM (FULL) so just raise an + * ERROR for that case. VACUUM (FULL, ANALYZE) does make use of it, so + * we'll permit that. + */ + if ((params.options & VACOPT_FULL) && !(params.options & VACOPT_ANALYZE) && + ring_size > -1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL"))); + /* * Make sure VACOPT_ANALYZE is specified if any column lists are present. */ @@ -341,7 +421,20 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
Is it worth moving this assert up above when we do the "sanity checking"
for VACUUM FULL with BUFFER_USAGE_LIMIT?
+ Assert(ring_size >= -1);
+ /* + * If BUFFER_USAGE_LIMIT was specified by the VACUUM command, that + * overrides the value of VacuumBufferUsageLimit. Otherwise, use + * VacuumBufferUsageLimit to define the size, which might be 0. We + * expliot that calling GetAccessStrategyWithSize with a zero size
s/expliot/exploit
I might rephrase the last sentence(s). Since it overrides it, I think it
is clear that if it is not specified, then the thing it overrides is
used. Then you could phrase the whole thing like:
"If BUFFER_USAGE_LIMIT was specified by the VACUUM or ANALYZE command,
it overrides the value of VacuumBufferUsageLimit. Either value may be
0, in which case GetAccessStrategyWithSize() will return NULL, which is
the expected behavior."
+ * returns NULL. + */ + if (ring_size > -1) + bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size); + else + bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit); + MemoryContextSwitchTo(old_context); }
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index c1e911b1b3..1b5f779384 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2287,11 +2287,21 @@ do_autovacuum(void) /* - * Create a buffer access strategy object for VACUUM to use. We want to - * use the same one across all the vacuum operations we perform, since the - * point is for VACUUM not to blow out the shared cache. + * Optionally, create a buffer access strategy object for VACUUM to use. + * When creating one, we want the same one across all tables being + * vacuumed this helps prevent autovacuum from blowing out shared buffers.
"When creating one" is a bit awkward. I would say something like "Use
the same BufferAccessStrategy object for all tables VACUUMed by this
worker to prevent autovacuum from blowing out shared buffers."
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c index f122709fbe..710b05cbc5 100644 --- a/src/backend/storage/buffer/freelist.c +++ b/src/backend/storage/buffer/freelist.c +/* + * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a + * number of buffers equivalent to the passed in size. + * + * If the given ring size is 0, no BufferAccessStrategy will be created and + * the function will return NULL. The ring size may not be negative. + */ +BufferAccessStrategy +GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb) +{ + int ring_buffers; + BufferAccessStrategy strategy; + + Assert(ring_size_kb >= 0); + + /* Figure out how many buffers ring_size_kb is */ + ring_buffers = ring_size_kb / (BLCKSZ / 1024);
Is there any BLCKSZ that could end up rounding down to 0 and resulting
in a divide by 0?
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 1b1d814254..011ec18015 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -139,7 +139,10 @@ int max_worker_processes = 8; int max_parallel_workers = 8; int MaxBackends = 0;-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */ +/* GUC parameters for vacuum */ +int VacuumBufferUsageLimit = 256;
So, I know we agreed to make it camel cased, but I couldn't help
mentioning the discussion over in [1]/messages/by-id/CAD21AoC5aDwARiqsL+KwHqnN7phub9AaMkbGkJ9aUCeETx8esw@mail.gmail.com in which Sawada-san says:
In vacuum.c, we use snake case for GUC parameters and camel case for
other global variables
Our variable doesn't have a corresponding global that is not a GUC, and
the current pattern is hardly consistent. But, I know we are discussing
following this convention, so I thought I would mention it.
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 06a86f9ac1..d4f9ff8077 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -263,6 +263,18 @@ extern PGDLLIMPORT double hash_mem_multiplier; extern PGDLLIMPORT int maintenance_work_mem; extern PGDLLIMPORT int max_parallel_maintenance_workers;
GUC name mentioned in comment is inconsistent with current GUC name.
+/* + * Upper and lower hard limits for the buffer access strategy ring size + * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT + * option to VACUUM and ANALYZE. + */ +#define MIN_BAS_VAC_RING_SIZE_KB 128 +#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index a1fad43657..d23e1a8ced 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -272,6 +272,18 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode FROM pg_class c, pg_class t WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';+-- BUFFER_USAGE_LIMIT option +VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
Is it worth adding a VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab test?
- Melanie
[1]: /messages/by-id/CAD21AoC5aDwARiqsL+KwHqnN7phub9AaMkbGkJ9aUCeETx8esw@mail.gmail.com
On Fri, 7 Apr 2023 at 05:20, Melanie Plageman <melanieplageman@gmail.com> wrote:
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
This still says that the default value is -1.
Oops, I had this staged but didn't commit and formed the patch with
"git diff master.." instead of "git diff master", so missed a few
staged changes.
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgmlI noticed you seemed to have removed the reference to the GUC
vacuum_buffer_usage_limit here. Was that intentional?
We may not need to mention "falling back" as I did before, however, the
GUC docs mention max/min values and such, which might be useful.
Unintentional. I removed it when removing the -1 stuff. It's useful to
keep something about the fallback, so I put that part back.
+ /* Allow specifying the default or disabling Buffer Access Strategy */ + if (*newval == -1 || *newval == 0) + return true;This should not check for -1 since that isn't the default anymore.
It should only need to check for 0 I think?
Thanks. That one was one of the staged fixes.
+ /* Value upper and lower hard limits are inclusive */ + if (*newval >= MIN_BAS_VAC_RING_SIZE_KB && + *newval <= MAX_BAS_VAC_RING_SIZE_KB) + return true; + + /* Value does not fall within any allowable range */ + GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be -1, 0 or between %d KB and %d KB", + MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);Also remove -1 here.
And this one.
* Primary entry point for manual VACUUM and ANALYZE commands * @@ -114,6 +139,8 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) bool disable_page_skipping = false; bool process_main = true; bool process_toast = true; + /* by default use buffer access strategy with default size */ + int ring_size = -1;We need to update this comment to something like, "use an invalid value
for ring_size" so it is clear whether or not the BUFFER_USAGE_LIMIT was
specified when making the access strategy later". Also, I think just
removing the comment would be okay, because this is the normal behavior
for initializing values, I think.
Yeah, I've moved the assignment away from the declaration and wrote
something along those lines.
@@ -240,6 +309,17 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));+ /* + * BUFFER_USAGE_LIMIT does nothing for VACUUM (FULL) so just raise an + * ERROR for that case. VACUUM (FULL, ANALYZE) does make use of it, so + * we'll permit that. + */ + if ((params.options & VACOPT_FULL) && !(params.options & VACOPT_ANALYZE) && + ring_size > -1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL"))); + /* * Make sure VACOPT_ANALYZE is specified if any column lists are present. */ @@ -341,7 +421,20 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
Is it worth moving this assert up above when we do the "sanity checking"
for VACUUM FULL with BUFFER_USAGE_LIMIT?
I didn't do this, but I did adjust that check to check ring_size != -1
and put that as the first condition. It's likely more rare to have
ring_size not set to -1, so probably should check that first.
s/expliot/exploit
oops
I might rephrase the last sentence(s). Since it overrides it, I think it
is clear that if it is not specified, then the thing it overrides is
used. Then you could phrase the whole thing like:"If BUFFER_USAGE_LIMIT was specified by the VACUUM or ANALYZE command,
it overrides the value of VacuumBufferUsageLimit. Either value may be
0, in which case GetAccessStrategyWithSize() will return NULL, which is
the expected behavior."
Fixed.
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index c1e911b1b3..1b5f779384 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2287,11 +2287,21 @@ do_autovacuum(void) /* - * Create a buffer access strategy object for VACUUM to use. We want to - * use the same one across all the vacuum operations we perform, since the - * point is for VACUUM not to blow out the shared cache. + * Optionally, create a buffer access strategy object for VACUUM to use. + * When creating one, we want the same one across all tables being + * vacuumed this helps prevent autovacuum from blowing out shared buffers."When creating one" is a bit awkward. I would say something like "Use
the same BufferAccessStrategy object for all tables VACUUMed by this
worker to prevent autovacuum from blowing out shared buffers."
Adjusted
+ /* Figure out how many buffers ring_size_kb is */ + ring_buffers = ring_size_kb / (BLCKSZ / 1024);Is there any BLCKSZ that could end up rounding down to 0 and resulting
in a divide by 0?
I removed that Assert() as I found quite a number of other places in
our code that assume BLCKSZ / 1024 is never 0.
So, I know we agreed to make it camel cased, but I couldn't help
mentioning the discussion over in [1] in which Sawada-san says:
I didn't change anything here. I'm happy to follow any rules about
this once they're defined. What we have today is horribly
inconsistent.
GUC name mentioned in comment is inconsistent with current GUC name.
+/* + * Upper and lower hard limits for the buffer access strategy ring size + * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT + * option to VACUUM and ANALYZE.
I did adjust this. I wasn't sure it was incorrect as I mentioned "GUC"
as in, the user facing setting.
Is it worth adding a VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab test?
I think so.
I've attached v16.
David
Attachments:
v16_buffer_usage_limit.patchapplication/octet-stream; name=v16_buffer_usage_limit.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bcc49aec45..220f9ee84c 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2001,6 +2001,35 @@ include_dir 'conf.d'
</listitem>
</varlistentry>
+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit">
+ <term>
+ <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>)
+ <indexterm>
+ <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ Specifies the size of <varname>shared_buffers</varname> to be reused
+ for each backend participating in a given invocation of
+ <command>VACUUM</command> or <command>ANALYZE</command> or in
+ autovacuum. This size is converted to the number of shared buffers
+ which will be reused as part of a
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>.
+ A setting of <literal>0</literal> will allow the operation to use any
+ number of <varname>shared_buffers</varname>. Otherwise valid sizes
+ range from <literal>128 KB</literal> to <literal>16 GB</literal>. If
+ the specified size would exceed 1/8 the size of
+ <varname>shared_buffers</varname>, the size is silently capped to
+ that value. The default value is <literal>256 KB</literal>. If this
+ value is specified without units, it is taken as kilobytes. This
+ parameter can be set at any time. It can be overridden for
+ <xref linkend="sql-vacuum"/> and <xref linkend="sql-analyze"/>
+ when passing the <option>BUFFER_USAGE_LIMIT</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-logical-decoding-work-mem" xreflabel="logical_decoding_work_mem">
<term><varname>logical_decoding_work_mem</varname> (<type>integer</type>)
<indexterm>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 2f94e89cb0..703ccbd0ba 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,6 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -95,6 +96,23 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>ANALYZE</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. When this option is not
+ specified, <command>ANALYZE</command> uses the value from
+ <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index b6d30b5764..223c94ddfb 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,6 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -345,6 +346,26 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>BUFFER_USAGE_LIMIT</literal></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for <command>VACUUM</command>. This size is used to
+ calculate the number of shared buffers which will be reused as part of
+ this strategy. <literal>0</literal> disables use of a
+ <literal>Buffer Access Strategy</literal>. If <option>ANALYZE</option>
+ is also specified, the <option>BUFFER_USAGE_LIMIT</option> value is used
+ for both the vacuum and analyze stages. This option can't be used with
+ the <option>FULL</option> option except if <option>ANALYZE</option> is
+ also specified. When this option is not specified,
+ <command>VACUUM</command> uses the value from
+ <xref linkend="guc-vacuum-buffer-usage-limit"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">boolean</replaceable></term>
<listitem>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index ea1d8960f4..995b4bd54a 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -56,6 +56,7 @@
#include "utils/acl.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/guc_hooks.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -95,6 +96,26 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def);
static bool vac_tid_reaped(ItemPointer itemptr, void *state);
static int vac_cmp_itemptr(const void *left, const void *right);
+/*
+ * GUC check function to ensure GUC value specified is within the allowable
+ * range.
+ */
+bool
+check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source)
+{
+ /* Value upper and lower hard limits are inclusive */
+ if (*newval == 0 || (*newval >= MIN_BAS_VAC_RING_SIZE_KB &&
+ *newval <= MAX_BAS_VAC_RING_SIZE_KB))
+ return true;
+
+ /* Value does not fall within any allowable range */
+ GUC_check_errdetail("\"vacuum_buffer_usage_limit\" must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB);
+
+ return false;
+}
+
/*
* Primary entry point for manual VACUUM and ANALYZE commands
*
@@ -114,6 +135,7 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
bool disable_page_skipping = false;
bool process_main = true;
bool process_toast = true;
+ int ring_size;
bool skip_database_stats = false;
bool only_database_stats = false;
MemoryContext vac_context;
@@ -126,6 +148,12 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
/* By default parallel vacuum is enabled */
params.nworkers = 0;
+ /*
+ * Set this to an invalid value so it is clear whether or not a
+ * BUFFER_USAGE_LIMIT was specified when making the access strategy.
+ */
+ ring_size = -1;
+
/* Parse options list */
foreach(lc, vacstmt->options)
{
@@ -136,6 +164,48 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
verbose = defGetBoolean(opt);
else if (strcmp(opt->defname, "skip_locked") == 0)
skip_locked = defGetBoolean(opt);
+ else if (strcmp(opt->defname, "buffer_usage_limit") == 0)
+ {
+ const char *hintmsg;
+ int result;
+ char *vac_buffer_size;
+
+ if (opt->arg == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("buffer_usage_limit option requires a valid value"),
+ parser_errposition(pstate, opt->location)));
+ }
+
+ vac_buffer_size = defGetString(opt);
+
+ if (!parse_int(vac_buffer_size, &result, GUC_UNIT_KB, &hintmsg))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value: \"%s\": is invalid for buffer_usage_limit",
+ vac_buffer_size),
+ hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ }
+
+ /*
+ * Check that the specified size falls within the hard upper and
+ * lower limits if it is not 0. We explicitly disallow -1 since
+ * that behavior can be obtained by not specifying
+ * BUFFER_USAGE_LIMIT.
+ */
+ if (result != 0 &&
+ (result < MIN_BAS_VAC_RING_SIZE_KB || result > MAX_BAS_VAC_RING_SIZE_KB))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("buffer_usage_limit option must be 0 or between %d KB and %d KB",
+ MIN_BAS_VAC_RING_SIZE_KB, MAX_BAS_VAC_RING_SIZE_KB)));
+ }
+
+ ring_size = result;
+ }
else if (!vacstmt->is_vacuumcmd)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -240,6 +310,17 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("VACUUM FULL cannot be performed in parallel")));
+ /*
+ * BUFFER_USAGE_LIMIT does nothing for VACUUM (FULL) so just raise an
+ * ERROR for that case. VACUUM (FULL, ANALYZE) does make use of it, so
+ * we'll permit that.
+ */
+ if (ring_size != -1 && (params.options & VACOPT_FULL) &&
+ !(params.options & VACOPT_ANALYZE))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL")));
+
/*
* Make sure VACOPT_ANALYZE is specified if any column lists are present.
*/
@@ -341,7 +422,19 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ Assert(ring_size >= -1);
+
+ /*
+ * If BUFFER_USAGE_LIMIT was specified by the VACUUM or ANALYZE
+ * command, it overrides the value of VacuumBufferUsageLimit. Either
+ * value may be 0, in which case GetAccessStrategyWithSize() will
+ * return NULL, effectively allowing full use of shared buffers.
+ */
+ if (ring_size != -1)
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size);
+ else
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
+
MemoryContextSwitchTo(old_context);
}
diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c
index 563117a8f6..f4b09488d0 100644
--- a/src/backend/commands/vacuumparallel.c
+++ b/src/backend/commands/vacuumparallel.c
@@ -87,6 +87,12 @@ typedef struct PVShared
*/
int maintenance_work_mem_worker;
+ /*
+ * The number of buffers each worker's Buffer Access Strategy ring should
+ * contain.
+ */
+ int ring_nbuffers;
+
/*
* Shared vacuum cost balance. During parallel vacuum,
* VacuumSharedCostBalance points to this value and it accumulates the
@@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes,
maintenance_work_mem / Min(parallel_workers, nindexes_mwm) :
maintenance_work_mem;
+ /* Use the same buffer size for all workers */
+ shared->ring_nbuffers = GetAccessStrategyBufferCount(bstrategy);
+
pg_atomic_init_u32(&(shared->cost_balance), 0);
pg_atomic_init_u32(&(shared->active_nworkers), 0);
pg_atomic_init_u32(&(shared->idx), 0);
@@ -1018,8 +1027,9 @@ parallel_vacuum_main(dsm_segment *seg, shm_toc *toc)
pvs.indname = NULL;
pvs.status = PARALLEL_INDVAC_STATUS_INITIAL;
- /* Each parallel VACUUM worker gets its own access strategy */
- pvs.bstrategy = GetAccessStrategy(BAS_VACUUM);
+ /* Each parallel VACUUM worker gets its own access strategy. */
+ pvs.bstrategy = GetAccessStrategyWithSize(BAS_VACUUM,
+ shared->ring_nbuffers * (BLCKSZ / 1024));
/* Setup error traceback support for ereport() */
errcallback.callback = parallel_vacuum_error_callback;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1e911b1b3..ad7b74071c 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2287,11 +2287,21 @@ do_autovacuum(void)
}
/*
- * Create a buffer access strategy object for VACUUM to use. We want to
- * use the same one across all the vacuum operations we perform, since the
- * point is for VACUUM not to blow out the shared cache.
+ * Optionally, create a buffer access strategy object for VACUUM to use.
+ * We use the same BufferAccessStrategy object for all tables VACUUMed by
+ * this worker to prevent autovacuum from blowing out shared buffers.
+ *
+ * VacuumBufferUsageLimit being set to 0 results in
+ * GetAccessStrategyWithSize returning NULL, effectively meaning we can
+ * use up to all of shared buffers.
+ *
+ * If we later enter failsafe mode on any of the tables being vacuumed, we
+ * will cease use of the BufferAccessStrategy only for that table.
+ *
+ * XXX should we consider adding code to adjust the size of this if
+ * VacuumBufferUsageLimit changes?
*/
- bstrategy = GetAccessStrategy(BAS_VACUUM);
+ bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit);
/*
* create a memory context to act as fake PortalContext, so that the
diff --git a/src/backend/storage/buffer/README b/src/backend/storage/buffer/README
index a775276ff2..b635d13839 100644
--- a/src/backend/storage/buffer/README
+++ b/src/backend/storage/buffer/README
@@ -229,12 +229,12 @@ update hint bits). In a scan that modifies every page in the scan, like a
bulk UPDATE or DELETE, the buffers in the ring will always be dirtied and
the ring strategy effectively degrades to the normal strategy.
-VACUUM uses a 256KB ring like sequential scans, but dirty pages are not
-removed from the ring. Instead, WAL is flushed if needed to allow reuse of
-the buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's
-buffers were sent to the freelist, which was effectively a buffer ring of 1
-buffer, resulting in excessive WAL flushing. Allowing VACUUM to update
-256KB between WAL flushes should be more efficient.
+VACUUM uses a ring like sequential scans, however, the size this ring
+controlled by the vacuum_buffer_usage_limit GUC. Dirty pages are not removed
+from the ring. Instead, WAL is flushed if needed to allow reuse of the
+buffers. Before introducing the buffer ring strategy in 8.3, VACUUM's buffers
+were sent to the freelist, which was effectively a buffer ring of 1 buffer,
+resulting in excessive WAL flushing.
Bulk writes work similarly to VACUUM. Currently this applies only to
COPY IN and CREATE TABLE AS SELECT. (Might it be interesting to make
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index f122709fbe..710b05cbc5 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -540,8 +540,7 @@ StrategyInitialize(bool init)
BufferAccessStrategy
GetAccessStrategy(BufferAccessStrategyType btype)
{
- BufferAccessStrategy strategy;
- int nbuffers;
+ int ring_size_kb;
/*
* Select ring size to use. See buffer/README for rationales.
@@ -556,13 +555,13 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL;
case BAS_BULKREAD:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
case BAS_BULKWRITE:
- nbuffers = 16 * 1024 * 1024 / BLCKSZ;
+ ring_size_kb = 16 * 1024;
break;
case BAS_VACUUM:
- nbuffers = 256 * 1024 / BLCKSZ;
+ ring_size_kb = 256;
break;
default:
@@ -571,21 +570,65 @@ GetAccessStrategy(BufferAccessStrategyType btype)
return NULL; /* keep compiler quiet */
}
- /* Make sure ring isn't an undue fraction of shared buffers */
- nbuffers = Min(NBuffers / 8, nbuffers);
+ return GetAccessStrategyWithSize(btype, ring_size_kb);
+}
+
+/*
+ * GetAccessStrategyWithSize -- create a BufferAccessStrategy object with a
+ * number of buffers equivalent to the passed in size.
+ *
+ * If the given ring size is 0, no BufferAccessStrategy will be created and
+ * the function will return NULL. The ring size may not be negative.
+ */
+BufferAccessStrategy
+GetAccessStrategyWithSize(BufferAccessStrategyType btype, int ring_size_kb)
+{
+ int ring_buffers;
+ BufferAccessStrategy strategy;
+
+ Assert(ring_size_kb >= 0);
+
+ /* Figure out how many buffers ring_size_kb is */
+ ring_buffers = ring_size_kb / (BLCKSZ / 1024);
+
+ /* 0 means unlimited, so no BufferAccessStrategy required */
+ if (ring_buffers == 0)
+ return NULL;
+
+ /* Cap to 1/8th of shared_buffers */
+ ring_buffers = Min(NBuffers / 8, ring_buffers);
+
+ /* NBuffers should never be less than 16, so this shouldn't happen */
+ Assert(ring_buffers > 0);
/* Allocate the object and initialize all elements to zeroes */
strategy = (BufferAccessStrategy)
palloc0(offsetof(BufferAccessStrategyData, buffers) +
- nbuffers * sizeof(Buffer));
+ ring_buffers * sizeof(Buffer));
/* Set fields that don't start out zero */
strategy->btype = btype;
- strategy->nbuffers = nbuffers;
+ strategy->nbuffers = ring_buffers;
return strategy;
}
+/*
+ * GetAccessStrategyBufferCount -- an accessor for the number of buffers in
+ * the ring
+ *
+ * Returns 0 on NULL input to match behavior of GetAccessStrategyWithSize()
+ * returning NULL with 0 size.
+ */
+int
+GetAccessStrategyBufferCount(BufferAccessStrategy strategy)
+{
+ if (strategy == NULL)
+ return 0;
+
+ return strategy->nbuffers;
+}
+
/*
* FreeAccessStrategy -- release a BufferAccessStrategy object
*
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 1b1d814254..011ec18015 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -139,7 +139,10 @@ int max_worker_processes = 8;
int max_parallel_workers = 8;
int MaxBackends = 0;
-int VacuumCostPageHit = 1; /* GUC parameters for vacuum */
+/* GUC parameters for vacuum */
+int VacuumBufferUsageLimit = 256;
+
+int VacuumCostPageHit = 1;
int VacuumCostPageMiss = 2;
int VacuumCostPageDirty = 20;
int VacuumCostLimit = 200;
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 8062589efd..e8e8245e91 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -2224,6 +2224,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_buffer_usage_limit", PGC_USERSET, RESOURCES_MEM,
+ gettext_noop("Sets the buffer pool size for VACUUM, ANALYZE, and autovacuum."),
+ NULL,
+ GUC_UNIT_KB
+ },
+ &VacuumBufferUsageLimit,
+ 256, 0, MAX_BAS_VAC_RING_SIZE_KB,
+ check_vacuum_buffer_usage_limit, NULL, NULL
+ },
+
{
{"shared_memory_size", PGC_INTERNAL, PRESET_OPTIONS,
gettext_noop("Shows the size of the server's main shared memory area (rounded up to the nearest MB)."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index ee49ca3937..e715aff3b8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -157,6 +157,9 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
+#vacuum_buffer_usage_limit = 256 # size of vacuum and analyze buffer access strategy ring.
+ # 0 to disable vacuum buffer access strategy
+ # range 128kB to 16GB
# - Disk -
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index e38a49e8bd..6614fd2e62 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2662,7 +2662,7 @@ psql_completion(const char *text, int start, int end)
* one word, so the above test is correct.
*/
if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
- COMPLETE_WITH("VERBOSE", "SKIP_LOCKED");
+ COMPLETE_WITH("VERBOSE", "SKIP_LOCKED", "BUFFER_USAGE_LIMIT");
else if (TailMatches("VERBOSE|SKIP_LOCKED"))
COMPLETE_WITH("ON", "OFF");
}
@@ -4620,7 +4620,7 @@ psql_completion(const char *text, int start, int end)
"DISABLE_PAGE_SKIPPING", "SKIP_LOCKED",
"INDEX_CLEANUP", "PROCESS_MAIN", "PROCESS_TOAST",
"TRUNCATE", "PARALLEL", "SKIP_DATABASE_STATS",
- "ONLY_DATABASE_STATS");
+ "ONLY_DATABASE_STATS", "BUFFER_USAGE_LIMIT");
else if (TailMatches("FULL|FREEZE|ANALYZE|VERBOSE|DISABLE_PAGE_SKIPPING|SKIP_LOCKED|PROCESS_MAIN|PROCESS_TOAST|TRUNCATE|SKIP_DATABASE_STATS|ONLY_DATABASE_STATS"))
COMPLETE_WITH("ON", "OFF");
else if (TailMatches("INDEX_CLEANUP"))
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 06a86f9ac1..c491ae56ef 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -263,6 +263,18 @@ extern PGDLLIMPORT double hash_mem_multiplier;
extern PGDLLIMPORT int maintenance_work_mem;
extern PGDLLIMPORT int max_parallel_maintenance_workers;
+/*
+ * Upper and lower hard limits for the buffer access strategy ring size
+ * specified by the VacuumBufferUsageLimit GUC and BUFFER_USAGE_LIMIT option
+ * to VACUUM and ANALYZE.
+ */
+#define MIN_BAS_VAC_RING_SIZE_KB 128
+#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
+
+/*
+ * Global values used by VACUUM and autovacuum.
+ */
+extern PGDLLIMPORT int VacuumBufferUsageLimit;
extern PGDLLIMPORT int VacuumCostPageHit;
extern PGDLLIMPORT int VacuumCostPageMiss;
extern PGDLLIMPORT int VacuumCostPageDirty;
diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h
index 788aa279ba..6ab00daa2e 100644
--- a/src/include/storage/bufmgr.h
+++ b/src/include/storage/bufmgr.h
@@ -260,7 +260,12 @@ extern Size BufferShmemSize(void);
extern void AtProcExit_LocalBuffers(void);
/* in freelist.c */
+
extern BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype);
+extern BufferAccessStrategy GetAccessStrategyWithSize(BufferAccessStrategyType btype,
+ int ring_size_kb);
+extern int GetAccessStrategyBufferCount(BufferAccessStrategy strategy);
+
extern void FreeAccessStrategy(BufferAccessStrategy strategy);
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index aeb3663071..f722fb250a 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -33,6 +33,8 @@ extern bool check_autovacuum_max_workers(int *newval, void **extra,
GucSource source);
extern bool check_autovacuum_work_mem(int *newval, void **extra,
GucSource source);
+extern bool check_vacuum_buffer_usage_limit(int *newval, void **extra,
+ GucSource source);
extern bool check_backtrace_functions(char **newval, void **extra,
GucSource source);
extern void assign_backtrace_functions(const char *newval, void *extra);
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index e5a312182e..8ebc09f64b 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -350,6 +350,24 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
f
(1 row)
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- try disabling the buffer usage limit
+VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+ERROR: buffer_usage_limit option must be 0 or between 128 KB and 16777216 KB
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+ERROR: buffer_usage_limit option must be 0 or between 128 KB and 16777216 KB
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+ERROR: value: "10000000000": is invalid for buffer_usage_limit
+HINT: Value exceeds integer range.
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+ERROR: BUFFER_USAGE_LIMIT cannot be specified for VACUUM FULL
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
-- ONLY_DATABASE_STATS option
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index a1fad43657..1336ab0e14 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -272,6 +272,20 @@ SELECT t.relfilenode = :toast_filenode AS is_same_toast_filenode
FROM pg_class c, pg_class t
WHERE c.reltoastrelid = t.oid AND c.relname = 'vac_option_tab';
+-- BUFFER_USAGE_LIMIT option
+VACUUM (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+ANALYZE (BUFFER_USAGE_LIMIT '512 kB') vac_option_tab;
+-- try disabling the buffer usage limit
+VACUUM (BUFFER_USAGE_LIMIT 0) vac_option_tab;
+-- value exceeds max size error
+VACUUM (BUFFER_USAGE_LIMIT 16777220) vac_option_tab;
+-- value is less than min size error
+VACUUM (BUFFER_USAGE_LIMIT 120) vac_option_tab;
+-- integer overflow error
+VACUUM (BUFFER_USAGE_LIMIT 10000000000) vac_option_tab;
+-- incompatible with VACUUM FULL error
+VACUUM (BUFFER_USAGE_LIMIT '512 kB', FULL) vac_option_tab;
+
-- SKIP_DATABASE_STATS option
VACUUM (SKIP_DATABASE_STATS) vactst;
+VACUUM uses a ring like sequential scans, however, the size this ring
+controlled by the vacuum_buffer_usage_limit GUC. Dirty pages are not removed
should say: ".. the size OF this ring IS .." ?
On Fri, Apr 07, 2023 at 09:12:32AM +1200, David Rowley wrote:
On Fri, 7 Apr 2023 at 05:20, Melanie Plageman <melanieplageman@gmail.com> wrote:
GUC name mentioned in comment is inconsistent with current GUC name.
+/* + * Upper and lower hard limits for the buffer access strategy ring size + * specified by the vacuum_buffer_usage_limit GUC and BUFFER_USAGE_LIMIT + * option to VACUUM and ANALYZE.I did adjust this. I wasn't sure it was incorrect as I mentioned "GUC"
as in, the user facing setting.
Oh, actually maybe you are right then.
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index bcc49aec45..220f9ee84c 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -2001,6 +2001,35 @@ include_dir 'conf.d' </listitem> </varlistentry>+ <varlistentry id="guc-vacuum-buffer-usage-limit" xreflabel="vacuum_buffer_usage_limit"> + <term> + <varname>vacuum_buffer_usage_limit</varname> (<type>integer</type>) + <indexterm> + <primary><varname>vacuum_buffer_usage_limit</varname> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + Specifies the size of <varname>shared_buffers</varname> to be reused + for each backend participating in a given invocation of + <command>VACUUM</command> or <command>ANALYZE</command> or in + autovacuum.
Rereading this, I think it is not a good sentence (my fault).
Perhaps we should use the same language as the BUFFER_USAGE_LIMIT
options. Something like:
Specifies the
<glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
ring buffer size used by each backend participating in a given
invocation of <command>VACUUM</command> or <command>ANALYZE</command> or
in autovacuum.
Last part is admittedly a bit awkward...
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index ea1d8960f4..995b4bd54a 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -56,6 +56,7 @@ #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/guc_hooks.h" #include "utils/memutils.h" #include "utils/pg_rusage.h" #include "utils/snapmgr.h" @@ -95,6 +96,26 @@ static VacOptValue get_vacoptval_from_boolean(DefElem *def); static bool vac_tid_reaped(ItemPointer itemptr, void *state); static int vac_cmp_itemptr(const void *left, const void *right);+/* + * GUC check function to ensure GUC value specified is within the allowable + * range. + */ +bool +check_vacuum_buffer_usage_limit(int *newval, void **extra, + GucSource source) +{
I'm not so hot on this comment. It seems very...generic. Like it could
be the comment on any GUC check function. I'm also okay with leaving it
as is.
@@ -341,7 +422,19 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel)
MemoryContext old_context = MemoryContextSwitchTo(vac_context);
- bstrategy = GetAccessStrategy(BAS_VACUUM); + Assert(ring_size >= -1); + + /* + * If BUFFER_USAGE_LIMIT was specified by the VACUUM or ANALYZE + * command, it overrides the value of VacuumBufferUsageLimit. Either + * value may be 0, in which case GetAccessStrategyWithSize() will + * return NULL, effectively allowing full use of shared buffers.
Maybe "unlimited" is better than "full"?
+ */ + if (ring_size != -1) + bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size); + else + bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, VacuumBufferUsageLimit); + MemoryContextSwitchTo(old_context); }diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c @@ -365,6 +371,9 @@ parallel_vacuum_init(Relation rel, Relation *indrels, int nindexes, maintenance_work_mem / Min(parallel_workers, nindexes_mwm) : maintenance_work_mem;+ /* Use the same buffer size for all workers */
I would say ring buffer size -- this sounds like it is the size of a
single buffer.
+ shared->ring_nbuffers = GetAccessStrategyBufferCount(bstrategy); + pg_atomic_init_u32(&(shared->cost_balance), 0); pg_atomic_init_u32(&(shared->active_nworkers), 0); pg_atomic_init_u32(&(shared->idx), 0);
+ * Upper and lower hard limits for the buffer access strategy ring size + * specified by the VacuumBufferUsageLimit GUC and BUFFER_USAGE_LIMIT option
I agree with your original usage of the actual GUC name, now that I
realize why you were doing it and am rereading it.
+ * to VACUUM and ANALYZE. + */ +#define MIN_BAS_VAC_RING_SIZE_KB 128 +#define MAX_BAS_VAC_RING_SIZE_KB (16 * 1024 * 1024)
Otherwise, LGTM.
On Fri, 7 Apr 2023 at 09:44, Melanie Plageman <melanieplageman@gmail.com> wrote:
Otherwise, LGTM.
Thanks for looking. I've also taken Justin's comments about the
README into account and fixed that part.
I've pushed the patch after a little more adjusting. I added some
text to the docs that mention larger VACUUM_BUFFER_LIMITs can speed up
vacuum and also a reason why they might not want to go nuts with it.
I've also just now pushed the vacuumdb patch too. I ended up adjusting
some of the ERROR messages in the main patch after the following not
so nice user experience:
$ vacuumdb --buffer-usage-limit=1TB --analyze postgres
vacuumdb: vacuuming database "postgres"
SQL: VACUUM (SKIP_DATABASE_STATS, ANALYZE, BUFFER_USAGE_LIMIT '1TB')
vacuumdb: error: processing of database "postgres" failed: ERROR:
buffer_usage_limit option must be 0 or between 128 KB and 16777216 KB
$ vacuumdb --buffer-usage-limit=128KB --analyze postgres
vacuumdb: vacuuming database "postgres"
SQL: VACUUM (SKIP_DATABASE_STATS, ANALYZE, BUFFER_USAGE_LIMIT '128KB')
vacuumdb: error: processing of database "postgres" failed: ERROR:
value: "128KB": is invalid for buffer_usage_limit
HINT: Valid units for this parameter are "B", "kB", "MB", "GB", and "TB".
David
On 07.04.23 02:52, David Rowley wrote:
On Fri, 7 Apr 2023 at 09:44, Melanie Plageman <melanieplageman@gmail.com> wrote:
Otherwise, LGTM.
Thanks for looking. I've also taken Justin's comments about the
README into account and fixed that part.I've pushed the patch after a little more adjusting. I added some
text to the docs that mention larger VACUUM_BUFFER_LIMITs can speed up
vacuum and also a reason why they might not want to go nuts with it.
I came across these new options and had a little bit of trouble figuring
them out from the documentation. Maybe this could be polished a bit.
vacuumdb --help says
--buffer-usage-limit=BUFSIZE
I can guess what a "SIZE" might be, but is "BUFSIZE" different from a
"SIZE"? Maybe simplify here.
On the vacuumdb man page, the placeholder is
<replaceable class="parameter">buffer_usage_limit</replaceable>
which is yet another way of phrasing it. Maybe also use "size" here?
The VACUUM man page says
BUFFER_USAGE_LIMIT [ <replaceable ...>string</replaceable> ]
which had me really confused. The detailed description later doesn't
give any further explanation of possible values, except that
<literal>0</literal> is apparently a possible value, which in my mind is
not a string. Then there is a link to guc-vacuum-buffer-usage-limit,
which lifts the mystery that this is really just an integer setting with
possible memory-size units, but it was really hard to figure that out
from the start!
Moreover, on the VACUUM man page, right below BUFFER_USAGE_LIMIT, it
explains the different kinds of accepted values, and "string" wasn't
added there. Maybe also change this to "size" here and add an
explanation there what kinds of sizes are possible.
Finally, the locations of the new options in the various documentation
places seems a bit random. The vacuumdb --help output and the man page
appear to be mostly alphabetical, so --buffer-usage-limit should be
after -a/--all. (Also note that right now the option isn't even in the
same place in the --help output versus the man page.)
The order of the options on the VACUUM man page doesn't make any sense
anymore. This isn't really the fault of this patch, but maybe it's time
to do a fresh reordering there.
On Fri, 14 Apr 2023 at 19:20, Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:
I came across these new options and had a little bit of trouble figuring
them out from the documentation. Maybe this could be polished a bit.vacuumdb --help says
--buffer-usage-limit=BUFSIZE
I can guess what a "SIZE" might be, but is "BUFSIZE" different from a
"SIZE"? Maybe simplify here.On the vacuumdb man page, the placeholder is
<replaceable class="parameter">buffer_usage_limit</replaceable>
which is yet another way of phrasing it. Maybe also use "size" here?
The VACUUM man page says
BUFFER_USAGE_LIMIT [ <replaceable ...>string</replaceable> ]
which had me really confused. The detailed description later doesn't
give any further explanation of possible values, except that
<literal>0</literal> is apparently a possible value, which in my mind is
not a string. Then there is a link to guc-vacuum-buffer-usage-limit,
which lifts the mystery that this is really just an integer setting with
possible memory-size units, but it was really hard to figure that out
from the start!Moreover, on the VACUUM man page, right below BUFFER_USAGE_LIMIT, it
explains the different kinds of accepted values, and "string" wasn't
added there. Maybe also change this to "size" here and add an
explanation there what kinds of sizes are possible.Finally, the locations of the new options in the various documentation
places seems a bit random. The vacuumdb --help output and the man page
appear to be mostly alphabetical, so --buffer-usage-limit should be
after -a/--all. (Also note that right now the option isn't even in the
same place in the --help output versus the man page.)
These are all valid points. I've attached a patch aiming to address
each of them.
The order of the options on the VACUUM man page doesn't make any sense
anymore. This isn't really the fault of this patch, but maybe it's time
to do a fresh reordering there.
Agreed, that likely wasn't a big problem say about 5 years ago when we
had far fewer options, but the number has grown quite a bit since
then.
Right after I fix the points you've mentioned seems a good time to address that.
David
Attachments:
fix_buffer_usage_limit_docs.patchapplication/octet-stream; name=fix_buffer_usage_limit_docs.patchDownload
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 94e30f1ce7..6f0bb4631b 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -28,7 +28,7 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
SKIP_LOCKED [ <replaceable class="parameter">boolean</replaceable> ]
- BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">size</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -110,7 +110,8 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
<xref linkend="guc-vacuum-buffer-usage-limit"/>. Higher settings can
allow <command>ANALYZE</command> to run more quickly, but having too
large a setting may cause too many other useful pages to be evicted from
- shared buffers.
+ shared buffers. The minimum value is <literal>128 kB</literal> and the
+ maximum value is <literal>16 GB</literal>.
</para>
</listitem>
</varlistentry>
@@ -129,6 +130,19 @@ ANALYZE [ VERBOSE ] [ <replaceable class="parameter">table_and_columns</replacea
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">size</replaceable></term>
+ <listitem>
+ <para>
+ Specifies an amount of memory in kilobytes. Sizes may also be specified
+ as a string containing the size followed by any one of the following
+ memory units: <literal>kB</literal> (kilobytes), <literal>MB</literal>
+ (megabytes), <literal>GB</literal> (gigabytes), or
+ <literal>TB</literal> (terabytes).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index dd0fbb8cb7..54f47a0c64 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -39,7 +39,7 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
PARALLEL <replaceable class="parameter">integer</replaceable>
SKIP_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
ONLY_DATABASE_STATS [ <replaceable class="parameter">boolean</replaceable> ]
- BUFFER_USAGE_LIMIT [ <replaceable class="parameter">string</replaceable> ]
+ BUFFER_USAGE_LIMIT [ <replaceable class="parameter">size</replaceable> ]
<phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase>
@@ -364,7 +364,8 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
<xref linkend="guc-vacuum-buffer-usage-limit"/>. Higher settings can
allow <command>VACUUM</command> to run more quickly, but having too
large a setting may cause too many other useful pages to be evicted from
- shared buffers.
+ shared buffers. The minimum value is <literal>128 kB</literal> and the
+ maximum value is <literal>16 GB</literal>.
</para>
</listitem>
</varlistentry>
@@ -392,6 +393,19 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ <replaceable class="paramet
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable class="parameter">size</replaceable></term>
+ <listitem>
+ <para>
+ Specifies an amount of memory in kilobytes. Sizes may also be specified
+ as a string containing the size followed by any one of the following
+ memory units: <literal>kB</literal> (kilobytes), <literal>MB</literal>
+ (megabytes), <literal>GB</literal> (gigabytes), or
+ <literal>TB</literal> (terabytes).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
diff --git a/doc/src/sgml/ref/vacuumdb.sgml b/doc/src/sgml/ref/vacuumdb.sgml
index 8280cf0fb0..caf97af215 100644
--- a/doc/src/sgml/ref/vacuumdb.sgml
+++ b/doc/src/sgml/ref/vacuumdb.sgml
@@ -121,6 +121,19 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--buffer-usage-limit <replaceable class="parameter">size</replaceable></option></term>
+ <listitem>
+ <para>
+ Specifies the
+ <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
+ ring buffer size for a given invocation of <application>vacuumdb</application>.
+ This size is used to calculate the number of shared buffers which will
+ be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option><optional>-d</optional> <replaceable class="parameter">dbname</replaceable></option></term>
<term><option><optional>--dbname=</optional><replaceable class="parameter">dbname</replaceable></option></term>
@@ -278,19 +291,6 @@ PostgreSQL documentation
</listitem>
</varlistentry>
- <varlistentry>
- <term><option>--buffer-usage-limit <replaceable class="parameter">buffer_usage_limit</replaceable></option></term>
- <listitem>
- <para>
- Specifies the
- <glossterm linkend="glossary-buffer-access-strategy">Buffer Access Strategy</glossterm>
- ring buffer size for a given invocation of <application>vacuumdb</application>.
- This size is used to calculate the number of shared buffers which will
- be reused as part of this strategy. See <xref linkend="sql-vacuum"/>.
- </para>
- </listitem>
- </varlistentry>
-
<varlistentry>
<term><option>-n <replaceable class="parameter">schema</replaceable></option></term>
<term><option>--schema=<replaceable class="parameter">schema</replaceable></option></term>
diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c
index 1241644ed5..687af9c1f3 100644
--- a/src/bin/scripts/vacuumdb.c
+++ b/src/bin/scripts/vacuumdb.c
@@ -1128,6 +1128,7 @@ help(const char *progname)
printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
printf(_("\nOptions:\n"));
printf(_(" -a, --all vacuum all databases\n"));
+ printf(_(" --buffer-usage-limit=SIZE size of ring buffer used for vacuum\n"));
printf(_(" -d, --dbname=DBNAME database to vacuum\n"));
printf(_(" --disable-page-skipping disable all page-skipping behavior\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
@@ -1136,7 +1137,6 @@ help(const char *progname)
printf(_(" --force-index-cleanup always remove index entries that point to dead tuples\n"));
printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
- printf(_(" --buffer-usage-limit=BUFSIZE size of ring buffer used for vacuum\n"));
printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
printf(_(" --no-index-cleanup don't remove index entries that point to dead tuples\n"));
printf(_(" --no-process-main skip the main relation\n"));
On Sat, 15 Apr 2023 at 12:59, David Rowley <dgrowleyml@gmail.com> wrote:
These are all valid points. I've attached a patch aiming to address
each of them.
I tweaked this a little further and pushed it.
David
On Sat, Apr 15, 2023 at 12:59:52PM +1200, David Rowley wrote:
On Fri, 14 Apr 2023 at 19:20, Peter Eisentraut
<peter.eisentraut@enterprisedb.com> wrote:I came across these new options and had a little bit of trouble figuring
them out from the documentation. Maybe this could be polished a bit.vacuumdb --help says
--buffer-usage-limit=BUFSIZE
I can guess what a "SIZE" might be, but is "BUFSIZE" different from a
"SIZE"? Maybe simplify here.On the vacuumdb man page, the placeholder is
<replaceable class="parameter">buffer_usage_limit</replaceable>
which is yet another way of phrasing it. Maybe also use "size" here?
The VACUUM man page says
BUFFER_USAGE_LIMIT [ <replaceable ...>string</replaceable> ]
which had me really confused. The detailed description later doesn't
give any further explanation of possible values, except that
<literal>0</literal> is apparently a possible value, which in my mind is
not a string. Then there is a link to guc-vacuum-buffer-usage-limit,
which lifts the mystery that this is really just an integer setting with
possible memory-size units, but it was really hard to figure that out
from the start!Moreover, on the VACUUM man page, right below BUFFER_USAGE_LIMIT, it
explains the different kinds of accepted values, and "string" wasn't
added there. Maybe also change this to "size" here and add an
explanation there what kinds of sizes are possible.Finally, the locations of the new options in the various documentation
places seems a bit random. The vacuumdb --help output and the man page
appear to be mostly alphabetical, so --buffer-usage-limit should be
after -a/--all. (Also note that right now the option isn't even in the
same place in the --help output versus the man page.)These are all valid points. I've attached a patch aiming to address
each of them.
I like that we are now using "size" consistently instead of bufsize etc.
The order of the options on the VACUUM man page doesn't make any sense
anymore. This isn't really the fault of this patch, but maybe it's time
to do a fresh reordering there.Agreed, that likely wasn't a big problem say about 5 years ago when we
had far fewer options, but the number has grown quite a bit since
then.Right after I fix the points you've mentioned seems a good time to address that.
Are we still thinking that reordering the VACUUM (and ANALYZE) options
makes sense. And, if so, should it be alphabetical within parameter
category? That is, all actual parameters (e.g. FULL and FREEZE) are
alphabetically organized first followed by all parameter types (e.g.
boolean and size) alphabetically listed?
- Melanie
On Tue, 18 Apr 2023 at 09:21, Melanie Plageman
<melanieplageman@gmail.com> wrote:
Are we still thinking that reordering the VACUUM (and ANALYZE) options
makes sense. And, if so, should it be alphabetical within parameter
category? That is, all actual parameters (e.g. FULL and FREEZE) are
alphabetically organized first followed by all parameter types (e.g.
boolean and size) alphabetically listed?
I've opened a thread for that [1]/messages/by-id/CAApHDvo1eWbt5PVpk0G=yCbBNgLU7KaRP6dCBHpNbFaBjyGyQA@mail.gmail.com.
David
[1]: /messages/by-id/CAApHDvo1eWbt5PVpk0G=yCbBNgLU7KaRP6dCBHpNbFaBjyGyQA@mail.gmail.com
Hi,
On Sun, Apr 16, 2023 at 9:09 AM David Rowley <dgrowleyml@gmail.com> wrote:
On Sat, 15 Apr 2023 at 12:59, David Rowley <dgrowleyml@gmail.com> wrote:
These are all valid points. I've attached a patch aiming to address
each of them.I tweaked this a little further and pushed it.
I realized that the value of vacuum_buffer_usage_limit parameter in
postgresql.conf.sample doesn't have the unit:
#vacuum_buffer_usage_limit = 256 # size of vacuum and analyze buffer
access strategy ring.
# 0 to disable vacuum buffer access strategy
# range 128kB to 16GB
It works but I think we might want to add the unit kB for
understandability and consistency with other GUC_UNIT_KB parameters.
I've attached a small patch that adds the unit and aligns the indent
of the comments to the perimeter parameters.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com
Attachments:
add_unit_to_vacuum_buffer_usage_limit.patchapplication/octet-stream; name=add_unit_to_vacuum_buffer_usage_limit.patchDownload
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 0609853995..b70c66ca87 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -158,9 +158,9 @@
# mmap
# (change requires restart)
#min_dynamic_shared_memory = 0MB # (change requires restart)
-#vacuum_buffer_usage_limit = 256 # size of vacuum and analyze buffer access strategy ring.
- # 0 to disable vacuum buffer access strategy
- # range 128kB to 16GB
+#vacuum_buffer_usage_limit = 256kB # size of vacuum and analyze buffer access strategy ring.
+ # 0 to disable vacuum buffer access strategy
+ # range 128kB to 16GB
# - Disk -
On Wed, 26 Apr 2023, 8:48 pm Masahiko Sawada, <sawada.mshk@gmail.com> wrote:
I realized that the value of vacuum_buffer_usage_limit parameter in
postgresql.conf.sample doesn't have the unit:#vacuum_buffer_usage_limit = 256 # size of vacuum and analyze buffer
access strategy ring.
# 0 to disable vacuum buffer access
strategy
# range 128kB to 16GBIt works but I think we might want to add the unit kB for
understandability and consistency with other GUC_UNIT_KB parameters.
I've attached a small patch that adds the unit and aligns the indent
of the comments to the perimeter parameters.
I'm not currently able to check, but if work_mem has a unit in the sample
conf then I agree that vacuum_buffer_usage_limit should too.
I'm fine for you to go ahead and adjust this, otherwise it'll be Monday
before I can.
David
Show quoted text
On 26 Apr 2023, at 13:26, David Rowley <dgrowleyml@gmail.com> wrote:
On Wed, 26 Apr 2023, 8:48 pm Masahiko Sawada, <sawada.mshk@gmail.com <mailto:sawada.mshk@gmail.com>> wrote:
It works but I think we might want to add the unit kB for
understandability and consistency with other GUC_UNIT_KB parameters.
I've attached a small patch that adds the unit and aligns the indent
of the comments to the perimeter parameters.I'm not currently able to check, but if work_mem has a unit in the sample conf then I agree that vacuum_buffer_usage_limit should too.
+1 work_mem and all other related options in this section has a unit in the
sample conf so adding this makes sense.
--
Daniel Gustafsson
On Wed, Apr 26, 2023 at 8:31 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 26 Apr 2023, at 13:26, David Rowley <dgrowleyml@gmail.com> wrote:
On Wed, 26 Apr 2023, 8:48 pm Masahiko Sawada, <sawada.mshk@gmail.com<mailto:sawada.mshk@gmail.com>> wrote:
It works but I think we might want to add the unit kB for
understandability and consistency with other GUC_UNIT_KB parameters.
I've attached a small patch that adds the unit and aligns the indent
of the comments to the perimeter parameters.I'm not currently able to check, but if work_mem has a unit in the
sample conf then I agree that vacuum_buffer_usage_limit should too.
+1 work_mem and all other related options in this section has a unit in the
sample conf so adding this makes sense.
Agreed.
for the patch, the other GUCs have a tab instead of a space between the
unit and the "#" of the first comment.
(not the fault of this patch but probably makes sense to fix now).
Otherwise, LGTM
- Melanie
On Wed, Apr 26, 2023 at 9:59 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Wed, Apr 26, 2023 at 8:31 AM Daniel Gustafsson <daniel@yesql.se> wrote:
On 26 Apr 2023, at 13:26, David Rowley <dgrowleyml@gmail.com> wrote:
On Wed, 26 Apr 2023, 8:48 pm Masahiko Sawada, <sawada.mshk@gmail.com <mailto:sawada.mshk@gmail.com>> wrote:It works but I think we might want to add the unit kB for
understandability and consistency with other GUC_UNIT_KB parameters.
I've attached a small patch that adds the unit and aligns the indent
of the comments to the perimeter parameters.I'm not currently able to check, but if work_mem has a unit in the sample conf then I agree that vacuum_buffer_usage_limit should too.
+1 work_mem and all other related options in this section has a unit in the
sample conf so adding this makes sense.Agreed.
for the patch, the other GUCs have a tab instead of a space between the unit and the "#" of the first comment.
(not the fault of this patch but probably makes sense to fix now).
Otherwise, LGTM
Thanks for the review! Pushed after incorporating a comment from Melanie.
Regards,
--
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com