diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 8872920..af11eb0 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -182,8 +182,7 @@ CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI automatically truncated after a crash or unclean shutdown. The contents of an unlogged table are also not replicated to standby servers. Any indexes created on an unlogged table are automatically unlogged as - well; however, unlogged GiST indexes are - currently not supported and cannot be created on an unlogged table. + well. diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 95d33c8..cf1f499 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -71,9 +71,22 @@ createTempGistContext(void) Datum gistbuildempty(PG_FUNCTION_ARGS) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unlogged GiST indexes are not supported"))); + Relation index = (Relation) PG_GETARG_POINTER(0); + Buffer buffer; + + /* Initialize the root page */ + buffer = ReadBufferExtended(index, INIT_FORKNUM, P_NEW, RBM_NORMAL, NULL); + LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + + /* Initialize and xlog buffer */ + START_CRIT_SECTION(); + GISTInitBuffer(buffer, F_LEAF); + MarkBufferDirty(buffer); + log_newpage_buffer(buffer); + END_CRIT_SECTION(); + + /* Unlock and release the buffer */ + UnlockReleaseBuffer(buffer); PG_RETURN_VOID(); } @@ -391,7 +404,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, dist, oldrlink, oldnsn, leftchildbuf, markfollowright); else - recptr = GetXLogRecPtrForTemp(); + recptr = GetXLogRecPtrForUnloggedRel(RelationIsUnlogged(rel)); for (ptr = dist; ptr; ptr = ptr->next) { @@ -448,7 +461,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, } else { - recptr = GetXLogRecPtrForTemp(); + recptr = GetXLogRecPtrForUnloggedRel(RelationIsUnlogged(rel)); PageSetLSN(page, recptr); } diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c index aec5b52..be7078e 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -158,16 +158,6 @@ gistbuild(PG_FUNCTION_ARGS) elog(ERROR, "index \"%s\" already contains data", RelationGetRelationName(index)); - /* - * We can't yet handle unlogged GiST indexes, because we depend on LSNs. - * This is duplicative of an error in gistbuildempty, but we want to check - * here so as to throw error before doing all the index-build work. - */ - if (heap->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unlogged GiST indexes are not supported"))); - /* no locking is needed */ buildstate.giststate = initGISTstate(index); @@ -204,7 +194,7 @@ gistbuild(PG_FUNCTION_ARGS) PageSetTLI(page, ThisTimeLineID); } else - PageSetLSN(page, GetXLogRecPtrForTemp()); + PageSetLSN(page, GetXLogRecPtrForUnloggedRel(RelationIsUnlogged(heap))); UnlockReleaseBuffer(buffer); diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 8b60774..1dce570 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -743,18 +743,3 @@ gistoptions(PG_FUNCTION_ARGS) PG_RETURN_BYTEA_P(rdopts); } - -/* - * Temporary GiST indexes are not WAL-logged, but we need LSNs to detect - * concurrent page splits anyway. GetXLogRecPtrForTemp() provides a fake - * sequence of LSNs for that purpose. Each call generates an LSN that is - * greater than any previous value returned by this function in the same - * session. - */ -XLogRecPtr -GetXLogRecPtrForTemp(void) -{ - static XLogRecPtr counter = 1; - counter++; - return counter; -} diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c index dcad36e..a00e1db 100644 --- a/src/backend/access/gist/gistvacuum.c +++ b/src/backend/access/gist/gistvacuum.c @@ -238,7 +238,7 @@ gistbulkdelete(PG_FUNCTION_ARGS) PageSetTLI(page, ThisTimeLineID); } else - PageSetLSN(page, GetXLogRecPtrForTemp()); + PageSetLSN(page, GetXLogRecPtrForUnloggedRel(RelationIsUnlogged(rel))); END_CRIT_SECTION(); } diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 51a515a..d477bf9 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -386,6 +386,9 @@ typedef struct XLogCtlData XLogRecPtr asyncXactLSN; /* LSN of newest async commit/abort */ XLogSegNo lastRemovedSegNo; /* latest removed/recycled XLOG segment */ + /* Protected by ulsn_lck: */ + XLogRecPtr unloggedLSN; /* Fake LSN for unlogged GiST index */ + /* Protected by WALWriteLock: */ XLogCtlWrite Write; @@ -476,6 +479,7 @@ typedef struct XLogCtlData XLogRecPtr lastFpwDisableRecPtr; slock_t info_lck; /* locks shared variables shown above */ + slock_t ulsn_lck; /* locks shared variable unloggedLSN */ } XLogCtlData; static XLogCtlData *XLogCtl = NULL; @@ -4208,6 +4212,40 @@ GetSystemIdentifier(void) } /* + * Temporary GiST indexes are not WAL-logged, but we need LSNs to detect + * concurrent page splits anyway. GetXLogRecPtrForUnloggedRel() provides a fake + * sequence of LSNs for that purpose. Each call generates an LSN that is + * greater than any previous value returned by this function in the same + * session using static counter + * Similarily unlogged GiST indexes are also not WAL-logged. But we need a + * persistent counter across clean shutdown. Use counter from ControlFile which + * is copied in XLogCtl.unloggedLSN to accomplish that + * If isUnlogged is true, return persistent counter from XLogCtl else return + * session wide temporary counter + */ +XLogRecPtr +GetXLogRecPtrForUnloggedRel(bool isUnlogged) +{ + XLogRecPtr nextUnloggedLSN; + static XLogRecPtr counter = 1; + + if (isUnlogged) + { + /* use volatile pointer to prevent code rearrangement */ + volatile XLogCtlData *xlogctl = XLogCtl; + + /* increment the unloggedLSN counter, need SpinLock */ + SpinLockAcquire(&xlogctl->ulsn_lck); + nextUnloggedLSN = xlogctl->unloggedLSN++; + SpinLockRelease(&xlogctl->ulsn_lck); + } + else + nextUnloggedLSN = counter++; /* TEMP relation, use static counter */ + + return nextUnloggedLSN; +} + +/* * Auto-tune the number of XLOG buffers. * * The preferred setting for wal_buffers is about 3% of shared_buffers, with @@ -4355,6 +4393,7 @@ XLOGShmemInit(void) XLogCtl->WalWriterSleeping = false; XLogCtl->Insert.currpage = (XLogPageHeader) (XLogCtl->pages); SpinLockInit(&XLogCtl->info_lck); + SpinLockInit(&XLogCtl->ulsn_lck); InitSharedLatch(&XLogCtl->recoveryWakeupLatch); /* @@ -4496,6 +4535,7 @@ BootStrapXLOG(void) ControlFile->time = checkPoint.time; ControlFile->checkPoint = checkPoint.redo; ControlFile->checkPointCopy = checkPoint; + ControlFile->unloggedLSN = 1; /* Set important parameter values for use when replaying WAL */ ControlFile->MaxConnections = MaxConnections; @@ -5505,6 +5545,9 @@ StartupXLOG(void) XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch; XLogCtl->ckptXid = checkPoint.nextXid; + /* initaiize unlogged LSN from Control File */ + XLogCtl->unloggedLSN = ControlFile->unloggedLSN; + /* * We must replay WAL entries using the same TimeLineID they were created * under, so temporarily adopt the TLI indicated by the checkpoint (see @@ -7034,6 +7077,10 @@ CreateCheckPoint(int flags) LWLockAcquire(ControlFileLock, LW_EXCLUSIVE); ControlFile->state = DB_SHUTDOWNING; ControlFile->time = (pg_time_t) time(NULL); + /* Store unloggedLSN value as we want it persistent across shutdown */ + SpinLockAcquire(&XLogCtl->ulsn_lck); + ControlFile->unloggedLSN = XLogCtl->unloggedLSN; + SpinLockRelease(&XLogCtl->ulsn_lck); UpdateControlFile(); LWLockRelease(ControlFileLock); } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 03ed41d..092dafd 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -1920,9 +1920,24 @@ FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) * Force XLOG flush up to buffer's LSN. This implements the basic WAL * rule that log updates must hit disk before any of the data-file changes * they describe do. + * However, this rule does not apply for unlogged relations, which will be + * lost after a crash anyway. Most unlogged relation pages do not bear LSNs + * since we never emit WAL records for them, and therefore flushing up + * through the buffer LSN would be useless, but harmless. However, GiST + * indexes use LSNs internally to track page-splits, and therefore + * temporary and unlogged GiST pages bear "fake" LSNs generated by + * GetXLogRecPtrForUnloggedRel. It is unlikely but possible that the fake + * LSN counter used for unlogged relations could advance past the WAL + * insertion point; and if it did happen, attempting to flush WAL through + * that location would fail, with disastrous system-wide consequences. To + * make sure that can't happen, skip the flush if the buffer isn't + * permanent. */ - recptr = BufferGetLSN(buf); - XLogFlush(recptr); + if (buf->flags & BM_PERMANENT) + { + recptr = BufferGetLSN(buf); + XLogFlush(recptr); + } /* * Now it's safe to write buffer to disk. Note that no one else should diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 357b8c9..8d4fa9d 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -16,6 +16,7 @@ #include "access/gist.h" #include "access/itup.h" +#include "access/xlog.h" #include "fmgr.h" #include "storage/bufmgr.h" #include "storage/buffile.h" @@ -511,8 +512,6 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno, GISTENTRY *entry2, bool isnull2, Datum *dst, bool *dstisnull); -extern XLogRecPtr GetXLogRecPtrForTemp(void); - /* gistvacuum.c */ extern Datum gistbulkdelete(PG_FUNCTION_ARGS); extern Datum gistvacuumcleanup(PG_FUNCTION_ARGS); diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 885b5fc..81ee1b3 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -294,6 +294,7 @@ extern char *XLogFileNameP(TimeLineID tli, XLogSegNo segno); extern void UpdateControlFile(void); extern uint64 GetSystemIdentifier(void); +extern XLogRecPtr GetXLogRecPtrForUnloggedRel(bool isUnlogged); extern Size XLOGShmemSize(void); extern void XLOGShmemInit(void); extern void BootStrapXLOG(void); diff --git a/src/include/catalog/pg_control.h b/src/include/catalog/pg_control.h index ead3a6e..17522d0 100644 --- a/src/include/catalog/pg_control.h +++ b/src/include/catalog/pg_control.h @@ -157,6 +157,7 @@ typedef struct ControlFileData XLogRecPtr backupStartPoint; XLogRecPtr backupEndPoint; bool backupEndRequired; + XLogRecPtr unloggedLSN; /* fake LSN for unlogged GiST index */ /* * Parameter settings that determine if the WAL can be used for archival diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index bde5f17..2acb34e 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -366,6 +366,13 @@ typedef struct StdRdOptions ((relation)->rd_rel->relpersistence == RELPERSISTENCE_PERMANENT) /* + * RelationIsUnlogged + * True if relation is unlogged. + */ +#define RelationIsUnlogged(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_UNLOGGED) + +/* * RelationUsesLocalBuffers * True if relation's pages are stored in local buffers. */