First non-stub implementation of shared free space map. It's not super
authorTom Lane
Mon, 2 Jul 2001 20:50:46 +0000 (20:50 +0000)
committerTom Lane
Mon, 2 Jul 2001 20:50:46 +0000 (20:50 +0000)
useful as yet, since its primary source of information is (full) VACUUM,
which makes a concerted effort to get rid of free space before telling
the map about it ... next stop is concurrent VACUUM ...

src/backend/commands/dbcommands.c
src/backend/commands/vacuum.c
src/backend/storage/freespace/freespace.c
src/backend/storage/smgr/smgr.c
src/include/storage/block.h
src/include/storage/freespace.h

index 424c9665f74fc051282b75ac00a28a328ff935fb..dca75bd71d9df34a3dedefa68f097a9171a570c2 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.75 2001/06/12 05:55:49 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.76 2001/07/02 20:50:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,7 +30,8 @@
 #include "commands/comment.h"
 #include "commands/dbcommands.h"
 #include "miscadmin.h"
-#include "storage/sinval.h"        /* for DatabaseHasActiveBackends */
+#include "storage/freespace.h"
+#include "storage/sinval.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/syscache.h"
@@ -372,6 +373,11 @@ dropdb(const char *dbname)
     */
    DropBuffers(db_id);
 
+   /*
+    * Also, clean out any entries in the shared free space map.
+    */
+   FreeSpaceMapForgetDatabase(db_id);
+
    /*
     * Remove the database's subdirectory and everything in it.
     */
index 9c5c8d9085d0e0616316120f56f3a2d557aeed4d..df486de5120cbd3b74747bca07476b82a72124a8 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.200 2001/06/29 20:14:27 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.201 2001/07/02 20:50:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,6 +37,7 @@
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
+#include "storage/freespace.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -146,6 +147,8 @@ static void vacuum_index(VacPageList vacpagelist, Relation indrel,
                         double num_tuples, int keep_tuples);
 static void scan_index(Relation indrel, double num_tuples);
 static VacPage tid_reaped(ItemPointer itemptr, VacPageList vacpagelist);
+static void vac_update_fsm(Relation onerel, VacPageList fraged_pages,
+                          BlockNumber rel_pages);
 static VacPage copy_vac_page(VacPage vacpage);
 static void vpage_insert(VacPageList vacpagelist, VacPage vpnew);
 static void get_indices(Relation relation, int *nindices, Relation **Irel);
@@ -579,6 +582,9 @@ vacuum_rel(Oid relid)
        activate_indexes_of_a_table(relid, true);
 #endif  /* NOT_USED */
 
+   /* update shared free space map with final free space info */
+   vac_update_fsm(onerel, &fraged_pages, vacrelstats->rel_pages);
+
    /* all done with this class, but hold lock until commit */
    heap_close(onerel, NoLock);
 
@@ -1157,6 +1163,10 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
         * useful as move targets, since we only want to move down.  Note
         * that since we stop the outer loop at last_move_dest_block, pages
         * removed here cannot have had anything moved onto them already.
+        *
+        * Also note that we don't change the stored fraged_pages list,
+        * only our local variable num_fraged_pages; so the forgotten pages
+        * are still available to be loaded into the free space map later.
         */
        while (num_fraged_pages > 0 &&
               fraged_pages->pagedesc[num_fraged_pages-1]->blkno >= blkno)
@@ -2080,6 +2090,8 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
    if (blkno < nblocks)
    {
        blkno = smgrtruncate(DEFAULT_SMGR, onerel, blkno);
+       onerel->rd_nblocks = blkno; /* update relcache immediately */
+       onerel->rd_targblock = InvalidBlockNumber;
        vacrelstats->rel_pages = blkno; /* set new number of blocks */
    }
 
@@ -2145,6 +2157,8 @@ vacuum_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages)
             RelationGetRelationName(onerel),
             vacrelstats->rel_pages, relblocks);
        relblocks = smgrtruncate(DEFAULT_SMGR, onerel, relblocks);
+       onerel->rd_nblocks = relblocks; /* update relcache immediately */
+       onerel->rd_targblock = InvalidBlockNumber;
        vacrelstats->rel_pages = relblocks;     /* set new number of
                                                 * blocks */
    }
@@ -2414,6 +2428,45 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
    heap_close(rd, RowExclusiveLock);
 }
 
+/*
+ * Update the shared Free Space Map with the info we now have about
+ * free space in the relation, discarding any old info the map may have.
+ */
+static void
+vac_update_fsm(Relation onerel, VacPageList fraged_pages,
+              BlockNumber rel_pages)
+{
+   int         nPages = fraged_pages->num_pages;
+   int         i;
+   BlockNumber *pages;
+   Size       *spaceAvail;
+
+   /* +1 to avoid palloc(0) */
+   pages = (BlockNumber *) palloc((nPages + 1) * sizeof(BlockNumber));
+   spaceAvail = (Size *) palloc((nPages + 1) * sizeof(Size));
+
+   for (i = 0; i < nPages; i++)
+   {
+       pages[i] = fraged_pages->pagedesc[i]->blkno;
+       spaceAvail[i] = 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!
+        */
+       if (pages[i] >= rel_pages)
+       {
+           nPages = i;
+           break;
+       }
+   }
+
+   MultiRecordFreeSpace(&onerel->rd_node,
+                        0, MaxBlockNumber,
+                        nPages, pages, spaceAvail);
+   pfree(pages);
+   pfree(spaceAvail);
+}
+
 /* Copy a VacPage structure */
 static VacPage
 copy_vac_page(VacPage vacpage)
index b4d0a605ed06b5b20112fd4d584055e256f2053c..a7866745f8288590b4d46a82a1e73f97ad71c623 100644 (file)
@@ -8,12 +8,52 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.2 2001/06/29 21:08:24 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.3 2001/07/02 20:50:46 tgl Exp $
+ *
+ *
+ * NOTES:
+ *
+ * The only really interesting aspect of this code is the heuristics for
+ * deciding how much information we can afford to keep about each relation,
+ * given that we have a limited amount of workspace in shared memory.
+ * These currently work as follows:
+ *
+ * The number of distinct relations tracked is limited by a configuration
+ * variable (MaxFSMRelations).  When this would be exceeded, we discard the
+ * least frequently used relation.  A previously-unknown relation is always
+ * entered into the map with useCount 1 on first reference, even if this
+ * causes an existing entry with higher useCount to be discarded.  This may
+ * cause a little bit of thrashing among the bottom entries in the list,
+ * but if we didn't do it then there'd be no way for a relation not in the
+ * map to get in once the map is full.  Note we allow a relation to be in the
+ * map even if no pages are currently stored for it: this allows us to track
+ * its useCount & threshold, which may eventually go high enough to give it
+ * priority for page storage.
+ *
+ * The total number of pages tracked is also set by a configuration variable
+ * (MaxFSMPages).  We allocate these dynamically among the known relations,
+ * giving preference to the more-frequently-referenced relations and those
+ * with smaller tuples.  This is done by a thresholding mechanism: for each
+ * relation we keep track of a current target threshold, and allow only pages
+ * with free space >= threshold to be stored in the map.  The threshold starts
+ * at BLCKSZ/2 (a somewhat arbitrary choice) for a newly entered relation.
+ * On each GetFreeSpace reference, the threshold is moved towards the
+ * request size using a slow exponential moving average; this means that
+ * for heavily used relations, the threshold will approach the average
+ * freespace request size (average tuple size).  Whenever we run out of
+ * storage space in the map, we double the threshold of all existing relations
+ * (but not to more than BLCKSZ, so as to prevent runaway thresholds).
+ * Infrequently used relations will thus tend to have large thresholds.
+ *
+ * XXX this thresholding mechanism is experimental; need to see how well
+ * it works in practice.
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include 
+
 #include "storage/freespace.h"
 #include "storage/itemid.h"
 #include "storage/shmem.h"
@@ -33,10 +73,12 @@ typedef struct FSMChunk FSMChunk;
 /* Header for whole map */
 struct FSMHeader
 {
-   HTAB       *relationHash;   /* hashtable of FSMRelation entries */
-   FSMRelation *relationList;  /* FSMRelations in order by recency of use */
-   int         numRelations;   /* number of FSMRelations now in use */
+   HTAB       *relHash;        /* hashtable of FSMRelation entries */
+   FSMRelation *relList;       /* FSMRelations in useCount order */
+   FSMRelation *relListTail;   /* tail of FSMRelation list */
+   int         numRels;        /* number of FSMRelations now in use */
    FSMChunk   *freeChunks;     /* linked list of currently-free chunks */
+   int         numFreeChunks;  /* number of free chunks */
 };
 
 /*
@@ -47,14 +89,28 @@ struct FSMHeader
 struct FSMRelation
 {
    RelFileNode key;            /* hash key (must be first) */
-   FSMRelation *nextRel;       /* next rel in order by recency of use */
-   FSMRelation *priorRel;      /* prior rel in order by recency of use */
+   FSMRelation *nextRel;       /* next rel in useCount order */
+   FSMRelation *priorRel;      /* prior rel in useCount order */
+   int         useCount;       /* use count for prioritizing rels */
+   Size        threshold;      /* minimum amount of free space to keep */
+   int         nextPage;       /* index (from 0) to start next search at */
+   int         numPages;       /* total number of pages we have info about */
+   int         numChunks;      /* number of FSMChunks allocated to rel */
    FSMChunk   *relChunks;      /* linked list of page info chunks */
 };
 
 #define SHMEM_FSMHASH_KEYSIZE  sizeof(RelFileNode)
 #define SHMEM_FSMHASH_DATASIZE (sizeof(FSMRelation) - SHMEM_FSMHASH_KEYSIZE)
 
+/*
+ * Info about individual pages in a relation is stored in chunks to reduce
+ * allocation overhead.  Note that we allow any chunk of a relation's list
+ * to be partly full, not only the last chunk; this speeds deletion of
+ * individual page entries.  When the total number of unused slots reaches
+ * CHUNKPAGES, we compact out the unused slots so that we can return a chunk
+ * to the freelist; but there's no point in doing the compaction before that.
+ */
+
 #define CHUNKPAGES  32         /* each chunk can store this many pages */
 
 struct FSMChunk
@@ -75,6 +131,33 @@ int     MaxFSMPages;
 static FSMHeader *FreeSpaceMap;    /* points to FSMHeader in shared memory */
 
 
+static FSMRelation *lookup_fsm_rel(RelFileNode *rel);
+static FSMRelation *create_fsm_rel(RelFileNode *rel);
+static void delete_fsm_rel(FSMRelation *fsmrel);
+static void link_fsm_rel_after(FSMRelation *fsmrel, FSMRelation *oldrel);
+static void unlink_fsm_rel(FSMRelation *fsmrel);
+static void free_chunk_chain(FSMChunk *fchunk);
+static BlockNumber find_free_space(FSMRelation *fsmrel, Size spaceNeeded);
+static void fsm_record_free_space(FSMRelation *fsmrel, BlockNumber page,
+                                 Size spaceAvail);
+static bool lookup_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page,
+                                 FSMChunk **outChunk, int *outChunkRelIndex);
+static bool insert_fsm_page_entry(FSMRelation *fsmrel,
+                                 BlockNumber page, Size spaceAvail,
+                                 FSMChunk *chunk, int chunkRelIndex);
+static bool push_fsm_page_entry(BlockNumber page, Size spaceAvail,
+                               FSMChunk *chunk, int chunkRelIndex);
+static void delete_fsm_page_entry(FSMRelation *fsmrel, FSMChunk *chunk,
+                                 int chunkRelIndex);
+static void compact_fsm_page_list(FSMRelation *fsmrel);
+static void acquire_fsm_free_space(void);
+
+
+/*
+ * Exported routines
+ */
+
+
 /*
  * InitFreeSpaceMap -- Initialize the freespace module.
  *
@@ -82,8 +165,7 @@ static FSMHeader *FreeSpaceMap;  /* points to FSMHeader in shared memory */
  * It builds the empty free space map table.  FreeSpaceLock must also be
  * initialized at some point, but is not touched here --- we assume there is
  * no need for locking, since only the calling process can be accessing shared
- * memory as yet.  FreeSpaceShmemSize() was called previously while computing
- * the space needed for shared memory.
+ * memory as yet.
  */
 void
 InitFreeSpaceMap(void)
@@ -104,17 +186,18 @@ InitFreeSpaceMap(void)
    info.datasize = SHMEM_FSMHASH_DATASIZE;
    info.hash = tag_hash;
 
-   FreeSpaceMap->relationHash = ShmemInitHash("Free Space Map Hash",
-                                              MaxFSMRelations / 10,
-                                              MaxFSMRelations,
-                                              &info,
-                                              (HASH_ELEM | HASH_FUNCTION));
+   FreeSpaceMap->relHash = ShmemInitHash("Free Space Map Hash",
+                                         MaxFSMRelations / 10,
+                                         MaxFSMRelations,
+                                         &info,
+                                         (HASH_ELEM | HASH_FUNCTION));
 
-   if (!FreeSpaceMap->relationHash)
+   if (!FreeSpaceMap->relHash)
        elog(FATAL, "Insufficient shared memory for free space map");
 
    /* Allocate FSMChunks and fill up the free-chunks list */
    nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1;
+   FreeSpaceMap->numFreeChunks = nchunks;
 
    chunks = (FSMChunk *) ShmemAlloc(nchunks * sizeof(FSMChunk));
    if (chunks == NULL)
@@ -130,21 +213,15 @@ InitFreeSpaceMap(void)
    FreeSpaceMap->freeChunks = prevchunk;
 }
 
-
+/*
+ * Estimate amount of shmem space needed for FSM.
+ */
 int
 FreeSpaceShmemSize(void)
 {
    int         size;
    int         nchunks;
 
-   /*
-    * There is no point in allowing less than one "chunk" per relation,
-    * so force MaxFSMPages to be at least CHUNKPAGES * MaxFSMRelations.
-    */
-   Assert(MaxFSMRelations > 0);
-   if (MaxFSMPages < CHUNKPAGES * MaxFSMRelations)
-       MaxFSMPages = CHUNKPAGES * MaxFSMRelations;
-
    /* table header */
    size = MAXALIGN(sizeof(FSMHeader));
 
@@ -161,27 +238,135 @@ FreeSpaceShmemSize(void)
    return size;
 }
 
+/*
+ * GetPageWithFreeSpace - try to find a page in the given relation with
+ *     at least the specified amount of free space.
+ *
+ * If successful, return the block number; if not, return InvalidBlockNumber.
+ *
+ * The caller must be prepared for the possibility that the returned page
+ * will turn out to have too little space available by the time the caller
+ * gets a lock on it.  In that case, the caller should report the actual
+ * amount of free space available on that page (via RecordFreeSpace) and
+ * then try again.  If InvalidBlockNumber is returned, extend the relation.
+ */
 BlockNumber
 GetPageWithFreeSpace(RelFileNode *rel, Size spaceNeeded)
 {
-   return InvalidBlockNumber;  /* stub */
+   FSMRelation *fsmrel;
+   BlockNumber freepage;
+
+   SpinAcquire(FreeSpaceLock);
+   /*
+    * We always add a rel to the hashtable when it is inquired about.
+    */
+   fsmrel = create_fsm_rel(rel);
+   /*
+    * Adjust the threshold towards the space request.  This essentially
+    * implements an exponential moving average with an equivalent period
+    * of about 63 requests.  Ignore silly requests, however, to ensure
+    * that the average stays in bounds.
+    *
+    * In theory, if the threshold increases here we should immediately
+    * delete any pages that fall below the new threshold.  In practice
+    * it seems OK to wait until we have a need to compact space.
+    */
+   if (spaceNeeded > 0 && spaceNeeded < BLCKSZ)
+   {
+       int     cur_avg = (int) fsmrel->threshold;
+
+       cur_avg += ((int) spaceNeeded - cur_avg) / 32;
+       fsmrel->threshold = (Size) cur_avg;
+   }
+   freepage = find_free_space(fsmrel, spaceNeeded);
+   SpinRelease(FreeSpaceLock);
+   return freepage;
 }
 
+/*
+ * RecordFreeSpace - record the amount of free space available on a page.
+ *
+ * The FSM is at liberty to forget about the page instead, if the amount of
+ * free space is too small to be interesting.  The only guarantee is that
+ * a subsequent request to get a page with more than spaceAvail space free
+ * will not return this page.
+ */
 void
 RecordFreeSpace(RelFileNode *rel, BlockNumber page, Size spaceAvail)
 {
-   /* stub */
+   FSMRelation *fsmrel;
+
+   /* Sanity check: ensure spaceAvail will fit into ItemLength */
+   AssertArg(spaceAvail < BLCKSZ);
+
+   SpinAcquire(FreeSpaceLock);
+   /*
+    * We choose not to add rels to the hashtable unless they've been
+    * inquired about with GetPageWithFreeSpace.  Also, a Record operation
+    * does not increase the useCount or change threshold, only Get does.
+    */
+   fsmrel = lookup_fsm_rel(rel);
+   if (fsmrel)
+       fsm_record_free_space(fsmrel, page, spaceAvail);
+   SpinRelease(FreeSpaceLock);
 }
 
+/*
+ * RecordAndGetPageWithFreeSpace - combo form to save one spinlock and
+ *     hash table lookup cycle.
+ */
 BlockNumber
 RecordAndGetPageWithFreeSpace(RelFileNode *rel,
                              BlockNumber oldPage,
                              Size oldSpaceAvail,
                              Size spaceNeeded)
 {
-   return InvalidBlockNumber;  /* stub */
+   FSMRelation *fsmrel;
+   BlockNumber freepage;
+
+   /* Sanity check: ensure spaceAvail will fit into ItemLength */
+   AssertArg(oldSpaceAvail < BLCKSZ);
+
+   SpinAcquire(FreeSpaceLock);
+   /*
+    * We always add a rel to the hashtable when it is inquired about.
+    */
+   fsmrel = create_fsm_rel(rel);
+   /*
+    * Adjust the threshold towards the space request, same as in
+    * GetPageWithFreeSpace.
+    *
+    * Note that we do this before storing data for oldPage, which means
+    * this isn't exactly equivalent to Record followed by Get; but it
+    * seems appropriate to adjust the threshold first.
+    */
+   if (spaceNeeded > 0 && spaceNeeded < BLCKSZ)
+   {
+       int     cur_avg = (int) fsmrel->threshold;
+
+       cur_avg += ((int) spaceNeeded - cur_avg) / 32;
+       fsmrel->threshold = (Size) cur_avg;
+   }
+   /* Do the Record */
+   fsm_record_free_space(fsmrel, oldPage, oldSpaceAvail);
+   /* Do the Get */
+   freepage = find_free_space(fsmrel, spaceNeeded);
+   SpinRelease(FreeSpaceLock);
+   return freepage;
 }
 
+/*
+ * 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).
+ */
 void
 MultiRecordFreeSpace(RelFileNode *rel,
                     BlockNumber minPage,
@@ -190,13 +375,668 @@ MultiRecordFreeSpace(RelFileNode *rel,
                     BlockNumber *pages,
                     Size *spaceAvail)
 {
-   /* stub */
+   FSMRelation *fsmrel;
+   int         i;
+
+   SpinAcquire(FreeSpaceLock);
+   fsmrel = lookup_fsm_rel(rel);
+   if (fsmrel)
+   {
+       /*
+        * Remove entries in specified range
+        */
+       if (minPage <= maxPage)
+       {
+           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;
+
+               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);
+       }
+       /*
+        * Add new entries, if appropriate.
+        *
+        * XXX we could probably be smarter about this than doing it
+        * completely separately for each one.  FIXME later.
+        */
+       for (i = 0; i < nPages; i++)
+           fsm_record_free_space(fsmrel, pages[i], spaceAvail[i]);
+   }
+   SpinRelease(FreeSpaceLock);
 }
 
+/*
+ * FreeSpaceMapForgetRel - forget all about a relation.
+ *
+ * This is called when a relation is deleted.  Although we could just let
+ * the rel age out of the map, it's better to reclaim and reuse the space
+ * sooner.
+ */
 void
 FreeSpaceMapForgetRel(RelFileNode *rel)
 {
-   /* stub */
+   FSMRelation *fsmrel;
+
+   SpinAcquire(FreeSpaceLock);
+   fsmrel = lookup_fsm_rel(rel);
+   if (fsmrel)
+       delete_fsm_rel(fsmrel);
+   SpinRelease(FreeSpaceLock);
+}
+
+/*
+ * FreeSpaceMapForgetDatabase - forget all relations of a database.
+ *
+ * This is called during DROP DATABASE.  As above, might as well reclaim
+ * map space sooner instead of later.
+ *
+ * XXX when we implement tablespaces, target Oid will need to be tablespace
+ * ID not database ID.
+ */
+void
+FreeSpaceMapForgetDatabase(Oid dbid)
+{
+   FSMRelation *fsmrel,
+              *nextrel;
+
+   SpinAcquire(FreeSpaceLock);
+   for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = nextrel)
+   {
+       nextrel = fsmrel->nextRel; /* in case we delete it */
+       if (fsmrel->key.tblNode == dbid)
+           delete_fsm_rel(fsmrel);
+   }
+   SpinRelease(FreeSpaceLock);
+}
+
+
+/*
+ * Internal routines.  These all assume the caller holds the FreeSpaceLock.
+ */
+
+/*
+ * Lookup a relation in the hash table.  If not present, return NULL.
+ * The relation's useCount is not changed.
+ */
+static FSMRelation *
+lookup_fsm_rel(RelFileNode *rel)
+{
+   FSMRelation *fsmrel;
+   bool        found;
+
+   fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+                                        (Pointer) rel,
+                                        HASH_FIND,
+                                        &found);
+   if (!fsmrel)
+       elog(ERROR, "FreeSpaceMap hashtable corrupted");
+
+   if (!found)
+       return NULL;
+
+   return fsmrel;
+}
+
+/*
+ * Lookup a relation in the hash table, creating an entry if not present.
+ *
+ * On successful lookup, the relation's useCount is incremented, possibly
+ * causing it to move up in the priority list.
+ */
+static FSMRelation *
+create_fsm_rel(RelFileNode *rel)
+{
+   FSMRelation *fsmrel,
+              *oldrel;
+   bool        found;
+
+   fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+                                        (Pointer) rel,
+                                        HASH_ENTER,
+                                        &found);
+   if (!fsmrel)
+       elog(ERROR, "FreeSpaceMap hashtable corrupted");
+
+   if (!found)
+   {
+       /* New hashtable entry, initialize it (hash_search set the key) */
+       fsmrel->useCount = 1;
+       fsmrel->threshold = BLCKSZ/2; /* starting point for new entry */
+       fsmrel->nextPage = 0;
+       fsmrel->numPages = 0;
+       fsmrel->numChunks = 0;
+       fsmrel->relChunks = NULL;
+       /* Discard lowest-priority existing rel, if we are over limit */
+       if (FreeSpaceMap->numRels >= MaxFSMRelations)
+           delete_fsm_rel(FreeSpaceMap->relListTail);
+       /*
+        * Add new entry in front of any others with useCount 1 (since it
+        * is more recently used than them).
+        */
+       oldrel = FreeSpaceMap->relListTail;
+       while (oldrel && oldrel->useCount <= 1)
+           oldrel = oldrel->priorRel;
+       link_fsm_rel_after(fsmrel, oldrel);
+       FreeSpaceMap->numRels++;
+   }
+   else
+   {
+       int     myCount;
+
+       /* Existing entry, advance its useCount */
+       if (++(fsmrel->useCount) >= INT_MAX/2)
+       {
+           /* When useCounts threaten to overflow, reduce 'em all 2X */
+           for (oldrel = FreeSpaceMap->relList;
+                oldrel != NULL;
+                oldrel = oldrel->nextRel)
+           {
+               oldrel->useCount >>= 1;
+           }
+       }
+       /* If warranted, move it up the priority list */
+       oldrel = fsmrel->priorRel;
+       myCount = fsmrel->useCount;
+       if (oldrel && oldrel->useCount <= myCount)
+       {
+           unlink_fsm_rel(fsmrel);
+           while (oldrel && oldrel->useCount <= myCount)
+               oldrel = oldrel->priorRel;
+           link_fsm_rel_after(fsmrel, oldrel);
+       }
+   }
+
+   return fsmrel;
+}
+
+/*
+ * Remove an existing FSMRelation entry.
+ */
+static void
+delete_fsm_rel(FSMRelation *fsmrel)
+{
+   FSMRelation *result;
+   bool        found;
+
+   free_chunk_chain(fsmrel->relChunks);
+   unlink_fsm_rel(fsmrel);
+   FreeSpaceMap->numRels--;
+   result = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+                                        (Pointer) &(fsmrel->key),
+                                        HASH_REMOVE,
+                                        &found);
+   if (!result || !found)
+       elog(ERROR, "FreeSpaceMap hashtable corrupted");
+}
+
+/*
+ * Link a FSMRelation into the priority list just after the given existing
+ * entry (or at the head of the list, if oldrel is NULL).
+ */
+static void
+link_fsm_rel_after(FSMRelation *fsmrel, FSMRelation *oldrel)
+{
+   if (oldrel == NULL)
+   {
+       /* insert at head */
+       fsmrel->priorRel = NULL;
+       fsmrel->nextRel = FreeSpaceMap->relList;
+       FreeSpaceMap->relList = fsmrel;
+       if (fsmrel->nextRel != NULL)
+           fsmrel->nextRel->priorRel = fsmrel;
+       else
+           FreeSpaceMap->relListTail = fsmrel;
+   }
+   else
+   {
+       /* insert after oldrel */
+       fsmrel->priorRel = oldrel;
+       fsmrel->nextRel = oldrel->nextRel;
+       oldrel->nextRel = fsmrel;
+       if (fsmrel->nextRel != NULL)
+           fsmrel->nextRel->priorRel = fsmrel;
+       else
+           FreeSpaceMap->relListTail = fsmrel;
+   }
+}
+
+/*
+ * Delink a FSMRelation from the priority list.
+ */
+static void
+unlink_fsm_rel(FSMRelation *fsmrel)
+{
+   if (fsmrel->priorRel != NULL)
+       fsmrel->priorRel->nextRel = fsmrel->nextRel;
+   else
+       FreeSpaceMap->relList = fsmrel->nextRel;
+   if (fsmrel->nextRel != NULL)
+       fsmrel->nextRel->priorRel = fsmrel->priorRel;
+   else
+       FreeSpaceMap->relListTail = fsmrel->priorRel;
+}
+
+/*
+ * Return all the FSMChunks in the chain starting at fchunk to the freelist.
+ * (Caller must handle unlinking them from wherever they were.)
+ */
+static void
+free_chunk_chain(FSMChunk *fchunk)
+{
+   int         nchunks;
+   FSMChunk   *lchunk;
+
+   if (fchunk == NULL)
+       return;
+   nchunks = 1;
+   lchunk = fchunk;
+   while (lchunk->next != NULL)
+   {
+       nchunks++;
+       lchunk = lchunk->next;
+   }
+   lchunk->next = FreeSpaceMap->freeChunks;
+   FreeSpaceMap->freeChunks = fchunk;
+   FreeSpaceMap->numFreeChunks += nchunks;
+}
+
+/*
+ * Look to see if a page with at least the specified amount of space is
+ * available in the given FSMRelation.  If so, return its page number,
+ * and advance the nextPage counter so that the next inquiry will return
+ * a different page if possible.  Return InvalidBlockNumber if no success.
+ */
+static BlockNumber
+find_free_space(FSMRelation *fsmrel, Size spaceNeeded)
+{
+   int         pagesToCheck,   /* outer loop counter */
+               pageIndex,      /* current page index relative to relation */
+               chunkRelIndex;  /* current page index relative to curChunk */
+   FSMChunk   *curChunk;
+
+   pageIndex = fsmrel->nextPage;
+   /* Last operation may have left nextPage pointing past end */
+   if (pageIndex >= fsmrel->numPages)
+       pageIndex = 0;
+   curChunk = fsmrel->relChunks;
+   chunkRelIndex = pageIndex;
+
+   for (pagesToCheck = fsmrel->numPages; pagesToCheck > 0; pagesToCheck--)
+   {
+       /*
+        * Make sure we are in the right chunk.  First time through, we
+        * may have to advance through multiple chunks; subsequent loops
+        * should do this at most once.
+        */
+       while (chunkRelIndex >= curChunk->numPages)
+       {
+           chunkRelIndex -= curChunk->numPages;
+           curChunk = curChunk->next;
+       }
+       /* Check the next page */
+       if ((Size) curChunk->bytes[chunkRelIndex] >= spaceNeeded)
+       {
+           fsmrel->nextPage = pageIndex+1;
+           return curChunk->pages[chunkRelIndex];
+       }
+       /* Advance pageIndex and chunkRelIndex, wrapping around if needed */
+       if (++pageIndex >= fsmrel->numPages)
+       {
+           pageIndex = 0;
+           curChunk = fsmrel->relChunks;
+           chunkRelIndex = 0;
+       }
+       else
+           chunkRelIndex++;
+   }
+
+   return InvalidBlockNumber;  /* nothing found */
+}
+
+/*
+ * fsm_record_free_space - guts of RecordFreeSpace, which is also used by
+ * RecordAndGetPageWithFreeSpace.
+ */
+static void
+fsm_record_free_space(FSMRelation *fsmrel, BlockNumber page, Size spaceAvail)
+{
+   FSMChunk   *chunk;
+   int         chunkRelIndex;
+
+   if (lookup_fsm_page_entry(fsmrel, page, &chunk, &chunkRelIndex))
+   {
+       /* Found an existing entry for page; update or delete it */
+       if (spaceAvail >= fsmrel->threshold)
+           chunk->bytes[chunkRelIndex] = (ItemLength) spaceAvail;
+       else
+           delete_fsm_page_entry(fsmrel, chunk, chunkRelIndex);
+   }
+   else
+   {
+       /*
+        * No existing entry; add one if spaceAvail exceeds threshold.
+        *
+        * CORNER CASE: if we have to do acquire_fsm_free_space then
+        * our own threshold will increase, possibly meaning that we
+        * shouldn't store the page after all.  Loop to redo the test
+        * if that happens.  The loop also covers the possibility that
+        * acquire_fsm_free_space must be executed more than once to
+        * free any space (ie, thresholds must be more than doubled).
+        */
+       while (spaceAvail >= fsmrel->threshold)
+       {
+           if (insert_fsm_page_entry(fsmrel, page, spaceAvail,
+                                     chunk, chunkRelIndex))
+               break;
+           /* No space, acquire some and recheck threshold */
+           acquire_fsm_free_space();
+           if (spaceAvail < fsmrel->threshold)
+               break;
+           /*
+            * Need to redo the lookup since our own page list may well
+            * have lost entries, so position is not correct anymore.
+            */
+           if (lookup_fsm_page_entry(fsmrel, page,
+                                     &chunk, &chunkRelIndex))
+               elog(ERROR, "fsm_record_free_space: unexpected match");
+       }
+   }
+}
+
+/*
+ * Look for an entry for a specific page (block number) in a FSMRelation.
+ * Returns TRUE if a matching entry exists, else FALSE.
+ *
+ * The output arguments *outChunk, *outChunkRelIndex are set to indicate where
+ * the entry exists (if TRUE result) or could be inserted (if FALSE result).
+ * *chunk is set to NULL if there is no place to insert, ie, the entry would
+ * need to be added to a new chunk.
+ */
+static bool
+lookup_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page,
+                     FSMChunk **outChunk, int *outChunkRelIndex)
+{
+   FSMChunk   *chunk;
+   int         chunkRelIndex;
+
+   for (chunk = fsmrel->relChunks; chunk; chunk = chunk->next)
+   {
+       int     numPages = chunk->numPages;
+
+       /* Can skip the chunk quickly if page must be after last in chunk */
+       if (numPages > 0 && page <= chunk->pages[numPages-1])
+       {
+           for (chunkRelIndex = 0; chunkRelIndex < numPages; chunkRelIndex++)
+           {
+               if (page <= chunk->pages[chunkRelIndex])
+               {
+                   *outChunk = chunk;
+                   *outChunkRelIndex = chunkRelIndex;
+                   return (page == chunk->pages[chunkRelIndex]);
+               }
+           }
+           /* Should not get here, given above test */
+           Assert(false);
+       }
+       /*
+        * If we are about to fall off the end, and there's space available
+        * in the end chunk, return a pointer to it.
+        */
+       if (chunk->next == NULL && numPages < CHUNKPAGES)
+       {
+           *outChunk = chunk;
+           *outChunkRelIndex = numPages;
+           return false;
+       }
+   }
+   /*
+    * Adding the page would require a new chunk (or, perhaps, compaction
+    * of available free space --- not my problem here).
+    */
+   *outChunk = NULL;
+   *outChunkRelIndex = 0;
+   return false;
+}
+
+/*
+ * Insert a new page entry into a FSMRelation's list at given position
+ * (chunk == NULL implies at end).
+ *
+ * If there is no space available to insert the entry, return FALSE.
+ */
+static bool
+insert_fsm_page_entry(FSMRelation *fsmrel, BlockNumber page, Size spaceAvail,
+                     FSMChunk *chunk, int chunkRelIndex)
+{
+   FSMChunk   *newChunk;
+   int         newChunkRelIndex;
+
+   if (fsmrel->numPages >= fsmrel->numChunks * CHUNKPAGES)
+   {
+       /* No free space within chunk list, so need another chunk */
+       if ((newChunk = FreeSpaceMap->freeChunks) == NULL)
+           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;
+           chunkRelIndex = 0;
+       }
+   }
+
+   /* Try to insert it the easy way, ie, just move down subsequent data */
+   if (chunk &&
+       push_fsm_page_entry(page, spaceAvail, chunk, chunkRelIndex))
+   {
+       fsmrel->numPages++;
+       fsmrel->nextPage++;     /* don't return same page twice running */
+       return true;
+   }
+   /*
+    * There is space available, but evidently it's before the place
+    * where the page entry needs to go.  Compact the list and try again.
+    * This will require us to redo the search for the appropriate place.
+    */
+   compact_fsm_page_list(fsmrel);
+   if (lookup_fsm_page_entry(fsmrel, page, &newChunk, &newChunkRelIndex))
+       elog(ERROR, "insert_fsm_page_entry: entry already exists!");
+   if (newChunk &&
+       push_fsm_page_entry(page, spaceAvail, newChunk, newChunkRelIndex))
+   {
+       fsmrel->numPages++;
+       fsmrel->nextPage++;     /* don't return same page twice running */
+       return true;
+   }
+   /* Shouldn't get here given the initial if-test for space available */
+   elog(ERROR, "insert_fsm_page_entry: failed to insert entry!");
+   return false;               /* keep compiler quiet */
+}
+
+/*
+ * 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.
+ */
+static bool
+push_fsm_page_entry(BlockNumber page, Size spaceAvail,
+                   FSMChunk *chunk, int chunkRelIndex)
+{
+   int         i;
+
+   if (chunk->numPages >= CHUNKPAGES)
+   {
+       if (chunk->next == NULL)
+           return false;       /* no space */
+       /* try to push chunk's last item to next chunk */
+       if (! push_fsm_page_entry(chunk->pages[CHUNKPAGES-1],
+                                 chunk->bytes[CHUNKPAGES-1],
+                                 chunk->next, 0))
+           return false;
+       /* successfully pushed it */
+       chunk->numPages--;
+   }
+   for (i = chunk->numPages; i > chunkRelIndex; i--)
+   {
+       chunk->pages[i] = chunk->pages[i-1];
+       chunk->bytes[i] = chunk->bytes[i-1];
+   }
+   chunk->numPages++;
+   chunk->pages[chunkRelIndex] = page;
+   chunk->bytes[chunkRelIndex] = (ItemLength) spaceAvail;
+   return true;
+}
+
+/*
+ * Delete one page entry from a FSMRelation's list.  Compact free space
+ * in the list, but only if a chunk can be returned to the freelist.
+ */
+static void
+delete_fsm_page_entry(FSMRelation *fsmrel, FSMChunk *chunk, int chunkRelIndex)
+{
+   int         i,
+               lim;
+
+   Assert(chunk && chunkRelIndex >= 0 && chunkRelIndex < chunk->numPages);
+   /* Compact out space in this chunk */
+   lim = --chunk->numPages;
+   for (i = chunkRelIndex; i < lim; i++)
+   {
+       chunk->pages[i] = chunk->pages[i+1];
+       chunk->bytes[i] = chunk->bytes[i+1];
+   }
+   /* Compact the whole list if a chunk can be freed */
+   fsmrel->numPages--;
+   if (fsmrel->numPages <= (fsmrel->numChunks-1) * CHUNKPAGES)
+       compact_fsm_page_list(fsmrel);
+}
+
+/*
+ * Remove any pages with free space less than the current threshold for the
+ * FSMRelation, compact out free slots in non-last chunks, and return any
+ * completely freed chunks to the freelist.
+ */
+static void
+compact_fsm_page_list(FSMRelation *fsmrel)
+{
+   Size        threshold = fsmrel->threshold;
+   FSMChunk   *srcChunk,
+              *dstChunk;
+   int         srcIndex,
+               dstIndex,
+               dstPages,
+               dstChunkCnt;
+
+   srcChunk = dstChunk = fsmrel->relChunks;
+   srcIndex = dstIndex = 0;
+   dstPages = 0;               /* total pages kept */
+   dstChunkCnt = 1;            /* includes current dstChunk */
+
+   while (srcChunk != NULL)
+   {
+       int     srcPages = srcChunk->numPages;
+
+       while (srcIndex < srcPages)
+       {
+           if ((Size) srcChunk->bytes[srcIndex] >= threshold)
+           {
+               if (dstIndex >= CHUNKPAGES)
+               {
+                   /*
+                    * At this point srcChunk must be pointing to a later
+                    * chunk, so it's OK to overwrite dstChunk->numPages.
+                    */
+                   dstChunk->numPages = dstIndex;
+                   dstChunk = dstChunk->next;
+                   dstChunkCnt++;
+                   dstIndex = 0;
+               }
+               dstChunk->pages[dstIndex] = srcChunk->pages[srcIndex];
+               dstChunk->bytes[dstIndex] = srcChunk->bytes[srcIndex];
+               dstIndex++;
+               dstPages++;
+           }
+           srcIndex++;
+       }
+       srcChunk = srcChunk->next;
+       srcIndex = 0;
+   }
+
+   if (dstPages == 0)
+   {
+       /* No chunks to be kept at all */
+       fsmrel->nextPage = 0;
+       fsmrel->numPages = 0;
+       fsmrel->numChunks = 0;
+       fsmrel->relChunks = NULL;
+       free_chunk_chain(dstChunk);
+   }
+   else
+   {
+       /* we deliberately don't change nextPage here */
+       fsmrel->numPages = dstPages;
+       fsmrel->numChunks = dstChunkCnt;
+       dstChunk->numPages = dstIndex;
+       free_chunk_chain(dstChunk->next);
+       dstChunk->next = NULL;
+   }
+}
+
+/*
+ * Acquire some free space by raising the thresholds of all FSMRelations.
+ *
+ * Note there is no guarantee as to how much space will be freed by a single
+ * invocation; caller may repeat if necessary.
+ */
+static void
+acquire_fsm_free_space(void)
+{
+   FSMRelation *fsmrel;
+
+   for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = fsmrel->nextRel)
+   {
+       fsmrel->threshold *= 2;
+       /* Limit thresholds to BLCKSZ so they can't get ridiculously large */
+       if (fsmrel->threshold > BLCKSZ)
+           fsmrel->threshold = BLCKSZ;
+       /* Release any pages that don't meet the new threshold */
+       compact_fsm_page_list(fsmrel);
+   }
 }
 
 
@@ -210,7 +1050,58 @@ FreeSpaceMapForgetRel(RelFileNode *rel)
 void
 DumpFreeSpace(void)
 {
-   /* stub */
+   FSMRelation *fsmrel;
+   FSMRelation *prevrel = NULL;
+   int         relNum = 0;
+   FSMChunk   *chunk;
+   int         chunkRelIndex;
+   int         nChunks;
+   int         nPages;
+
+   for (fsmrel = FreeSpaceMap->relList; fsmrel; fsmrel = fsmrel->nextRel)
+   {
+       relNum++;
+       fprintf(stderr, "Map %d: rel %u/%u useCount %d threshold %u nextPage %d\nMap= ",
+               relNum, fsmrel->key.tblNode, fsmrel->key.relNode,
+               fsmrel->useCount, fsmrel->threshold, fsmrel->nextPage);
+       nChunks = nPages = 0;
+       for (chunk = fsmrel->relChunks; chunk; chunk = chunk->next)
+       {
+           int     numPages = chunk->numPages;
+
+           nChunks++;
+           for (chunkRelIndex = 0; chunkRelIndex < numPages; chunkRelIndex++)
+           {
+               nPages++;
+               fprintf(stderr, " %u:%u",
+                       chunk->pages[chunkRelIndex],
+                       chunk->bytes[chunkRelIndex]);
+           }
+       }
+       fprintf(stderr, "\n");
+       /* Cross-check local counters and list links */
+       if (nPages != fsmrel->numPages)
+           fprintf(stderr, "DumpFreeSpace: %d pages in rel, but numPages = %d\n",
+                   nPages, fsmrel->numPages);
+       if (nChunks != fsmrel->numChunks)
+           fprintf(stderr, "DumpFreeSpace: %d chunks in rel, but numChunks = %d\n",
+                   nChunks, fsmrel->numChunks);
+       if (prevrel != fsmrel->priorRel)
+           fprintf(stderr, "DumpFreeSpace: broken list links\n");
+       prevrel = fsmrel;
+   }
+   if (prevrel != FreeSpaceMap->relListTail)
+       fprintf(stderr, "DumpFreeSpace: broken list links\n");
+   /* Cross-check global counters */
+   if (relNum != FreeSpaceMap->numRels)
+       fprintf(stderr, "DumpFreeSpace: %d rels in list, but numRels = %d\n",
+               relNum, FreeSpaceMap->numRels);
+   nChunks = 0;
+   for (chunk = FreeSpaceMap->freeChunks; chunk; chunk = chunk->next)
+       nChunks++;
+   if (nChunks != FreeSpaceMap->numFreeChunks)
+       fprintf(stderr, "DumpFreeSpace: %d chunks in list, but numFreeChunks = %d\n",
+               nChunks, FreeSpaceMap->numFreeChunks);
 }
 
 #endif  /* FREESPACE_DEBUG */
index 9e33c54d52e3d6f9ca421cff0ed6ef746cd2e927..1cc078c8f6a15e92a0f470966be75a54b3a3cd21 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/storage/smgr/smgr.c,v 1.51 2001/06/29 21:08:24 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/storage/smgr/smgr.c,v 1.52 2001/07/02 20:50:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -469,11 +469,13 @@ smgrtruncate(int16 which, Relation reln, BlockNumber nblocks)
    if (smgrsw[which].smgr_truncate)
    {
        /*
-        * Tell the free space map to forget this relation, so that it
-        * stops caching info about the deleted blocks.  XXX perhaps
-        * tell it to forget only info about blocks beyond nblocks?
+        * Tell the free space map to forget anything it may have stored
+        * for the about-to-be-deleted blocks.  We want to be sure it won't
+        * return bogus block numbers later on.
         */
-       FreeSpaceMapForgetRel(&reln->rd_node);
+       MultiRecordFreeSpace(&reln->rd_node,
+                            nblocks, MaxBlockNumber,
+                            0, NULL, NULL);
 
        newblks = (*(smgrsw[which].smgr_truncate)) (reln, nblocks);
        if (newblks == InvalidBlockNumber)
index 870954dd39e0d670fb123185c8bd0b46eab2c59d..d13f3c8f06e560a21572684df251a6bd7c11be9b 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: block.h,v 1.12 2001/01/24 19:43:27 momjian Exp $
+ * $Id: block.h,v 1.13 2001/07/02 20:50:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -32,6 +32,8 @@ typedef uint32 BlockNumber;
 
 #define InvalidBlockNumber     ((BlockNumber) 0xFFFFFFFF)
 
+#define MaxBlockNumber         ((BlockNumber) 0xFFFFFFFE)
+
 /*
  * BlockId:
  *
index 083accccab8724246bed6421dee7bfa85cecbcd8..0f11dd02f93dd2cac49c96dbe2e7ba9776915742 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: freespace.h,v 1.1 2001/06/27 23:31:39 tgl Exp $
+ * $Id: freespace.h,v 1.2 2001/07/02 20:50:46 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -45,6 +45,7 @@ extern void MultiRecordFreeSpace(RelFileNode *rel,
                                 BlockNumber *pages,
                                 Size *spaceAvail);
 extern void FreeSpaceMapForgetRel(RelFileNode *rel);
+extern void FreeSpaceMapForgetDatabase(Oid dbid);
 
 #ifdef FREESPACE_DEBUG
 extern void DumpFreeSpace(void);