diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/vacuum.c | 95 | ||||
-rw-r--r-- | src/backend/commands/vacuumparallel.c | 14 | ||||
-rw-r--r-- | src/backend/postmaster/autovacuum.c | 18 | ||||
-rw-r--r-- | src/backend/storage/buffer/README | 12 | ||||
-rw-r--r-- | src/backend/storage/buffer/freelist.c | 61 | ||||
-rw-r--r-- | src/backend/utils/init/globals.c | 5 | ||||
-rw-r--r-- | src/backend/utils/misc/guc_tables.c | 11 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 3 |
8 files changed, 196 insertions, 23 deletions
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 9386c08a556..1980e7664bc 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -57,6 +57,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" @@ -121,6 +122,26 @@ 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 * * This is mainly a preparation wrapper for the real operations that will @@ -139,6 +160,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; @@ -151,6 +173,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) { @@ -161,6 +189,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), @@ -266,6 +336,17 @@ ExecVacuum(ParseState *pstate, VacuumStmt *vacstmt, bool isTopLevel) 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. */ if (!(params.options & VACOPT_ANALYZE)) @@ -366,7 +447,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) + ring_size = VacuumBufferUsageLimit; + + bstrategy = GetAccessStrategyWithSize(BAS_VACUUM, ring_size); + MemoryContextSwitchTo(old_context); } diff --git a/src/backend/commands/vacuumparallel.c b/src/backend/commands/vacuumparallel.c index e200d5caf82..87ea5c52426 100644 --- a/src/backend/commands/vacuumparallel.c +++ b/src/backend/commands/vacuumparallel.c @@ -88,6 +88,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 * balance of each parallel vacuum worker. @@ -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 90cfc18f100..53c8f8d79cb 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2349,11 +2349,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 a775276ff2c..011af7aff3e 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 of this ring is +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 f122709fbe2..1c804fd2f59 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,22 +570,66 @@ 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. ring_size_kb must 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 * * A simple pfree would do at the moment, but we would prefer that callers diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 1b1d8142548..011ec18015a 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 8062589efd5..e8e8245e91f 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -2225,6 +2225,17 @@ struct config_int ConfigureNamesInt[] = }, { + {"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)."), NULL, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index ee49ca39370..e715aff3b81 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 - |