Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gqrIU-0003cq-79 for pgsql-hackers@arkaria.postgresql.org; Tue, 05 Feb 2019 03:21:19 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gqrIS-0003JW-JE for pgsql-hackers@arkaria.postgresql.org; Tue, 05 Feb 2019 03:21:16 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gqrIS-0003JP-5M for pgsql-hackers@lists.postgresql.org; Tue, 05 Feb 2019 03:21:16 +0000 Received: from mail.postgrespro.ru ([93.174.131.138]) by magus.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gqrIO-0004GZ-Va for pgsql-hackers@postgresql.org; Tue, 05 Feb 2019 03:21:15 +0000 Received: from localhost (localhost [127.0.0.1]) by mail.postgrespro.ru (Postfix) with ESMTP id 6F50421C78D0; Tue, 5 Feb 2019 06:21:11 +0300 (MSK) X-Virus-Scanned: Debian amavisd-new at postgrespro.ru X-Spam-Flag: NO X-Spam-Score: 0 X-Spam-Level: X-Spam-Status: No, score=x tagged_above=-99 required=4 WHITELISTED tests=[] autolearn=unavailable Received: from [192.168.0.105] (unknown [5.79.129.173]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (Client did not present a certificate) by mail.postgrespro.ru (Postfix) with ESMTPSA id E66B221C788C; Tue, 5 Feb 2019 06:21:10 +0300 (MSK) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=postgrespro.ru; s=mail; t=1549336871; bh=8+a5KH89shOJMQiDPN6Kl4N6vu2VP2XApDY85/ePDec=; h=Subject:To:Cc:References:From:Date:In-Reply-To; b=WBO7ilhpi5BKOOp6tFZNmJ7MN1MB8EN4tK5ZwTR0sPIQdRsz7sbW6MOpTVWhi+hVT ZNYdoiV3O/KJpCq3GHNkVJFn5VzNwFY2wPdRqR/UNO/kV+8TAfEbam4i15wgDwTFB8 WYUEV9PIyj5w+OK83TobniX98e8RYbiJHXXpN2Xk= Subject: Re: Reduce amount of WAL generated by CREATE INDEX for gist, gin and sp-gist To: Michael Paquier Cc: Dmitry Dolgov <9erthalion6@gmail.com>, Heikki Linnakangas , a.lubennikova@postgrespro.ru, PostgreSQL-development References: <18f0b868-6888-27eb-22cc-982709c3d0f7@postgrespro.ru> <1cdb9c52-e8fb-124b-3949-d3be8a6a7451@postgrespro.ru> <20190204050413.GI29064@paquier.xyz> From: Andrey Lepikhov Organization: Postgres Professional Message-ID: Date: Tue, 5 Feb 2019 08:21:09 +0500 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.4.0 MIME-Version: 1.0 In-Reply-To: <20190204050413.GI29064@paquier.xyz> Content-Type: multipart/mixed; boundary="------------F161D6F86D13969C52E2CA53" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------F161D6F86D13969C52E2CA53 Content-Type: text/plain; charset=windows-1252 Content-Transfer-Encoding: quoted-printable On 04.02.2019 10:04, Michael Paquier wrote: > On Tue, Dec 18, 2018 at 10:41:48AM +0500, Andrey Lepikhov wrote: >> Ok. It is used only for demonstration. >=20 > The latest patch set needs a rebase, so moved to next CF, waiting on > author as this got no reviews. The new version in attachment. > -- > Michael >=20 --=20 Andrey Lepikhov Postgres Professional https://postgrespro.com The Russian Postgres Company --------------F161D6F86D13969C52E2CA53 Content-Type: text/x-patch; name="0001-Relation-into-WAL-function.patch" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="0001-Relation-into-WAL-function.patch" =46rom afc270f37cd082fb6e9f4ad694ea4cb123d98062 Mon Sep 17 00:00:00 2001 From: "Andrey V. Lepikhov" Date: Tue, 5 Feb 2019 08:11:47 +0500 Subject: [PATCH 1/4] Relation-into-WAL-function --- src/backend/access/spgist/spgutils.c | 1 + src/backend/access/transam/generic_xlog.c | 39 +++++++++++++++++++++++ src/include/access/generic_xlog.h | 3 ++ 3 files changed, 43 insertions(+) diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/sp= gist/spgutils.c index 8e63c1fad2..b782bc2338 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -550,6 +550,7 @@ SpGistInitBuffer(Buffer b, uint16 f) { Assert(BufferGetPageSize(b) =3D=3D BLCKSZ); SpGistInitPage(BufferGetPage(b), f); + MarkBufferDirty(b); } =20 /* diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/acce= ss/transam/generic_xlog.c index 5b00b7275b..643da48345 100644 --- a/src/backend/access/transam/generic_xlog.c +++ b/src/backend/access/transam/generic_xlog.c @@ -542,3 +542,42 @@ generic_mask(char *page, BlockNumber blkno) =20 mask_unused_space(page); } + +/* + * Function to write generic xlog for every existing block of a relation= =2E + * Caller is responsible for locking the relation exclusively. + */ +void +generic_log_relation(Relation rel) +{ + BlockNumber blkno; + BlockNumber nblocks =3D RelationGetNumberOfBlocks(rel); + + for (blkno =3D 0; blkno < nblocks; ) + { + GenericXLogState *state; + Buffer buffer[MAX_GENERIC_XLOG_PAGES]; + int i, + blocks_pack; + + CHECK_FOR_INTERRUPTS(); + + blocks_pack =3D ((nblocks-blkno) < MAX_GENERIC_XLOG_PAGES) ? + (nblocks-blkno) : MAX_GENERIC_XLOG_PAGES; + + state =3D GenericXLogStart(rel); + + for (i =3D 0 ; i < blocks_pack; i++) + { + buffer[i] =3D ReadBuffer(rel, blkno); + LockBuffer(buffer[i], BUFFER_LOCK_EXCLUSIVE); + GenericXLogRegisterBuffer(state, buffer[i], GENERIC_XLOG_FULL_IMAGE);= + blkno++; + } + + GenericXLogFinish(state); + + for (i =3D 0 ; i < blocks_pack; i++) + UnlockReleaseBuffer(buffer[i]); + } +} diff --git a/src/include/access/generic_xlog.h b/src/include/access/gener= ic_xlog.h index cb5b5b713a..e3bbf014cc 100644 --- a/src/include/access/generic_xlog.h +++ b/src/include/access/generic_xlog.h @@ -42,4 +42,7 @@ extern const char *generic_identify(uint8 info); extern void generic_desc(StringInfo buf, XLogReaderState *record); extern void generic_mask(char *pagedata, BlockNumber blkno); =20 +/* other utils */ +extern void generic_log_relation(Relation rel); + #endif /* GENERIC_XLOG_H */ --=20 2.17.1 --------------F161D6F86D13969C52E2CA53 Content-Type: text/x-patch; name="0002-GIN-Optimal-WAL-Usage.patch" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="0002-GIN-Optimal-WAL-Usage.patch" =46rom 25dc935da9d5502a06fb9dc1eea4912fa0f48be1 Mon Sep 17 00:00:00 2001 From: "Andrey V. Lepikhov" Date: Tue, 5 Feb 2019 08:13:29 +0500 Subject: [PATCH 2/4] GIN-Optimal-WAL-Usage --- src/backend/access/gin/ginbtree.c | 6 ++--- src/backend/access/gin/gindatapage.c | 10 ++++---- src/backend/access/gin/ginentrypage.c | 2 +- src/backend/access/gin/gininsert.c | 30 ++++++++++-------------- src/backend/access/gin/ginutil.c | 4 ++-- src/backend/access/gin/ginvacuum.c | 2 +- src/backend/access/gin/ginxlog.c | 33 --------------------------- src/backend/access/rmgrdesc/gindesc.c | 6 ----- src/include/access/gin.h | 3 ++- src/include/access/ginxlog.h | 2 -- 10 files changed, 27 insertions(+), 71 deletions(-) diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/g= inbtree.c index 533949e46a..9f82eef8c3 100644 --- a/src/backend/access/gin/ginbtree.c +++ b/src/backend/access/gin/ginbtree.c @@ -396,7 +396,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack, /* It will fit, perform the insertion */ START_CRIT_SECTION(); =20 - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { XLogBeginInsert(); XLogRegisterBuffer(0, stack->buffer, REGBUF_STANDARD); @@ -417,7 +417,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack, MarkBufferDirty(childbuf); } =20 - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { XLogRecPtr recptr; ginxlogInsert xlrec; @@ -595,7 +595,7 @@ ginPlaceToPage(GinBtree btree, GinBtreeStack *stack, } =20 /* write WAL record */ - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { XLogRecPtr recptr; =20 diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gi= n/gindatapage.c index 3ad8b76710..a73cf944b3 100644 --- a/src/backend/access/gin/gindatapage.c +++ b/src/backend/access/gin/gindatapage.c @@ -593,7 +593,7 @@ dataBeginPlaceToPageLeaf(GinBtree btree, Buffer buf, = GinBtreeStack *stack, * Great, all the items fit on a single page. If needed, prepare data= * for a WAL record describing the changes we'll make. */ - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) computeLeafRecompressWALData(leaf); =20 /* @@ -630,6 +630,7 @@ dataBeginPlaceToPageLeaf(GinBtree btree, Buffer buf, = GinBtreeStack *stack, * subsequent insertions will probably also go to the end. This packs * the index somewhat tighter when appending to a table, which is very= * common. + * */ if (!btree->isBuild) { @@ -719,7 +720,7 @@ dataExecPlaceToPageLeaf(GinBtree btree, Buffer buf, G= inBtreeStack *stack, dataPlaceToPageLeafRecompress(buf, leaf); =20 /* If needed, register WAL data built by computeLeafRecompressWALData *= / - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { XLogRegisterBufData(0, leaf->walinfo, leaf->walinfolen); } @@ -1152,7 +1153,7 @@ dataExecPlaceToPageInternal(GinBtree btree, Buffer = buf, GinBtreeStack *stack, pitem =3D (PostingItem *) insertdata; GinDataPageAddPostingItem(page, pitem, off); =20 - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { /* * This must be static, because it has to survive until XLogInsert, @@ -1773,6 +1774,7 @@ createPostingTree(Relation index, ItemPointerData *= items, uint32 nitems, Pointer ptr; int nrootitems; int rootsize; + bool is_build =3D (buildStats !=3D NULL); =20 /* Construct the new root page in memory first. */ tmppage =3D (Page) palloc(BLCKSZ); @@ -1826,7 +1828,7 @@ createPostingTree(Relation index, ItemPointerData *= items, uint32 nitems, PageRestoreTempPage(tmppage, page); MarkBufferDirty(buffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !is_build) { XLogRecPtr recptr; ginxlogCreatePostingTree data; diff --git a/src/backend/access/gin/ginentrypage.c b/src/backend/access/g= in/ginentrypage.c index 4889de2a4f..1f5ba33d51 100644 --- a/src/backend/access/gin/ginentrypage.c +++ b/src/backend/access/gin/ginentrypage.c @@ -571,7 +571,7 @@ entryExecPlaceToPage(GinBtree btree, Buffer buf, GinB= treeStack *stack, elog(ERROR, "failed to add item to index page in \"%s\"", RelationGetRelationName(btree->index)); =20 - if (RelationNeedsWAL(btree->index)) + if (RelationNeedsWAL(btree->index) && !btree->isBuild) { /* * This must be static, because it has to survive until XLogInsert, diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/= gininsert.c index 524ac5be8b..6307743afd 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -17,6 +17,7 @@ #include "access/gin_private.h" #include "access/ginxlog.h" #include "access/xloginsert.h" +#include "access/generic_xlog.h" #include "catalog/index.h" #include "miscadmin.h" #include "storage/bufmgr.h" @@ -194,6 +195,7 @@ ginEntryInsert(GinState *ginstate, buildStats->nEntries++; =20 ginPrepareEntryScan(&btree, attnum, key, category, ginstate); + btree.isBuild =3D (buildStats !=3D NULL); =20 stack =3D ginFindLeafPage(&btree, false, false, NULL); page =3D BufferGetPage(stack->buffer); @@ -346,23 +348,6 @@ ginbuild(Relation heap, Relation index, IndexInfo *i= ndexInfo) GinInitBuffer(RootBuffer, GIN_LEAF); MarkBufferDirty(RootBuffer); =20 - if (RelationNeedsWAL(index)) - { - XLogRecPtr recptr; - Page page; - - XLogBeginInsert(); - XLogRegisterBuffer(0, MetaBuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);= - XLogRegisterBuffer(1, RootBuffer, REGBUF_WILL_INIT); - - recptr =3D XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_INDEX); - - page =3D BufferGetPage(RootBuffer); - PageSetLSN(page, recptr); - - page =3D BufferGetPage(MetaBuffer); - PageSetLSN(page, recptr); - } =20 UnlockReleaseBuffer(MetaBuffer); UnlockReleaseBuffer(RootBuffer); @@ -417,7 +402,16 @@ ginbuild(Relation heap, Relation index, IndexInfo *i= ndexInfo) * Update metapage stats */ buildstate.buildStats.nTotalPages =3D RelationGetNumberOfBlocks(index);= - ginUpdateStats(index, &buildstate.buildStats); + ginUpdateStats(index, &buildstate.buildStats, true); + + /* + * Create generic wal records for all pages of relation, if necessary. + * It seems reasonable not to generate WAL, if we recieved interrupt + * signal. + */ + CHECK_FOR_INTERRUPTS(); + if (RelationNeedsWAL(index)) + generic_log_relation(index); =20 /* * Return statistics diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/gi= nutil.c index afc20232ac..51b20bca6e 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -661,7 +661,7 @@ ginGetStats(Relation index, GinStatsData *stats) * Note: nPendingPages and ginVersion are *not* copied over */ void -ginUpdateStats(Relation index, const GinStatsData *stats) +ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)= { Buffer metabuffer; Page metapage; @@ -691,7 +691,7 @@ ginUpdateStats(Relation index, const GinStatsData *st= ats) =20 MarkBufferDirty(metabuffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !is_build) { XLogRecPtr recptr; ginxlogUpdateMeta data; diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/= ginvacuum.c index dfe885b101..b9a28d1863 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -759,7 +759,7 @@ ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDele= teResult *stats) =20 /* Update the metapage with accurate page and entry counts */ idxStat.nTotalPages =3D npages; - ginUpdateStats(info->index, &idxStat); + ginUpdateStats(info->index, &idxStat, false); =20 /* Finally, vacuum the FSM */ IndexFreeSpaceMapVacuum(info->index); diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/gi= nxlog.c index c467ffa346..b648af1ff6 100644 --- a/src/backend/access/gin/ginxlog.c +++ b/src/backend/access/gin/ginxlog.c @@ -40,36 +40,6 @@ ginRedoClearIncompleteSplit(XLogReaderState *record, u= int8 block_id) UnlockReleaseBuffer(buffer); } =20 -static void -ginRedoCreateIndex(XLogReaderState *record) -{ - XLogRecPtr lsn =3D record->EndRecPtr; - Buffer RootBuffer, - MetaBuffer; - Page page; - - MetaBuffer =3D XLogInitBufferForRedo(record, 0); - Assert(BufferGetBlockNumber(MetaBuffer) =3D=3D GIN_METAPAGE_BLKNO); - page =3D (Page) BufferGetPage(MetaBuffer); - - GinInitMetabuffer(MetaBuffer); - - PageSetLSN(page, lsn); - MarkBufferDirty(MetaBuffer); - - RootBuffer =3D XLogInitBufferForRedo(record, 1); - Assert(BufferGetBlockNumber(RootBuffer) =3D=3D GIN_ROOT_BLKNO); - page =3D (Page) BufferGetPage(RootBuffer); - - GinInitBuffer(RootBuffer, GIN_LEAF); - - PageSetLSN(page, lsn); - MarkBufferDirty(RootBuffer); - - UnlockReleaseBuffer(RootBuffer); - UnlockReleaseBuffer(MetaBuffer); -} - static void ginRedoCreatePTree(XLogReaderState *record) { @@ -767,9 +737,6 @@ gin_redo(XLogReaderState *record) oldCtx =3D MemoryContextSwitchTo(opCtx); switch (info) { - case XLOG_GIN_CREATE_INDEX: - ginRedoCreateIndex(record); - break; case XLOG_GIN_CREATE_PTREE: ginRedoCreatePTree(record); break; diff --git a/src/backend/access/rmgrdesc/gindesc.c b/src/backend/access/r= mgrdesc/gindesc.c index ef30ce16b0..f3f4e1b214 100644 --- a/src/backend/access/rmgrdesc/gindesc.c +++ b/src/backend/access/rmgrdesc/gindesc.c @@ -78,9 +78,6 @@ gin_desc(StringInfo buf, XLogReaderState *record) =20 switch (info) { - case XLOG_GIN_CREATE_INDEX: - /* no further information */ - break; case XLOG_GIN_CREATE_PTREE: /* no further information */ break; @@ -188,9 +185,6 @@ gin_identify(uint8 info) =20 switch (info & ~XLR_INFO_MASK) { - case XLOG_GIN_CREATE_INDEX: - id =3D "CREATE_INDEX"; - break; case XLOG_GIN_CREATE_PTREE: id =3D "CREATE_PTREE"; break; diff --git a/src/include/access/gin.h b/src/include/access/gin.h index 61fa697039..d559ffc703 100644 --- a/src/include/access/gin.h +++ b/src/include/access/gin.h @@ -71,6 +71,7 @@ extern int gin_pending_list_limit; =20 /* ginutil.c */ extern void ginGetStats(Relation index, GinStatsData *stats); -extern void ginUpdateStats(Relation index, const GinStatsData *stats); +extern void ginUpdateStats(Relation index, + const GinStatsData *stats, bool is_build); =20 #endif /* GIN_H */ diff --git a/src/include/access/ginxlog.h b/src/include/access/ginxlog.h index 9bd4e0b9ba..2c5d743cac 100644 --- a/src/include/access/ginxlog.h +++ b/src/include/access/ginxlog.h @@ -16,8 +16,6 @@ #include "lib/stringinfo.h" #include "storage/off.h" =20 -#define XLOG_GIN_CREATE_INDEX 0x00 - #define XLOG_GIN_CREATE_PTREE 0x10 =20 typedef struct ginxlogCreatePostingTree --=20 2.17.1 --------------F161D6F86D13969C52E2CA53 Content-Type: text/x-patch; name="0003-GIST-Optimal-WAL-Usage.patch" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="0003-GIST-Optimal-WAL-Usage.patch" =46rom f032a0645d86c57e3b89a294dfb46cfad1119825 Mon Sep 17 00:00:00 2001 From: "Andrey V. Lepikhov" Date: Tue, 5 Feb 2019 08:14:41 +0500 Subject: [PATCH 3/4] GIST-Optimal-WAL-Usage --- src/backend/access/gist/gist.c | 47 ++++++++++++++++++-------- src/backend/access/gist/gistbuild.c | 32 ++++++++++-------- src/backend/access/gist/gistutil.c | 2 +- src/backend/access/gist/gistxlog.c | 22 ------------ src/backend/access/rmgrdesc/gistdesc.c | 5 --- src/include/access/gist_private.h | 7 ++-- src/include/access/gistxlog.h | 1 - 7 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gis= t.c index b75b3a8dac..710c1bfd3a 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -173,7 +173,7 @@ gistinsert(Relation r, Datum *values, bool *isnull, values, isnull, true /* size is currently bogus */ ); itup->t_tid =3D *ht_ctid; =20 - gistdoinsert(r, itup, 0, giststate, heapRel); + gistdoinsert(r, itup, 0, giststate, heapRel, false); =20 /* cleanup */ MemoryContextSwitchTo(oldCxt); @@ -220,7 +220,8 @@ gistplacetopage(Relation rel, Size freespace, GISTSTA= TE *giststate, Buffer leftchildbuf, List **splitinfo, bool markfollowright, - Relation heapRel) + Relation heapRel, + bool is_build) { BlockNumber blkno =3D BufferGetBlockNumber(buffer); Page page =3D BufferGetPage(buffer); @@ -459,7 +460,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTA= TE *giststate, * insertion for that. NB: The number of pages and data segments * specified here must match the calculations in gistXLogSplit()! */ - if (RelationNeedsWAL(rel)) + if (RelationNeedsWAL(rel) && !is_build) XLogEnsureRecordSpace(npage, 1 + npage * 2); =20 START_CRIT_SECTION(); @@ -480,18 +481,20 @@ gistplacetopage(Relation rel, Size freespace, GISTS= TATE *giststate, PageRestoreTempPage(dist->page, BufferGetPage(dist->buffer)); dist->page =3D BufferGetPage(dist->buffer); =20 - /* Write the WAL record */ - if (RelationNeedsWAL(rel)) + /* + * Write the WAL record. + * Do not write XLog entry if the insertion is caused by + * index build process. + */ + if (RelationNeedsWAL(rel) && !is_build) recptr =3D gistXLogSplit(is_leaf, - dist, oldrlink, oldnsn, leftchildbuf, - markfollowright); + dist, oldrlink, oldnsn, leftchildbuf, + markfollowright); else recptr =3D gistGetFakeLSN(rel); =20 for (ptr =3D dist; ptr; ptr =3D ptr->next) - { PageSetLSN(ptr->page, recptr); - } =20 /* * Return the new child buffers to the caller. @@ -545,7 +548,8 @@ gistplacetopage(Relation rel, Size freespace, GISTSTA= TE *giststate, if (BufferIsValid(leftchildbuf)) MarkBufferDirty(leftchildbuf); =20 - if (RelationNeedsWAL(rel)) + + if (RelationNeedsWAL(rel) && !is_build) { OffsetNumber ndeloffs =3D 0, deloffs[1]; @@ -568,6 +572,7 @@ gistplacetopage(Relation rel, Size freespace, GISTSTA= TE *giststate, PageSetLSN(page, recptr); } =20 + if (newblkno) *newblkno =3D blkno; } @@ -584,17 +589,28 @@ gistplacetopage(Relation rel, Size freespace, GISTS= TATE *giststate, * the full page image. There's a chicken-and-egg problem: if we update= d * the child pages first, we wouldn't know the recptr of the WAL record= * we're about to write. + * + * We use fakeLSNs for inserions caused by index build. And when it is + * finished, we write generic_xlog entry for each index page and update= + * all LSNs. In order to keep NSNs less then LSNs after this update, we= + * set NSN to InvalidXLogRecPtr, which is the smallest possible NSN. */ + if (BufferIsValid(leftchildbuf)) { Page leftpg =3D BufferGetPage(leftchildbuf); + XLogRecPtr fakerecptr =3D InvalidXLogRecPtr; =20 - GistPageSetNSN(leftpg, recptr); - GistClearFollowRight(leftpg); + if (!is_build) + GistPageSetNSN(leftpg, recptr); + else + GistPageSetNSN(leftpg, fakerecptr); =20 + GistClearFollowRight(leftpg); PageSetLSN(leftpg, recptr); } =20 + END_CRIT_SECTION(); =20 return is_split; @@ -605,9 +621,8 @@ gistplacetopage(Relation rel, Size freespace, GISTSTA= TE *giststate, * this routine assumes it is invoked in a short-lived memory context, * so it does not bother releasing palloc'd allocations. */ -void gistdoinsert(Relation r, IndexTuple itup, Size freespace, - GISTSTATE *giststate, Relation heapRel) + GISTSTATE *giststate, Relation heapRel, bool is_build) { ItemId iid; IndexTuple idxtuple; @@ -620,6 +635,7 @@ gistdoinsert(Relation r, IndexTuple itup, Size freesp= ace, state.freespace =3D freespace; state.r =3D r; state.heapRel =3D heapRel; + state.is_build =3D is_build; =20 /* Start from the root */ firststack.blkno =3D GIST_ROOT_BLKNO; @@ -1237,7 +1253,8 @@ gistinserttuples(GISTInsertState *state, GISTInsert= Stack *stack, leftchild, &splitinfo, true, - state->heapRel); + state->heapRel, + state->is_build); =20 /* * Before recursing up in case the page was split, release locks on the= diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gis= t/gistbuild.c index bd142a3560..2e98e55187 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -17,6 +17,7 @@ #include =20 #include "access/genam.h" +#include "access/generic_xlog.h" #include "access/gist_private.h" #include "access/gistxlog.h" #include "access/xloginsert.h" @@ -180,18 +181,12 @@ gistbuild(Relation heap, Relation index, IndexInfo = *indexInfo) =20 MarkBufferDirty(buffer); =20 - if (RelationNeedsWAL(index)) - { - XLogRecPtr recptr; - - XLogBeginInsert(); - XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT); - - recptr =3D XLogInsert(RM_GIST_ID, XLOG_GIST_CREATE_INDEX); - PageSetLSN(page, recptr); - } - else - PageSetLSN(page, gistGetFakeLSN(heap)); + /* + * Do not write index pages to WAL unitl index build is finished. + * But we still need increasing LSNs on each page, so use FakeLSN, + * even for relations which eventually need WAL. + */ + PageSetLSN(page, gistGetFakeLSN(heap)); =20 UnlockReleaseBuffer(buffer); =20 @@ -224,6 +219,15 @@ gistbuild(Relation heap, Relation index, IndexInfo *= indexInfo) =20 freeGISTstate(buildstate.giststate); =20 + /* + * Create generic wal records for all pages of relation, if necessary. + * It seems reasonable not to generate WAL, if we recieved interrupt + * signal. + */ + CHECK_FOR_INTERRUPTS(); + if (RelationNeedsWAL(index)) + generic_log_relation(index); + /* * Return statistics */ @@ -486,7 +490,7 @@ gistBuildCallback(Relation index, * locked, we call gistdoinsert directly. */ gistdoinsert(index, itup, buildstate->freespace, - buildstate->giststate, buildstate->heaprel); + buildstate->giststate, buildstate->heaprel, true); } =20 /* Update tuple count and total size. */ @@ -693,7 +697,7 @@ gistbufferinginserttuples(GISTBuildState *buildstate,= Buffer buffer, int level, InvalidBuffer, &splitinfo, false, - buildstate->heaprel); + buildstate->heaprel, true); =20 /* * If this is a root split, update the root path item kept in memory. T= his diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist= /gistutil.c index 8d3dfad27b..f7088e2d94 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -949,6 +949,7 @@ gistproperty(Oid index_oid, int attno, * Temporary and unlogged GiST indexes are not WAL-logged, but we need L= SNs * to detect concurrent page splits anyway. This function provides a fak= e * sequence of LSNs for that purpose. + * Persistent relations are also not WAL-logged while we build index. */ XLogRecPtr gistGetFakeLSN(Relation rel) @@ -969,7 +970,6 @@ gistGetFakeLSN(Relation rel) * Unlogged relations are accessible from other backends, and survive * (clean) restarts. GetFakeLSNForUnloggedRel() handles that for us. */ - Assert(rel->rd_rel->relpersistence =3D=3D RELPERSISTENCE_UNLOGGED); return GetFakeLSNForUnloggedRel(); } } diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist= /gistxlog.c index 408bd5390a..66d5bb831f 100644 --- a/src/backend/access/gist/gistxlog.c +++ b/src/backend/access/gist/gistxlog.c @@ -489,25 +489,6 @@ gistRedoPageSplitRecord(XLogReaderState *record) UnlockReleaseBuffer(firstbuffer); } =20 -static void -gistRedoCreateIndex(XLogReaderState *record) -{ - XLogRecPtr lsn =3D record->EndRecPtr; - Buffer buffer; - Page page; - - buffer =3D XLogInitBufferForRedo(record, 0); - Assert(BufferGetBlockNumber(buffer) =3D=3D GIST_ROOT_BLKNO); - page =3D (Page) BufferGetPage(buffer); - - GISTInitBuffer(buffer, F_LEAF); - - PageSetLSN(page, lsn); - - MarkBufferDirty(buffer); - UnlockReleaseBuffer(buffer); -} - void gist_redo(XLogReaderState *record) { @@ -532,9 +513,6 @@ gist_redo(XLogReaderState *record) case XLOG_GIST_PAGE_SPLIT: gistRedoPageSplitRecord(record); break; - case XLOG_GIST_CREATE_INDEX: - gistRedoCreateIndex(record); - break; default: elog(PANIC, "gist_redo: unknown op code %u", info); } diff --git a/src/backend/access/rmgrdesc/gistdesc.c b/src/backend/access/= rmgrdesc/gistdesc.c index e468c9e15a..75f1ed747e 100644 --- a/src/backend/access/rmgrdesc/gistdesc.c +++ b/src/backend/access/rmgrdesc/gistdesc.c @@ -52,8 +52,6 @@ gist_desc(StringInfo buf, XLogReaderState *record) case XLOG_GIST_PAGE_SPLIT: out_gistxlogPageSplit(buf, (gistxlogPageSplit *) rec); break; - case XLOG_GIST_CREATE_INDEX: - break; } } =20 @@ -73,9 +71,6 @@ gist_identify(uint8 info) case XLOG_GIST_PAGE_SPLIT: id =3D "PAGE_SPLIT"; break; - case XLOG_GIST_CREATE_INDEX: - id =3D "CREATE_INDEX"; - break; } =20 return id; diff --git a/src/include/access/gist_private.h b/src/include/access/gist_= private.h index 3698942f9d..b1680bfd6d 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -242,6 +242,7 @@ typedef struct Relation r; Relation heapRel; Size freespace; /* free space to be left */ + bool is_build; =20 GISTInsertStack *stack; } GISTInsertState; @@ -391,7 +392,8 @@ extern void gistdoinsert(Relation r, IndexTuple itup, Size freespace, GISTSTATE *GISTstate, - Relation heapRel); + Relation heapRel, + bool is_build); =20 /* A List of these is returned from gistplacetopage() in *splitinfo */ typedef struct @@ -407,7 +409,8 @@ extern bool gistplacetopage(Relation rel, Size freesp= ace, GISTSTATE *giststate, Buffer leftchildbuf, List **splitinfo, bool markleftchild, - Relation heapRel); + Relation heapRel, + bool is_build); =20 extern SplitedPageLayout *gistSplit(Relation r, Page page, IndexTuple *i= tup, int len, GISTSTATE *giststate); diff --git a/src/include/access/gistxlog.h b/src/include/access/gistxlog.= h index 5117aabf1a..2ee80e244e 100644 --- a/src/include/access/gistxlog.h +++ b/src/include/access/gistxlog.h @@ -22,7 +22,6 @@ /* #define XLOG_GIST_NEW_ROOT 0x20 */ /* not used anymore */ #define XLOG_GIST_PAGE_SPLIT 0x30 /* #define XLOG_GIST_INSERT_COMPLETE 0x40 */ /* not used anymore */ -#define XLOG_GIST_CREATE_INDEX 0x50 /* #define XLOG_GIST_PAGE_DELETE 0x60 */ /* not used anymore */ =20 /* --=20 2.17.1 --------------F161D6F86D13969C52E2CA53 Content-Type: text/x-patch; name="0004-SPGIST-Optimal-WAL-Usage.patch" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="0004-SPGIST-Optimal-WAL-Usage.patch" =46rom 3dbe3d827173244e9bda928147f35e0929266215 Mon Sep 17 00:00:00 2001 From: "Andrey V. Lepikhov" Date: Tue, 5 Feb 2019 08:15:06 +0500 Subject: [PATCH 4/4] SPGIST-Optimal-WAL-Usage --- src/backend/access/rmgrdesc/spgdesc.c | 5 ---- src/backend/access/spgist/spgdoinsert.c | 12 ++++----- src/backend/access/spgist/spginsert.c | 24 +++-------------- src/backend/access/spgist/spgxlog.c | 35 ------------------------- src/include/access/spgxlog.h | 1 - 5 files changed, 10 insertions(+), 67 deletions(-) diff --git a/src/backend/access/rmgrdesc/spgdesc.c b/src/backend/access/r= mgrdesc/spgdesc.c index 37af31a764..40c1c8b3f9 100644 --- a/src/backend/access/rmgrdesc/spgdesc.c +++ b/src/backend/access/rmgrdesc/spgdesc.c @@ -24,8 +24,6 @@ spg_desc(StringInfo buf, XLogReaderState *record) =20 switch (info) { - case XLOG_SPGIST_CREATE_INDEX: - break; case XLOG_SPGIST_ADD_LEAF: { spgxlogAddLeaf *xlrec =3D (spgxlogAddLeaf *) rec; @@ -88,9 +86,6 @@ spg_identify(uint8 info) =20 switch (info & ~XLR_INFO_MASK) { - case XLOG_SPGIST_CREATE_INDEX: - id =3D "CREATE_INDEX"; - break; case XLOG_SPGIST_ADD_LEAF: id =3D "ADD_LEAF"; break; diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access= /spgist/spgdoinsert.c index 0d07b8b291..c34c44cd8b 100644 --- a/src/backend/access/spgist/spgdoinsert.c +++ b/src/backend/access/spgist/spgdoinsert.c @@ -289,7 +289,7 @@ addLeafTuple(Relation index, SpGistState *state, SpGi= stLeafTuple leafTuple, =20 MarkBufferDirty(current->buffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; int flags; @@ -516,7 +516,7 @@ moveLeafs(Relation index, SpGistState *state, MarkBufferDirty(current->buffer); MarkBufferDirty(nbuf); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; =20 @@ -1334,7 +1334,7 @@ doPickSplit(Relation index, SpGistState *state, saveCurrent.buffer =3D InvalidBuffer; } =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; int flags; @@ -1531,7 +1531,7 @@ spgAddNodeAction(Relation index, SpGistState *state= , =20 MarkBufferDirty(current->buffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; =20 @@ -1644,7 +1644,7 @@ spgAddNodeAction(Relation index, SpGistState *state= , =20 MarkBufferDirty(saveCurrent.buffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; int flags; @@ -1840,7 +1840,7 @@ spgSplitNodeAction(Relation index, SpGistState *sta= te, =20 MarkBufferDirty(current->buffer); =20 - if (RelationNeedsWAL(index)) + if (RelationNeedsWAL(index) && !state->isBuild) { XLogRecPtr recptr; =20 diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/s= pgist/spginsert.c index f428a15138..5da0183a9b 100644 --- a/src/backend/access/spgist/spginsert.c +++ b/src/backend/access/spgist/spginsert.c @@ -21,6 +21,7 @@ #include "access/spgxlog.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "access/generic_xlog.h" #include "catalog/index.h" #include "miscadmin.h" #include "storage/bufmgr.h" @@ -104,26 +105,6 @@ spgbuild(Relation heap, Relation index, IndexInfo *i= ndexInfo) SpGistInitBuffer(nullbuffer, SPGIST_LEAF | SPGIST_NULLS); MarkBufferDirty(nullbuffer); =20 - if (RelationNeedsWAL(index)) - { - XLogRecPtr recptr; - - XLogBeginInsert(); - - /* - * Replay will re-initialize the pages, so don't take full pages - * images. No other data to log. - */ - XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);= - XLogRegisterBuffer(1, rootbuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);= - XLogRegisterBuffer(2, nullbuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);= - - recptr =3D XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_CREATE_INDEX); - - PageSetLSN(BufferGetPage(metabuffer), recptr); - PageSetLSN(BufferGetPage(rootbuffer), recptr); - PageSetLSN(BufferGetPage(nullbuffer), recptr); - } =20 END_CRIT_SECTION(); =20 @@ -150,6 +131,9 @@ spgbuild(Relation heap, Relation index, IndexInfo *in= dexInfo) =20 SpGistUpdateMetaPage(index); =20 + if (RelationNeedsWAL(index)) + generic_log_relation(index); + result =3D (IndexBuildResult *) palloc0(sizeof(IndexBuildResult)); result->heap_tuples =3D reltuples; result->index_tuples =3D buildstate.indtuples; diff --git a/src/backend/access/spgist/spgxlog.c b/src/backend/access/spg= ist/spgxlog.c index 71836ee8a5..ebe6ae8715 100644 --- a/src/backend/access/spgist/spgxlog.c +++ b/src/backend/access/spgist/spgxlog.c @@ -72,38 +72,6 @@ addOrReplaceTuple(Page page, Item tuple, int size, Off= setNumber offset) size); } =20 -static void -spgRedoCreateIndex(XLogReaderState *record) -{ - XLogRecPtr lsn =3D record->EndRecPtr; - Buffer buffer; - Page page; - - buffer =3D XLogInitBufferForRedo(record, 0); - Assert(BufferGetBlockNumber(buffer) =3D=3D SPGIST_METAPAGE_BLKNO); - page =3D (Page) BufferGetPage(buffer); - SpGistInitMetapage(page); - PageSetLSN(page, lsn); - MarkBufferDirty(buffer); - UnlockReleaseBuffer(buffer); - - buffer =3D XLogInitBufferForRedo(record, 1); - Assert(BufferGetBlockNumber(buffer) =3D=3D SPGIST_ROOT_BLKNO); - SpGistInitBuffer(buffer, SPGIST_LEAF); - page =3D (Page) BufferGetPage(buffer); - PageSetLSN(page, lsn); - MarkBufferDirty(buffer); - UnlockReleaseBuffer(buffer); - - buffer =3D XLogInitBufferForRedo(record, 2); - Assert(BufferGetBlockNumber(buffer) =3D=3D SPGIST_NULL_BLKNO); - SpGistInitBuffer(buffer, SPGIST_LEAF | SPGIST_NULLS); - page =3D (Page) BufferGetPage(buffer); - PageSetLSN(page, lsn); - MarkBufferDirty(buffer); - UnlockReleaseBuffer(buffer); -} - static void spgRedoAddLeaf(XLogReaderState *record) { @@ -976,9 +944,6 @@ spg_redo(XLogReaderState *record) oldCxt =3D MemoryContextSwitchTo(opCtx); switch (info) { - case XLOG_SPGIST_CREATE_INDEX: - spgRedoCreateIndex(record); - break; case XLOG_SPGIST_ADD_LEAF: spgRedoAddLeaf(record); break; diff --git a/src/include/access/spgxlog.h b/src/include/access/spgxlog.h index 6527fc9eb1..8199b3f250 100644 --- a/src/include/access/spgxlog.h +++ b/src/include/access/spgxlog.h @@ -18,7 +18,6 @@ #include "storage/off.h" =20 /* XLOG record types for SPGiST */ -#define XLOG_SPGIST_CREATE_INDEX 0x00 #define XLOG_SPGIST_ADD_LEAF 0x10 #define XLOG_SPGIST_MOVE_LEAFS 0x20 #define XLOG_SPGIST_ADD_NODE 0x30 --=20 2.17.1 --------------F161D6F86D13969C52E2CA53 Content-Type: application/pgp-keys; name="pEpkey.asc" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="pEpkey.asc" -----BEGIN PGP PUBLIC KEY BLOCK----- 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3D =3DsBNA -----END PGP PUBLIC KEY BLOCK----- --------------F161D6F86D13969C52E2CA53--