From a844cd4392bcea4dd3c04ff501675fa9534fc955 Mon Sep 17 00:00:00 2001 From: Kirk Jamison Date: Tue, 11 Jun 2019 01:41:43 +0000 Subject: [PATCH] Speedup truncates of relation forks Whenever we truncate relations, it scans the shared buffers thrice (one per fork) which can be time-consuming. This patch improves the performance of relation truncates by initially marking the pages-to-be-truncated of relation forks, then simultaneously truncating them, resulting to an improved performance in VACUUM, autovacuum operations and their recovery performance. --- contrib/pg_visibility/pg_visibility.c | 17 +++- src/backend/access/heap/visibilitymap.c | 31 +++----- src/backend/catalog/storage.c | 126 ++++++++++++++++++++++++++---- src/backend/storage/buffer/bufmgr.c | 31 +++++--- src/backend/storage/freespace/freespace.c | 38 +++------ src/backend/storage/smgr/smgr.c | 24 +++--- src/include/access/visibilitymap.h | 2 +- src/include/storage/bufmgr.h | 4 +- src/include/storage/freespace.h | 2 +- src/include/storage/smgr.h | 7 +- 10 files changed, 193 insertions(+), 89 deletions(-) diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c index 1372bb6..2499415 100644 --- a/contrib/pg_visibility/pg_visibility.c +++ b/contrib/pg_visibility/pg_visibility.c @@ -383,6 +383,10 @@ pg_truncate_visibility_map(PG_FUNCTION_ARGS) { Oid relid = PG_GETARG_OID(0); Relation rel; + ForkNumber forks[MAX_FORKNUM]; + BlockNumber blocks[MAX_FORKNUM]; + BlockNumber newnblocks = InvalidBlockNumber; + int nforks = 0; rel = relation_open(relid, AccessExclusiveLock); @@ -392,7 +396,18 @@ pg_truncate_visibility_map(PG_FUNCTION_ARGS) RelationOpenSmgr(rel); rel->rd_smgr->smgr_vm_nblocks = InvalidBlockNumber; - visibilitymap_truncate(rel, 0); + blocks[nforks] = visibilitymap_mark_truncate(rel, 0); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = VISIBILITYMAP_FORKNUM; + newnblocks = blocks[nforks]; + nforks++; + } + smgrtruncate(rel->rd_smgr, forks, blocks, nforks); + + /* Update the local smgr_vm_nblocks setting */ + if (rel->rd_smgr) + rel->rd_smgr->smgr_vm_nblocks = newnblocks; if (RelationNeedsWAL(rel)) { diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index 64dfe06..2f1379c 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -17,7 +17,7 @@ * visibilitymap_set - set a bit in a previously pinned page * visibilitymap_get_status - get status of bits * visibilitymap_count - count number of bits set in visibility map - * visibilitymap_truncate - truncate the visibility map + * visibilitymap_mark_truncate - mark the about-to-be-truncated VM * * NOTES * @@ -430,7 +430,10 @@ visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_fro } /* - * visibilitymap_truncate - truncate the visibility map + * visibilitymap_mark_truncate - mark the about-to-be-truncated VM + * + * Formerly, this function truncates VM relation forks. Instead, this just + * marks the dirty buffers. * * The caller must hold AccessExclusiveLock on the relation, to ensure that * other backends receive the smgr invalidation event that this function sends @@ -438,8 +441,8 @@ visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_fro * * nheapblocks is the new size of the heap. */ -void -visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) +BlockNumber +visibilitymap_mark_truncate(Relation rel, BlockNumber nheapblocks) { BlockNumber newnblocks; @@ -459,7 +462,7 @@ visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) * nothing to truncate. */ if (!smgrexists(rel->rd_smgr, VISIBILITYMAP_FORKNUM)) - return; + return InvalidBlockNumber; /* * Unless the new size is exactly at a visibility map page boundary, the @@ -480,7 +483,7 @@ visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) if (!BufferIsValid(mapBuffer)) { /* nothing to do, the file was already smaller */ - return; + return InvalidBlockNumber; } page = BufferGetPage(mapBuffer); @@ -528,20 +531,10 @@ visibilitymap_truncate(Relation rel, BlockNumber nheapblocks) if (smgrnblocks(rel->rd_smgr, VISIBILITYMAP_FORKNUM) <= newnblocks) { /* nothing to do, the file was already smaller than requested size */ - return; + return InvalidBlockNumber; } - - /* Truncate the unused VM pages, and send smgr inval message */ - smgrtruncate(rel->rd_smgr, VISIBILITYMAP_FORKNUM, newnblocks); - - /* - * We might as well update the local smgr_vm_nblocks setting. smgrtruncate - * sent an smgr cache inval message, which will cause other backends to - * invalidate their copy of smgr_vm_nblocks, and this one too at the next - * command boundary. But this ensures it isn't outright wrong until then. - */ - if (rel->rd_smgr) - rel->rd_smgr->smgr_vm_nblocks = newnblocks; + else + return newnblocks; } /* diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 3cc886f..3151632 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -231,6 +231,11 @@ RelationTruncate(Relation rel, BlockNumber nblocks) { bool fsm; bool vm; + ForkNumber forks[MAX_FORKNUM]; + BlockNumber blocks[MAX_FORKNUM]; + BlockNumber new_nfsmblocks = InvalidBlockNumber; /* FSM blocks */ + BlockNumber newnblocks = InvalidBlockNumber; /* VM blocks */ + int nforks = 0; /* Open it at the smgr level if not already done */ RelationOpenSmgr(rel); @@ -242,15 +247,34 @@ RelationTruncate(Relation rel, BlockNumber nblocks) rel->rd_smgr->smgr_fsm_nblocks = InvalidBlockNumber; rel->rd_smgr->smgr_vm_nblocks = InvalidBlockNumber; - /* Truncate the FSM first if it exists */ + /* + * We used to truncate FSM and VM forks here. Now we only mark the + * dirty buffers of all forks about-to-be-truncated if they exist. + */ + fsm = smgrexists(rel->rd_smgr, FSM_FORKNUM); if (fsm) - FreeSpaceMapTruncateRel(rel, nblocks); + { + blocks[nforks] = MarkFreeSpaceMapTruncateRel(rel, nblocks); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = FSM_FORKNUM; + new_nfsmblocks= blocks[nforks]; /* FSM blocks */ + nforks++; + } + } - /* Truncate the visibility map too if it exists. */ vm = smgrexists(rel->rd_smgr, VISIBILITYMAP_FORKNUM); if (vm) - visibilitymap_truncate(rel, nblocks); + { + blocks[nforks] = visibilitymap_mark_truncate(rel, nblocks); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = VISIBILITYMAP_FORKNUM; + newnblocks = blocks[nforks]; /* VM blocks */ + nforks++; + } + } /* * We WAL-log the truncation before actually truncating, which means @@ -263,9 +287,7 @@ RelationTruncate(Relation rel, BlockNumber nblocks) */ if (RelationNeedsWAL(rel)) { - /* - * Make an XLOG entry reporting the file truncation. - */ + /* Make an XLOG entry reporting the file truncation */ XLogRecPtr lsn; xl_smgr_truncate xlrec; @@ -290,8 +312,33 @@ RelationTruncate(Relation rel, BlockNumber nblocks) XLogFlush(lsn); } - /* Do the real work */ - smgrtruncate(rel->rd_smgr, MAIN_FORKNUM, nblocks); + /* Mark the MAIN fork */ + forks[nforks] = MAIN_FORKNUM; + blocks[nforks] = nblocks; + nforks++; + + /* Truncate relation forks simultaneously */ + smgrtruncate(rel->rd_smgr, forks, blocks, nforks); + + /* + * We might as well update the local smgr_fsm_nblocks and smgr_vm_nblocks + * setting. smgrtruncate sent an smgr cache inval message, which will cause + * other backends to invalidate their copy of smgr_fsm_nblocks and + * smgr_vm_nblocks, and this one too at the next command boundary. But this + * ensures it isn't outright wrong until then. + */ + if (rel->rd_smgr) + { + rel->rd_smgr->smgr_fsm_nblocks = new_nfsmblocks; + rel->rd_smgr->smgr_vm_nblocks = newnblocks; + } + + /* + * Update upper-level FSM pages to account for the truncation. This is + * important because the just-truncated pages were likely marked as + * all-free, and would be preferentially selected. + */ + FreeSpaceMapVacuumRange(rel, new_nfsmblocks, InvalidBlockNumber); } /* @@ -588,6 +635,14 @@ smgr_redo(XLogReaderState *record) xl_smgr_truncate *xlrec = (xl_smgr_truncate *) XLogRecGetData(record); SMgrRelation reln; Relation rel; + ForkNumber forks[MAX_FORKNUM]; + BlockNumber blocks[MAX_FORKNUM]; + BlockNumber new_nfsmblocks = InvalidBlockNumber; + BlockNumber newnblocks = InvalidBlockNumber; + int nforks = 0; + bool fsm_fork = false; + bool main_fork = false; + bool vm_fork = false; reln = smgropen(xlrec->rnode, InvalidBackendId); @@ -616,23 +671,62 @@ smgr_redo(XLogReaderState *record) */ XLogFlush(lsn); + /* + * To speedup recovery, we mark the about-to-be-truncated blocks of + * relation forks first, then truncate those simultaneously later. + */ if ((xlrec->flags & SMGR_TRUNCATE_HEAP) != 0) { - smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno); - - /* Also tell xlogutils.c about it */ - XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno); + forks[nforks] = MAIN_FORKNUM; + blocks[nforks] = xlrec->blkno; + nforks++; + main_fork = true; } - /* Truncate FSM and VM too */ rel = CreateFakeRelcacheEntry(xlrec->rnode); if ((xlrec->flags & SMGR_TRUNCATE_FSM) != 0 && smgrexists(reln, FSM_FORKNUM)) - FreeSpaceMapTruncateRel(rel, xlrec->blkno); + { + blocks[nforks] = MarkFreeSpaceMapTruncateRel(rel, xlrec->blkno); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = FSM_FORKNUM; + new_nfsmblocks= blocks[nforks]; + nforks++; + fsm_fork = true; + } + } if ((xlrec->flags & SMGR_TRUNCATE_VM) != 0 && smgrexists(reln, VISIBILITYMAP_FORKNUM)) - visibilitymap_truncate(rel, xlrec->blkno); + { + blocks[nforks] = visibilitymap_mark_truncate(rel, xlrec->blkno); + if (BlockNumberIsValid(blocks[nforks])) + { + forks[nforks] = VISIBILITYMAP_FORKNUM; + newnblocks = blocks[nforks]; + nforks++; + vm_fork = true; + } + } + + /* Truncate relation forks simultaneously */ + if (main_fork || fsm_fork || vm_fork) + smgrtruncate(reln, forks, blocks, nforks); + + /* Also tell xlogutils.c about it */ + if (main_fork) + XLogTruncateRelation(xlrec->rnode, MAIN_FORKNUM, xlrec->blkno); + + /* Update the local smgr_fsm_nblocks and smgr_vm_nblocks setting */ + if (rel->rd_smgr) + { + rel->rd_smgr->smgr_fsm_nblocks = new_nfsmblocks; + rel->rd_smgr->smgr_vm_nblocks = newnblocks; + } + + /* Update upper-level FSM pages to account for the truncation */ + FreeSpaceMapVacuumRange(rel, new_nfsmblocks, InvalidBlockNumber); FreeFakeRelcacheEntry(rel); } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7332e6b..123429c 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -2899,8 +2899,8 @@ BufferGetLSNAtomic(Buffer buffer) /* --------------------------------------------------------------------- * DropRelFileNodeBuffers * - * This function removes from the buffer pool all the pages of the - * specified relation fork that have block numbers >= firstDelBlock. + * This function simultaneously removes from the buffer pool all the + * pages of the relation forks that have block numbers >= firstDelBlock. * (In particular, with firstDelBlock = 0, all pages are removed.) * Dirty pages are simply dropped, without bothering to write them * out first. Therefore, this is NOT rollback-able, and so should be @@ -2923,8 +2923,8 @@ BufferGetLSNAtomic(Buffer buffer) * -------------------------------------------------------------------- */ void -DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum, - BlockNumber firstDelBlock) +DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum, + BlockNumber *firstDelBlock, int nforks) { int i; @@ -2932,7 +2932,11 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum, if (RelFileNodeBackendIsTemp(rnode)) { if (rnode.backend == MyBackendId) - DropRelFileNodeLocalBuffers(rnode.node, forkNum, firstDelBlock); + { + for (int j = 0; j < nforks; j++) + DropRelFileNodeLocalBuffers(rnode.node, forkNum[j], + firstDelBlock[j]); + } return; } @@ -2940,6 +2944,7 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum, { BufferDesc *bufHdr = GetBufferDescriptor(i); uint32 buf_state; + int k = 0; /* * We can make this a tad faster by prechecking the buffer tag before @@ -2961,11 +2966,17 @@ DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber forkNum, continue; buf_state = LockBufHdr(bufHdr); - if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) && - bufHdr->tag.forkNum == forkNum && - bufHdr->tag.blockNum >= firstDelBlock) - InvalidateBuffer(bufHdr); /* releases spinlock */ - else + for (k = 0; k < nforks; k++) + { + if (RelFileNodeEquals(bufHdr->tag.rnode, rnode.node) && + bufHdr->tag.forkNum == forkNum[k] && + bufHdr->tag.blockNum >= firstDelBlock[k]) + { + InvalidateBuffer(bufHdr); /* releases spinlock */ + break; + } + } + if (k >= nforks) UnlockBufHdr(bufHdr, buf_state); } } diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c index c17b3f4..708c7cb 100644 --- a/src/backend/storage/freespace/freespace.c +++ b/src/backend/storage/freespace/freespace.c @@ -247,7 +247,10 @@ GetRecordedFreeSpace(Relation rel, BlockNumber heapBlk) } /* - * FreeSpaceMapTruncateRel - adjust for truncation of a relation. + * MarkFreeSpaceMapTruncateRel - adjust for truncation of a relation. + * + * Formerly, this function truncates FSM relation forks. Instead, this just + * marks the dirty buffers and returns a block number. * * The caller must hold AccessExclusiveLock on the relation, to ensure that * other backends receive the smgr invalidation event that this function sends @@ -255,8 +258,8 @@ GetRecordedFreeSpace(Relation rel, BlockNumber heapBlk) * * nblocks is the new size of the heap. */ -void -FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks) +BlockNumber +MarkFreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks) { BlockNumber new_nfsmblocks; FSMAddress first_removed_address; @@ -270,7 +273,7 @@ FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks) * truncate. */ if (!smgrexists(rel->rd_smgr, FSM_FORKNUM)) - return; + return InvalidBlockNumber; /* Get the location in the FSM of the first removed heap block */ first_removed_address = fsm_get_location(nblocks, &first_removed_slot); @@ -285,7 +288,7 @@ FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks) { buf = fsm_readbuf(rel, first_removed_address, false); if (!BufferIsValid(buf)) - return; /* nothing to do; the FSM was already smaller */ + return InvalidBlockNumber; /* nothing to do; the FSM was already smaller */ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); /* NO EREPORT(ERROR) from here till changes are logged */ @@ -310,33 +313,16 @@ FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks) UnlockReleaseBuffer(buf); new_nfsmblocks = fsm_logical_to_physical(first_removed_address) + 1; + return new_nfsmblocks; } else { new_nfsmblocks = fsm_logical_to_physical(first_removed_address); if (smgrnblocks(rel->rd_smgr, FSM_FORKNUM) <= new_nfsmblocks) - return; /* nothing to do; the FSM was already smaller */ + return InvalidBlockNumber; /* nothing to do; the FSM was already smaller */ + else + return new_nfsmblocks; } - - /* Truncate the unused FSM pages, and send smgr inval message */ - smgrtruncate(rel->rd_smgr, FSM_FORKNUM, new_nfsmblocks); - - /* - * We might as well update the local smgr_fsm_nblocks setting. - * smgrtruncate sent an smgr cache inval message, which will cause other - * backends to invalidate their copy of smgr_fsm_nblocks, and this one too - * at the next command boundary. But this ensures it isn't outright wrong - * until then. - */ - if (rel->rd_smgr) - rel->rd_smgr->smgr_fsm_nblocks = new_nfsmblocks; - - /* - * Update upper-level FSM pages to account for the truncation. This is - * important because the just-truncated pages were likely marked as - * all-free, and would be preferentially selected. - */ - FreeSpaceMapVacuumRange(rel, nblocks, InvalidBlockNumber); } /* diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c index dba8c39..b37560e 100644 --- a/src/backend/storage/smgr/smgr.c +++ b/src/backend/storage/smgr/smgr.c @@ -508,19 +508,21 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo) * already. */ void -smgrdounlinkfork(SMgrRelation reln, ForkNumber forknum, bool isRedo) +smgrdounlinkfork(SMgrRelation reln, ForkNumber *forknum, bool isRedo, int nforks) { RelFileNodeBackend rnode = reln->smgr_rnode; int which = reln->smgr_which; + int i; /* Close the fork at smgr level */ - smgrsw[which].smgr_close(reln, forknum); + for (i = 0; i < nforks; i++) + smgrsw[which].smgr_close(reln, forknum[i]); /* * Get rid of any remaining buffers for the fork. bufmgr will just drop * them without bothering to write the contents. */ - DropRelFileNodeBuffers(rnode, forknum, 0); + DropRelFileNodeBuffers(rnode, forknum, 0, nforks); /* * It'd be nice to tell the stats collector to forget it immediately, too. @@ -546,7 +548,8 @@ smgrdounlinkfork(SMgrRelation reln, ForkNumber forknum, bool isRedo) * ERROR, because we've already decided to commit or abort the current * xact. */ - smgrsw[which].smgr_unlink(rnode, forknum, isRedo); + for (i = 0; i < nforks; i++) + smgrsw[which].smgr_unlink(rnode, forknum[i], isRedo); } /* @@ -643,13 +646,15 @@ smgrnblocks(SMgrRelation reln, ForkNumber forknum) * The truncation is done immediately, so this can't be rolled back. */ void -smgrtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) +smgrtruncate(SMgrRelation reln, ForkNumber *forknum, BlockNumber *nblocks, int nforks) { + int i; + /* * Get rid of any buffers for the about-to-be-deleted blocks. bufmgr will * just drop them without bothering to write the contents. */ - DropRelFileNodeBuffers(reln->smgr_rnode, forknum, nblocks); + DropRelFileNodeBuffers(reln->smgr_rnode, forknum, nblocks, nforks); /* * Send a shared-inval message to force other backends to close any smgr @@ -663,10 +668,9 @@ smgrtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) */ CacheInvalidateSmgr(reln->smgr_rnode); - /* - * Do the truncation. - */ - smgrsw[reln->smgr_which].smgr_truncate(reln, forknum, nblocks); + /* Do the truncation */ + for (i = 0; i < nforks; i++) + smgrsw[reln->smgr_which].smgr_truncate(reln, forknum[i], nblocks[i]); } /* diff --git a/src/include/access/visibilitymap.h b/src/include/access/visibilitymap.h index 2d88043..4735d5f 100644 --- a/src/include/access/visibilitymap.h +++ b/src/include/access/visibilitymap.h @@ -44,6 +44,6 @@ extern void visibilitymap_set(Relation rel, BlockNumber heapBlk, Buffer heapBuf, uint8 flags); extern uint8 visibilitymap_get_status(Relation rel, BlockNumber heapBlk, Buffer *vmbuf); extern void visibilitymap_count(Relation rel, BlockNumber *all_visible, BlockNumber *all_frozen); -extern void visibilitymap_truncate(Relation rel, BlockNumber nheapblocks); +extern BlockNumber visibilitymap_mark_truncate(Relation rel, BlockNumber nheapblocks); #endif /* VISIBILITYMAP_H */ diff --git a/src/include/storage/bufmgr.h b/src/include/storage/bufmgr.h index 509f4b7..5be6c0d 100644 --- a/src/include/storage/bufmgr.h +++ b/src/include/storage/bufmgr.h @@ -190,8 +190,8 @@ extern BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, extern void FlushOneBuffer(Buffer buffer); extern void FlushRelationBuffers(Relation rel); extern void FlushDatabaseBuffers(Oid dbid); -extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode, - ForkNumber forkNum, BlockNumber firstDelBlock); +extern void DropRelFileNodeBuffers(RelFileNodeBackend rnode, ForkNumber *forkNum, + BlockNumber *firstDelBlock, int nforks); extern void DropRelFileNodesAllBuffers(RelFileNodeBackend *rnodes, int nnodes); extern void DropDatabaseBuffers(Oid dbid); diff --git a/src/include/storage/freespace.h b/src/include/storage/freespace.h index 8d8c465..bf19a67 100644 --- a/src/include/storage/freespace.h +++ b/src/include/storage/freespace.h @@ -30,7 +30,7 @@ extern void RecordPageWithFreeSpace(Relation rel, BlockNumber heapBlk, extern void XLogRecordPageWithFreeSpace(RelFileNode rnode, BlockNumber heapBlk, Size spaceAvail); -extern void FreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks); +extern BlockNumber MarkFreeSpaceMapTruncateRel(Relation rel, BlockNumber nblocks); extern void FreeSpaceMapVacuum(Relation rel); extern void FreeSpaceMapVacuumRange(Relation rel, BlockNumber start, BlockNumber end); diff --git a/src/include/storage/smgr.h b/src/include/storage/smgr.h index d286c8c..ff70b09 100644 --- a/src/include/storage/smgr.h +++ b/src/include/storage/smgr.h @@ -90,7 +90,8 @@ extern void smgrclosenode(RelFileNodeBackend rnode); extern void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo); extern void smgrdounlink(SMgrRelation reln, bool isRedo); extern void smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo); -extern void smgrdounlinkfork(SMgrRelation reln, ForkNumber forknum, bool isRedo); +extern void smgrdounlinkfork(SMgrRelation reln, ForkNumber *forknum, + bool isRedo, int nforks); extern void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync); extern void smgrprefetch(SMgrRelation reln, ForkNumber forknum, @@ -102,8 +103,8 @@ extern void smgrwrite(SMgrRelation reln, ForkNumber forknum, extern void smgrwriteback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber nblocks); extern BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum); -extern void smgrtruncate(SMgrRelation reln, ForkNumber forknum, - BlockNumber nblocks); +extern void smgrtruncate(SMgrRelation reln, ForkNumber *forknum, + BlockNumber *nblocks, int nforks); extern void smgrimmedsync(SMgrRelation reln, ForkNumber forknum); extern void AtEOXact_SMgr(void); -- 1.8.3.1