avoid O(N^2) behavior. Problem noted and fixed by Stephen Marshall <smarshall@wsicorp.com>,
with some help from Tom Lane.
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.237 2002/09/04 20:31:16 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.238 2002/09/20 19:56:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
pfree(vtlinks);
}
- elog(elevel, "Pages %u: Changed %u, reaped %u, Empty %u, New %u; \
-Tup %.0f: Vac %.0f, Keep/VTL %.0f/%u, UnUsed %.0f, MinLen %lu, MaxLen %lu; \
-Re-using: Free/Avail. Space %.0f/%.0f; EndEmpty/Avail. Pages %u/%u.\n\t%s",
+ elog(elevel, "Pages %u: Changed %u, reaped %u, Empty %u, New %u; "
+ "Tup %.0f: Vac %.0f, Keep/VTL %.0f/%u, UnUsed %.0f, MinLen %lu, "
+ "MaxLen %lu; Re-using: Free/Avail. Space %.0f/%.0f; "
+ "EndEmpty/Avail. Pages %u/%u.\n\t%s",
nblocks, changed_pages, vacuum_pages->num_pages, empty_pages,
new_pages, num_tuples, tups_vacuumed,
nkeep, vacrelstats->num_vtlinks,
{
if (stats->num_index_tuples > num_tuples ||
!vac_is_partial_index(indrel))
- elog(WARNING, "Index %s: NUMBER OF INDEX' TUPLES (%.0f) IS NOT THE SAME AS HEAP' (%.0f).\
-\n\tRecreate the index.",
+ elog(WARNING, "Index %s: NUMBER OF INDEX' TUPLES (%.0f) IS NOT THE SAME AS HEAP' (%.0f)."
+ "\n\tRecreate the index.",
RelationGetRelationName(indrel),
stats->num_index_tuples, num_tuples);
}
{
if (stats->num_index_tuples > num_tuples + keep_tuples ||
!vac_is_partial_index(indrel))
- elog(WARNING, "Index %s: NUMBER OF INDEX' TUPLES (%.0f) IS NOT THE SAME AS HEAP' (%.0f).\
-\n\tRecreate the index.",
+ elog(WARNING, "Index %s: NUMBER OF INDEX' TUPLES (%.0f) IS NOT THE SAME AS HEAP' (%.0f)."
+ "\n\tRecreate the index.",
RelationGetRelationName(indrel),
stats->num_index_tuples, num_tuples);
}
{
int nPages = fraged_pages->num_pages;
int i;
- BlockNumber *pages;
- Size *spaceAvail;
+ PageFreeSpaceInfo *pageSpaces;
/* +1 to avoid palloc(0) */
- pages = (BlockNumber *) palloc((nPages + 1) * sizeof(BlockNumber));
- spaceAvail = (Size *) palloc((nPages + 1) * sizeof(Size));
+ pageSpaces = (PageFreeSpaceInfo *)
+ palloc((nPages + 1) * sizeof(PageFreeSpaceInfo));
for (i = 0; i < nPages; i++)
{
- pages[i] = fraged_pages->pagedesc[i]->blkno;
- spaceAvail[i] = fraged_pages->pagedesc[i]->free;
+ pageSpaces[i].blkno = fraged_pages->pagedesc[i]->blkno;
+ pageSpaces[i].avail = fraged_pages->pagedesc[i]->free;
/*
* fraged_pages may contain entries for pages that we later
* decided to truncate from the relation; don't enter them into
- * the map!
+ * the free space map!
*/
- if (pages[i] >= rel_pages)
+ if (pageSpaces[i].blkno >= rel_pages)
{
nPages = i;
break;
}
}
- MultiRecordFreeSpace(&onerel->rd_node,
- 0, MaxBlockNumber,
- nPages, pages, spaceAvail);
- pfree(pages);
- pfree(spaceAvail);
+ MultiRecordFreeSpace(&onerel->rd_node, 0, nPages, pageSpaces);
+
+ pfree(pageSpaces);
}
/* Copy a VacPage structure */
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.19 2002/09/04 20:31:17 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.20 2002/09/20 19:56:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* We use a simple array until it fills up, then convert to heap */
bool fs_is_heap; /* are we using heap organization? */
int num_free_pages; /* current # of entries */
- int max_free_pages; /* # slots allocated in arrays */
- BlockNumber *free_pages; /* array or heap of block numbers */
- Size *free_spaceavail; /* array or heap of available space */
+ int max_free_pages; /* # slots allocated in array */
+ PageFreeSpaceInfo *free_pages; /* array or heap of blkno/avail */
} LVRelStats;
static bool dummy_tid_reaped(ItemPointer itemptr, void *state);
static void lazy_update_fsm(Relation onerel, LVRelStats *vacrelstats);
static int vac_cmp_itemptr(const void *left, const void *right);
+static int vac_cmp_page_spaces(const void *left, const void *right);
/*
lazy_scan_index(Irel[i], vacrelstats);
}
- elog(elevel, "Pages %u: Changed %u, Empty %u; \
-Tup %.0f: Vac %.0f, Keep %.0f, UnUsed %.0f.\n\tTotal %s",
+ elog(elevel, "Pages %u: Changed %u, Empty %u; Tup %.0f: Vac %.0f, Keep %.0f, UnUsed %.0f.\n\tTotal %s",
nblocks, changed_pages, empty_pages,
num_tuples, tups_vacuumed, nkeep, nunused,
vac_show_rusage(&ru0));
{
BlockNumber old_rel_pages = vacrelstats->rel_pages;
BlockNumber new_rel_pages;
- BlockNumber *pages;
- Size *spaceavail;
+ PageFreeSpaceInfo *pageSpaces;
int n;
int i,
j;
* Drop free-space info for removed blocks; these must not get entered
* into the FSM!
*/
- pages = vacrelstats->free_pages;
- spaceavail = vacrelstats->free_spaceavail;
+ pageSpaces = vacrelstats->free_pages;
n = vacrelstats->num_free_pages;
j = 0;
for (i = 0; i < n; i++)
{
- if (pages[i] < new_rel_pages)
+ if (pageSpaces[i].blkno < new_rel_pages)
{
- pages[j] = pages[i];
- spaceavail[j] = spaceavail[i];
+ pageSpaces[j] = pageSpaces[i];
j++;
}
}
vacrelstats->num_free_pages = j;
+ /* We destroyed the heap ordering, so mark array unordered */
+ vacrelstats->fs_is_heap = false;
/*
* We keep the exclusive lock until commit (perhaps not necessary)?
vacrelstats->fs_is_heap = false;
vacrelstats->num_free_pages = 0;
vacrelstats->max_free_pages = maxpages;
- vacrelstats->free_pages = (BlockNumber *)
- palloc(maxpages * sizeof(BlockNumber));
- vacrelstats->free_spaceavail = (Size *)
- palloc(maxpages * sizeof(Size));
+ vacrelstats->free_pages = (PageFreeSpaceInfo *)
+ palloc(maxpages * sizeof(PageFreeSpaceInfo));
}
/*
BlockNumber page,
Size avail)
{
- BlockNumber *pages;
- Size *spaceavail;
+ PageFreeSpaceInfo *pageSpaces;
int n;
/* Ignore pages with little free space */
return;
/* Copy pointers to local variables for notational simplicity */
- pages = vacrelstats->free_pages;
- spaceavail = vacrelstats->free_spaceavail;
+ pageSpaces = vacrelstats->free_pages;
n = vacrelstats->max_free_pages;
/* If we haven't filled the array yet, just keep adding entries */
if (vacrelstats->num_free_pages < n)
{
- pages[vacrelstats->num_free_pages] = page;
- spaceavail[vacrelstats->num_free_pages] = avail;
+ pageSpaces[vacrelstats->num_free_pages].blkno = page;
+ pageSpaces[vacrelstats->num_free_pages].avail = avail;
vacrelstats->num_free_pages++;
return;
}
/*----------
* The rest of this routine works with "heap" organization of the
* free space arrays, wherein we maintain the heap property
- * spaceavail[(j-1) div 2] <= spaceavail[j] for 0 < j < n.
+ * avail[(j-1) div 2] <= avail[j] for 0 < j < n.
* In particular, the zero'th element always has the smallest available
* space and can be discarded to make room for a new page with more space.
* See Knuth's discussion of heap-based priority queues, sec 5.2.3;
while (--l >= 0)
{
- BlockNumber R = pages[l];
- Size K = spaceavail[l];
+ BlockNumber R = pageSpaces[l].blkno;
+ Size K = pageSpaces[l].avail;
int i; /* i is where the "hole" is */
i = l;
if (j >= n)
break;
- if (j + 1 < n && spaceavail[j] > spaceavail[j + 1])
+ if (j + 1 < n && pageSpaces[j].avail > pageSpaces[j + 1].avail)
j++;
- if (K <= spaceavail[j])
+ if (K <= pageSpaces[j].avail)
break;
- pages[i] = pages[j];
- spaceavail[i] = spaceavail[j];
+ pageSpaces[i] = pageSpaces[j];
i = j;
}
- pages[i] = R;
- spaceavail[i] = K;
+ pageSpaces[i].blkno = R;
+ pageSpaces[i].avail = K;
}
vacrelstats->fs_is_heap = true;
}
/* If new page has more than zero'th entry, insert it into heap */
- if (avail > spaceavail[0])
+ if (avail > pageSpaces[0].avail)
{
/*
* Notionally, we replace the zero'th entry with the new data, and
if (j >= n)
break;
- if (j + 1 < n && spaceavail[j] > spaceavail[j + 1])
+ if (j + 1 < n && pageSpaces[j].avail > pageSpaces[j + 1].avail)
j++;
- if (avail <= spaceavail[j])
+ if (avail <= pageSpaces[j].avail)
break;
- pages[i] = pages[j];
- spaceavail[i] = spaceavail[j];
+ pageSpaces[i] = pageSpaces[j];
i = j;
}
- pages[i] = page;
- spaceavail[i] = avail;
+ pageSpaces[i].blkno = page;
+ pageSpaces[i].avail = avail;
}
}
static void
lazy_update_fsm(Relation onerel, LVRelStats *vacrelstats)
{
+ PageFreeSpaceInfo *pageSpaces = vacrelstats->free_pages;
+ int nPages = vacrelstats->num_free_pages;
+
/*
- * Since MultiRecordFreeSpace doesn't currently impose any
- * restrictions on the ordering of the input, we can just pass it the
- * arrays as-is, whether they are in heap or linear order.
+ * Sort data into order, as required by MultiRecordFreeSpace.
*/
- MultiRecordFreeSpace(&onerel->rd_node,
- 0, MaxBlockNumber,
- vacrelstats->num_free_pages,
- vacrelstats->free_pages,
- vacrelstats->free_spaceavail);
+ if (nPages > 1)
+ qsort(pageSpaces, nPages, sizeof(PageFreeSpaceInfo),
+ vac_cmp_page_spaces);
+
+ MultiRecordFreeSpace(&onerel->rd_node, 0, nPages, pageSpaces);
}
/*
return 0;
}
+
+static int
+vac_cmp_page_spaces(const void *left, const void *right)
+{
+ PageFreeSpaceInfo *linfo = (PageFreeSpaceInfo *) left;
+ PageFreeSpaceInfo *rinfo = (PageFreeSpaceInfo *) right;
+
+ if (linfo->blkno < rinfo->blkno)
+ return -1;
+ else if (linfo->blkno > rinfo->blkno)
+ return 1;
+ return 0;
+}
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.13 2002/09/04 20:31:25 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.14 2002/09/20 19:56:01 tgl Exp $
*
*
* NOTES:
* about */
int numChunks; /* number of FSMChunks allocated to rel */
FSMChunk *relChunks; /* linked list of page info chunks */
+ FSMChunk *lastChunk; /* last chunk in linked list */
};
/*
static bool insert_fsm_page_entry(FSMRelation *fsmrel,
BlockNumber page, Size spaceAvail,
FSMChunk *chunk, int chunkRelIndex);
+static bool append_fsm_chunk(FSMRelation *fsmrel);
static bool push_fsm_page_entry(BlockNumber page, Size spaceAvail,
FSMChunk *chunk, int chunkRelIndex);
static void delete_fsm_page_entry(FSMRelation *fsmrel, FSMChunk *chunk,
* MultiRecordFreeSpace - record available-space info about multiple pages
* of a relation in one call.
*
- * First, if minPage <= maxPage, the FSM must discard any entries it has for
- * pages in that page number range (inclusive). This allows obsolete info
- * to be discarded. Second, if nPages > 0, record the page numbers and free
- * space amounts in the given arrays. As with RecordFreeSpace, the FSM is at
- * liberty to discard some of the information. However, it *must* discard
- * previously stored info in the minPage..maxPage range (for example, this
- * case is used to remove info about deleted pages during relation truncation).
+ * First, the FSM must discard any entries it has for pages >= minPage.
+ * This allows obsolete info to be discarded (for example, it is used when
+ * truncating a relation). Any entries before minPage should be kept.
+ *
+ * Second, if nPages > 0, record the page numbers and free space amounts in
+ * the given pageSpaces[] array. As with RecordFreeSpace, the FSM is at
+ * liberty to discard some of this information. The pageSpaces[] array must
+ * be sorted in order by blkno, and may not contain entries before minPage.
*/
void
MultiRecordFreeSpace(RelFileNode *rel,
BlockNumber minPage,
- BlockNumber maxPage,
int nPages,
- BlockNumber *pages,
- Size *spaceAvail)
+ PageFreeSpaceInfo *pageSpaces)
{
FSMRelation *fsmrel;
int i;
if (fsmrel)
{
/*
- * Remove entries in specified range
+ * Remove entries >= minPage
*/
- if (minPage <= maxPage)
+ FSMChunk *chunk;
+ int chunkRelIndex;
+
+ /* Use lookup to locate first entry >= minPage */
+ lookup_fsm_page_entry(fsmrel, minPage, &chunk, &chunkRelIndex);
+ /* Set free space to 0 for each page >= minPage */
+ while (chunk)
{
- FSMChunk *chunk;
- int chunkRelIndex;
- bool done;
-
- /* Use lookup to locate first entry >= minPage */
- lookup_fsm_page_entry(fsmrel, minPage, &chunk, &chunkRelIndex);
- /* Set free space to 0 for each page within range */
- done = false;
- while (chunk && !done)
- {
- int numPages = chunk->numPages;
+ int numPages = chunk->numPages;
- for (; chunkRelIndex < numPages; chunkRelIndex++)
- {
- if (chunk->pages[chunkRelIndex] > maxPage)
- {
- done = true;
- break;
- }
- chunk->bytes[chunkRelIndex] = 0;
- }
- chunk = chunk->next;
- chunkRelIndex = 0;
- }
- /* Now compact out the zeroed entries */
- compact_fsm_page_list(fsmrel);
+ for (i = chunkRelIndex; i < numPages; i++)
+ chunk->bytes[i] = 0;
+ chunk = chunk->next;
+ chunkRelIndex = 0;
}
+ /* Now compact out the zeroed entries, along with any other junk */
+ compact_fsm_page_list(fsmrel);
/*
* Add new entries, if appropriate.
*
- * XXX we could probably be smarter about this than doing it
- * completely separately for each one. FIXME later.
- *
- * One thing we can do is short-circuit the process entirely if a
- * page (a) has too little free space to be recorded, and (b) is
- * within the minPage..maxPage range --- then we deleted any old
- * entry above, and we aren't going to make a new one. This is
- * particularly useful since in most cases, all the passed pages
- * will in fact be in the minPage..maxPage range.
+ * This can be much cheaper than a full fsm_record_free_space()
+ * call because we know we are appending to the end of the relation.
*/
for (i = 0; i < nPages; i++)
{
- BlockNumber page = pages[i];
- Size avail = spaceAvail[i];
+ BlockNumber page = pageSpaces[i].blkno;
+ Size avail = pageSpaces[i].avail;
+ FSMChunk *chunk;
+
+ /* Check caller provides sorted data */
+ if (i > 0 ? (page <= pageSpaces[i-1].blkno) : (page < minPage))
+ elog(ERROR, "MultiRecordFreeSpace: data not in page order");
+
+ /* Ignore pages too small to fit */
+ if (avail < fsmrel->threshold)
+ continue;
+
+ /* Get another chunk if needed */
+ /* We may need to loop if acquire_fsm_free_space() fails */
+ while ((chunk = fsmrel->lastChunk) == NULL ||
+ chunk->numPages >= CHUNKPAGES)
+ {
+ if (!append_fsm_chunk(fsmrel))
+ acquire_fsm_free_space();
+ }
+
+ /* Recheck in case threshold was raised by acquire */
+ if (avail < fsmrel->threshold)
+ continue;
- if (avail >= fsmrel->threshold ||
- page < minPage || page > maxPage)
- fsm_record_free_space(fsmrel, page, avail);
+ /* Okay to store */
+ chunk->pages[chunk->numPages] = page;
+ chunk->bytes[chunk->numPages] = (ItemLength) avail;
+ chunk->numPages++;
+ fsmrel->numPages++;
}
}
LWLockRelease(FreeSpaceLock);
fsmrel->numPages = 0;
fsmrel->numChunks = 0;
fsmrel->relChunks = NULL;
+ fsmrel->lastChunk = NULL;
/* Discard lowest-priority existing rel, if we are over limit */
if (FreeSpaceMap->numRels >= MaxFSMRelations)
delete_fsm_rel(FreeSpaceMap->relListTail);
if (fsmrel->numPages >= fsmrel->numChunks * CHUNKPAGES)
{
/* No free space within chunk list, so need another chunk */
- FSMChunk *newChunk;
-
- if ((newChunk = FreeSpaceMap->freeChunks) == NULL)
+ if (!append_fsm_chunk(fsmrel))
return false; /* can't do it */
- FreeSpaceMap->freeChunks = newChunk->next;
- FreeSpaceMap->numFreeChunks--;
- newChunk->next = NULL;
- newChunk->numPages = 0;
- if (fsmrel->relChunks == NULL)
- fsmrel->relChunks = newChunk;
- else
- {
- FSMChunk *priorChunk = fsmrel->relChunks;
-
- while (priorChunk->next != NULL)
- priorChunk = priorChunk->next;
- priorChunk->next = newChunk;
- }
- fsmrel->numChunks++;
if (chunk == NULL)
{
/* Original search found that new page belongs at end */
- chunk = newChunk;
+ chunk = fsmrel->lastChunk;
chunkRelIndex = 0;
}
}
}
/*
+ * Add one chunk to a FSMRelation's chunk list, if possible.
+ *
+ * Returns TRUE if successful, FALSE if no space available. Note that on
+ * success, the new chunk is easily accessible via fsmrel->lastChunk.
+ */
+static bool
+append_fsm_chunk(FSMRelation *fsmrel)
+{
+ FSMChunk *newChunk;
+
+ /* Remove a chunk from the freelist */
+ if ((newChunk = FreeSpaceMap->freeChunks) == NULL)
+ return false; /* can't do it */
+ FreeSpaceMap->freeChunks = newChunk->next;
+ FreeSpaceMap->numFreeChunks--;
+
+ /* Initialize chunk to empty */
+ newChunk->next = NULL;
+ newChunk->numPages = 0;
+
+ /* Link it into FSMRelation */
+ if (fsmrel->relChunks == NULL)
+ fsmrel->relChunks = newChunk;
+ else
+ fsmrel->lastChunk->next = newChunk;
+ fsmrel->lastChunk = newChunk;
+ fsmrel->numChunks++;
+
+ return true;
+}
+
+/*
* Auxiliary routine for insert_fsm_page_entry: try to push entries to the
* right to insert at chunk/chunkRelIndex. Return TRUE if successful.
* Note that the FSMRelation's own fields are not updated.
fsmrel->numPages = 0;
fsmrel->numChunks = 0;
fsmrel->relChunks = NULL;
+ fsmrel->lastChunk = NULL;
free_chunk_chain(dstChunk);
}
else
dstChunk->numPages = dstIndex;
free_chunk_chain(dstChunk->next);
dstChunk->next = NULL;
+ fsmrel->lastChunk = dstChunk;
}
}
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/smgr/smgr.c,v 1.60 2002/09/04 20:31:26 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/smgr/smgr.c,v 1.61 2002/09/20 19:56:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* for the about-to-be-deleted blocks. We want to be sure it
* won't return bogus block numbers later on.
*/
- MultiRecordFreeSpace(&reln->rd_node,
- nblocks, MaxBlockNumber,
- 0, NULL, NULL);
+ MultiRecordFreeSpace(&reln->rd_node, nblocks, 0, NULL);
newblks = (*(smgrsw[which].smgr_truncate)) (reln, nblocks);
if (newblks == InvalidBlockNumber)
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: freespace.h,v 1.7 2002/06/20 20:29:52 momjian Exp $
+ * $Id: freespace.h,v 1.8 2002/09/20 19:56:01 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "storage/relfilenode.h"
+/*
+ * exported types
+ */
+typedef struct PageFreeSpaceInfo
+{
+ BlockNumber blkno; /* which page in relation */
+ Size avail; /* space available on this page */
+} PageFreeSpaceInfo;
+
+
extern int MaxFSMRelations;
extern int MaxFSMPages;
Size spaceNeeded);
extern void MultiRecordFreeSpace(RelFileNode *rel,
BlockNumber minPage,
- BlockNumber maxPage,
int nPages,
- BlockNumber *pages,
- Size *spaceAvail);
+ PageFreeSpaceInfo *pageSpaces);
extern void FreeSpaceMapForgetRel(RelFileNode *rel);
extern void FreeSpaceMapForgetDatabase(Oid dbid);