hbache_v5.patch
application/octet-stream
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v5
| File | + | − |
|---|---|---|
| src/backend/utils/time/tqual.c | 390 | 60 |
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
old mode 100644
new mode 100755
index 2fd32e0..d734626
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -7,12 +7,27 @@
* "hint" status bits if we see that the inserting or deleting transaction
* has now committed or aborted (and it is safe to set the hint bits).
* If the hint bits are changed, SetBufferCommitInfoNeedsSave is called on
* the passed-in buffer. The caller must hold not only a pin, but at least
* shared buffer content lock on the buffer containing the tuple.
*
+ * Hint bits are maintained directly on the tuple itself and on a separate
+ * in-memory cache. If the hint bit is found from the cache, the hint bit
+ * is set on the tuple but the page is not marked dirty. This is done to
+ * reduce i/o resulting from hint bit only changes. The cache itself is
+ * exactly like the clog page, and uses a similar page size, but does not
+ * have to. Cache maintenance occurs every Nth (typically 100) cache miss
+ * that successfully fetches the transaction status from clog.
+ *
+ * It's tempting to try and expand the simple last transaction status in
+ * transam.c but this check happens too late in the critical visibility
+ * routines to provide much benefit. For the fastest possible cache
+ * performance with the least amount of impact on scan heavy cases, you have
+ * to maintain the cache here if you want to avoid dirtying the page
+ * based on interaction with the cache.
+ *
* NOTE: must check TransactionIdIsInProgress (which looks in PGPROC array)
* before TransactionIdDidCommit/TransactionIdDidAbort (which look in
* pg_clog). Otherwise we have a race condition: we might decide that a
* just-committed transaction crashed, because none of the tests succeed.
* xact.c is careful to record commit/abort in pg_clog before it unsets
* MyProc->xid in PGPROC array. That fixes that problem, but it also
@@ -69,26 +84,297 @@
/* Static variables representing various special snapshot semantics */
SnapshotData SnapshotNowData = {HeapTupleSatisfiesNow};
SnapshotData SnapshotSelfData = {HeapTupleSatisfiesSelf};
SnapshotData SnapshotAnyData = {HeapTupleSatisfiesAny};
SnapshotData SnapshotToastData = {HeapTupleSatisfiesToast};
+/* hint bit cache definitions and structures */
+
+#define HBCACHE_PAGESZ 8192
+/* 8192 bytes of stororage in each hint bit cache page */
+#define HBCACHE_PAGE_COUNT 4 /* how many pages to keep */
+#define HBCACHE_ROLLUPSZ 100
+/* 100 cache misses are stored in array before re-filling cache */
+#define HBCACHE_HIT_THRESHOLD 5
+/* cache pagees with less than 5 interesting transactions per cache re-fill
+ * are not considered interesting
+ */
+
+#define HBCACHE_BITS_PER_XACT 2
+#define HBCACHE_XACTS_PER_BYTE 4
+#define HBCACHE_XACTS_PER_PAGE (HBCACHE_PAGESZ * HBCACHE_XACTS_PER_BYTE)
+#define HBCACHE_XACT_BITMASK ((1 << HBCACHE_BITS_PER_XACT) - 1)
+
+#define HBCACHE_COMITTED 1
+#define HBCACHE_INVALID 2
+
+#define HBCache_TransactionIdToPage(xid) ((xid) / (TransactionId) HBCACHE_XACTS_PER_PAGE)
+#define HBCache_TransactionIdToPgIndex(xid) ((xid) % (TransactionId) HBCACHE_XACTS_PER_PAGE)
+#define HBCache_TransactionIdToByte(xid) (HBCache_TransactionIdToPgIndex(xid) / HBCACHE_XACTS_PER_BYTE)
+#define HBCache_TransactionIdToBIndex(xid) ((xid) % (TransactionId) HBCACHE_XACTS_PER_BYTE)
+
+
+/* describes a hint bit page in memory */
+typedef struct
+{
+ int XidPage; /* this page describes a range of transaction ids */
+ int HitCount; /* number of cache hits on this page */
+ bool Clear;
+ /* during rollup, signals the page is changing and cache data needs to
+ * be cleared
+ */
+} HintBitCachePageHeader;
+
/* local functions */
static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
+static HintBitCachePageHeader HintbitCachePageList[HBCACHE_PAGE_COUNT]
+ = {{-1, 0, false}, {-1, 0, false}, {-1, 0, false}, {-1, 0, false}};
+static char HintBitCachePageList[HBCACHE_PAGE_COUNT][HBCACHE_PAGESZ];
+static int HintBitCachePageRollupList[HBCACHE_ROLLUPSZ];
+static int HintBitCacheMissCount = 0;
+
+/* qsort comparison function */
+static int
+int_cmp(const void *p1, const void *p2)
+{
+ int v1 = *((const int *) p1);
+ int v2 = *((const int *) p2);
+
+ if (v1 < v2)
+ return -1;
+ if (v1 > v2)
+ return 1;
+ return 0;
+}
+
+/*
+ * RollUpHintBitCache()
+ *
+ * Whenever a transaction is checked against the hint bit cache and the status
+ * is not there (a cache miss), the transaction's page is added to the
+ * rollup array. When that array fills, the rollup array is processed into
+ * a new set of cache pages.
+ *
+ * The algorithm is to sort the rollup array and iterate it counting the
+ * number of identical pages as we go. When we hit a new page (or the
+ * end of the array) and the number of counted pages is over a minimum
+ * threshold, the page is moved into the cache if it has more cache hits
+ * than any page that is already in the cache.
+ */
+static void RollUpHintBitCache()
+{
+ TransactionId LastPage = HintBitCachePageRollupList[0];
+ /* The page we are duplicate tracking. */
+
+ int PageIdx;
+ int HitCount = 0;
+ /* effectively initializes to 1: the first iteration of the loop will
+ * always match LastPage and increment this value
+ */
+
+ int Page;
+ int PageMinHits = HBCACHE_ROLLUPSZ + 1;
+ /* The smallest number of hits for a page currently in the cache. It's
+ * scanned comparing as we go, so initialize it to an impossibly high
+ * value here */
+
+ int PageMinIdx;
+ /* The cache page index of the page with the smallest number of hits.
+ * if a new cache page higher number of hits with this one, it is
+ * swapped in its place.
+ */
+ int XidPage;
+
+ bool AlreadyInCache;
+
+ /* sort the list in order to make counting identical pages easier */
+ qsort(HintBitCachePageRollupList, HBCACHE_ROLLUPSZ, sizeof(int), int_cmp);
+
+ for (PageIdx = 0; PageIdx < HBCACHE_ROLLUPSZ; PageIdx++)
+ {
+ /* If page is the same as the last one, increment the hitcount
+ * or reset it and do cache management based on the number of hits
+ */
+ XidPage = HintBitCachePageRollupList[PageIdx];
+
+ if(XidPage == LastPage)
+ HitCount++;
+ else
+ {
+ /* Now check the page represented by LastPage to see if its
+ * interesting for cache purposes. To avoid thrashing cache pages
+ * in and out, a minimum threshold of hits has to be met before
+ * putting a page in the cache.
+ */
+ if (HitCount >= HBCACHE_HIT_THRESHOLD)
+ {
+CheckLastXidBlock:
+ /* Look through the pages, find the one with the smallest
+ * hit count, and save off the index.
+ */
+ AlreadyInCache = false;
+
+ for (Page = 0; Page < HBCACHE_PAGE_COUNT; Page++)
+ {
+ if (HintbitCachePageList[Page].HitCount < PageMinHits)
+ {
+ PageMinIdx = Page;
+ PageMinHits = HintbitCachePageList[Page].HitCount;
+ }
+
+ /* If this page is alredy in the cache, we can leave it
+ * an without clearing it.
+ */
+ if (HintbitCachePageList[Page].XidPage == LastPage)
+ AlreadyInCache = true;
+ }
+
+ /* does the page we are examining have a higher number of
+ * cache hits than any page currently in the cache?
+ */
+ if (!AlreadyInCache && HitCount > PageMinHits)
+ {
+ /* mark the cache buffer with the new page, hint count
+ * and clear flag.
+ */
+ HintbitCachePageList[PageMinIdx].XidPage = LastPage;
+ HintbitCachePageList[PageMinIdx].HitCount = HitCount;
+ HintbitCachePageList[PageMinIdx].Clear = true;
+ }
+ }
+
+ /* reset the hit count, since we've already technically already
+ * seen this page in this iteration, HitCount is initialized
+ * to 1, and this page is now the one tracked via LastPage.
+ */
+ HitCount = 1;
+ LastPage = XidPage;
+ }
+ }
+
+ /* When the loop is exited, the last block of identical pages may have
+ * never gotten checked via the XidPage == LastPage condition.
+ * Sneakily jump back into the loop if necessary.
+ *
+ * jump back into the loop to check it but decrement PageIdx first so
+ * that the final LastPage assignment does't look outside the array.
+ */
+ if(HitCount >= HBCACHE_HIT_THRESHOLD)
+ {
+ /* Since HitCount is reset when a page is processed, checking it
+ * is enough to guarantee the last page in the loop was never
+ * processed.
+ */
+ goto CheckLastXidBlock;
+ }
+
+ /* Cache pages that are new have to be cleared. Look for ones marked as
+ * such and reset the hit count for all pages.
+ */
+ for (Page = 0; Page < HBCACHE_PAGE_COUNT; Page++)
+ {
+ if(HintbitCachePageList[PageMinIdx].Clear)
+ memset(HintBitCachePageList[Page], 0, HBCACHE_PAGESZ);
+
+ HintbitCachePageList[PageMinIdx].Clear = 0;
+ HintbitCachePageList[PageMinIdx].HitCount = 0;
+ }
+
+ HintBitCacheMissCount = 0;
+ return;
+}
+
+/*
+ * HintBitCacheXidStatus()
+ *
+ * Compute the page for a TransactionId, and compare it against the pages
+ * currently in the cache. If the page is found, resolve the hint bit
+ * status's position in the block and return it.
+ */
+static inline int
+HintBitCacheXidStatus(TransactionId xid)
+{
+ int XidPage = HBCache_TransactionIdToPage(xid);
+ int Page;
+
+ for(Page = 0; Page < HBCACHE_PAGE_COUNT; Page++)
+ {
+ /* is the transaction status in the range defined by the page? */
+ if(HintbitCachePageList[Page].XidPage == XidPage)
+ {
+ int byteno = HBCache_TransactionIdToByte(xid);
+ int bshift = HBCache_TransactionIdToBIndex(xid) * HBCACHE_BITS_PER_XACT;
+ char *byteptr = HintBitCachePageList[Page] + byteno;
+
+ return (*byteptr >> bshift) & HBCACHE_XACT_BITMASK;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * SetXidInHintBitCache()
+ *
+ * Compute the page for a TransactionId, and compare it against the pages
+ * currently in the cache. If the page is found, compute the bit position
+ * and set it to 1 (true). If it is not found, put the TransactionId in the
+ * array that holds cache misses. If that array fills, rebuild the cache.
+ */
+static inline void
+SetXidInHintBitCache(TransactionId xid, int hbcache_status)
+{
+ int XidPage = HBCache_TransactionIdToPage(xid);
+ int Page;
+
+ for(Page = 0; Page < HBCACHE_PAGE_COUNT; Page++)
+ {
+ if(XidPage == HintbitCachePageList[Page].XidPage)
+ {
+ int byteno = HBCache_TransactionIdToByte(xid);
+ int bshift = HBCache_TransactionIdToBIndex(xid) * HBCACHE_BITS_PER_XACT;
+ char *byteptr = HintBitCachePageList[Page] + byteno;
+ char byteval = *byteptr;
+
+ byteval &= ~(((1 << HBCACHE_BITS_PER_XACT) - 1) << bshift);
+ byteval |= (hbcache_status << bshift);
+ *byteptr = byteval;
+
+ /* there is a minute chance of overflow here, but it doesn't
+ * seem worthwhile to test for it
+ */
+ HintbitCachePageList[Page].HitCount++;
+ break;
+ }
+ }
+
+ HintBitCachePageRollupList[HintBitCacheMissCount++] = XidPage;
+
+ /* Enough transactions have fallen through the cache that it's time to
+ * rebuild it. Hopefully we don't have to do this too often
+ */
+ if (HintBitCacheMissCount == HBCACHE_ROLLUPSZ)
+ RollUpHintBitCache();
+
+ return;
+}
+
/*
* SetHintBits()
*
* Set commit/abort hint bits on a tuple, if appropriate at this time.
*
* It is only safe to set a transaction-committed hint bit if we know the
* transaction's commit record has been flushed to disk. We cannot change
* the LSN of the page here because we may hold only a share lock on the
* buffer, so we can't use the LSN to interlock this; we have to just refrain
* from setting the hint bit until some future re-examination of the tuple.
+ * The ability of the hint bit to be set is returned to the caller (the hint
+ * bit cache only saves safe bits in the cache to avoid repeated checks).
*
* We can always set hint bits when marking a transaction aborted. (Some
* code in heapam.c relies on that!)
*
* Also, if we are cleaning up HEAP_MOVED_IN or HEAP_MOVED_OFF entries, then
* we can always set the hint bits, since pre-9.0 VACUUM FULL always used
@@ -101,27 +387,35 @@ static bool XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot);
* Normal commits may be asynchronous, so for those we need to get the LSN
* of the transaction and then check whether this is flushed.
*
* The caller should pass xid as the XID of the transaction to check, or
* InvalidTransactionId if no check is needed.
*/
-static inline void
+static inline bool
SetHintBits(HeapTupleHeader tuple, Buffer buffer,
uint16 infomask, TransactionId xid)
{
if (TransactionIdIsValid(xid))
{
/* NB: xid must be known committed here! */
XLogRecPtr commitLSN = TransactionIdGetCommitLSN(xid);
if (XLogNeedsFlush(commitLSN))
- return; /* not flushed yet, so don't set hint */
+ return false; /* not flushed yet, so don't set hint */
}
tuple->t_infomask |= infomask;
SetBufferCommitInfoNeedsSave(buffer);
+ return true;
+}
+
+static inline void
+SetHintBitsCache(HeapTupleHeader tuple, Buffer buffer,
+ uint16 infomask, TransactionId xid)
+{
+ tuple->t_infomask |= infomask;
}
/*
* HeapTupleSetHintBits --- exported version of SetHintBits()
*
* This must be separate because of C99's brain-dead notions about how to
@@ -911,91 +1205,127 @@ HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot,
bool
HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot,
Buffer buffer)
{
if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED))
{
+ int hbcache_status;
+
if (tuple->t_infomask & HEAP_XMIN_INVALID)
return false;
- /* Used by pre-9.0 binary upgrades */
- if (tuple->t_infomask & HEAP_MOVED_OFF)
- {
- TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+ hbcache_status = HintBitCacheXidStatus(HeapTupleHeaderGetXmin(tuple));
- if (TransactionIdIsCurrentTransactionId(xvac))
- return false;
- if (!TransactionIdIsInProgress(xvac))
- {
- if (TransactionIdDidCommit(xvac))
- {
- SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
- InvalidTransactionId);
- return false;
- }
- SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- InvalidTransactionId);
- }
+ if (hbcache_status == HBCACHE_COMITTED)
+ {
+ /* The hint bit was not set on the tuple, but it was set in the
+ * cache. Since cache bits are only set if the transaction's
+ * commit record has been flushed to disk, we can call a quicker
+ * version of SetHintBits that does not check the commit record
+ * and, because the page is not marked dirty, does not have to
+ * lock the buffer.
+ */
+ SetHintBitsCache(tuple, buffer, HEAP_XMIN_COMMITTED,
+ HeapTupleHeaderGetXmin(tuple));
}
- /* Used by pre-9.0 binary upgrades */
- else if (tuple->t_infomask & HEAP_MOVED_IN)
+ else if (hbcache_status == HBCACHE_INVALID)
{
- TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+ SetHintBitsCache(tuple, buffer, HEAP_XMIN_INVALID,
+ HeapTupleHeaderGetXmin(tuple));
- if (!TransactionIdIsCurrentTransactionId(xvac))
+ return false;
+ }
+ else
+ {
+ /* Used by pre-9.0 binary upgrades */
+ if (tuple->t_infomask & HEAP_MOVED_OFF)
{
- if (TransactionIdIsInProgress(xvac))
+ TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+ if (TransactionIdIsCurrentTransactionId(xvac))
return false;
- if (TransactionIdDidCommit(xvac))
+ if (!TransactionIdIsInProgress(xvac))
+ {
+ if (TransactionIdDidCommit(xvac))
+ {
+ SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+ InvalidTransactionId);
+ return false;
+ }
SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
InvalidTransactionId);
- else
+ }
+ }
+ /* Used by pre-9.0 binary upgrades */
+ else if (tuple->t_infomask & HEAP_MOVED_IN)
+ {
+ TransactionId xvac = HeapTupleHeaderGetXvac(tuple);
+
+ if (!TransactionIdIsCurrentTransactionId(xvac))
{
- SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
- InvalidTransactionId);
- return false;
+ if (TransactionIdIsInProgress(xvac))
+ return false;
+ if (TransactionIdDidCommit(xvac))
+ SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+ InvalidTransactionId);
+ else
+ {
+ SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+ InvalidTransactionId);
+ return false;
+ }
}
}
- }
- else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
- {
- if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
- return false; /* inserted after scan started */
+ else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple)))
+ {
+ if (HeapTupleHeaderGetCmin(tuple) >= snapshot->curcid)
+ return false; /* inserted after scan started */
- if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
- return true;
+ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */
+ return true;
- if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
- return true;
+ if (tuple->t_infomask & HEAP_IS_LOCKED) /* not deleter */
+ return true;
- Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
+ Assert(!(tuple->t_infomask & HEAP_XMAX_IS_MULTI));
- if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
- {
- /* deleting subtransaction must have aborted */
- SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
- InvalidTransactionId);
- return true;
+ if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tuple)))
+ {
+ /* deleting subtransaction must have aborted */
+ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID,
+ InvalidTransactionId);
+ return true;
+ }
+
+ if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
+ return true; /* deleted after scan started */
+ else
+ return false; /* deleted before scan started */
}
+ else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
+ return false;
+ else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
+ {
+ /* Set the hint bit on the tuple, and put it in the cache
+ * if it was actually set.
+ */
+ if(SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
+ HeapTupleHeaderGetXmin(tuple)))
+ SetXidInHintBitCache(HeapTupleHeaderGetXmin(tuple),
+ HBCACHE_COMITTED);
- if (HeapTupleHeaderGetCmax(tuple) >= snapshot->curcid)
- return true; /* deleted after scan started */
+ }
else
- return false; /* deleted before scan started */
- }
- else if (TransactionIdIsInProgress(HeapTupleHeaderGetXmin(tuple)))
- return false;
- else if (TransactionIdDidCommit(HeapTupleHeaderGetXmin(tuple)))
- SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED,
- HeapTupleHeaderGetXmin(tuple));
- else
- {
- /* it must have aborted or crashed */
- SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
- InvalidTransactionId);
- return false;
+ {
+ /* it must have aborted or crashed */
+ if(SetHintBits(tuple, buffer, HEAP_XMIN_INVALID,
+ InvalidTransactionId))
+ SetXidInHintBitCache(HeapTupleHeaderGetXmin(tuple),
+ HBCACHE_INVALID);
+ return false;
+ }
}
}
/*
* By here, the inserting transaction has committed - have to check
* when...