diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml new file mode 100644 index 6b2ee28..c0ba24a *** a/doc/src/sgml/ref/create_index.sgml --- b/doc/src/sgml/ref/create_index.sgml *************** CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] *** 294,301 **** The optional WITH clause specifies storage parameters for the index. Each index method has its own set of allowed ! storage parameters. The B-tree, hash, GiST and SP-GiST index methods all ! accept this parameter: --- 294,301 ---- The optional WITH clause specifies storage parameters for the index. Each index method has its own set of allowed ! storage parameters. The B-tree, hash, GIN, GiST and SP-GiST index methods ! all accept this parameter: diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c new file mode 100644 index f008fab..418ecec *** a/src/backend/access/common/reloptions.c --- b/src/backend/access/common/reloptions.c *************** *** 15,20 **** --- 15,21 ---- #include "postgres.h" + #include "access/gin_private.h" #include "access/gist_private.h" #include "access/hash.h" #include "access/htup_details.h" *************** static relopt_int intRelOpts[] = *** 133,138 **** --- 134,147 ---- }, { { + "fillfactor", + "Packs gin index pages only to this percentage", + RELOPT_KIND_GIN + }, + GIN_DEFAULT_FILLFACTOR, GIN_MIN_FILLFACTOR, 100 + }, + { + { "autovacuum_vacuum_threshold", "Minimum number of tuple updates or deletes prior to vacuum", RELOPT_KIND_HEAP | RELOPT_KIND_TOAST diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c new file mode 100644 index 8e81f6c..25efdb1 *** a/src/backend/access/gin/gindatapage.c --- b/src/backend/access/gin/gindatapage.c *************** typedef struct *** 55,60 **** --- 55,62 ---- dlist_node *lastleft; /* last segment on left page */ int lsize; /* total size on left page */ int rsize; /* total size on right page */ + int maxdatasize; /* max data size per page + according to fillfactor */ bool oldformat; /* page is in pre-9.4 format on disk */ } disassembledLeaf; *************** GinPageDeletePostingItem(Page page, Offs *** 423,428 **** --- 425,452 ---- } /* + * Returns max data size on the page according to index fillfactor. + */ + int + GinGetMaxDataSize(Relation index) + { + int fillfactor; + + /* Grab option values */ + if (index->rd_options) + { + GinOptions *options = (GinOptions *) index->rd_options; + fillfactor = options->fillfactor; + } + else + { + fillfactor = GIN_DEFAULT_FILLFACTOR; + } + + return GinDataPageMaxDataSize - BLCKSZ * (100 - fillfactor) / 100; + } + + /* * Places keys to leaf data page and fills WAL record. */ static GinPlaceToPageRC *************** dataPlaceToPageLeaf(GinBtree btree, Buff *** 485,490 **** --- 509,515 ---- oldCxt = MemoryContextSwitchTo(tmpCxt); leaf = disassembleLeaf(page); + leaf->maxdatasize = GinGetMaxDataSize(btree->index); /* * Are we appending to the end of the page? IOW, are all the new items *************** dataPlaceToPageLeaf(GinBtree btree, Buff *** 511,525 **** /* * If we're appending to the end of the page, we will append as many items ! * as we can fit (after splitting), and stop when the pages becomes full. ! * Otherwise we have to limit the number of new items to insert, because ! * once we start packing we can't just stop when we run out of space, ! * because we must make sure that all the old items still fit. */ if (GinPageIsCompressed(page)) freespace = GinDataLeafPageGetFreeSpace(page); else freespace = 0; if (append) { /* --- 536,561 ---- /* * If we're appending to the end of the page, we will append as many items ! * as we can fit up to the given fillfactor at build (after splitting), ! * and stop when the pages becomes full at this rate. Otherwise we have to ! * limit the number of new items to insert, because once we start packing ! * we can't just stop when we run out of space, because we must make sure ! * that all the old items still fit. */ if (GinPageIsCompressed(page)) + { freespace = GinDataLeafPageGetFreeSpace(page); + if (btree->isBuild) + { + freespace -= GinDataPageMaxDataSize - leaf->maxdatasize; + freespace = Max(0, freespace); + } + } else + { freespace = 0; + } + if (append) { /* *************** disassembleLeaf(Page page) *** 1280,1285 **** --- 1316,1322 ---- Pointer segend; leaf = palloc0(sizeof(disassembledLeaf)); + leaf->maxdatasize = GinDataPageMaxDataSize; dlist_init(&leaf->segments); if (GinPageIsCompressed(page)) *************** leafRepackItems(disassembledLeaf *leaf, *** 1591,1597 **** * copying to the page. Did we exceed the size that fits on one page? */ segsize = SizeOfGinPostingList(seginfo->seg); ! if (pgused + segsize > GinDataPageMaxDataSize) { if (!needsplit) { --- 1628,1634 ---- * copying to the page. Did we exceed the size that fits on one page? */ segsize = SizeOfGinPostingList(seginfo->seg); ! if (pgused + segsize > leaf->maxdatasize) { if (!needsplit) { *************** createPostingTree(Relation index, ItemPo *** 1683,1688 **** --- 1720,1726 ---- Pointer ptr; int nrootitems; int rootsize; + int maxdatasize; /* Construct the new root page in memory first. */ tmppage = (Page) palloc(BLCKSZ); *************** createPostingTree(Relation index, ItemPo *** 1695,1700 **** --- 1733,1739 ---- */ nrootitems = 0; rootsize = 0; + maxdatasize = GinGetMaxDataSize(index); ptr = (Pointer) GinDataLeafPageGetPostingList(tmppage); while (nrootitems < nitems) { *************** createPostingTree(Relation index, ItemPo *** 1707,1713 **** GinPostingListSegmentMaxSize, &npacked); segsize = SizeOfGinPostingList(segment); ! if (rootsize + segsize > GinDataPageMaxDataSize) break; memcpy(ptr, segment, segsize); --- 1746,1752 ---- GinPostingListSegmentMaxSize, &npacked); segsize = SizeOfGinPostingList(segment); ! if (rootsize + segsize > maxdatasize) break; memcpy(ptr, segment, segsize); diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c new file mode 100644 index 445466b..1f7835e *** a/src/backend/access/gin/ginutil.c --- b/src/backend/access/gin/ginutil.c *************** ginoptions(PG_FUNCTION_ARGS) *** 527,533 **** static const relopt_parse_elt tab[] = { {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, ! pendingListCleanupSize)} }; options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, --- 527,534 ---- static const relopt_parse_elt tab[] = { {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, ! pendingListCleanupSize)}, ! {"fillfactor", RELOPT_TYPE_INT, offsetof(GinOptions, fillfactor)} }; options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h new file mode 100644 index cf2ef80..10420c5 *** a/src/include/access/gin_private.h --- b/src/include/access/gin_private.h *************** typedef struct GinOptions *** 323,328 **** --- 323,329 ---- int32 vl_len_; /* varlena header (do not touch directly!) */ bool useFastUpdate; /* use fast updates? */ int pendingListCleanupSize; /* maximum size of pending list */ + int fillfactor; /* page fillfactor in percent (20..100) */ } GinOptions; #define GIN_DEFAULT_USE_FASTUPDATE true *************** typedef struct GinOptions *** 335,340 **** --- 336,343 ---- ((GinOptions *) (relation)->rd_options)->pendingListCleanupSize : \ gin_pending_list_limit) + #define GIN_MIN_FILLFACTOR 20 + #define GIN_DEFAULT_FILLFACTOR 90 /* Macros for buffer lock/unlock operations */ #define GIN_UNLOCK BUFFER_LOCK_UNLOCK *************** extern BlockNumber createPostingTree(Rel *** 752,757 **** --- 755,761 ---- GinStatsData *buildStats); extern void GinDataPageAddPostingItem(Page page, PostingItem *data, OffsetNumber offset); extern void GinPageDeletePostingItem(Page page, OffsetNumber offset); + extern int GinGetMaxDataSize(Relation index); extern void ginInsertItemPointers(Relation index, BlockNumber rootBlkno, ItemPointerData *items, uint32 nitem, GinStatsData *buildStats);