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);