1 /*-------------------------------------------------------------------------
4 * routines for handling GIN posting tree pages.
7 * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * src/backend/access/gin/gindatapage.c
12 *-------------------------------------------------------------------------
17 #include "access/gin_private.h"
18 #include "access/heapam_xlog.h"
19 #include "lib/ilist.h"
20 #include "miscadmin.h"
21 #include "utils/memutils.h"
22 #include "utils/rel.h"
25 * Size of the posting lists stored on leaf pages, in bytes. The code can
26 * deal with any size, but random access is more efficient when a number of
27 * smaller lists are stored, rather than one big list.
29 #define GinPostingListSegmentMaxSize 256
32 * Existing posting lists smaller than this are recompressed, when inserting
35 #define GinPostingListSegmentMinSize 192
38 * At least this many items fit in a GinPostingListSegmentMaxSize-bytes
39 * long segment. This is used when estimating how much space is required
40 * for N items, at minimum.
42 #define MinTuplesPerSegment ((GinPostingListSegmentMaxSize - 2) / 6)
45 * A working struct for manipulating a posting tree leaf page.
49 dlist_head segments; /* a list of leafSegmentInfos */
52 * The following fields represent how the segments are split across
53 * pages, if a page split is required. Filled in by leafRepackItems.
55 dlist_node *lastleft; /* last segment on left page */
56 int lsize; /* total size on left page */
57 int rsize; /* total size on right page */
62 dlist_node node; /* linked list pointers */
65 * The following fields represent the items in this segment.
66 * If 'items' is not NULL, it contains a palloc'd array of the items
67 * in this segment. If 'seg' is not NULL, it contains the items in an
68 * already-compressed format. It can point to an on-disk page (!modified),
69 * or a palloc'd segment in memory. If both are set, they must represent
74 int nitems; /* # of items in 'items', if items != NULL */
76 bool modified; /* is this segment on page already? */
79 static ItemPointer dataLeafPageGetUncompressed(Page page, int *nitems);
80 static void dataSplitPageInternal(GinBtree btree, Buffer origbuf,
82 void *insertdata, BlockNumber updateblkno,
83 XLogRecData **prdata, Page *newlpage, Page *newrpage);
85 static disassembledLeaf *disassembleLeaf(Page page);
86 static bool leafRepackItems(disassembledLeaf *leaf, ItemPointer remaining);
87 static bool addItemsToLeaf(disassembledLeaf *leaf, ItemPointer newItems, int nNewItems);
90 static void dataPlaceToPageLeafRecompress(Buffer buf,
91 disassembledLeaf *leaf,
92 XLogRecData **prdata);
93 static void dataPlaceToPageLeafSplit(Buffer buf,
94 disassembledLeaf *leaf,
95 ItemPointerData lbound, ItemPointerData rbound,
96 XLogRecData **prdata, Page lpage, Page rpage);
99 * Read all TIDs from leaf data page to single uncompressed array.
102 GinDataLeafPageGetItems(Page page, int *nitems)
106 if (GinPageIsCompressed(page))
108 GinPostingList *ptr = GinDataLeafPageGetPostingList(page);
109 Size len = GinDataLeafPageGetPostingListSize(page);
111 result = ginPostingListDecodeAllSegments(ptr, len, nitems);
115 ItemPointer tmp = dataLeafPageGetUncompressed(page, nitems);
117 result = palloc((*nitems) * sizeof(ItemPointerData));
118 memcpy(result, tmp, (*nitems) * sizeof(ItemPointerData));
125 * Places all TIDs from leaf data page to bitmap.
128 GinDataLeafPageGetItemsToTbm(Page page, TIDBitmap *tbm)
130 ItemPointer uncompressed;
133 if (GinPageIsCompressed(page))
135 GinPostingList *segment = GinDataLeafPageGetPostingList(page);
136 Size len = GinDataLeafPageGetPostingListSize(page);
138 nitems = ginPostingListDecodeAllSegmentsToTbm(segment, len, tbm);
142 uncompressed = dataLeafPageGetUncompressed(page, &nitems);
145 tbm_add_tuples(tbm, uncompressed, nitems, false);
152 * Get pointer to the uncompressed array of items on a pre-9.4 format
153 * uncompressed leaf page. The number of items in the array is returned in
157 dataLeafPageGetUncompressed(Page page, int *nitems)
161 Assert(!GinPageIsCompressed(page));
164 * In the old pre-9.4 page format, the whole page content is used for
165 * uncompressed items, and the number of items is stored in 'maxoff'
167 items = (ItemPointer) GinDataPageGetData(page);
168 *nitems = GinPageGetOpaque(page)->maxoff;
174 * Check if we should follow the right link to find the item we're searching
177 * Compares inserting item pointer with the right bound of the current page.
180 dataIsMoveRight(GinBtree btree, Page page)
182 ItemPointer iptr = GinDataPageGetRightBound(page);
184 if (GinPageRightMost(page))
187 return (ginCompareItemPointers(&btree->itemptr, iptr) > 0) ? TRUE : FALSE;
191 * Find correct PostingItem in non-leaf page. It is assumed that this is
192 * the correct page, and the searched value SHOULD be on the page.
195 dataLocateItem(GinBtree btree, GinBtreeStack *stack)
200 PostingItem *pitem = NULL;
202 Page page = BufferGetPage(stack->buffer);
204 Assert(!GinPageIsLeaf(page));
205 Assert(GinPageIsData(page));
209 stack->off = FirstOffsetNumber;
210 stack->predictNumber *= GinPageGetOpaque(page)->maxoff;
211 return btree->getLeftMostChild(btree, page);
214 low = FirstOffsetNumber;
215 maxoff = high = GinPageGetOpaque(page)->maxoff;
222 OffsetNumber mid = low + ((high - low) / 2);
224 pitem = GinDataPageGetPostingItem(page, mid);
229 * Right infinity, page already correctly chosen with a help of
236 pitem = GinDataPageGetPostingItem(page, mid);
237 result = ginCompareItemPointers(&btree->itemptr, &(pitem->key));
243 return PostingItemGetBlockNumber(pitem);
251 Assert(high >= FirstOffsetNumber && high <= maxoff);
254 pitem = GinDataPageGetPostingItem(page, high);
255 return PostingItemGetBlockNumber(pitem);
259 * Find link to blkno on non-leaf page, returns offset of PostingItem
262 dataFindChildPtr(GinBtree btree, Page page, BlockNumber blkno, OffsetNumber storedOff)
265 maxoff = GinPageGetOpaque(page)->maxoff;
268 Assert(!GinPageIsLeaf(page));
269 Assert(GinPageIsData(page));
271 /* if page isn't changed, we return storedOff */
272 if (storedOff >= FirstOffsetNumber && storedOff <= maxoff)
274 pitem = GinDataPageGetPostingItem(page, storedOff);
275 if (PostingItemGetBlockNumber(pitem) == blkno)
279 * we hope, that needed pointer goes to right. It's true if there
282 for (i = storedOff + 1; i <= maxoff; i++)
284 pitem = GinDataPageGetPostingItem(page, i);
285 if (PostingItemGetBlockNumber(pitem) == blkno)
289 maxoff = storedOff - 1;
293 for (i = FirstOffsetNumber; i <= maxoff; i++)
295 pitem = GinDataPageGetPostingItem(page, i);
296 if (PostingItemGetBlockNumber(pitem) == blkno)
300 return InvalidOffsetNumber;
304 * Return blkno of leftmost child
307 dataGetLeftMostPage(GinBtree btree, Page page)
311 Assert(!GinPageIsLeaf(page));
312 Assert(GinPageIsData(page));
313 Assert(GinPageGetOpaque(page)->maxoff >= FirstOffsetNumber);
315 pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
316 return PostingItemGetBlockNumber(pitem);
320 * Add PostingItem to a non-leaf page.
323 GinDataPageAddPostingItem(Page page, PostingItem *data, OffsetNumber offset)
325 OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
328 Assert(PostingItemGetBlockNumber(data) != InvalidBlockNumber);
329 Assert(!GinPageIsLeaf(page));
331 if (offset == InvalidOffsetNumber)
333 ptr = (char *) GinDataPageGetPostingItem(page, maxoff + 1);
337 ptr = (char *) GinDataPageGetPostingItem(page, offset);
338 if (offset != maxoff + 1)
339 memmove(ptr + sizeof(PostingItem),
341 (maxoff - offset + 1) * sizeof(PostingItem));
343 memcpy(ptr, data, sizeof(PostingItem));
345 GinPageGetOpaque(page)->maxoff++;
349 * Delete posting item from non-leaf page
352 GinPageDeletePostingItem(Page page, OffsetNumber offset)
354 OffsetNumber maxoff = GinPageGetOpaque(page)->maxoff;
356 Assert(!GinPageIsLeaf(page));
357 Assert(offset >= FirstOffsetNumber && offset <= maxoff);
359 if (offset != maxoff)
360 memmove(GinDataPageGetPostingItem(page, offset),
361 GinDataPageGetPostingItem(page, offset + 1),
362 sizeof(PostingItem) * (maxoff - offset));
364 GinPageGetOpaque(page)->maxoff--;
368 * Places keys to leaf data page and fills WAL record.
370 static GinPlaceToPageRC
371 dataPlaceToPageLeaf(GinBtree btree, Buffer buf, GinBtreeStack *stack,
372 void *insertdata, XLogRecData **prdata,
373 Page *newlpage, Page *newrpage)
375 GinBtreeDataLeafInsertData *items = insertdata;
376 ItemPointer newItems = &items->items[items->curitem];
377 int maxitems = items->nitem - items->curitem;
378 Page page = BufferGetPage(buf);
380 ItemPointerData rbound;
381 ItemPointerData lbound;
386 MemoryContext tmpCxt;
387 MemoryContext oldCxt;
388 disassembledLeaf *leaf;
389 leafSegmentInfo *lastleftinfo;
390 ItemPointerData maxOldItem;
391 ItemPointerData remaining;
393 Assert(GinPageIsData(page));
395 rbound = *GinDataPageGetRightBound(page);
398 * Count how many of the new items belong to this page.
400 if (!GinPageRightMost(page))
402 for (i = 0; i < maxitems; i++)
404 if (ginCompareItemPointers(&newItems[i], &rbound) > 0)
407 * This needs to go to some other location in the tree. (The
408 * caller should've chosen the insert location so that at least
409 * the first item goes here.)
419 * The following operations do quite a lot of small memory allocations,
420 * create a temporary memory context so that we don't need to keep track
421 * of them individually.
423 tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
424 "Gin split temporary context",
425 ALLOCSET_DEFAULT_MINSIZE,
426 ALLOCSET_DEFAULT_INITSIZE,
427 ALLOCSET_DEFAULT_MAXSIZE);
428 oldCxt = MemoryContextSwitchTo(tmpCxt);
430 leaf = disassembleLeaf(page);
433 * Are we appending to the end of the page? IOW, are all the new items
434 * larger than any of the existing items.
436 if (!dlist_is_empty(&leaf->segments))
438 lastleftinfo = dlist_container(leafSegmentInfo, node,
439 dlist_tail_node(&leaf->segments));
440 if (!lastleftinfo->items)
441 lastleftinfo->items = ginPostingListDecode(lastleftinfo->seg,
442 &lastleftinfo->nitems);
443 maxOldItem = lastleftinfo->items[lastleftinfo->nitems - 1];
444 if (ginCompareItemPointers(&newItems[0], &maxOldItem) >= 0)
451 ItemPointerSetMin(&maxOldItem);
456 * If we're appending to the end of the page, we will append as many items
457 * as we can fit (after splitting), and stop when the pages becomes full.
458 * Otherwise we have to limit the number of new items to insert, because
459 * once we start packing we can't just stop when we run out of space,
460 * because we must make sure that all the old items still fit.
462 if (GinPageIsCompressed(page))
463 freespace = GinDataLeafPageGetFreeSpace(page);
469 * Even when appending, trying to append more items than will fit is
470 * not completely free, because we will merge the new items and old
471 * items into an array below. In the best case, every new item fits in
472 * a single byte, and we can use all the free space on the old page as
473 * well as the new page. For simplicity, ignore segment overhead etc.
475 maxitems = Min(maxitems, freespace + GinDataLeafMaxContentSize);
480 * Calculate a conservative estimate of how many new items we can fit
481 * on the two pages after splitting.
483 * We can use any remaining free space on the old page to store full
484 * segments, as well as the new page. Each full-sized segment can hold
485 * at least MinTuplesPerSegment items
489 nnewsegments = freespace / GinPostingListSegmentMaxSize;
490 nnewsegments += GinDataLeafMaxContentSize / GinPostingListSegmentMaxSize;
491 maxitems = Min(maxitems, nnewsegments * MinTuplesPerSegment);
494 /* Add the new items to the segments */
495 if (!addItemsToLeaf(leaf, newItems, maxitems))
497 /* all items were duplicates, we have nothing to do */
498 items->curitem += maxitems;
500 MemoryContextSwitchTo(oldCxt);
501 MemoryContextDelete(tmpCxt);
507 * Pack the items back to compressed segments, ready for writing to disk.
509 needsplit = leafRepackItems(leaf, &remaining);
512 * Did all the new items fit?
514 * If we're appending, it's OK if they didn't. But as a sanity check,
515 * verify that all the old items fit.
517 if (ItemPointerIsValid(&remaining))
519 if (!append || ItemPointerCompare(&maxOldItem, &remaining) >= 0)
520 elog(ERROR, "could not split GIN page; all old items didn't fit");
522 /* Count how many of the new items did fit. */
523 for (i = 0; i < maxitems; i++)
525 if (ginCompareItemPointers(&newItems[i], &remaining) >= 0)
529 elog(ERROR, "could not split GIN page; no new items fit");
536 * Great, all the items fit on a single page. Write the segments to
537 * the page, and WAL-log appropriately.
539 * Once we start modifying the page, there's no turning back. The
540 * caller is responsible for calling END_CRIT_SECTION() after writing
543 START_CRIT_SECTION();
544 dataPlaceToPageLeafRecompress(buf, leaf, prdata);
547 elog(DEBUG2, "appended %d new items to block %u; %d bytes (%d to go)",
548 maxitems, BufferGetBlockNumber(buf), (int) leaf->lsize,
549 items->nitem - items->curitem - maxitems);
551 elog(DEBUG2, "inserted %d new items to block %u; %d bytes (%d to go)",
552 maxitems, BufferGetBlockNumber(buf), (int) leaf->lsize,
553 items->nitem - items->curitem - maxitems);
560 * We already divided the segments between the left and the right
561 * page. The left page was filled as full as possible, and the rest
562 * overflowed to the right page. When building a new index, that's
563 * good, because the table is scanned from beginning to end and there
564 * won't be any more insertions to the left page during the build.
565 * This packs the index as tight as possible. But otherwise, split
566 * 50/50, by moving segments from the left page to the right page
567 * until they're balanced.
569 * As a further heuristic, when appending items to the end of the
570 * page, split 75/25, one the assumption that subsequent insertions
571 * will probably also go to the end. This packs the index somewhat
572 * tighter when appending to a table, which is very common.
576 while (dlist_has_prev(&leaf->segments, leaf->lastleft))
578 lastleftinfo = dlist_container(leafSegmentInfo, node, leaf->lastleft);
580 segsize = SizeOfGinPostingList(lastleftinfo->seg);
583 if ((leaf->lsize - segsize) - (leaf->lsize - segsize) < BLCKSZ / 4)
588 if ((leaf->lsize - segsize) - (leaf->rsize + segsize) < 0)
592 /* don't consider segments moved to right as unmodified */
593 lastleftinfo->modified = true;
594 leaf->lsize -= segsize;
595 leaf->rsize += segsize;
596 leaf->lastleft = dlist_prev_node(&leaf->segments, leaf->lastleft);
599 Assert(leaf->lsize <= GinDataLeafMaxContentSize);
600 Assert(leaf->rsize <= GinDataLeafMaxContentSize);
603 * Fetch the max item in the left page's last segment; it becomes the
604 * right bound of the page.
606 lastleftinfo = dlist_container(leafSegmentInfo, node, leaf->lastleft);
607 if (!lastleftinfo->items)
608 lastleftinfo->items = ginPostingListDecode(lastleftinfo->seg,
609 &lastleftinfo->nitems);
610 lbound = lastleftinfo->items[lastleftinfo->nitems - 1];
612 *newlpage = MemoryContextAlloc(oldCxt, BLCKSZ);
613 *newrpage = MemoryContextAlloc(oldCxt, BLCKSZ);
615 dataPlaceToPageLeafSplit(buf, leaf, lbound, rbound,
616 prdata, *newlpage, *newrpage);
618 Assert(GinPageRightMost(page) ||
619 ginCompareItemPointers(GinDataPageGetRightBound(*newlpage),
620 GinDataPageGetRightBound(*newrpage)) < 0);
623 elog(DEBUG2, "appended %d items to block %u; split %d/%d (%d to go)",
624 maxitems, BufferGetBlockNumber(buf), (int) leaf->lsize, (int) leaf->rsize,
625 items->nitem - items->curitem - maxitems);
627 elog(DEBUG2, "inserted %d items to block %u; split %d/%d (%d to go)",
628 maxitems, BufferGetBlockNumber(buf), (int) leaf->lsize, (int) leaf->rsize,
629 items->nitem - items->curitem - maxitems);
632 MemoryContextSwitchTo(oldCxt);
633 MemoryContextDelete(tmpCxt);
635 items->curitem += maxitems;
637 return needsplit ? SPLIT : INSERTED;
641 * Vacuum a posting tree leaf page.
644 ginVacuumPostingTreeLeaf(Relation indexrel, Buffer buffer, GinVacuumState *gvs)
646 Page page = BufferGetPage(buffer);
647 disassembledLeaf *leaf;
648 bool removedsomething = false;
651 leaf = disassembleLeaf(page);
653 /* Vacuum each segment. */
654 dlist_foreach(iter, &leaf->segments)
656 leafSegmentInfo *seginfo = dlist_container(leafSegmentInfo, node, iter.cur);
662 seginfo->items = ginPostingListDecode(seginfo->seg,
665 oldsegsize = SizeOfGinPostingList(seginfo->seg);
667 oldsegsize = GinDataLeafMaxContentSize;
669 cleaned = ginVacuumItemPointers(gvs,
673 pfree(seginfo->items);
674 seginfo->items = NULL;
682 seginfo->seg = ginCompressPostingList(cleaned,
686 /* Removing an item never increases the size of the segment */
687 if (npacked != ncleaned)
688 elog(ERROR, "could not fit vacuumed posting list");
693 seginfo->items = NULL;
695 seginfo->nitems = ncleaned;
696 seginfo->modified = true;
698 removedsomething = true;
703 * If we removed any items, reconstruct the page from the pieces.
705 * We don't try to re-encode the segments here, even though some of them
706 * might be really small, now that we've removed some items from them.
707 * It seems like a waste of effort, as there isn't really any benefit from
708 * larger segments per se; larger segments only help you to pack more
709 * items in the same space. We might as well delay doing that until the
710 * next insertion, which will need to re-encode at least part of the page
713 * Also note if the page was in uncompressed, pre-9.4 format before, it
714 * is now represented as one huge segment that contains all the items.
715 * It might make sense to split that, to speed up random access, but we
716 * don't bother. You'll have to REINDEX anyway if you want the full gain
717 * of the new tighter index format.
719 if (removedsomething)
721 XLogRecData *payloadrdata;
723 START_CRIT_SECTION();
724 dataPlaceToPageLeafRecompress(buffer, leaf, &payloadrdata);
726 MarkBufferDirty(buffer);
728 if (RelationNeedsWAL(indexrel))
732 ginxlogVacuumDataLeafPage xlrec;
734 xlrec.node = indexrel->rd_node;
735 xlrec.blkno = BufferGetBlockNumber(buffer);
737 rdata.buffer = InvalidBuffer;
738 rdata.data = (char *) &xlrec;
739 rdata.len = offsetof(ginxlogVacuumDataLeafPage, data);
740 rdata.next = payloadrdata;
742 recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_DATA_LEAF_PAGE, &rdata);
743 PageSetLSN(page, recptr);
751 * Assemble a disassembled posting tree leaf page back to a buffer.
753 * *prdata is filled with WAL information about this operation. The caller
754 * is responsible for inserting to the WAL, along with any other information
755 * about the operation that triggered this recompression.
757 * NOTE: The segment pointers can point directly to the same buffer, with
758 * the limitation that any earlier segment must not overlap with an original,
759 * later segment. In other words, some segments may point the original buffer
760 * as long as you don't make any segments larger. Currently, leafRepackItems
761 * satisies this rule because it rewrites all segments after the first
762 * modified one, and vacuum can only make segments shorter.
765 dataPlaceToPageLeafRecompress(Buffer buf, disassembledLeaf *leaf,
766 XLogRecData **prdata)
768 Page page = BufferGetPage(buf);
772 bool modified = false;
775 * these must be static so they can be returned to caller (no pallocs
776 * since we're in a critical section!)
778 static ginxlogRecompressDataLeaf recompress_xlog;
779 static XLogRecData rdata[2];
781 ptr = (char *) GinDataLeafPageGetPostingList(page);
785 dlist_foreach(iter, &leaf->segments)
787 leafSegmentInfo *seginfo = dlist_container(leafSegmentInfo, node, iter.cur);
790 if (seginfo->modified)
794 * Nothing to do with empty segments, except keep track if they've been
797 if (seginfo->seg == NULL)
799 Assert(seginfo->items == NULL);
803 segsize = SizeOfGinPostingList(seginfo->seg);
806 unmodifiedsize += segsize;
810 * Use memmove rather than memcpy, in case the segment points
813 memmove(ptr, seginfo->seg, segsize);
818 Assert(newsize <= GinDataLeafMaxContentSize);
819 GinDataLeafPageSetPostingListSize(page, newsize);
821 /* Reset these in case the page was in pre-9.4 format before */
822 GinPageSetCompressed(page);
823 GinPageGetOpaque(page)->maxoff = InvalidOffsetNumber;
826 recompress_xlog.length = (uint16) newsize;
827 recompress_xlog.unmodifiedsize = unmodifiedsize;
829 rdata[0].buffer = InvalidBuffer;
830 rdata[0].data = (char *) &recompress_xlog;
831 rdata[0].len = offsetof(ginxlogRecompressDataLeaf, newdata);
832 rdata[0].next = &rdata[1];
834 rdata[1].buffer = buf;
835 rdata[1].buffer_std = TRUE;
836 rdata[1].data = ((char *) GinDataLeafPageGetPostingList(page)) + unmodifiedsize;
837 rdata[1].len = newsize - unmodifiedsize;
838 rdata[1].next = NULL;
844 * Like dataPlaceToPageLeafRecompress, but writes the disassembled leaf
845 * segments to two pages instead of one.
847 * This is different from the non-split cases in that this does not modify
848 * the original page directly, but to temporary in-memory copies of the new
849 * left and right pages.
852 dataPlaceToPageLeafSplit(Buffer buf, disassembledLeaf *leaf,
853 ItemPointerData lbound, ItemPointerData rbound,
854 XLogRecData **prdata, Page lpage, Page rpage)
861 dlist_node *firstright;
862 leafSegmentInfo *seginfo;
863 /* these must be static so they can be returned to caller */
864 static ginxlogSplitDataLeaf split_xlog;
865 static XLogRecData rdata[3];
867 /* Initialize temporary pages to hold the new left and right pages */
868 GinInitPage(lpage, GIN_DATA | GIN_LEAF | GIN_COMPRESSED, BLCKSZ);
869 GinInitPage(rpage, GIN_DATA | GIN_LEAF | GIN_COMPRESSED, BLCKSZ);
872 * Copy the segments that go to the left page.
874 * XXX: We should skip copying the unmodified part of the left page, like
875 * we do when recompressing.
878 ptr = (char *) GinDataLeafPageGetPostingList(lpage);
879 firstright = dlist_next_node(&leaf->segments, leaf->lastleft);
880 for (node = dlist_head_node(&leaf->segments);
882 node = dlist_next_node(&leaf->segments, node))
884 seginfo = dlist_container(leafSegmentInfo, node, node);
885 segsize = SizeOfGinPostingList(seginfo->seg);
887 memcpy(ptr, seginfo->seg, segsize);
891 Assert(lsize == leaf->lsize);
892 GinDataLeafPageSetPostingListSize(lpage, lsize);
893 *GinDataPageGetRightBound(lpage) = lbound;
895 /* Copy the segments that go to the right page */
896 ptr = (char *) GinDataLeafPageGetPostingList(rpage);
898 for (node = firstright;
900 node = dlist_next_node(&leaf->segments, node))
902 seginfo = dlist_container(leafSegmentInfo, node, node);
903 segsize = SizeOfGinPostingList(seginfo->seg);
905 memcpy(ptr, seginfo->seg, segsize);
909 if (!dlist_has_next(&leaf->segments, node))
912 Assert(rsize == leaf->rsize);
913 GinDataLeafPageSetPostingListSize(rpage, rsize);
914 *GinDataPageGetRightBound(rpage) = rbound;
916 /* Create WAL record */
917 split_xlog.lsize = lsize;
918 split_xlog.rsize = rsize;
919 split_xlog.lrightbound = lbound;
920 split_xlog.rrightbound = rbound;
922 rdata[0].buffer = InvalidBuffer;
923 rdata[0].data = (char *) &split_xlog;
924 rdata[0].len = sizeof(ginxlogSplitDataLeaf);
925 rdata[0].next = &rdata[1];
927 rdata[1].buffer = InvalidBuffer;
928 rdata[1].data = (char *) GinDataLeafPageGetPostingList(lpage);
929 rdata[1].len = lsize;
930 rdata[1].next = &rdata[2];
932 rdata[2].buffer = InvalidBuffer;
933 rdata[2].data = (char *) GinDataLeafPageGetPostingList(rpage);
934 rdata[2].len = rsize;
935 rdata[2].next = NULL;
941 * Place a PostingItem to page, and fill a WAL record.
943 * If the item doesn't fit, returns false without modifying the page.
945 * In addition to inserting the given item, the downlink of the existing item
946 * at 'off' is updated to point to 'updateblkno'.
948 static GinPlaceToPageRC
949 dataPlaceToPageInternal(GinBtree btree, Buffer buf, GinBtreeStack *stack,
950 void *insertdata, BlockNumber updateblkno,
951 XLogRecData **prdata, Page *newlpage, Page *newrpage)
953 Page page = BufferGetPage(buf);
954 OffsetNumber off = stack->off;
956 /* these must be static so they can be returned to caller */
957 static XLogRecData rdata;
958 static ginxlogInsertDataInternal data;
960 /* split if we have to */
961 if (GinNonLeafDataPageGetFreeSpace(page) < sizeof(PostingItem))
963 dataSplitPageInternal(btree, buf, stack, insertdata, updateblkno,
964 prdata, newlpage, newrpage);
969 Assert(GinPageIsData(page));
971 START_CRIT_SECTION();
973 /* Update existing downlink to point to next page (on internal page) */
974 pitem = GinDataPageGetPostingItem(page, off);
975 PostingItemSetBlockNumber(pitem, updateblkno);
978 pitem = (PostingItem *) insertdata;
979 GinDataPageAddPostingItem(page, pitem, off);
982 data.newitem = *pitem;
985 rdata.buffer_std = false;
986 rdata.data = (char *) &data;
987 rdata.len = sizeof(ginxlogInsertDataInternal);
994 * Places an item (or items) to a posting tree. Calls relevant function of
995 * internal of leaf page because they are handled very differently.
997 static GinPlaceToPageRC
998 dataPlaceToPage(GinBtree btree, Buffer buf, GinBtreeStack *stack,
999 void *insertdata, BlockNumber updateblkno,
1000 XLogRecData **prdata,
1001 Page *newlpage, Page *newrpage)
1003 Page page = BufferGetPage(buf);
1005 Assert(GinPageIsData(page));
1007 if (GinPageIsLeaf(page))
1008 return dataPlaceToPageLeaf(btree, buf, stack, insertdata,
1009 prdata, newlpage, newrpage);
1011 return dataPlaceToPageInternal(btree, buf, stack,
1012 insertdata, updateblkno,
1013 prdata, newlpage, newrpage);
1017 * Split page and fill WAL record. Returns a new temp buffer filled with data
1018 * that should go to the left page. The original buffer is left untouched.
1021 dataSplitPageInternal(GinBtree btree, Buffer origbuf,
1022 GinBtreeStack *stack,
1023 void *insertdata, BlockNumber updateblkno,
1024 XLogRecData **prdata, Page *newlpage, Page *newrpage)
1026 Page oldpage = BufferGetPage(origbuf);
1027 OffsetNumber off = stack->off;
1028 int nitems = GinPageGetOpaque(oldpage)->maxoff;
1029 Size pageSize = PageGetPageSize(oldpage);
1030 ItemPointerData oldbound = *GinDataPageGetRightBound(oldpage);
1034 OffsetNumber separator;
1036 /* these must be static so they can be returned to caller */
1037 static ginxlogSplitDataInternal data;
1038 static XLogRecData rdata[4];
1039 static PostingItem allitems[(BLCKSZ / sizeof(PostingItem)) + 1];
1041 lpage = PageGetTempPage(oldpage);
1042 rpage = PageGetTempPage(oldpage);
1043 GinInitPage(lpage, GinPageGetOpaque(oldpage)->flags, pageSize);
1044 GinInitPage(rpage, GinPageGetOpaque(oldpage)->flags, pageSize);
1049 * First construct a new list of PostingItems, which includes all the
1050 * old items, and the new item.
1052 memcpy(allitems, GinDataPageGetPostingItem(oldpage, FirstOffsetNumber),
1053 (off - 1) * sizeof(PostingItem));
1055 allitems[off - 1] = *((PostingItem *) insertdata);
1056 memcpy(&allitems[off], GinDataPageGetPostingItem(oldpage, off),
1057 (nitems - (off - 1)) * sizeof(PostingItem));
1060 /* Update existing downlink to point to next page */
1061 PostingItemSetBlockNumber(&allitems[off], updateblkno);
1064 * When creating a new index, fit as many tuples as possible on the left
1065 * page, on the assumption that the table is scanned from beginning to
1066 * end. This packs the index as tight as possible.
1068 if (btree->isBuild && GinPageRightMost(oldpage))
1069 separator = GinNonLeafDataPageGetFreeSpace(rpage) / sizeof(PostingItem);
1071 separator = nitems / 2;
1073 memcpy(GinDataPageGetPostingItem(lpage, FirstOffsetNumber), allitems, separator * sizeof(PostingItem));
1074 GinPageGetOpaque(lpage)->maxoff = separator;
1075 memcpy(GinDataPageGetPostingItem(rpage, FirstOffsetNumber),
1076 &allitems[separator], (nitems - separator) * sizeof(PostingItem));
1077 GinPageGetOpaque(rpage)->maxoff = nitems - separator;
1079 /* set up right bound for left page */
1080 bound = GinDataPageGetRightBound(lpage);
1081 *bound = GinDataPageGetPostingItem(lpage,
1082 GinPageGetOpaque(lpage)->maxoff)->key;
1084 /* set up right bound for right page */
1085 *GinDataPageGetRightBound(rpage) = oldbound;
1087 data.separator = separator;
1088 data.nitem = nitems;
1089 data.rightbound = oldbound;
1091 rdata[0].buffer = InvalidBuffer;
1092 rdata[0].data = (char *) &data;
1093 rdata[0].len = sizeof(ginxlogSplitDataInternal);
1094 rdata[0].next = &rdata[1];
1096 rdata[1].buffer = InvalidBuffer;
1097 rdata[1].data = (char *) allitems;
1098 rdata[1].len = nitems * sizeof(PostingItem);
1099 rdata[1].next = NULL;
1106 * Construct insertion payload for inserting the downlink for given buffer.
1109 dataPrepareDownlink(GinBtree btree, Buffer lbuf)
1111 PostingItem *pitem = palloc(sizeof(PostingItem));
1112 Page lpage = BufferGetPage(lbuf);
1114 PostingItemSetBlockNumber(pitem, BufferGetBlockNumber(lbuf));
1115 pitem->key = *GinDataPageGetRightBound(lpage);
1121 * Fills new root by right bound values from child.
1122 * Also called from ginxlog, should not use btree
1125 ginDataFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage)
1130 li.key = *GinDataPageGetRightBound(lpage);
1131 PostingItemSetBlockNumber(&li, lblkno);
1132 GinDataPageAddPostingItem(root, &li, InvalidOffsetNumber);
1134 ri.key = *GinDataPageGetRightBound(rpage);
1135 PostingItemSetBlockNumber(&ri, rblkno);
1136 GinDataPageAddPostingItem(root, &ri, InvalidOffsetNumber);
1140 /*** Functions to work with disassembled leaf pages ***/
1143 * Disassemble page into a disassembledLeaf struct.
1145 static disassembledLeaf *
1146 disassembleLeaf(Page page)
1148 disassembledLeaf *leaf;
1149 GinPostingList *seg;
1153 leaf = palloc0(sizeof(disassembledLeaf));
1154 dlist_init(&leaf->segments);
1156 if (GinPageIsCompressed(page))
1159 * Create a leafSegment entry for each segment.
1161 seg = GinDataLeafPageGetPostingList(page);
1162 segbegin = (Pointer) seg;
1163 segend = segbegin + GinDataLeafPageGetPostingListSize(page);
1164 while ((Pointer) seg < segend)
1166 leafSegmentInfo *seginfo = palloc(sizeof(leafSegmentInfo));
1169 seginfo->items = NULL;
1170 seginfo->nitems = 0;
1171 seginfo->modified = false;
1172 dlist_push_tail(&leaf->segments, &seginfo->node);
1174 seg = GinNextPostingListSegment(seg);
1180 * A pre-9.4 format uncompressed page is represented by a single
1181 * segment, with an array of items.
1183 ItemPointer uncompressed;
1185 leafSegmentInfo *seginfo;
1187 uncompressed = dataLeafPageGetUncompressed(page, &nuncompressed);
1189 seginfo = palloc(sizeof(leafSegmentInfo));
1191 seginfo->seg = NULL;
1192 seginfo->items = palloc(nuncompressed * sizeof(ItemPointerData));
1193 memcpy(seginfo->items, uncompressed, nuncompressed * sizeof(ItemPointerData));
1194 seginfo->nitems = nuncompressed;
1195 /* make sure we rewrite this to disk */
1196 seginfo->modified = true;
1198 dlist_push_tail(&leaf->segments, &seginfo->node);
1205 * Distribute newItems to the segments.
1207 * Any segments that acquire new items are decoded, and the new items are
1208 * merged with the old items.
1210 * Returns true if any new items were added. False means they were all
1211 * duplicates of existing items on the page.
1214 addItemsToLeaf(disassembledLeaf *leaf, ItemPointer newItems, int nNewItems)
1217 ItemPointer nextnew = newItems;
1218 int newleft = nNewItems;
1219 bool modified = false;
1222 * If the page is completely empty, just construct one new segment to
1223 * hold all the new items.
1225 if (dlist_is_empty(&leaf->segments))
1227 /* create a new segment for the new entries */
1228 leafSegmentInfo *seginfo = palloc(sizeof(leafSegmentInfo));
1230 seginfo->seg = NULL;
1231 seginfo->items = newItems;
1232 seginfo->nitems = nNewItems;
1233 seginfo->modified = true;
1234 dlist_push_tail(&leaf->segments, &seginfo->node);
1238 dlist_foreach(iter, &leaf->segments)
1240 leafSegmentInfo *cur = (leafSegmentInfo *) dlist_container(leafSegmentInfo, node, iter.cur);
1242 ItemPointer tmpitems;
1246 * How many of the new items fall into this segment?
1248 if (!dlist_has_next(&leaf->segments, iter.cur))
1252 leafSegmentInfo *next;
1253 ItemPointerData next_first;
1255 next = (leafSegmentInfo *) dlist_container(leafSegmentInfo, node,
1256 dlist_next_node(&leaf->segments, iter.cur));
1258 next_first = next->items[0];
1261 Assert(next->seg != NULL);
1262 next_first = next->seg->first;
1266 while (nthis < newleft && ginCompareItemPointers(&nextnew[nthis], &next_first) < 0)
1272 /* Merge the new items with the existing items. */
1274 cur->items = ginPostingListDecode(cur->seg, &cur->nitems);
1276 tmpitems = palloc((cur->nitems + nthis) * sizeof(ItemPointerData));
1277 ntmpitems = ginMergeItemPointers(tmpitems,
1278 cur->items, cur->nitems,
1280 if (ntmpitems != cur->nitems)
1282 cur->items = tmpitems;
1283 cur->nitems = ntmpitems;
1285 modified = cur->modified = true;
1298 * Recompresses all segments that have been modified.
1300 * If not all the items fit on two pages (ie. after split), we store as
1301 * many items as fit, and set *remaining to the first item that didn't fit.
1302 * If all items fit, *remaining is set to invalid.
1304 * Returns true if the page has to be split.
1306 * XXX: Actually, this re-encodes all segments after the first one that was
1307 * modified, to make sure the new segments are all more or less of equal
1308 * length. That's unnecessarily aggressive; if we've only added a single item
1309 * to one segment, for example, we could re-encode just that single segment,
1310 * as long as it's still smaller than, say, 2x the normal segment size.
1313 leafRepackItems(disassembledLeaf *leaf, ItemPointer remaining)
1316 ItemPointer allitems;
1321 dlist_mutable_iter miter;
1322 dlist_head recode_list;
1326 ItemPointerSetInvalid(remaining);
1329 * Find the first segment that needs to be re-coded. Move all segments
1330 * that need recoding to separate list, and count the total number of
1331 * items in them. Also, add up the number of bytes in unmodified segments
1334 dlist_init(&recode_list);
1338 dlist_foreach_modify(miter, &leaf->segments)
1340 leafSegmentInfo *seginfo = dlist_container(leafSegmentInfo, node, miter.cur);
1342 /* If the segment was modified, re-encode it */
1343 if (seginfo->modified || seginfo->seg == NULL)
1346 * Also re-encode abnormally small or large segments. (Vacuum can
1347 * leave behind small segments, and conversion from pre-9.4 format
1348 * can leave behind large segments).
1350 else if (SizeOfGinPostingList(seginfo->seg) < GinPostingListSegmentMinSize)
1352 else if (SizeOfGinPostingList(seginfo->seg) > GinPostingListSegmentMaxSize)
1357 if (!seginfo->items)
1358 seginfo->items = ginPostingListDecode(seginfo->seg,
1360 nrecode += seginfo->nitems;
1362 dlist_delete(miter.cur);
1363 dlist_push_tail(&recode_list, &seginfo->node);
1366 pgused += SizeOfGinPostingList(seginfo->seg);
1370 return false; /* nothing to do */
1373 * Construct one big array of the items that need to be re-encoded.
1375 allitems = palloc(nrecode * sizeof(ItemPointerData));
1377 dlist_foreach(iter, &recode_list)
1379 leafSegmentInfo *seginfo = dlist_container(leafSegmentInfo, node, iter.cur);
1380 memcpy(&allitems[nallitems], seginfo->items, seginfo->nitems * sizeof(ItemPointerData));
1381 nallitems += seginfo->nitems;
1383 Assert(nallitems == nrecode);
1386 * Start packing the items into segments. Stop when we have consumed
1387 * enough space to fill both pages, or we run out of items.
1391 while (totalpacked < nallitems)
1393 leafSegmentInfo *seginfo;
1395 GinPostingList *seg;
1398 seg = ginCompressPostingList(&allitems[totalpacked],
1399 nallitems - totalpacked,
1400 GinPostingListSegmentMaxSize,
1402 segsize = SizeOfGinPostingList(seg);
1403 if (pgused + segsize > GinDataLeafMaxContentSize)
1407 /* switch to right page */
1409 leaf->lastleft = dlist_tail_node(&leaf->segments);
1411 leaf->lsize = pgused;
1416 /* filled both pages */
1417 *remaining = allitems[totalpacked];
1422 seginfo = palloc(sizeof(leafSegmentInfo));
1424 seginfo->items = &allitems[totalpacked];
1425 seginfo->nitems = npacked;
1426 seginfo->modified = true;
1428 dlist_push_tail(&leaf->segments, &seginfo->node);
1431 totalpacked += npacked;
1436 leaf->lsize = pgused;
1440 leaf->rsize = pgused;
1442 Assert(leaf->lsize <= GinDataLeafMaxContentSize);
1443 Assert(leaf->rsize <= GinDataLeafMaxContentSize);
1449 /*** Functions that are exported to the rest of the GIN code ***/
1452 * Creates new posting tree containing the given TIDs. Returns the page
1453 * number of the root of the new posting tree.
1455 * items[] must be in sorted order with no duplicates.
1458 createPostingTree(Relation index, ItemPointerData *items, uint32 nitems,
1459 GinStatsData *buildStats)
1469 * Create the root page.
1471 buffer = GinNewBuffer(index);
1472 page = BufferGetPage(buffer);
1473 blkno = BufferGetBlockNumber(buffer);
1475 START_CRIT_SECTION();
1477 GinInitPage(page, GIN_DATA | GIN_LEAF | GIN_COMPRESSED, BLCKSZ);
1478 GinPageGetOpaque(page)->rightlink = InvalidBlockNumber;
1481 * Write as many of the items to the root page as fit. In segments
1482 * of max GinPostingListSegmentMaxSize bytes each.
1486 ptr = (Pointer) GinDataLeafPageGetPostingList(page);
1487 while (nrootitems < nitems)
1489 GinPostingList *segment;
1493 segment = ginCompressPostingList(&items[nrootitems],
1494 nitems - nrootitems,
1495 GinPostingListSegmentMaxSize,
1497 segsize = SizeOfGinPostingList(segment);
1498 if (rootsize + segsize > GinDataLeafMaxContentSize)
1501 memcpy(ptr, segment, segsize);
1503 rootsize += segsize;
1504 nrootitems += npacked;
1507 GinDataLeafPageSetPostingListSize(page, rootsize);
1508 MarkBufferDirty(buffer);
1510 elog(DEBUG2, "created GIN posting tree with %d items", nrootitems);
1512 if (RelationNeedsWAL(index))
1515 XLogRecData rdata[2];
1516 ginxlogCreatePostingTree data;
1518 data.node = index->rd_node;
1520 data.size = rootsize;
1522 rdata[0].buffer = InvalidBuffer;
1523 rdata[0].data = (char *) &data;
1524 rdata[0].len = sizeof(ginxlogCreatePostingTree);
1525 rdata[0].next = &rdata[1];
1527 rdata[1].buffer = InvalidBuffer;
1528 rdata[1].data = (char *) GinDataLeafPageGetPostingList(page);
1529 rdata[1].len = rootsize;
1530 rdata[1].next = NULL;
1532 recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_CREATE_PTREE, rdata);
1533 PageSetLSN(page, recptr);
1536 UnlockReleaseBuffer(buffer);
1540 /* During index build, count the newly-added data page */
1542 buildStats->nDataPages++;
1545 * Add any remaining TIDs to the newly-created posting tree.
1547 if (nitems > nrootitems)
1549 ginInsertItemPointers(index, blkno,
1551 nitems - nrootitems,
1559 ginPrepareDataScan(GinBtree btree, Relation index, BlockNumber rootBlkno)
1561 memset(btree, 0, sizeof(GinBtreeData));
1563 btree->index = index;
1564 btree->rootBlkno = rootBlkno;
1566 btree->findChildPage = dataLocateItem;
1567 btree->getLeftMostChild = dataGetLeftMostPage;
1568 btree->isMoveRight = dataIsMoveRight;
1569 btree->findItem = NULL;
1570 btree->findChildPtr = dataFindChildPtr;
1571 btree->placeToPage = dataPlaceToPage;
1572 btree->fillRoot = ginDataFillRoot;
1573 btree->prepareDownlink = dataPrepareDownlink;
1575 btree->isData = TRUE;
1576 btree->fullScan = FALSE;
1577 btree->isBuild = FALSE;
1581 * Inserts array of item pointers, may execute several tree scan (very rare)
1584 ginInsertItemPointers(Relation index, BlockNumber rootBlkno,
1585 ItemPointerData *items, uint32 nitem,
1586 GinStatsData *buildStats)
1589 GinBtreeDataLeafInsertData insertdata;
1590 GinBtreeStack *stack;
1592 ginPrepareDataScan(&btree, index, rootBlkno);
1593 btree.isBuild = (buildStats != NULL);
1594 insertdata.items = items;
1595 insertdata.nitem = nitem;
1596 insertdata.curitem = 0;
1598 while (insertdata.curitem < insertdata.nitem)
1600 /* search for the leaf page where the first item should go to */
1601 btree.itemptr = insertdata.items[insertdata.curitem];
1602 stack = ginFindLeafPage(&btree, false);
1604 ginInsertValue(&btree, stack, &insertdata, buildStats);
1609 * Starts a new scan on a posting tree.
1612 ginScanBeginPostingTree(Relation index, BlockNumber rootBlkno)
1615 GinBtreeStack *stack;
1617 ginPrepareDataScan(&btree, index, rootBlkno);
1619 btree.fullScan = TRUE;
1621 stack = ginFindLeafPage(&btree, TRUE);