diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 26ddf32..c7bb25a 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -42,6 +42,17 @@ static OffsetNumber _bt_findinsertloc(Relation rel,
 									  BTStack stack,
 									  Relation heapRel);
 static void _bt_stepright(Relation rel, BTInsertState insertstate, BTStack stack);
+static void _bt_delete_and_insert(Relation rel,
+					Buffer buf,
+					IndexTuple newitup,
+					OffsetNumber newitemoff);
+static void _bt_insertonpg_in_posting(Relation rel, BTScanInsert itup_key,
+						   Buffer buf,
+						   Buffer cbuf,
+						   BTStack stack,
+						   IndexTuple itup,
+						   OffsetNumber newitemoff,
+						   bool split_only_page, int in_posting_offset);
 static void _bt_insertonpg(Relation rel, BTScanInsert itup_key,
 						   Buffer buf,
 						   Buffer cbuf,
@@ -51,7 +62,7 @@ static void _bt_insertonpg(Relation rel, BTScanInsert itup_key,
 						   bool split_only_page);
 static Buffer _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf,
 						Buffer cbuf, OffsetNumber newitemoff, Size newitemsz,
-						IndexTuple newitem);
+						IndexTuple newitem, int in_posting_offset);
 static void _bt_insert_parent(Relation rel, Buffer buf, Buffer rbuf,
 							  BTStack stack, bool is_root, bool is_only);
 static bool _bt_pgaddtup(Page page, Size itemsize, IndexTuple itup,
@@ -300,10 +311,17 @@ top:
 		 * search bounds established within _bt_check_unique when insertion is
 		 * checkingunique.
 		 */
+		insertstate.in_posting_offset = 0;
 		newitemoff = _bt_findinsertloc(rel, &insertstate, checkingunique,
 									   stack, heapRel);
-		_bt_insertonpg(rel, itup_key, insertstate.buf, InvalidBuffer, stack,
-					   itup, newitemoff, false);
+
+		if (insertstate.in_posting_offset)
+			_bt_insertonpg_in_posting(rel, itup_key, insertstate.buf,
+									  InvalidBuffer, stack, itup, newitemoff,
+									  false, insertstate.in_posting_offset);
+		else
+			_bt_insertonpg(rel, itup_key, insertstate.buf, InvalidBuffer,
+						   stack, itup, newitemoff, false);
 	}
 	else
 	{
@@ -914,6 +932,162 @@ _bt_stepright(Relation rel, BTInsertState insertstate, BTStack stack)
 	insertstate->bounds_valid = false;
 }
 
+/*
+ * Delete tuple on newitemoff offset and insert newitup at the same offset.
+ * All checks of free space must have been done before calling this function.
+ *
+ * For use in posting tuple's update.
+ */
+static void
+_bt_delete_and_insert(Relation rel,
+					Buffer buf,
+					IndexTuple newitup,
+					OffsetNumber newitemoff)
+{
+	Page page = BufferGetPage(buf);
+	Size newitupsz = IndexTupleSize(newitup);
+
+	newitupsz = MAXALIGN(newitupsz);
+
+	START_CRIT_SECTION();
+
+	PageIndexTupleDelete(page, newitemoff);
+
+	if (!_bt_pgaddtup(page, newitupsz, newitup, newitemoff))
+		elog(ERROR, "failed to insert compressed item in index \"%s\"",
+			RelationGetRelationName(rel));
+
+	MarkBufferDirty(buf);
+
+	/* Xlog stuff */
+	if (RelationNeedsWAL(rel))
+	{
+		xl_btree_insert xlrec;
+		XLogRecPtr	recptr;
+		BTPageOpaque pageop = (BTPageOpaque) PageGetSpecialPointer(page);
+
+		xlrec.offnum = newitemoff;
+
+		XLogBeginInsert();
+		XLogRegisterData((char *) &xlrec, SizeOfBtreeInsert);
+
+		Assert(P_ISLEAF(pageop));
+
+		/*
+		 * Force pull page write to keep code simple
+		 * TODO: think of using XLOG_BTREE_INSERT_LEAF with a new tuple's data
+		 */
+		XLogRegisterBuffer(0, buf, REGBUF_STANDARD | REGBUF_FORCE_IMAGE);
+		recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_INSERT_LEAF);
+		PageSetLSN(page, recptr);
+	}
+
+	END_CRIT_SECTION();
+}
+
+/*
+ * _bt_insertonpg_in_posting() --
+ *		Insert a tuple on a particular page in the index
+ *		(compression aware version).
+ *
+ * If new tuple's key is equal to the key of a posting tuple that already
+ * exists on the page and it's TID falls inside the min/max range of
+ * existing posting list, update the posting tuple.
+ *
+ * It only can happen on leaf page.
+ *
+ * newitemoff - offset of the posting tuple we must update
+ * in_posting_offset - position of the new tuple's TID in posting list
+ *
+ * If necessary, split the page.
+ */
+static void
+_bt_insertonpg_in_posting(Relation rel,
+			   BTScanInsert itup_key,
+			   Buffer buf,
+			   Buffer cbuf,
+			   BTStack stack,
+			   IndexTuple itup,
+			   OffsetNumber newitemoff,
+			   bool split_only_page,
+			   int in_posting_offset)
+{
+	IndexTuple oldtup;
+	IndexTuple lefttup;
+	IndexTuple righttup;
+	ItemPointerData *ipd;
+	IndexTuple 		newitup;
+	Page			page;
+	int				nipd, nipd_right;
+
+	page = BufferGetPage(buf);
+	/* get old posting tuple */
+	oldtup = (IndexTuple) PageGetItem(page, PageGetItemId(page, newitemoff));
+	Assert(BTreeTupleIsPosting(oldtup));
+	nipd = BTreeTupleGetNPosting(oldtup);
+
+	/* At first, check if the new itempointer fits into the tuple's posting list.
+	*  Also check if new itempointer fits into the page.
+	 * If not, posting tuple's split is required in both cases.
+	 */
+	if ((BTMaxItemSize(page) < (IndexTupleSize(oldtup) + sizeof(ItemIdData))) ||
+		PageGetFreeSpace(page) < IndexTupleSize(oldtup) + sizeof(ItemPointerData))
+	{
+		/*
+		 * Split posting tuple into two halves.
+		 * Left tuple contains all item pointes less than the new one
+		 * and right tuple contains new item pointer and all to the right.
+		 * TODO Probably we can come up with more clever algorithm.
+		 */
+		lefttup = BTreeFormPostingTuple(oldtup, BTreeTupleGetPosting(oldtup), in_posting_offset);
+
+		nipd_right = nipd - in_posting_offset + 1;
+		ipd = palloc0(sizeof(ItemPointerData)*(nipd_right));
+		/* insert new item pointer */
+		memcpy(ipd, itup, sizeof(ItemPointerData));
+		/* copy item pointers from old tuple */
+		memcpy(ipd+1,
+			   BTreeTupleGetPostingN(oldtup, in_posting_offset),
+			   sizeof(ItemPointerData)*(nipd-in_posting_offset));
+
+		righttup = BTreeFormPostingTuple(oldtup, ipd, nipd_right);
+
+		/*
+		 * Replace old tuple with a left tuple on a page.
+		 * And insert righttuple using ordinary _bt_insertonpg() function
+		 * If split is required, _bt_insertonpg will handle it.
+		 */
+		_bt_delete_and_insert(rel, buf, lefttup, newitemoff);
+		_bt_insertonpg(rel, itup_key, buf, InvalidBuffer,
+						stack, righttup, newitemoff, false);
+
+		pfree(ipd);
+		pfree(lefttup);
+		pfree(righttup);
+	}
+	else
+	{
+		ipd = palloc0(sizeof(ItemPointerData)*(nipd + 1));
+
+		/* copy item pointers from old tuple into ipd */
+		memcpy(ipd, BTreeTupleGetPosting(oldtup), sizeof(ItemPointerData)*in_posting_offset);
+		/* add item pointer of the new tuple into ipd */
+		memcpy(ipd+in_posting_offset, itup, sizeof(ItemPointerData));
+		/* copy item pointers from old tuple into ipd */
+		memcpy(ipd+in_posting_offset+1,
+			BTreeTupleGetPostingN(oldtup, in_posting_offset),
+			sizeof(ItemPointerData)*(nipd-in_posting_offset));
+
+		newitup = BTreeFormPostingTuple(itup, ipd, nipd+1);
+
+		_bt_delete_and_insert(rel, buf, newitup, newitemoff);
+
+		pfree(ipd);
+		pfree(newitup);
+		_bt_relbuf(rel, buf);
+	}
+}
+
 /*----------
  *	_bt_insertonpg() -- Insert a tuple on a particular page in the index.
  *
@@ -1010,7 +1184,7 @@ _bt_insertonpg(Relation rel,
 				 BlockNumberIsValid(RelationGetTargetBlock(rel))));
 
 		/* split the buffer into left and right halves */
-		rbuf = _bt_split(rel, itup_key, buf, cbuf, newitemoff, itemsz, itup);
+		rbuf = _bt_split(rel, itup_key, buf, cbuf, newitemoff, itemsz, itup, 0);
 		PredicateLockPageSplit(rel,
 							   BufferGetBlockNumber(buf),
 							   BufferGetBlockNumber(rbuf));
@@ -1228,7 +1402,8 @@ _bt_insertonpg(Relation rel,
  */
 static Buffer
 _bt_split(Relation rel, BTScanInsert itup_key, Buffer buf, Buffer cbuf,
-		  OffsetNumber newitemoff, Size newitemsz, IndexTuple newitem)
+		  OffsetNumber newitemoff, Size newitemsz, IndexTuple newitem,
+		  int in_posting_offset)
 {
 	Buffer		rbuf;
 	Page		origpage;
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 49a1aae..58a050f 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -507,7 +507,7 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate)
 
 		/* We have low <= mid < high, so mid points at a real slot */
 
-		result = _bt_compare(rel, key, page, mid);
+		result = _bt_compare_posting(rel, key, page, mid, &(insertstate->in_posting_offset));
 
 		if (result >= cmpval)
 			low = mid + 1;
@@ -536,6 +536,45 @@ _bt_binsrch_insert(Relation rel, BTInsertState insertstate)
 	return low;
 }
 
+/*
+ * Compare insertion-type scankey to tuple on a page,
+ * taking into account posting tuples.
+ * If the key of the posting tuple is equal to scankey,
+ * find exact postition inside the posting list,
+ * using TID as extra attribut.
+ */
+int32
+_bt_compare_posting(Relation rel,
+			BTScanInsert key,
+			Page page,
+			OffsetNumber offnum,
+			int *in_posting_offset)
+{
+	IndexTuple itup = (IndexTuple) PageGetItem(page,
+											   PageGetItemId(page, offnum));
+	int result = _bt_compare(rel, key, page, offnum);
+	if (BTreeTupleIsPosting(itup) && result == 0)
+	{
+		int low, high, mid, res;
+
+		low = 0;
+		high = BTreeTupleGetNPosting(itup);
+
+		while (high > low)
+		{
+			mid = low + ((high - low) / 2);
+			res = ItemPointerCompare(key->scantid, BTreeTupleGetPostingN(itup, mid));
+
+			if (res == -1)
+				high = mid;
+			else
+				low = mid + 1;
+		}
+		*in_posting_offset = mid;
+	}
+		return result;
+}
+
 /*----------
  *	_bt_compare() -- Compare insertion-type scankey to tuple on a page.
  *
@@ -668,64 +707,112 @@ _bt_compare(Relation rel,
 	 * Use the heap TID attribute and scantid to try to break the tie.  The
 	 * rules are the same as any other key attribute -- only the
 	 * representation differs.
-	 * TODO when itup is a posting tuple, the check becomes more complex.
-	 * we have an option that key nor smaller, nor larger than the tuple,
-	 * but exactly in between of BTreeTupleGetMinTID to BTreeTupleGetMaxTID.
+	 *
+	 * When itup is a posting tuple, the check becomes more complex.
+	 * It is possible that the scankey belongs to the tuple's posting list
+	 * TID range.
+	 * _bt_compare() is multipurpose, so it just returns 0 for a fact that
+	 * key matches tuple at this offset.
+	 * Use special _bt_compare_posting() wrapper function to handle this case
+	 * and perform recheck for posting tuple, finding exact position of the
+	 * scankey.
 	 */
-	heapTid = BTreeTupleGetHeapTID(itup);
-	if (key->scantid == NULL)
+	if (!BTreeTupleIsPosting(itup))
 	{
+		heapTid = BTreeTupleGetHeapTID(itup);
+		if (key->scantid == NULL)
+		{
+			/*
+			* Most searches have a scankey that is considered greater than a
+			* truncated pivot tuple if and when the scankey has equal values for
+			* attributes up to and including the least significant untruncated
+			* attribute in tuple.
+			*
+			* For example, if an index has the minimum two attributes (single
+			* user key attribute, plus heap TID attribute), and a page's high key
+			* is ('foo', -inf), and scankey is ('foo', <omitted>), the search
+			* will not descend to the page to the left.  The search will descend
+			* right instead.  The truncated attribute in pivot tuple means that
+			* all non-pivot tuples on the page to the left are strictly < 'foo',
+			* so it isn't necessary to descend left.  In other words, search
+			* doesn't have to descend left because it isn't interested in a match
+			* that has a heap TID value of -inf.
+			*
+			* However, some searches (pivotsearch searches) actually require that
+			* we descend left when this happens.  -inf is treated as a possible
+			* match for omitted scankey attribute(s).  This is needed by page
+			* deletion, which must re-find leaf pages that are targets for
+			* deletion using their high keys.
+			*
+			* Note: the heap TID part of the test ensures that scankey is being
+			* compared to a pivot tuple with one or more truncated key
+			* attributes.
+			*
+			* Note: pg_upgrade'd !heapkeyspace indexes must always descend to the
+			* left here, since they have no heap TID attribute (and cannot have
+			* any -inf key values in any case, since truncation can only remove
+			* non-key attributes).  !heapkeyspace searches must always be
+			* prepared to deal with matches on both sides of the pivot once the
+			* leaf level is reached.
+			*/
+			if (key->heapkeyspace && !key->pivotsearch &&
+				key->keysz == ntupatts && heapTid == NULL)
+				return 1;
+
+			/* All provided scankey arguments found to be equal */
+			return 0;
+		}
+
 		/*
-		 * Most searches have a scankey that is considered greater than a
-		 * truncated pivot tuple if and when the scankey has equal values for
-		 * attributes up to and including the least significant untruncated
-		 * attribute in tuple.
-		 *
-		 * For example, if an index has the minimum two attributes (single
-		 * user key attribute, plus heap TID attribute), and a page's high key
-		 * is ('foo', -inf), and scankey is ('foo', <omitted>), the search
-		 * will not descend to the page to the left.  The search will descend
-		 * right instead.  The truncated attribute in pivot tuple means that
-		 * all non-pivot tuples on the page to the left are strictly < 'foo',
-		 * so it isn't necessary to descend left.  In other words, search
-		 * doesn't have to descend left because it isn't interested in a match
-		 * that has a heap TID value of -inf.
-		 *
-		 * However, some searches (pivotsearch searches) actually require that
-		 * we descend left when this happens.  -inf is treated as a possible
-		 * match for omitted scankey attribute(s).  This is needed by page
-		 * deletion, which must re-find leaf pages that are targets for
-		 * deletion using their high keys.
-		 *
-		 * Note: the heap TID part of the test ensures that scankey is being
-		 * compared to a pivot tuple with one or more truncated key
-		 * attributes.
-		 *
-		 * Note: pg_upgrade'd !heapkeyspace indexes must always descend to the
-		 * left here, since they have no heap TID attribute (and cannot have
-		 * any -inf key values in any case, since truncation can only remove
-		 * non-key attributes).  !heapkeyspace searches must always be
-		 * prepared to deal with matches on both sides of the pivot once the
-		 * leaf level is reached.
-		 */
-		if (key->heapkeyspace && !key->pivotsearch &&
-			key->keysz == ntupatts && heapTid == NULL)
+		* Treat truncated heap TID as minus infinity, since scankey has a key
+		* attribute value (scantid) that would otherwise be compared directly
+		*/
+		Assert(key->keysz == IndexRelationGetNumberOfKeyAttributes(rel));
+		if (heapTid == NULL)
 			return 1;
 
-		/* All provided scankey arguments found to be equal */
-		return 0;
+		Assert(ntupatts >= IndexRelationGetNumberOfKeyAttributes(rel));
+		return ItemPointerCompare(key->scantid, heapTid);
 	}
+	else
+	{
+		heapTid = BTreeTupleGetMinTID(itup);
+		if (key->scantid != NULL && heapTid != NULL)
+		{
+			int cmp = ItemPointerCompare(key->scantid, heapTid);
+			if (cmp == -1 || cmp == 0)
+			{
+				elog(DEBUG4, "offnum %d Scankey (%u,%u) is less than posting tuple (%u,%u)",
+							offnum, ItemPointerGetBlockNumberNoCheck(key->scantid),
+							ItemPointerGetOffsetNumberNoCheck(key->scantid),
+							ItemPointerGetBlockNumberNoCheck(heapTid),
+							ItemPointerGetOffsetNumberNoCheck(heapTid));
+				return cmp;
+			}
 
-	/*
-	 * Treat truncated heap TID as minus infinity, since scankey has a key
-	 * attribute value (scantid) that would otherwise be compared directly
-	 */
-	Assert(key->keysz == IndexRelationGetNumberOfKeyAttributes(rel));
-	if (heapTid == NULL)
-		return 1;
+			heapTid = BTreeTupleGetMaxTID(itup);
+			cmp = ItemPointerCompare(key->scantid, heapTid);
+			if (cmp == 1)
+			{
+				elog(DEBUG4, "offnum %d Scankey (%u,%u) is greater than posting tuple (%u,%u)",
+							offnum, ItemPointerGetBlockNumberNoCheck(key->scantid),
+							ItemPointerGetOffsetNumberNoCheck(key->scantid),
+							ItemPointerGetBlockNumberNoCheck(heapTid),
+							ItemPointerGetOffsetNumberNoCheck(heapTid));
+				return cmp;
+			}
 
-	Assert(ntupatts >= IndexRelationGetNumberOfKeyAttributes(rel));
-	return ItemPointerCompare(key->scantid, heapTid);
+			/* if we got here, scantid is inbetween of posting items of the tuple */
+			elog(DEBUG4, "offnum %d Scankey (%u,%u) is between posting items (%u,%u) and (%u,%u)",
+							offnum, ItemPointerGetBlockNumberNoCheck(key->scantid),
+							ItemPointerGetOffsetNumberNoCheck(key->scantid),
+							ItemPointerGetBlockNumberNoCheck(BTreeTupleGetMinTID(itup)),
+							ItemPointerGetOffsetNumberNoCheck(BTreeTupleGetMinTID(itup)),
+							ItemPointerGetBlockNumberNoCheck(heapTid),
+							ItemPointerGetOffsetNumberNoCheck(heapTid));
+			return 0;
+		}
+	}
 }
 
 /*
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 7d0d456..918043f 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -675,6 +675,13 @@ typedef struct BTInsertStateData
 	Buffer		buf;
 
 	/*
+	 * if _bt_binsrch_insert() found the location
+	 * inside existing posting list,
+	 * save the position inside the list.
+	 */
+	int	in_posting_offset;
+
+	/*
 	 * Cache of bounds within the current buffer.  Only used for insertions
 	 * where _bt_check_unique is called.  See _bt_binsrch_insert and
 	 * _bt_findinsertloc for details.
@@ -953,6 +960,8 @@ extern Buffer _bt_moveright(Relation rel, BTScanInsert key, Buffer buf,
 							bool forupdate, BTStack stack, int access, Snapshot snapshot);
 extern OffsetNumber _bt_binsrch_insert(Relation rel, BTInsertState insertstate);
 extern int32 _bt_compare(Relation rel, BTScanInsert key, Page page, OffsetNumber offnum);
+extern int32 _bt_compare_posting(Relation rel, BTScanInsert key, Page page,
+						 OffsetNumber offnum, int *in_posting_offset);
 extern bool _bt_first(IndexScanDesc scan, ScanDirection dir);
 extern bool _bt_next(IndexScanDesc scan, ScanDirection dir);
 extern Buffer _bt_get_endpoint(Relation rel, uint32 level, bool rightmost,
