Remove pg_vlock locking from VACUUM, allowing multiple VACUUMs to run in
parallel --- and, not incidentally, removing a common reason for needing manual cleanup by the DB admin after a crash. Remove initial global delete of pg_statistics rows in VACUUM ANALYZE; this was not only bad for performance of other backends that had to run without stats for a while, but it was fundamentally broken because it was done outside any transaction. Surprising we didn't see more consequences of that. Detect attempt to run VACUUM inside a transaction block. Check for query cancel request before starting vacuum of each table. Clean up vacuum's private portal storage if vacuum is aborted.
This commit is contained in:
parent
d2914c38b6
commit
aa903cf07c
1 changed files with 69 additions and 97 deletions
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.126 1999/11/25 00:15:57 momjian Exp $
|
||||
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.127 1999/11/28 02:10:01 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "access/genam.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/catalog.h"
|
||||
#include "catalog/catname.h"
|
||||
#include "catalog/index.h"
|
||||
|
@ -33,6 +34,7 @@
|
|||
#include "parser/parse_oper.h"
|
||||
#include "storage/sinval.h"
|
||||
#include "storage/smgr.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/portal.h"
|
||||
|
@ -46,7 +48,6 @@
|
|||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
/* #include <port-protos.h> *//* Why? */
|
||||
|
||||
bool VacuumRunning = false;
|
||||
|
||||
|
@ -80,11 +81,10 @@ static void vc_scanoneind(Relation indrel, int num_tuples);
|
|||
static void vc_attrstats(Relation onerel, VRelStats *vacrelstats, HeapTuple tuple);
|
||||
static void vc_bucketcpy(Form_pg_attribute attr, Datum value, Datum *bucket, int *bucket_len);
|
||||
static void vc_updstats(Oid relid, int num_pages, int num_tuples, bool hasindex, VRelStats *vacrelstats);
|
||||
static void vc_delhilowstats(Oid relid, int attcnt, int *attnums);
|
||||
static void vc_delstats(Oid relid, int attcnt, int *attnums);
|
||||
static VPageDescr vc_tidreapped(ItemPointer itemptr, VPageList vpl);
|
||||
static void vc_reappage(VPageList vpl, VPageDescr vpc);
|
||||
static void vc_vpinsert(VPageList vpl, VPageDescr vpnew);
|
||||
static void vc_free(VRelList vrl);
|
||||
static void vc_getindices(Oid relid, int *nindices, Relation **Irel);
|
||||
static void vc_clsindices(int nindices, Relation *Irel);
|
||||
static void vc_mkindesc(Relation onerel, int nindices, Relation *Irel, IndDesc **Idesc);
|
||||
|
@ -98,61 +98,55 @@ static bool vc_enough_space(VPageDescr vpd, Size len);
|
|||
void
|
||||
vacuum(char *vacrel, bool verbose, bool analyze, List *va_spec)
|
||||
{
|
||||
char *pname;
|
||||
MemoryContext old;
|
||||
PortalVariableMemory pmem;
|
||||
NameData VacRel;
|
||||
PortalVariableMemory pmem;
|
||||
MemoryContext old;
|
||||
List *le;
|
||||
List *va_cols = NIL;
|
||||
|
||||
if (va_spec != NIL && !analyze)
|
||||
elog(ERROR, "Can't vacuum columns, only tables. You can 'vacuum analyze' columns.");
|
||||
|
||||
/*
|
||||
* Create a portal for safe memory across transctions. We need to
|
||||
* palloc the name space for it because our hash function expects the
|
||||
* name to be on a longword boundary. CreatePortal copies the name to
|
||||
* safe storage for us.
|
||||
* 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! Furthermore, the forced
|
||||
* commit that occurs before truncating the relation's file would have
|
||||
* the effect of committing the rest of the user's transaction too,
|
||||
* which would certainly not be the desired behavior.
|
||||
*/
|
||||
pname = (char *) palloc(strlen(VACPNAME) + 1);
|
||||
strcpy(pname, VACPNAME);
|
||||
vc_portal = CreatePortal(pname);
|
||||
pfree(pname);
|
||||
if (IsTransactionBlock())
|
||||
elog(ERROR, "VACUUM cannot run inside a BEGIN/END block");
|
||||
|
||||
/* initialize vacuum cleaner, particularly vc_portal */
|
||||
vc_init();
|
||||
|
||||
if (verbose)
|
||||
MESSAGE_LEVEL = NOTICE;
|
||||
else
|
||||
MESSAGE_LEVEL = DEBUG;
|
||||
|
||||
/* vacrel gets de-allocated on transaction commit */
|
||||
/* vacrel gets de-allocated on transaction commit, so copy it */
|
||||
if (vacrel)
|
||||
strcpy(NameStr(VacRel), vacrel);
|
||||
|
||||
/* must also copy the column list, if any, to safe storage */
|
||||
pmem = PortalGetVariableMemory(vc_portal);
|
||||
old = MemoryContextSwitchTo((MemoryContext) pmem);
|
||||
|
||||
if (va_spec != NIL && !analyze)
|
||||
elog(ERROR, "Can't vacuum columns, only tables. You can 'vacuum analyze' columns.");
|
||||
|
||||
foreach(le, va_spec)
|
||||
{
|
||||
char *col = (char *) lfirst(le);
|
||||
char *dest;
|
||||
|
||||
dest = (char *) palloc(strlen(col) + 1);
|
||||
strcpy(dest, col);
|
||||
va_cols = lappend(va_cols, dest);
|
||||
va_cols = lappend(va_cols, pstrdup(col));
|
||||
}
|
||||
MemoryContextSwitchTo(old);
|
||||
|
||||
/* initialize vacuum cleaner */
|
||||
vc_init();
|
||||
|
||||
/* vacuum the database */
|
||||
if (vacrel)
|
||||
vc_vacuum(&VacRel, analyze, va_cols);
|
||||
else
|
||||
vc_vacuum(NULL, analyze, NIL);
|
||||
|
||||
PortalDestroy(&vc_portal);
|
||||
|
||||
/* clean up */
|
||||
vc_shutdown();
|
||||
}
|
||||
|
@ -160,14 +154,17 @@ vacuum(char *vacrel, bool verbose, bool analyze, List *va_spec)
|
|||
/*
|
||||
* vc_init(), vc_shutdown() -- start up and shut down the vacuum cleaner.
|
||||
*
|
||||
* We run exactly one vacuum cleaner at a time. We use the file system
|
||||
* to guarantee an exclusive lock on vacuuming, since a single vacuum
|
||||
* cleaner instantiation crosses transaction boundaries, and we'd lose
|
||||
* postgres-style locks at the end of every transaction.
|
||||
* Formerly, there was code here to prevent more than one VACUUM from
|
||||
* executing concurrently in the same database. However, there's no
|
||||
* good reason to prevent that, and manually removing lockfiles after
|
||||
* a vacuum crash was a pain for dbadmins. So, forget about lockfiles,
|
||||
* and just rely on the exclusive lock we grab on each target table
|
||||
* to ensure that there aren't two VACUUMs running on the same table
|
||||
* at the same time.
|
||||
*
|
||||
* The strangeness with committing and starting transactions in the
|
||||
* init and shutdown routines is due to the fact that the vacuum cleaner
|
||||
* is invoked via a sql command, and so is already executing inside
|
||||
* is invoked via an SQL command, and so is already executing inside
|
||||
* a transaction. We need to leave ourselves in a predictable state
|
||||
* on entry and exit to the vacuum cleaner. We commit the transaction
|
||||
* started in PostgresMain() inside vc_init(), and start one in
|
||||
|
@ -177,27 +174,23 @@ vacuum(char *vacrel, bool verbose, bool analyze, List *va_spec)
|
|||
static void
|
||||
vc_init()
|
||||
{
|
||||
int fd;
|
||||
|
||||
#ifndef __CYGWIN32__
|
||||
if ((fd = open("pg_vlock", O_CREAT | O_EXCL, 0600)) < 0)
|
||||
#else
|
||||
if ((fd = open("pg_vlock", O_CREAT | O_EXCL | O_BINARY, 0600)) < 0)
|
||||
#endif
|
||||
{
|
||||
elog(ERROR, "Can't create lock file. Is another vacuum cleaner running?\n\
|
||||
\tIf not, you may remove the pg_vlock file in the %s\n\
|
||||
\tdirectory", DatabasePath);
|
||||
}
|
||||
close(fd);
|
||||
char *pname;
|
||||
|
||||
/*
|
||||
* By here, exclusive open on the lock file succeeded. If we abort
|
||||
* for any reason during vacuuming, we need to remove the lock file.
|
||||
* Create a portal for safe memory across transactions. We need to
|
||||
* palloc the name space for it because our hash function expects the
|
||||
* name to be on a longword boundary. CreatePortal copies the name to
|
||||
* safe storage for us.
|
||||
*/
|
||||
pname = pstrdup(VACPNAME);
|
||||
vc_portal = CreatePortal(pname);
|
||||
pfree(pname);
|
||||
|
||||
/*
|
||||
* Set flag to indicate that vc_portal must be removed after an error.
|
||||
* This global variable is checked in the transaction manager on xact
|
||||
* abort, and the routine vc_abort() is called if necessary.
|
||||
*/
|
||||
|
||||
VacuumRunning = true;
|
||||
|
||||
/* matches the StartTransaction in PostgresMain() */
|
||||
|
@ -221,25 +214,28 @@ vc_shutdown()
|
|||
*/
|
||||
unlink(RELCACHE_INIT_FILENAME);
|
||||
|
||||
/* remove the vacuum cleaner lock file */
|
||||
if (unlink("pg_vlock") < 0)
|
||||
elog(ERROR, "vacuum: can't destroy lock file!");
|
||||
/*
|
||||
* Release our portal for cross-transaction memory.
|
||||
*/
|
||||
PortalDestroy(&vc_portal);
|
||||
|
||||
/* okay, we're done */
|
||||
VacuumRunning = false;
|
||||
|
||||
/* matches the CommitTransaction in PostgresMain() */
|
||||
StartTransactionCommand();
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
vc_abort()
|
||||
{
|
||||
/* on abort, remove the vacuum cleaner lock file */
|
||||
unlink("pg_vlock");
|
||||
|
||||
/* Clear flag first, to avoid recursion if PortalDestroy elog's */
|
||||
VacuumRunning = false;
|
||||
|
||||
/*
|
||||
* Release our portal for cross-transaction memory.
|
||||
*/
|
||||
PortalDestroy(&vc_portal);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -259,14 +255,9 @@ vc_vacuum(NameData *VacRelP, bool analyze, List *va_cols)
|
|||
/* get list of relations */
|
||||
vrl = vc_getrels(VacRelP);
|
||||
|
||||
if (analyze && VacRelP == NULL && vrl != NULL)
|
||||
vc_delhilowstats(InvalidOid, 0, NULL);
|
||||
|
||||
/* vacuum each heap relation */
|
||||
for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next)
|
||||
vc_vacone(cur->vrl_relid, analyze, va_cols);
|
||||
|
||||
vc_free(vrl);
|
||||
}
|
||||
|
||||
static VRelList
|
||||
|
@ -381,6 +372,13 @@ vc_vacone(Oid relid, bool analyze, List *va_cols)
|
|||
|
||||
StartTransactionCommand();
|
||||
|
||||
/*
|
||||
* Check for user-requested abort. Note we want this to be inside
|
||||
* a transaction, so xact.c doesn't issue useless NOTICE.
|
||||
*/
|
||||
if (QueryCancel)
|
||||
CancelQuery();
|
||||
|
||||
/*
|
||||
* Race condition -- if the pg_class tuple has gone away since the
|
||||
* last time we saw it, we don't need to vacuum it.
|
||||
|
@ -500,7 +498,8 @@ vc_vacone(Oid relid, bool analyze, List *va_cols)
|
|||
stats->outfunc = InvalidOid;
|
||||
}
|
||||
vacrelstats->va_natts = attr_cnt;
|
||||
vc_delhilowstats(relid, ((attnums) ? attr_cnt : 0), attnums);
|
||||
/* delete existing pg_statistics rows for relation */
|
||||
vc_delstats(relid, ((attnums) ? attr_cnt : 0), attnums);
|
||||
if (attnums)
|
||||
pfree(attnums);
|
||||
}
|
||||
|
@ -2453,7 +2452,7 @@ vc_updstats(Oid relid, int num_pages, int num_tuples, bool hasindex, VRelStats *
|
|||
CatalogIndexInsert(irelations, Num_pg_statistic_indices, sd, stup);
|
||||
CatalogCloseIndices(Num_pg_statistic_indices, irelations);
|
||||
}
|
||||
|
||||
|
||||
/* release allocated space */
|
||||
pfree(DatumGetPointer(values[Anum_pg_statistic_stacommonval-1]));
|
||||
pfree(DatumGetPointer(values[Anum_pg_statistic_staloval-1]));
|
||||
|
@ -2478,11 +2477,12 @@ vc_updstats(Oid relid, int num_pages, int num_tuples, bool hasindex, VRelStats *
|
|||
}
|
||||
|
||||
/*
|
||||
* vc_delhilowstats() -- delete pg_statistics rows
|
||||
* vc_delstats() -- delete pg_statistics rows for a relation
|
||||
*
|
||||
* If a list of attribute numbers is given, only zap stats for those attrs.
|
||||
*/
|
||||
static void
|
||||
vc_delhilowstats(Oid relid, int attcnt, int *attnums)
|
||||
vc_delstats(Oid relid, int attcnt, int *attnums)
|
||||
{
|
||||
Relation pgstatistic;
|
||||
HeapScanDesc scan;
|
||||
|
@ -2491,15 +2491,9 @@ vc_delhilowstats(Oid relid, int attcnt, int *attnums)
|
|||
|
||||
pgstatistic = heap_openr(StatisticRelationName, RowExclusiveLock);
|
||||
|
||||
if (relid != InvalidOid)
|
||||
{
|
||||
ScanKeyEntryInitialize(&key, 0x0, Anum_pg_statistic_starelid,
|
||||
F_OIDEQ,
|
||||
ObjectIdGetDatum(relid));
|
||||
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 1, &key);
|
||||
}
|
||||
else
|
||||
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 0, NULL);
|
||||
ScanKeyEntryInitialize(&key, 0x0, Anum_pg_statistic_starelid,
|
||||
F_OIDEQ, ObjectIdGetDatum(relid));
|
||||
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 1, &key);
|
||||
|
||||
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
||||
{
|
||||
|
@ -2572,28 +2566,6 @@ vc_vpinsert(VPageList vpl, VPageDescr vpnew)
|
|||
|
||||
}
|
||||
|
||||
static void
|
||||
vc_free(VRelList vrl)
|
||||
{
|
||||
VRelList p_vrl;
|
||||
MemoryContext old;
|
||||
PortalVariableMemory pmem;
|
||||
|
||||
pmem = PortalGetVariableMemory(vc_portal);
|
||||
old = MemoryContextSwitchTo((MemoryContext) pmem);
|
||||
|
||||
while (vrl != (VRelList) NULL)
|
||||
{
|
||||
|
||||
/* free rel list entry */
|
||||
p_vrl = vrl;
|
||||
vrl = vrl->vrl_next;
|
||||
pfree(p_vrl);
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(old);
|
||||
}
|
||||
|
||||
static void *
|
||||
vc_find_eq(void *bot, int nelem, int size, void *elm,
|
||||
int (*compar) (const void *, const void *))
|
||||
|
|
Loading…
Reference in a new issue