Commit fd267c1e authored by Tom Lane's avatar Tom Lane

Skip ambulkdelete scan if there's nothing to delete and the index is not

partial.  None of the existing AMs do anything useful except counting
tuples when there's nothing to delete, and we can get a tuple count
from the heap as long as it's not a partial index.  (hash actually can
skip anyway because it maintains a tuple count in the index metapage.)
GIST is not currently able to exploit this optimization because, due to
failure to index NULLs, GIST is always effectively partial.  Possibly
we should fix that sometime.
Simon Riggs w/ some review by Tom Lane.
parent a02f6ce3
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.7 2005/11/04 23:14:00 petere Exp $ $PostgreSQL: pgsql/doc/src/sgml/indexam.sgml,v 2.8 2006/02/11 23:31:32 tgl Exp $
--> -->
<chapter id="indexam"> <chapter id="indexam">
...@@ -200,6 +200,12 @@ ambulkdelete (Relation indexRelation, ...@@ -200,6 +200,12 @@ ambulkdelete (Relation indexRelation,
struct containing statistics about the effects of the deletion operation. struct containing statistics about the effects of the deletion operation.
</para> </para>
<para>
If <literal>callback_state</> is NULL then no tuples are to be deleted.
The index AM may choose to optimize this case (eg by not scanning the
index) but it is still expected to deliver accurate statistics.
</para>
<para> <para>
<programlisting> <programlisting>
IndexBulkDeleteResult * IndexBulkDeleteResult *
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/gist/gistvacuum.c,v 1.13 2006/02/11 17:14:08 momjian Exp $ * $PostgreSQL: pgsql/src/backend/access/gist/gistvacuum.c,v 1.14 2006/02/11 23:31:33 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -25,16 +25,19 @@ ...@@ -25,16 +25,19 @@
#include "storage/freespace.h" #include "storage/freespace.h"
#include "storage/smgr.h" #include "storage/smgr.h"
/* filled by gistbulkdelete, cleared by gistvacuumpcleanup */
static bool needFullVacuum = false;
typedef struct GistBulkDeleteResult
{
IndexBulkDeleteResult std; /* common state */
bool needFullVacuum;
} GistBulkDeleteResult;
typedef struct typedef struct
{ {
GISTSTATE giststate; GISTSTATE giststate;
Relation index; Relation index;
MemoryContext opCtx; MemoryContext opCtx;
IndexBulkDeleteResult *result; GistBulkDeleteResult *result;
} GistVacuum; } GistVacuum;
typedef struct typedef struct
...@@ -44,6 +47,7 @@ typedef struct ...@@ -44,6 +47,7 @@ typedef struct
bool emptypage; bool emptypage;
} ArrayTuple; } ArrayTuple;
static ArrayTuple static ArrayTuple
gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion) gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
{ {
...@@ -125,7 +129,7 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion) ...@@ -125,7 +129,7 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
if (chldtuple.ituplen > 1) if (chldtuple.ituplen > 1)
{ {
/* /*
* child was splitted, so we need mark completion * child was split, so we need mark completion
* insert(split) * insert(split)
*/ */
int j; int j;
...@@ -262,7 +266,7 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion) ...@@ -262,7 +266,7 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
needwrite = true; needwrite = true;
res.emptypage = true; res.emptypage = true;
GistPageSetDeleted(page); GistPageSetDeleted(page);
gv->result->pages_deleted++; gv->result->std.pages_deleted++;
} }
} }
else else
...@@ -329,9 +333,9 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion) ...@@ -329,9 +333,9 @@ gistVacuumUpdate(GistVacuum *gv, BlockNumber blkno, bool needunion)
} }
/* /*
* For usial vacuum just update FSM, for full vacuum * For usual vacuum just update FSM, for full vacuum
* reforms parent tuples if some of childs was deleted or changed, * reforms parent tuples if some of childs was deleted or changed,
* update invalid tuples (they can exsist from last crash recovery only), * update invalid tuples (they can exist from last crash recovery only),
* tries to get smaller index * tries to get smaller index
*/ */
...@@ -340,7 +344,7 @@ gistvacuumcleanup(PG_FUNCTION_ARGS) ...@@ -340,7 +344,7 @@ gistvacuumcleanup(PG_FUNCTION_ARGS)
{ {
Relation rel = (Relation) PG_GETARG_POINTER(0); Relation rel = (Relation) PG_GETARG_POINTER(0);
IndexVacuumCleanupInfo *info = (IndexVacuumCleanupInfo *) PG_GETARG_POINTER(1); IndexVacuumCleanupInfo *info = (IndexVacuumCleanupInfo *) PG_GETARG_POINTER(1);
IndexBulkDeleteResult *stats = (IndexBulkDeleteResult *) PG_GETARG_POINTER(2); GistBulkDeleteResult *stats = (GistBulkDeleteResult *) PG_GETARG_POINTER(2);
BlockNumber npages, BlockNumber npages,
blkno; blkno;
BlockNumber nFreePages, BlockNumber nFreePages,
...@@ -377,13 +381,11 @@ gistvacuumcleanup(PG_FUNCTION_ARGS) ...@@ -377,13 +381,11 @@ gistvacuumcleanup(PG_FUNCTION_ARGS)
freeGISTstate(&(gv.giststate)); freeGISTstate(&(gv.giststate));
MemoryContextDelete(gv.opCtx); MemoryContextDelete(gv.opCtx);
} }
else if (needFullVacuum) else if (stats->needFullVacuum)
ereport(NOTICE, ereport(NOTICE,
(errmsg("index \"%s\" needs VACUUM FULL or REINDEX to finish crash recovery", (errmsg("index \"%s\" needs VACUUM FULL or REINDEX to finish crash recovery",
RelationGetRelationName(rel)))); RelationGetRelationName(rel))));
needFullVacuum = false;
if (info->vacuum_full) if (info->vacuum_full)
needLock = false; /* relation locked with AccessExclusiveLock */ needLock = false; /* relation locked with AccessExclusiveLock */
else else
...@@ -438,23 +440,30 @@ gistvacuumcleanup(PG_FUNCTION_ARGS) ...@@ -438,23 +440,30 @@ gistvacuumcleanup(PG_FUNCTION_ARGS)
if (lastBlock > lastFilledBlock) if (lastBlock > lastFilledBlock)
RelationTruncate(rel, lastFilledBlock + 1); RelationTruncate(rel, lastFilledBlock + 1);
stats->pages_removed = lastBlock - lastFilledBlock; stats->std.pages_removed = lastBlock - lastFilledBlock;
} }
RecordIndexFreeSpace(&rel->rd_node, nFreePages, freePages); RecordIndexFreeSpace(&rel->rd_node, nFreePages, freePages);
pfree(freePages); pfree(freePages);
/* return statistics */ /* return statistics */
stats->pages_free = nFreePages; stats->std.pages_free = nFreePages;
if (needLock) if (needLock)
LockRelationForExtension(rel, ExclusiveLock); LockRelationForExtension(rel, ExclusiveLock);
stats->num_pages = RelationGetNumberOfBlocks(rel); stats->std.num_pages = RelationGetNumberOfBlocks(rel);
if (needLock) if (needLock)
UnlockRelationForExtension(rel, ExclusiveLock); UnlockRelationForExtension(rel, ExclusiveLock);
if (info->vacuum_full) if (info->vacuum_full)
UnlockRelation(rel, AccessExclusiveLock); UnlockRelation(rel, AccessExclusiveLock);
/* if gistbulkdelete skipped the scan, use heap's tuple count */
if (stats->std.num_index_tuples < 0)
{
Assert(info->num_heap_tuples >= 0);
stats->std.num_index_tuples = info->num_heap_tuples;
}
PG_RETURN_POINTER(stats); PG_RETURN_POINTER(stats);
} }
...@@ -500,15 +509,33 @@ gistbulkdelete(PG_FUNCTION_ARGS) ...@@ -500,15 +509,33 @@ gistbulkdelete(PG_FUNCTION_ARGS)
Relation rel = (Relation) PG_GETARG_POINTER(0); Relation rel = (Relation) PG_GETARG_POINTER(0);
IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(1); IndexBulkDeleteCallback callback = (IndexBulkDeleteCallback) PG_GETARG_POINTER(1);
void *callback_state = (void *) PG_GETARG_POINTER(2); void *callback_state = (void *) PG_GETARG_POINTER(2);
IndexBulkDeleteResult *result = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult)); GistBulkDeleteResult *result;
GistBDItem *stack, GistBDItem *stack,
*ptr; *ptr;
bool needLock; bool needLock;
stack = (GistBDItem *) palloc0(sizeof(GistBDItem)); result = (GistBulkDeleteResult *) palloc0(sizeof(GistBulkDeleteResult));
stack->blkno = GIST_ROOT_BLKNO; /*
needFullVacuum = false; * We can skip the scan entirely if there's nothing to delete (indicated
* by callback_state == NULL) and the index isn't partial. For a partial
* index we must scan in order to derive a trustworthy tuple count.
*
* XXX as of PG 8.2 this is dead code because GIST indexes are always
* effectively partial ... but keep it anyway in case our null-handling
* gets fixed.
*/
if (callback_state || vac_is_partial_index(rel))
{
stack = (GistBDItem *) palloc0(sizeof(GistBDItem));
stack->blkno = GIST_ROOT_BLKNO;
}
else
{
/* skip scan and set flag for gistvacuumcleanup */
stack = NULL;
result->std.num_index_tuples = -1;
}
while (stack) while (stack)
{ {
...@@ -561,11 +588,11 @@ gistbulkdelete(PG_FUNCTION_ARGS) ...@@ -561,11 +588,11 @@ gistbulkdelete(PG_FUNCTION_ARGS)
i--; i--;
maxoff--; maxoff--;
ntodelete++; ntodelete++;
result->tuples_removed += 1; result->std.tuples_removed += 1;
Assert(maxoff == PageGetMaxOffsetNumber(page)); Assert(maxoff == PageGetMaxOffsetNumber(page));
} }
else else
result->num_index_tuples += 1; result->std.num_index_tuples += 1;
} }
if (ntodelete) if (ntodelete)
...@@ -615,7 +642,7 @@ gistbulkdelete(PG_FUNCTION_ARGS) ...@@ -615,7 +642,7 @@ gistbulkdelete(PG_FUNCTION_ARGS)
stack->next = ptr; stack->next = ptr;
if (GistTupleIsInvalid(idxtuple)) if (GistTupleIsInvalid(idxtuple))
needFullVacuum = true; result->needFullVacuum = true;
} }
} }
...@@ -634,7 +661,7 @@ gistbulkdelete(PG_FUNCTION_ARGS) ...@@ -634,7 +661,7 @@ gistbulkdelete(PG_FUNCTION_ARGS)
if (needLock) if (needLock)
LockRelationForExtension(rel, ExclusiveLock); LockRelationForExtension(rel, ExclusiveLock);
result->num_pages = RelationGetNumberOfBlocks(rel); result->std.num_pages = RelationGetNumberOfBlocks(rel);
if (needLock) if (needLock)
UnlockRelationForExtension(rel, ExclusiveLock); UnlockRelationForExtension(rel, ExclusiveLock);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.85 2006/02/11 17:14:08 momjian Exp $ * $PostgreSQL: pgsql/src/backend/access/hash/hash.c,v 1.86 2006/02/11 23:31:33 tgl Exp $
* *
* NOTES * NOTES
* This file contains only the public interface routines. * This file contains only the public interface routines.
...@@ -517,6 +517,18 @@ hashbulkdelete(PG_FUNCTION_ARGS) ...@@ -517,6 +517,18 @@ hashbulkdelete(PG_FUNCTION_ARGS)
cur_maxbucket = orig_maxbucket; cur_maxbucket = orig_maxbucket;
loop_top: loop_top:
/*
* If we don't have anything to delete, skip the scan, and report the
* number of tuples shown in the metapage. (Unlike btree and gist,
* we can trust this number even for a partial index.)
*/
if (!callback_state)
{
cur_bucket = cur_maxbucket + 1;
num_index_tuples = local_metapage.hashm_ntuples;
}
while (cur_bucket <= cur_maxbucket) while (cur_bucket <= cur_maxbucket)
{ {
BlockNumber bucket_blkno; BlockNumber bucket_blkno;
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.89 2006/02/11 17:14:08 momjian Exp $ * $PostgreSQL: pgsql/src/backend/access/index/indexam.c,v 1.90 2006/02/11 23:31:33 tgl Exp $
* *
* INTERFACE ROUTINES * INTERFACE ROUTINES
* index_open - open an index relation by relation OID * index_open - open an index relation by relation OID
...@@ -685,6 +685,10 @@ index_getmulti(IndexScanDesc scan, ...@@ -685,6 +685,10 @@ index_getmulti(IndexScanDesc scan,
* callback routine tells whether a given main-heap tuple is * callback routine tells whether a given main-heap tuple is
* to be deleted * to be deleted
* *
* if callback_state is NULL then there are no tuples to be deleted;
* index AM can choose to avoid work in this case, but must still
* follow the protocol of returning statistical info.
*
* return value is an optional palloc'd struct of statistics * return value is an optional palloc'd struct of statistics
* ---------------- * ----------------
*/ */
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.138 2006/02/11 17:14:08 momjian Exp $ * $PostgreSQL: pgsql/src/backend/access/nbtree/nbtree.c,v 1.139 2006/02/11 23:31:33 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -564,8 +564,22 @@ btbulkdelete(PG_FUNCTION_ARGS) ...@@ -564,8 +564,22 @@ btbulkdelete(PG_FUNCTION_ARGS)
* further to its right, which the indexscan will have no pin on.) We can * further to its right, which the indexscan will have no pin on.) We can
* skip obtaining exclusive lock on empty pages though, since no indexscan * skip obtaining exclusive lock on empty pages though, since no indexscan
* could be stopped on those. * could be stopped on those.
*
* We can skip the scan entirely if there's nothing to delete (indicated
* by callback_state == NULL) and the index isn't partial. For a partial
* index we must scan in order to derive a trustworthy tuple count.
*/ */
buf = _bt_get_endpoint(rel, 0, false); if (callback_state || vac_is_partial_index(rel))
{
buf = _bt_get_endpoint(rel, 0, false);
}
else
{
/* skip scan and set flag for btvacuumcleanup */
buf = InvalidBuffer;
num_index_tuples = -1;
}
if (BufferIsValid(buf)) /* check for empty index */ if (BufferIsValid(buf)) /* check for empty index */
{ {
for (;;) for (;;)
...@@ -836,6 +850,13 @@ btvacuumcleanup(PG_FUNCTION_ARGS) ...@@ -836,6 +850,13 @@ btvacuumcleanup(PG_FUNCTION_ARGS)
stats->pages_deleted = pages_deleted; stats->pages_deleted = pages_deleted;
stats->pages_free = nFreePages; stats->pages_free = nFreePages;
/* if btbulkdelete skipped the scan, use heap's tuple count */
if (stats->num_index_tuples < 0)
{
Assert(info->num_heap_tuples >= 0);
stats->num_index_tuples = info->num_heap_tuples;
}
PG_RETURN_POINTER(stats); PG_RETURN_POINTER(stats);
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.323 2006/02/11 17:14:09 momjian Exp $ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.324 2006/02/11 23:31:33 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -2955,6 +2955,7 @@ scan_index(Relation indrel, double num_tuples) ...@@ -2955,6 +2955,7 @@ scan_index(Relation indrel, double num_tuples)
/* Do post-VACUUM cleanup, even though we deleted nothing */ /* Do post-VACUUM cleanup, even though we deleted nothing */
vcinfo.vacuum_full = true; vcinfo.vacuum_full = true;
vcinfo.message_level = elevel; vcinfo.message_level = elevel;
vcinfo.num_heap_tuples = num_tuples;
stats = index_vacuum_cleanup(indrel, &vcinfo, stats); stats = index_vacuum_cleanup(indrel, &vcinfo, stats);
...@@ -3022,6 +3023,7 @@ vacuum_index(VacPageList vacpagelist, Relation indrel, ...@@ -3022,6 +3023,7 @@ vacuum_index(VacPageList vacpagelist, Relation indrel,
/* Do post-VACUUM cleanup */ /* Do post-VACUUM cleanup */
vcinfo.vacuum_full = true; vcinfo.vacuum_full = true;
vcinfo.message_level = elevel; vcinfo.message_level = elevel;
vcinfo.num_heap_tuples = num_tuples + keep_tuples;
stats = index_vacuum_cleanup(indrel, &vcinfo, stats); stats = index_vacuum_cleanup(indrel, &vcinfo, stats);
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.65 2006/02/11 17:14:09 momjian Exp $ * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.66 2006/02/11 23:31:34 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -625,6 +625,7 @@ lazy_scan_index(Relation indrel, LVRelStats *vacrelstats) ...@@ -625,6 +625,7 @@ lazy_scan_index(Relation indrel, LVRelStats *vacrelstats)
/* Do post-VACUUM cleanup, even though we deleted nothing */ /* Do post-VACUUM cleanup, even though we deleted nothing */
vcinfo.vacuum_full = false; vcinfo.vacuum_full = false;
vcinfo.message_level = elevel; vcinfo.message_level = elevel;
vcinfo.num_heap_tuples = vacrelstats->rel_tuples;
stats = index_vacuum_cleanup(indrel, &vcinfo, stats); stats = index_vacuum_cleanup(indrel, &vcinfo, stats);
...@@ -697,6 +698,9 @@ lazy_vacuum_index(Relation indrel, ...@@ -697,6 +698,9 @@ lazy_vacuum_index(Relation indrel,
/* Do post-VACUUM cleanup */ /* Do post-VACUUM cleanup */
vcinfo.vacuum_full = false; vcinfo.vacuum_full = false;
vcinfo.message_level = elevel; vcinfo.message_level = elevel;
/* We don't yet know rel_tuples, so pass -1 */
/* index_bulk_delete can't have skipped scan anyway ... */
vcinfo.num_heap_tuples = -1;
stats = index_vacuum_cleanup(indrel, &vcinfo, stats); stats = index_vacuum_cleanup(indrel, &vcinfo, stats);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/access/genam.h,v 1.56 2006/02/11 17:14:09 momjian Exp $ * $PostgreSQL: pgsql/src/include/access/genam.h,v 1.57 2006/02/11 23:31:34 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -51,6 +51,7 @@ typedef struct IndexVacuumCleanupInfo ...@@ -51,6 +51,7 @@ typedef struct IndexVacuumCleanupInfo
{ {
bool vacuum_full; /* VACUUM FULL (we have exclusive lock) */ bool vacuum_full; /* VACUUM FULL (we have exclusive lock) */
int message_level; /* ereport level for progress messages */ int message_level; /* ereport level for progress messages */
double num_heap_tuples; /* tuples remaining in heap */
} IndexVacuumCleanupInfo; } IndexVacuumCleanupInfo;
/* Struct for heap-or-index scans of system tables */ /* Struct for heap-or-index scans of system tables */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment