* POSTGRES free space map for quickly finding free space in relations
*
*
- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/freespace/freespace.c,v 1.24 2003/10/29 17:36:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/freespace/freespace.c,v 1.49 2005/10/15 02:49:25 momjian Exp $
*
*
* NOTES:
/* Header for whole map */
struct FSMHeader
{
- HTAB *relHash; /* hashtable of FSMRelation entries */
FSMRelation *usageList; /* FSMRelations in usage-recency order */
FSMRelation *usageListTail; /* tail of usage-recency list */
FSMRelation *firstRel; /* FSMRelations in arena storage order */
int MaxFSMPages;
static FSMHeader *FreeSpaceMap; /* points to FSMHeader in shared memory */
+static HTAB *FreeSpaceMapRelHash; /* points to (what used to be)
+ * FSMHeader->relHash */
+static void CheckFreeSpaceMapStatistics(int elevel, int numRels,
+ double needed);
static FSMRelation *lookup_fsm_rel(RelFileNode *rel);
static FSMRelation *create_fsm_rel(RelFileNode *rel);
static void delete_fsm_rel(FSMRelation *fsmrel);
{
HASHCTL info;
int nchunks;
+ bool found;
/* Create table header */
- FreeSpaceMap = (FSMHeader *) ShmemAlloc(sizeof(FSMHeader));
+ FreeSpaceMap = (FSMHeader *) ShmemInitStruct("Free Space Map Header",
+ sizeof(FSMHeader),
+ &found);
if (FreeSpaceMap == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("insufficient shared memory for free space map")));
- MemSet(FreeSpaceMap, 0, sizeof(FSMHeader));
+ errmsg("insufficient shared memory for free space map")));
+ if (!found)
+ MemSet(FreeSpaceMap, 0, sizeof(FSMHeader));
/* Create hashtable for FSMRelations */
info.keysize = sizeof(RelFileNode);
info.entrysize = sizeof(FSMRelation);
info.hash = tag_hash;
- FreeSpaceMap->relHash = ShmemInitHash("Free Space Map Hash",
- MaxFSMRelations / 10,
- MaxFSMRelations,
- &info,
- (HASH_ELEM | HASH_FUNCTION));
+ FreeSpaceMapRelHash = ShmemInitHash("Free Space Map Hash",
+ MaxFSMRelations + 1,
+ MaxFSMRelations + 1,
+ &info,
+ (HASH_ELEM | HASH_FUNCTION));
- if (!FreeSpaceMap->relHash)
+ if (!FreeSpaceMapRelHash)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("insufficient shared memory for free space map")));
+ errmsg("insufficient shared memory for free space map")));
+
+ if (found)
+ return;
+
/* Allocate page-storage arena */
nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1;
if (nchunks <= MaxFSMRelations)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("max_fsm_pages must exceed max_fsm_relations * %d",
- CHUNKPAGES)));
+ errmsg("max_fsm_pages must exceed max_fsm_relations * %d",
+ CHUNKPAGES)));
- FreeSpaceMap->arena = (char *) ShmemAlloc(nchunks * CHUNKBYTES);
+ FreeSpaceMap->arena = (char *) ShmemAlloc((Size) nchunks * CHUNKBYTES);
if (FreeSpaceMap->arena == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("insufficient shared memory for free space map")));
+ errmsg("insufficient shared memory for free space map")));
FreeSpaceMap->totalChunks = nchunks;
FreeSpaceMap->usedChunks = 0;
/*
* Estimate amount of shmem space needed for FSM.
*/
-int
+Size
FreeSpaceShmemSize(void)
{
- int size;
+ Size size;
int nchunks;
/* table header */
size = MAXALIGN(sizeof(FSMHeader));
/* hash table, including the FSMRelation objects */
- size += hash_estimate_size(MaxFSMRelations, sizeof(FSMRelation));
+ size = add_size(size, hash_estimate_size(MaxFSMRelations + 1,
+ sizeof(FSMRelation)));
/* page-storage arena */
nchunks = (MaxFSMPages - 1) / CHUNKPAGES + 1;
-
- if (nchunks >= (INT_MAX / CHUNKBYTES))
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("max_fsm_pages is too large")));
-
- size += MAXALIGN(nchunks * CHUNKBYTES);
+ size = add_size(size, mul_size(nchunks, CHUNKBYTES));
return size;
}
fsmrel = create_fsm_rel(rel);
/*
- * Update the moving average of space requests. This code implements
- * an exponential moving average with an equivalent period of about 63
- * requests. Ignore silly requests, however, to ensure that the
- * average stays sane.
+ * Update the moving average of space requests. This code implements an
+ * exponential moving average with an equivalent period of about 63
+ * requests. Ignore silly requests, however, to ensure that the average
+ * stays sane.
*/
if (spaceNeeded > 0 && spaceNeeded < BLCKSZ)
{
LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
/*
- * Note we don't record info about a relation unless there's already
- * an FSM entry for it, implying someone has done GetPageWithFreeSpace
- * for it. Inactive rels thus will not clutter the map simply by
- * being vacuumed.
+ * Note we don't record info about a relation unless there's already an
+ * FSM entry for it, implying someone has done GetPageWithFreeSpace for
+ * it. Inactive rels thus will not clutter the map simply by being
+ * vacuumed.
*/
fsmrel = lookup_fsm_rel(rel);
if (fsmrel)
curAllocPages = curAlloc * CHUNKPAGES;
/*
- * If the data fits in our current allocation, just copy it;
- * otherwise must compress.
+ * If the data fits in our current allocation, just copy it; otherwise
+ * must compress.
*/
newLocation = (FSMPageData *)
(FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES);
LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
/*
- * Note we don't record info about a relation unless there's already
- * an FSM entry for it, implying someone has done GetFreeIndexPage for
- * it. Inactive rels thus will not clutter the map simply by being
- * vacuumed.
+ * Note we don't record info about a relation unless there's already an
+ * FSM entry for it, implying someone has done GetFreeIndexPage for it.
+ * Inactive rels thus will not clutter the map simply by being vacuumed.
*/
fsmrel = lookup_fsm_rel(rel);
if (fsmrel)
curAllocPages = curAlloc * INDEXCHUNKPAGES;
/*
- * If the data fits in our current allocation, just copy it;
- * otherwise must compress. But compression is easy: we merely
- * forget extra pages.
+ * If the data fits in our current allocation, just copy it; otherwise
+ * must compress. But compression is easy: we merely forget extra
+ * pages.
*/
newLocation = (IndexFSMPageData *)
(FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES);
*
* 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)
for (fsmrel = FreeSpaceMap->usageList; fsmrel; fsmrel = nextrel)
{
nextrel = fsmrel->nextUsage; /* in case we delete it */
- if (fsmrel->key.tblNode == dbid)
+ if (fsmrel->key.dbNode == dbid)
delete_fsm_rel(fsmrel);
}
LWLockRelease(FreeSpaceLock);
fsmrel != NULL;
fsmrel = fsmrel->nextPhysical)
storedPages += fsmrel->storedPages;
+
/* Copy other stats before dropping lock */
numRels = FreeSpaceMap->numRels;
sumRequests = FreeSpaceMap->sumRequests;
needed = (sumRequests + numRels) * CHUNKPAGES;
ereport(elevel,
- (errmsg("free space map: %d relations, %d pages stored; %.0f total pages needed",
- numRels, storedPages, needed),
- errdetail("Allocated FSM size: %d relations + %d pages = %.0f kB shared memory.",
- MaxFSMRelations, MaxFSMPages,
- (double) FreeSpaceShmemSize() / 1024.0)));
+ (errmsg("free space map contains %d pages in %d relations",
+ storedPages, numRels),
+ errdetail("A total of %.0f page slots are in use (including overhead).\n"
+ "%.0f page slots are required to track all free space.\n"
+ "Current limits are: %d page slots, %d relations, using %.0f KB.",
+ Min(needed, MaxFSMPages),
+ needed, MaxFSMPages, MaxFSMRelations,
+ (double) FreeSpaceShmemSize() / 1024.0)));
+
+ CheckFreeSpaceMapStatistics(NOTICE, numRels, needed);
+ /* Print to server logs too because is deals with a config variable. */
+ CheckFreeSpaceMapStatistics(LOG, numRels, needed);
+}
+
+static void
+CheckFreeSpaceMapStatistics(int elevel, int numRels, double needed)
+{
+ if (numRels == MaxFSMRelations)
+ ereport(elevel,
+ (errmsg("max_fsm_relations(%d) equals the number of relations checked",
+ MaxFSMRelations),
+ errhint("You have >= %d relations.\n"
+ "Consider increasing the configuration parameter \"max_fsm_relations\".",
+ numRels)));
+ else if (needed > MaxFSMPages)
+ ereport(elevel,
+ (errmsg("the number of page slots needed (%.0f) exceeds max_fsm_pages (%d)",
+ needed, MaxFSMPages),
+ errhint("Consider increasing the configuration parameter \"max_fsm_pages\"\n"
+ "to a value over %.0f.", needed)));
}
/*
* forma --- if anyone else is still accessing FSM, there's a problem.
*/
void
-DumpFreeSpaceMap(void)
+DumpFreeSpaceMap(int code, Datum arg)
{
FILE *fp;
- char cachefilename[MAXPGPATH];
FsmCacheFileHeader header;
FSMRelation *fsmrel;
/* Try to create file */
- snprintf(cachefilename, sizeof(cachefilename), "%s/%s",
- DataDir, FSM_CACHE_FILENAME);
-
- unlink(cachefilename); /* in case it exists w/wrong permissions */
+ unlink(FSM_CACHE_FILENAME); /* in case it exists w/wrong permissions */
- fp = AllocateFile(cachefilename, PG_BINARY_W);
+ fp = AllocateFile(FSM_CACHE_FILENAME, PG_BINARY_W);
if (fp == NULL)
{
- elog(LOG, "could not write \"%s\": %m", cachefilename);
+ elog(LOG, "could not write \"%s\": %m", FSM_CACHE_FILENAME);
return;
}
/* Clean up */
LWLockRelease(FreeSpaceLock);
- FreeFile(fp);
+ if (FreeFile(fp))
+ {
+ elog(LOG, "could not write \"%s\": %m", FSM_CACHE_FILENAME);
+ /* Remove busted cache file */
+ unlink(FSM_CACHE_FILENAME);
+ }
return;
write_failed:
- elog(LOG, "could not write \"%s\": %m", cachefilename);
+ elog(LOG, "could not write \"%s\": %m", FSM_CACHE_FILENAME);
/* Clean up */
LWLockRelease(FreeSpaceLock);
FreeFile(fp);
/* Remove busted cache file */
- unlink(cachefilename);
+ unlink(FSM_CACHE_FILENAME);
}
/*
LoadFreeSpaceMap(void)
{
FILE *fp;
- char cachefilename[MAXPGPATH];
FsmCacheFileHeader header;
int relno;
/* Try to open file */
- snprintf(cachefilename, sizeof(cachefilename), "%s/%s",
- DataDir, FSM_CACHE_FILENAME);
-
- fp = AllocateFile(cachefilename, PG_BINARY_R);
+ fp = AllocateFile(FSM_CACHE_FILENAME, PG_BINARY_R);
if (fp == NULL)
{
if (errno != ENOENT)
- elog(LOG, "could not read \"%s\": %m", cachefilename);
+ elog(LOG, "could not read \"%s\": %m", FSM_CACHE_FILENAME);
return;
}
header.version != FSM_CACHE_VERSION ||
header.numRels < 0)
{
- elog(LOG, "bogus file header in \"%s\"", cachefilename);
+ elog(LOG, "bogus file header in \"%s\"", FSM_CACHE_FILENAME);
goto read_failed;
}
relheader.lastPageCount < 0 ||
relheader.storedPages < 0)
{
- elog(LOG, "bogus rel header in \"%s\"", cachefilename);
+ elog(LOG, "bogus rel header in \"%s\"", FSM_CACHE_FILENAME);
goto read_failed;
}
len = nPages * sizeof(IndexFSMPageData);
else
len = nPages * sizeof(FSMPageData);
- data = (char *) palloc(len + 1); /* +1 to avoid palloc(0) */
+ data = (char *) palloc(len);
if (fread(data, 1, len, fp) != len)
{
- elog(LOG, "premature EOF in \"%s\"", cachefilename);
+ elog(LOG, "premature EOF in \"%s\"", FSM_CACHE_FILENAME);
pfree(data);
goto read_failed;
}
/*
- * Okay, create the FSM entry and insert data into it. Since the
- * rels were stored in reverse usage order, at the end of the loop
- * they will be correctly usage-ordered in memory; and if
- * MaxFSMRelations is less than it used to be, we will correctly
- * drop the least recently used ones.
+ * Okay, create the FSM entry and insert data into it. Since the rels
+ * were stored in reverse usage order, at the end of the loop they
+ * will be correctly usage-ordered in memory; and if MaxFSMRelations
+ * is less than it used to be, we will correctly drop the least
+ * recently used ones.
*/
fsmrel = create_fsm_rel(&relheader.key);
fsmrel->avgRequest = relheader.avgRequest;
/*
* If the data fits in our current allocation, just copy it;
- * otherwise must compress. But compression is easy: we
- * merely forget extra pages.
+ * otherwise must compress. But compression is easy: we merely
+ * forget extra pages.
*/
newLocation = (IndexFSMPageData *)
(FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES);
FreeFile(fp);
/* Remove cache file before it can become stale; see notes above */
- unlink(cachefilename);
+ unlink(FSM_CACHE_FILENAME);
}
{
FSMRelation *fsmrel;
- fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+ fsmrel = (FSMRelation *) hash_search(FreeSpaceMapRelHash,
(void *) rel,
HASH_FIND,
NULL);
FSMRelation *fsmrel;
bool found;
- fsmrel = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+ fsmrel = (FSMRelation *) hash_search(FreeSpaceMapRelHash,
(void *) rel,
HASH_ENTER,
&found);
- if (!fsmrel)
- ereport(ERROR,
- (errcode(ERRCODE_OUT_OF_MEMORY),
- errmsg("out of shared memory")));
if (!found)
{
unlink_fsm_rel_usage(fsmrel);
unlink_fsm_rel_storage(fsmrel);
FreeSpaceMap->numRels--;
- result = (FSMRelation *) hash_search(FreeSpaceMap->relHash,
+ result = (FSMRelation *) hash_search(FreeSpaceMapRelHash,
(void *) &(fsmrel->key),
HASH_REMOVE,
NULL);
myAlloc = fsm_calc_target_allocation(myRequest);
/*
- * Need to reallocate space if (a) my target allocation is more than
- * my current allocation, AND (b) my actual immediate need
- * (myRequest+1 chunks) is more than my current allocation. Otherwise
- * just store the new data in-place.
+ * Need to reallocate space if (a) my target allocation is more than my
+ * current allocation, AND (b) my actual immediate need (myRequest+1
+ * chunks) is more than my current allocation. Otherwise just store the
+ * new data in-place.
*/
curAlloc = fsm_current_allocation(fsmrel);
if (myAlloc > curAlloc && (myRequest + 1) > curAlloc && nPages > 0)
if (spaceAvail >= spaceNeeded)
{
/*
- * Found what we want --- adjust the entry, and update
- * nextPage.
+ * Found what we want --- adjust the entry, and update nextPage.
*/
FSMPageSetSpace(page, spaceAvail - spaceNeeded);
fsmrel->nextPage = pageIndex + 1;
BlockNumber result;
/*
- * If isIndex isn't set, it could be that RecordIndexFreeSpace() has
- * never yet been called on this relation, and we're still looking at
- * the default setting from create_fsm_rel(). If so, just act as
- * though there's no space.
+ * If isIndex isn't set, it could be that RecordIndexFreeSpace() has never
+ * yet been called on this relation, and we're still looking at the
+ * default setting from create_fsm_rel(). If so, just act as though
+ * there's no space.
*/
if (!fsmrel->isIndex)
{
}
/*
- * For indexes, there's no need for the nextPage state variable; we
- * just remove and return the first available page. (We could save
- * cycles here by returning the last page, but it seems better to
- * encourage re-use of lower-numbered pages.)
+ * For indexes, there's no need for the nextPage state variable; we just
+ * remove and return the first available page. (We could save cycles here
+ * by returning the last page, but it seems better to encourage re-use of
+ * lower-numbered pages.)
*/
if (fsmrel->storedPages <= 0)
return InvalidBlockNumber; /* no pages available */
else
{
/*
- * No existing entry; ignore the call. We used to add the page to
- * the FSM --- but in practice, if the page hasn't got enough
- * space to satisfy the caller who's kicking it back to us, then
- * it's probably uninteresting to everyone else as well.
+ * No existing entry; ignore the call. We used to add the page to the
+ * FSM --- but in practice, if the page hasn't got enough space to
+ * satisfy the caller who's kicking it back to us, then it's probably
+ * uninteresting to everyone else as well.
*/
}
}
compact_fsm_storage(void)
{
int nextChunkIndex = 0;
+ bool did_push = false;
FSMRelation *fsmrel;
for (fsmrel = FreeSpaceMap->firstRel;
newAllocPages = newAlloc * INDEXCHUNKPAGES;
else
newAllocPages = newAlloc * CHUNKPAGES;
- newChunkIndex = nextChunkIndex;
- nextChunkIndex += newAlloc;
/*
* Determine current size, current and new locations
*/
curChunks = fsm_current_chunks(fsmrel);
oldChunkIndex = fsmrel->firstChunk;
- newLocation = FreeSpaceMap->arena + newChunkIndex * CHUNKBYTES;
oldLocation = FreeSpaceMap->arena + oldChunkIndex * CHUNKBYTES;
+ newChunkIndex = nextChunkIndex;
+ newLocation = FreeSpaceMap->arena + newChunkIndex * CHUNKBYTES;
/*
* It's possible that we have to move data down, not up, if the
* allocations of previous rels expanded. This normally means that
- * our allocation expanded too (or at least got no worse), and
- * ditto for later rels. So there should be room to move all our
- * data down without dropping any --- but we might have to push down
- * following rels to acquire the room. We don't want to do the push
- * more than once, so pack everything against the end of the arena
- * if so.
+ * our allocation expanded too (or at least got no worse), and ditto
+ * for later rels. So there should be room to move all our data down
+ * without dropping any --- but we might have to push down following
+ * rels to acquire the room. We don't want to do the push more than
+ * once, so pack everything against the end of the arena if so.
*
- * In corner cases where roundoff has affected our allocation, it's
- * possible that we have to move down and compress our data too.
- * Since this case is extremely infrequent, we do not try to be smart
- * about it --- we just drop pages from the end of the rel's data.
+ * In corner cases where we are on the short end of a roundoff choice
+ * that we were formerly on the long end of, it's possible that we
+ * have to move down and compress our data too. In fact, even after
+ * pushing down the following rels, there might not be as much space
+ * as we computed for this rel above --- that would imply that some
+ * following rel(s) are also on the losing end of roundoff choices. We
+ * could handle this fairly by doing the per-rel compactions
+ * out-of-order, but that seems like way too much complexity to deal
+ * with a very infrequent corner case. Instead, we simply drop pages
+ * from the end of the current rel's data until it fits.
*/
if (newChunkIndex > oldChunkIndex)
{
fsmrel->storedPages = newAllocPages;
curChunks = fsm_current_chunks(fsmrel);
}
+ /* is there enough space? */
if (fsmrel->nextPhysical != NULL)
limitChunkIndex = fsmrel->nextPhysical->firstChunk;
else
limitChunkIndex = FreeSpaceMap->totalChunks;
if (newChunkIndex + curChunks > limitChunkIndex)
{
- /* need to push down additional rels */
- push_fsm_rels_after(fsmrel);
- /* recheck for safety */
+ /* not enough space, push down following rels */
+ if (!did_push)
+ {
+ push_fsm_rels_after(fsmrel);
+ did_push = true;
+ }
+ /* now is there enough space? */
if (fsmrel->nextPhysical != NULL)
limitChunkIndex = fsmrel->nextPhysical->firstChunk;
else
limitChunkIndex = FreeSpaceMap->totalChunks;
if (newChunkIndex + curChunks > limitChunkIndex)
- elog(PANIC, "insufficient room in FSM");
+ {
+ /* uh-oh, forcibly cut the allocation to fit */
+ newAlloc = limitChunkIndex - newChunkIndex;
+
+ /*
+ * If newAlloc < 0 at this point, we are moving the rel's
+ * firstChunk into territory currently assigned to a later
+ * rel. This is okay so long as we do not copy any data.
+ * The rels will be back in nondecreasing firstChunk order
+ * at completion of the compaction pass.
+ */
+ if (newAlloc < 0)
+ newAlloc = 0;
+ if (fsmrel->isIndex)
+ newAllocPages = newAlloc * INDEXCHUNKPAGES;
+ else
+ newAllocPages = newAlloc * CHUNKPAGES;
+ fsmrel->storedPages = newAllocPages;
+ curChunks = fsm_current_chunks(fsmrel);
+ }
}
memmove(newLocation, oldLocation, curChunks * CHUNKBYTES);
}
else if (newAllocPages < fsmrel->storedPages)
{
/*
- * Need to compress the page data. For an index,
- * "compression" just means dropping excess pages; otherwise
- * we try to keep the ones with the most space.
+ * Need to compress the page data. For an index, "compression"
+ * just means dropping excess pages; otherwise we try to keep the
+ * ones with the most space.
*/
if (fsmrel->isIndex)
{
memmove(newLocation, oldLocation, curChunks * CHUNKBYTES);
}
fsmrel->firstChunk = newChunkIndex;
+ nextChunkIndex += newAlloc;
}
Assert(nextChunkIndex <= FreeSpaceMap->totalChunks);
FreeSpaceMap->usedChunks = nextChunkIndex;
oldChunkIndex = fsmrel->firstChunk;
if (newChunkIndex < oldChunkIndex)
{
- /* trouble... */
- elog(PANIC, "insufficient room in FSM");
+ /* we're pushing down, how can it move up? */
+ elog(PANIC, "inconsistent entry sizes in FSM");
}
else if (newChunkIndex > oldChunkIndex)
{
{
int chunkCount;
+ /* Make sure storedPages==0 produces right answer */
+ if (fsmrel->storedPages <= 0)
+ return 0;
/* Convert page count to chunk count */
if (fsmrel->isIndex)
chunkCount = (fsmrel->storedPages - 1) / INDEXCHUNKPAGES + 1;
else
chunkCount = (fsmrel->storedPages - 1) / CHUNKPAGES + 1;
- /* Make sure storedPages==0 produces right answer */
- if (chunkCount < 0)
- chunkCount = 0;
return chunkCount;
}
for (fsmrel = FreeSpaceMap->usageList; fsmrel; fsmrel = fsmrel->nextUsage)
{
relNum++;
- fprintf(stderr, "Map %d: rel %u/%u isIndex %d avgRequest %u lastPageCount %d nextPage %d\nMap= ",
- relNum, fsmrel->key.tblNode, fsmrel->key.relNode,
+ fprintf(stderr, "Map %d: rel %u/%u/%u isIndex %d avgRequest %u lastPageCount %d nextPage %d\nMap= ",
+ relNum,
+ fsmrel->key.spcNode, fsmrel->key.dbNode, fsmrel->key.relNode,
(int) fsmrel->isIndex, fsmrel->avgRequest,
fsmrel->lastPageCount, fsmrel->nextPage);
if (fsmrel->isIndex)