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
|
* 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/genam.h"
|
||||||
#include "access/heapam.h"
|
#include "access/heapam.h"
|
||||||
|
#include "access/xact.h"
|
||||||
#include "catalog/catalog.h"
|
#include "catalog/catalog.h"
|
||||||
#include "catalog/catname.h"
|
#include "catalog/catname.h"
|
||||||
#include "catalog/index.h"
|
#include "catalog/index.h"
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
#include "parser/parse_oper.h"
|
#include "parser/parse_oper.h"
|
||||||
#include "storage/sinval.h"
|
#include "storage/sinval.h"
|
||||||
#include "storage/smgr.h"
|
#include "storage/smgr.h"
|
||||||
|
#include "tcop/tcopprot.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/inval.h"
|
#include "utils/inval.h"
|
||||||
#include "utils/portal.h"
|
#include "utils/portal.h"
|
||||||
|
@ -46,7 +48,6 @@
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* #include <port-protos.h> *//* Why? */
|
|
||||||
|
|
||||||
bool VacuumRunning = false;
|
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_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_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_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 VPageDescr vc_tidreapped(ItemPointer itemptr, VPageList vpl);
|
||||||
static void vc_reappage(VPageList vpl, VPageDescr vpc);
|
static void vc_reappage(VPageList vpl, VPageDescr vpc);
|
||||||
static void vc_vpinsert(VPageList vpl, VPageDescr vpnew);
|
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_getindices(Oid relid, int *nindices, Relation **Irel);
|
||||||
static void vc_clsindices(int nindices, Relation *Irel);
|
static void vc_clsindices(int nindices, Relation *Irel);
|
||||||
static void vc_mkindesc(Relation onerel, int nindices, Relation *Irel, IndDesc **Idesc);
|
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
|
void
|
||||||
vacuum(char *vacrel, bool verbose, bool analyze, List *va_spec)
|
vacuum(char *vacrel, bool verbose, bool analyze, List *va_spec)
|
||||||
{
|
{
|
||||||
char *pname;
|
|
||||||
MemoryContext old;
|
|
||||||
PortalVariableMemory pmem;
|
|
||||||
NameData VacRel;
|
NameData VacRel;
|
||||||
|
PortalVariableMemory pmem;
|
||||||
|
MemoryContext old;
|
||||||
List *le;
|
List *le;
|
||||||
List *va_cols = NIL;
|
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
|
* We cannot run VACUUM inside a user transaction block; if we were
|
||||||
* palloc the name space for it because our hash function expects the
|
* inside a transaction, then our commit- and start-transaction-command
|
||||||
* name to be on a longword boundary. CreatePortal copies the name to
|
* calls would not have the intended effect! Furthermore, the forced
|
||||||
* safe storage for us.
|
* 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);
|
if (IsTransactionBlock())
|
||||||
strcpy(pname, VACPNAME);
|
elog(ERROR, "VACUUM cannot run inside a BEGIN/END block");
|
||||||
vc_portal = CreatePortal(pname);
|
|
||||||
pfree(pname);
|
/* initialize vacuum cleaner, particularly vc_portal */
|
||||||
|
vc_init();
|
||||||
|
|
||||||
if (verbose)
|
if (verbose)
|
||||||
MESSAGE_LEVEL = NOTICE;
|
MESSAGE_LEVEL = NOTICE;
|
||||||
else
|
else
|
||||||
MESSAGE_LEVEL = DEBUG;
|
MESSAGE_LEVEL = DEBUG;
|
||||||
|
|
||||||
/* vacrel gets de-allocated on transaction commit */
|
/* vacrel gets de-allocated on transaction commit, so copy it */
|
||||||
if (vacrel)
|
if (vacrel)
|
||||||
strcpy(NameStr(VacRel), vacrel);
|
strcpy(NameStr(VacRel), vacrel);
|
||||||
|
|
||||||
|
/* must also copy the column list, if any, to safe storage */
|
||||||
pmem = PortalGetVariableMemory(vc_portal);
|
pmem = PortalGetVariableMemory(vc_portal);
|
||||||
old = MemoryContextSwitchTo((MemoryContext) pmem);
|
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)
|
foreach(le, va_spec)
|
||||||
{
|
{
|
||||||
char *col = (char *) lfirst(le);
|
char *col = (char *) lfirst(le);
|
||||||
char *dest;
|
|
||||||
|
|
||||||
dest = (char *) palloc(strlen(col) + 1);
|
va_cols = lappend(va_cols, pstrdup(col));
|
||||||
strcpy(dest, col);
|
|
||||||
va_cols = lappend(va_cols, dest);
|
|
||||||
}
|
}
|
||||||
MemoryContextSwitchTo(old);
|
MemoryContextSwitchTo(old);
|
||||||
|
|
||||||
/* initialize vacuum cleaner */
|
|
||||||
vc_init();
|
|
||||||
|
|
||||||
/* vacuum the database */
|
/* vacuum the database */
|
||||||
if (vacrel)
|
if (vacrel)
|
||||||
vc_vacuum(&VacRel, analyze, va_cols);
|
vc_vacuum(&VacRel, analyze, va_cols);
|
||||||
else
|
else
|
||||||
vc_vacuum(NULL, analyze, NIL);
|
vc_vacuum(NULL, analyze, NIL);
|
||||||
|
|
||||||
PortalDestroy(&vc_portal);
|
|
||||||
|
|
||||||
/* clean up */
|
/* clean up */
|
||||||
vc_shutdown();
|
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.
|
* 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
|
* Formerly, there was code here to prevent more than one VACUUM from
|
||||||
* to guarantee an exclusive lock on vacuuming, since a single vacuum
|
* executing concurrently in the same database. However, there's no
|
||||||
* cleaner instantiation crosses transaction boundaries, and we'd lose
|
* good reason to prevent that, and manually removing lockfiles after
|
||||||
* postgres-style locks at the end of every transaction.
|
* 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
|
* The strangeness with committing and starting transactions in the
|
||||||
* init and shutdown routines is due to the fact that the vacuum cleaner
|
* 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
|
* a transaction. We need to leave ourselves in a predictable state
|
||||||
* on entry and exit to the vacuum cleaner. We commit the transaction
|
* on entry and exit to the vacuum cleaner. We commit the transaction
|
||||||
* started in PostgresMain() inside vc_init(), and start one in
|
* 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
|
static void
|
||||||
vc_init()
|
vc_init()
|
||||||
{
|
{
|
||||||
int fd;
|
char *pname;
|
||||||
|
|
||||||
#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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* By here, exclusive open on the lock file succeeded. If we abort
|
* Create a portal for safe memory across transactions. We need to
|
||||||
* for any reason during vacuuming, we need to remove the lock file.
|
* 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
|
* This global variable is checked in the transaction manager on xact
|
||||||
* abort, and the routine vc_abort() is called if necessary.
|
* abort, and the routine vc_abort() is called if necessary.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
VacuumRunning = true;
|
VacuumRunning = true;
|
||||||
|
|
||||||
/* matches the StartTransaction in PostgresMain() */
|
/* matches the StartTransaction in PostgresMain() */
|
||||||
|
@ -221,25 +214,28 @@ vc_shutdown()
|
||||||
*/
|
*/
|
||||||
unlink(RELCACHE_INIT_FILENAME);
|
unlink(RELCACHE_INIT_FILENAME);
|
||||||
|
|
||||||
/* remove the vacuum cleaner lock file */
|
/*
|
||||||
if (unlink("pg_vlock") < 0)
|
* Release our portal for cross-transaction memory.
|
||||||
elog(ERROR, "vacuum: can't destroy lock file!");
|
*/
|
||||||
|
PortalDestroy(&vc_portal);
|
||||||
|
|
||||||
/* okay, we're done */
|
/* okay, we're done */
|
||||||
VacuumRunning = false;
|
VacuumRunning = false;
|
||||||
|
|
||||||
/* matches the CommitTransaction in PostgresMain() */
|
/* matches the CommitTransaction in PostgresMain() */
|
||||||
StartTransactionCommand();
|
StartTransactionCommand();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
vc_abort()
|
vc_abort()
|
||||||
{
|
{
|
||||||
/* on abort, remove the vacuum cleaner lock file */
|
/* Clear flag first, to avoid recursion if PortalDestroy elog's */
|
||||||
unlink("pg_vlock");
|
|
||||||
|
|
||||||
VacuumRunning = false;
|
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 */
|
/* get list of relations */
|
||||||
vrl = vc_getrels(VacRelP);
|
vrl = vc_getrels(VacRelP);
|
||||||
|
|
||||||
if (analyze && VacRelP == NULL && vrl != NULL)
|
|
||||||
vc_delhilowstats(InvalidOid, 0, NULL);
|
|
||||||
|
|
||||||
/* vacuum each heap relation */
|
/* vacuum each heap relation */
|
||||||
for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next)
|
for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next)
|
||||||
vc_vacone(cur->vrl_relid, analyze, va_cols);
|
vc_vacone(cur->vrl_relid, analyze, va_cols);
|
||||||
|
|
||||||
vc_free(vrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static VRelList
|
static VRelList
|
||||||
|
@ -381,6 +372,13 @@ vc_vacone(Oid relid, bool analyze, List *va_cols)
|
||||||
|
|
||||||
StartTransactionCommand();
|
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
|
* Race condition -- if the pg_class tuple has gone away since the
|
||||||
* last time we saw it, we don't need to vacuum it.
|
* 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;
|
stats->outfunc = InvalidOid;
|
||||||
}
|
}
|
||||||
vacrelstats->va_natts = attr_cnt;
|
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)
|
if (attnums)
|
||||||
pfree(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);
|
CatalogIndexInsert(irelations, Num_pg_statistic_indices, sd, stup);
|
||||||
CatalogCloseIndices(Num_pg_statistic_indices, irelations);
|
CatalogCloseIndices(Num_pg_statistic_indices, irelations);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* release allocated space */
|
/* release allocated space */
|
||||||
pfree(DatumGetPointer(values[Anum_pg_statistic_stacommonval-1]));
|
pfree(DatumGetPointer(values[Anum_pg_statistic_stacommonval-1]));
|
||||||
pfree(DatumGetPointer(values[Anum_pg_statistic_staloval-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
|
static void
|
||||||
vc_delhilowstats(Oid relid, int attcnt, int *attnums)
|
vc_delstats(Oid relid, int attcnt, int *attnums)
|
||||||
{
|
{
|
||||||
Relation pgstatistic;
|
Relation pgstatistic;
|
||||||
HeapScanDesc scan;
|
HeapScanDesc scan;
|
||||||
|
@ -2491,15 +2491,9 @@ vc_delhilowstats(Oid relid, int attcnt, int *attnums)
|
||||||
|
|
||||||
pgstatistic = heap_openr(StatisticRelationName, RowExclusiveLock);
|
pgstatistic = heap_openr(StatisticRelationName, RowExclusiveLock);
|
||||||
|
|
||||||
if (relid != InvalidOid)
|
ScanKeyEntryInitialize(&key, 0x0, Anum_pg_statistic_starelid,
|
||||||
{
|
F_OIDEQ, ObjectIdGetDatum(relid));
|
||||||
ScanKeyEntryInitialize(&key, 0x0, Anum_pg_statistic_starelid,
|
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 1, &key);
|
||||||
F_OIDEQ,
|
|
||||||
ObjectIdGetDatum(relid));
|
|
||||||
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 1, &key);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
scan = heap_beginscan(pgstatistic, false, SnapshotNow, 0, NULL);
|
|
||||||
|
|
||||||
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
|
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 *
|
static void *
|
||||||
vc_find_eq(void *bot, int nelem, int size, void *elm,
|
vc_find_eq(void *bot, int nelem, int size, void *elm,
|
||||||
int (*compar) (const void *, const void *))
|
int (*compar) (const void *, const void *))
|
||||||
|
|
Loading…
Reference in a new issue