/*-------------------------------------------------------------------------
*
* freelist.c
- * routines for manipulating the buffer pool's replacement strategy
- * freelist.
+ * routines for manipulating the buffer pool's replacement strategy.
*
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/storage/buffer/freelist.c,v 1.31 2003/08/04 02:40:03 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/storage/buffer/freelist.c,v 1.32 2003/11/13 00:40:01 wieck Exp $
*
*-------------------------------------------------------------------------
*/
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
+#include "access/xact.h"
+#define STRAT_LIST_UNUSED -1
+#define STRAT_LIST_B1 0
+#define STRAT_LIST_T1 1
+#define STRAT_LIST_T2 2
+#define STRAT_LIST_B2 3
+#define STRAT_NUM_LISTS 4
+
+#ifndef MAX
+#define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif
+#ifndef MIN
+#define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif
+
+/*
+ * The Cache Directory Block (CDB) of the Adaptive Replacement Cache (ARC)
+ */
+typedef struct bufstratcdb
+{
+ int prev; /* links in the queue */
+ int next;
+ int list; /* current list */
+ BufferTag buf_tag; /* buffer key */
+ Buffer buf_id; /* currently assigned data buffer */
+ TransactionId t1_xid; /* the xid this entry went onto T1 */
+} BufferStrategyCDB;
+
+/*
+ * The shared ARC control information.
+ */
+typedef struct bufstratcontrol
+{
+
+ int target_T1_size; /* What T1 size are we aiming for */
+ int listUnusedCDB; /* All unused StrategyCDB */
+ int listHead[STRAT_NUM_LISTS]; /* ARC lists B1, T1, T2 and B2 */
+ int listTail[STRAT_NUM_LISTS];
+ int listSize[STRAT_NUM_LISTS];
+ Buffer listFreeBuffers; /* List of unused buffers */
+
+ long num_lookup; /* Some hit statistics */
+ long num_hit[STRAT_NUM_LISTS];
+ time_t stat_report;
+
+ BufferStrategyCDB cdb[1]; /* The cache directory */
+} BufferStrategyControl;
+
+
+static BufferStrategyControl *StrategyControl = NULL;
+static BufferStrategyCDB *StrategyCDB = NULL;
+
+static int strategy_cdb_found;
+static int strategy_cdb_replace;
+static int strategy_get_from;
+
+int BufferStrategyStatInterval = 0;
+
+static bool strategy_hint_vacuum;
+static TransactionId strategy_vacuum_xid;
+
+
+#define T1_TARGET StrategyControl->target_T1_size
+#define B1_LENGTH StrategyControl->listSize[STRAT_LIST_B1]
+#define T1_LENGTH StrategyControl->listSize[STRAT_LIST_T1]
+#define T2_LENGTH StrategyControl->listSize[STRAT_LIST_T2]
+#define B2_LENGTH StrategyControl->listSize[STRAT_LIST_B2]
-static BufferDesc *SharedFreeList;
/*
- * State-checking macros
+ * Macro to remove a CDB from whichever list it currently is on
*/
+#define STRAT_LIST_REMOVE(cdb) \
+{ \
+ AssertMacro((cdb)->list >= 0 && (cdb)->list < STRAT_NUM_LISTS); \
+ if ((cdb)->prev < 0) \
+ StrategyControl->listHead[(cdb)->list] = (cdb)->next; \
+ else \
+ StrategyCDB[(cdb)->prev].next = (cdb)->next; \
+ if ((cdb)->next < 0) \
+ StrategyControl->listTail[(cdb)->list] = (cdb)->prev; \
+ else \
+ StrategyCDB[(cdb)->next].prev = (cdb)->prev; \
+ StrategyControl->listSize[(cdb)->list]--; \
+ (cdb)->list = STRAT_LIST_UNUSED; \
+}
-#define IsInQueue(bf) \
-( \
- AssertMacro((bf->freeNext != INVALID_DESCRIPTOR)), \
- AssertMacro((bf->freePrev != INVALID_DESCRIPTOR)), \
- AssertMacro((bf->flags & BM_FREE)) \
-)
+/*
+ * Macro to add a CDB to the tail of a list (MRU position)
+ */
+#define STRAT_MRU_INSERT(cdb,l) \
+{ \
+ AssertMacro((cdb)->list == STRAT_LIST_UNUSED); \
+ if (StrategyControl->listTail[(l)] < 0) \
+ { \
+ (cdb)->prev = (cdb)->next = -1; \
+ StrategyControl->listHead[(l)] = \
+ StrategyControl->listTail[(l)] = \
+ ((cdb) - StrategyCDB); \
+ } \
+ else \
+ { \
+ (cdb)->next = -1; \
+ (cdb)->prev = StrategyControl->listTail[(l)]; \
+ StrategyCDB[StrategyControl->listTail[(l)]].next = \
+ ((cdb) - StrategyCDB); \
+ StrategyControl->listTail[(l)] = \
+ ((cdb) - StrategyCDB); \
+ } \
+ StrategyControl->listSize[(l)]++; \
+ (cdb)->list = (l); \
+}
-#define IsNotInQueue(bf) \
-( \
- AssertMacro((bf->freeNext == INVALID_DESCRIPTOR)), \
- AssertMacro((bf->freePrev == INVALID_DESCRIPTOR)), \
- AssertMacro(! (bf->flags & BM_FREE)) \
-)
+/*
+ * Macro to add a CDB to the head of a list (LRU position)
+ */
+#define STRAT_LRU_INSERT(cdb,l) \
+{ \
+ AssertMacro((cdb)->list == STRAT_LIST_UNUSED); \
+ if (StrategyControl->listHead[(l)] < 0) \
+ { \
+ (cdb)->prev = (cdb)->next = -1; \
+ StrategyControl->listHead[(l)] = \
+ StrategyControl->listTail[(l)] = \
+ ((cdb) - StrategyCDB); \
+ } \
+ else \
+ { \
+ (cdb)->prev = -1; \
+ (cdb)->next = StrategyControl->listHead[(l)]; \
+ StrategyCDB[StrategyControl->listHead[(l)]].prev = \
+ ((cdb) - StrategyCDB); \
+ StrategyControl->listHead[(l)] = \
+ ((cdb) - StrategyCDB); \
+ } \
+ StrategyControl->listSize[(l)]++; \
+ (cdb)->list = (l); \
+}
/*
- * AddBufferToFreelist
+ * StrategyBufferLookup
*
- * In theory, this is the only routine that needs to be changed
- * if the buffer replacement strategy changes. Just change
- * the manner in which buffers are added to the freelist queue.
- * Currently, they are added on an LRU basis.
+ * Lookup a page request in the cache directory. A buffer is only
+ * returned for a T1 or T2 cache hit. B1 and B2 hits are only
+ * remembered here to later affect the behaviour.
*/
-static void
-AddBufferToFreelist(BufferDesc *bf)
+BufferDesc *
+StrategyBufferLookup(BufferTag *tagPtr, bool recheck)
{
-#ifdef BMTRACE
- _bm_trace(bf->tag.relId.dbId, bf->tag.relId.relId, bf->tag.blockNum,
- BufferDescriptorGetBuffer(bf), BMT_DEALLOC);
-#endif /* BMTRACE */
- IsNotInQueue(bf);
-
- /* change bf so it points to inFrontOfNew and its successor */
- bf->freePrev = SharedFreeList->freePrev;
- bf->freeNext = Free_List_Descriptor;
-
- /* insert new into chain */
- BufferDescriptors[bf->freeNext].freePrev = bf->buf_id;
- BufferDescriptors[bf->freePrev].freeNext = bf->buf_id;
+ BufferStrategyCDB *cdb;
+ time_t now;
+
+ if (BufferStrategyStatInterval > 0)
+ {
+ time(&now);
+ if (StrategyControl->stat_report + BufferStrategyStatInterval < now)
+ {
+ long all_hit, b1_hit, t1_hit, t2_hit, b2_hit;
+ ErrorContextCallback *errcxtold;
+
+ if (StrategyControl->num_lookup == 0)
+ {
+ all_hit = b1_hit = t1_hit = t2_hit = b2_hit = 0;
+ }
+ else
+ {
+ b1_hit = (StrategyControl->num_hit[STRAT_LIST_B1] * 100 /
+ StrategyControl->num_lookup);
+ t1_hit = (StrategyControl->num_hit[STRAT_LIST_T1] * 100 /
+ StrategyControl->num_lookup);
+ t2_hit = (StrategyControl->num_hit[STRAT_LIST_T2] * 100 /
+ StrategyControl->num_lookup);
+ b2_hit = (StrategyControl->num_hit[STRAT_LIST_B2] * 100 /
+ StrategyControl->num_lookup);
+ all_hit = b1_hit + t1_hit + t2_hit + b2_hit;
+ }
+
+ errcxtold = error_context_stack;
+ error_context_stack = NULL;
+ elog(DEBUG1, "ARC T1target=%5d B1len=%5d T1len=%5d T2len=%5d B2len=%5d",
+ T1_TARGET, B1_LENGTH, T1_LENGTH, T2_LENGTH, B2_LENGTH);
+ elog(DEBUG1, "ARC total =%4ld%% B1hit=%4ld%% T1hit=%4ld%% T2hit=%4ld%% B2hit=%4ld%%",
+ all_hit, b1_hit, t1_hit, t2_hit, b2_hit);
+ error_context_stack = errcxtold;
+
+ StrategyControl->num_lookup = 0;
+ StrategyControl->num_hit[STRAT_LIST_B1] = 0;
+ StrategyControl->num_hit[STRAT_LIST_T1] = 0;
+ StrategyControl->num_hit[STRAT_LIST_T2] = 0;
+ StrategyControl->num_hit[STRAT_LIST_B2] = 0;
+ StrategyControl->stat_report = now;
+ }
+ }
+
+ /*
+ * Count lookups
+ */
+ StrategyControl->num_lookup++;
+
+ /*
+ * Lookup the block in the shared hash table
+ */
+ strategy_cdb_found = BufTableLookup(tagPtr);
+
+ /*
+ * Handle CDB lookup miss
+ */
+ if (strategy_cdb_found < 0)
+ {
+ if (!recheck)
+ {
+ /*
+ * This is an initial lookup and we have a complete
+ * cache miss (block found nowhere). This means we
+ * remember according to the current T1 size and the
+ * target T1 size from where we take a block if we
+ * need one later.
+ */
+ if (T1_LENGTH >= MAX(1, T1_TARGET))
+ strategy_get_from = STRAT_LIST_T1;
+ else
+ strategy_get_from = STRAT_LIST_T2;
+ }
+
+ /* report cache miss */
+ return NULL;
+ }
+
+ /*
+ * We found a CDB
+ */
+ cdb = &StrategyCDB[strategy_cdb_found];
+
+ /*
+ * Count hits
+ */
+ StrategyControl->num_hit[cdb->list]++;
+
+ /*
+ * If this is a T2 hit, we simply move the CDB to the
+ * T2 MRU position and return the found buffer.
+ */
+ if (cdb->list == STRAT_LIST_T2)
+ {
+ STRAT_LIST_REMOVE(cdb);
+ STRAT_MRU_INSERT(cdb, STRAT_LIST_T2);
+
+ return &BufferDescriptors[cdb->buf_id];
+ }
+
+ /*
+ * If this is a T1 hit, we move the buffer to the T2 MRU
+ * only if another transaction had read it into T1. This is
+ * required because any UPDATE or DELETE in PostgreSQL does
+ * multiple ReadBuffer(), first during the scan, later during
+ * the heap_update() or heap_delete().
+ */
+ if (cdb->list == STRAT_LIST_T1)
+ {
+ if (!TransactionIdIsCurrentTransactionId(cdb->t1_xid))
+ {
+ STRAT_LIST_REMOVE(cdb);
+ STRAT_MRU_INSERT(cdb, STRAT_LIST_T2);
+ }
+
+ return &BufferDescriptors[cdb->buf_id];
+ }
+
+ /*
+ * In the case of a recheck we don't care about B1 or B2 hits here.
+ * The bufmgr does this call only to make sure noone faulted in the
+ * block while we where busy flushing another. Now for this really
+ * to end up as a B1 or B2 cache hit, we must have been flushing for
+ * quite some time as the block not only must have been read, but
+ * also traveled through the queue and evicted from the T cache again
+ * already.
+ */
+ if (recheck)
+ return NULL;
+
+ /*
+ * Adjust the target size of the T1 cache depending on if this is
+ * a B1 or B2 hit.
+ */
+ switch (cdb->list)
+ {
+ case STRAT_LIST_B1:
+ /*
+ * B1 hit means that the T1 cache is probably too
+ * small. Adjust the T1 target size and continue
+ * below.
+ */
+ T1_TARGET = MIN(T1_TARGET + MAX(B2_LENGTH / B1_LENGTH, 1),
+ Data_Descriptors);
+ break;
+
+ case STRAT_LIST_B2:
+ /*
+ * B2 hit means that the T2 cache is probably too
+ * small. Adjust the T1 target size and continue
+ * below.
+ */
+ T1_TARGET = MAX(T1_TARGET - MAX(B1_LENGTH / B2_LENGTH, 1), 0);
+ break;
+
+ default:
+ elog(ERROR, "Buffer hash table corrupted - CDB on list %d found",
+ cdb->list);
+ }
+
+ /*
+ * Decide where to take from if we will be out of
+ * free blocks later in StrategyGetBuffer().
+ */
+ if (T1_LENGTH >= MAX(1, T1_TARGET))
+ strategy_get_from = STRAT_LIST_T1;
+ else
+ strategy_get_from = STRAT_LIST_T2;
+
+ /*
+ * Even if we had seen the block in the past, it's data is
+ * not currently in memory ... cache miss to the bufmgr.
+ */
+ return NULL;
}
+
+/*
+ * StrategyGetBuffer
+ *
+ * Called by the bufmgr to get the next candidate buffer to use in
+ * BufferAlloc(). The only hard requirement BufferAlloc() has is that
+ * this buffer must not currently be pinned.
+ */
+BufferDesc *
+StrategyGetBuffer(void)
+{
+ int cdb_id;
+ BufferDesc *buf;
+
+ if (StrategyControl->listFreeBuffers < 0)
+ {
+ /* We don't have a free buffer, must take one from T1 or T2 */
+
+ if (strategy_get_from == STRAT_LIST_T1)
+ {
+ /*
+ * We should take the first unpinned buffer from T1.
+ */
+ cdb_id = StrategyControl->listHead[STRAT_LIST_T1];
+ while (cdb_id >= 0)
+ {
+ buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
+ if (buf->refcount == 0)
+ {
+ strategy_cdb_replace = cdb_id;
+ Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T1);
+ return buf;
+ }
+ cdb_id = StrategyCDB[cdb_id].next;
+ }
+
+ /*
+ * No unpinned T1 buffer found - pardon T2 cache.
+ */
+ cdb_id = StrategyControl->listHead[STRAT_LIST_T2];
+ while (cdb_id >= 0)
+ {
+ buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
+ if (buf->refcount == 0)
+ {
+ strategy_cdb_replace = cdb_id;
+ Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T2);
+ return buf;
+ }
+ cdb_id = StrategyCDB[cdb_id].next;
+ }
+
+ /*
+ * No unpinned buffers at all!!!
+ */
+ elog(ERROR, "StrategyGetBuffer(): Out of unpinned buffers");
+ }
+ else
+ {
+ /*
+ * We should take the first unpinned buffer from T2.
+ */
+ cdb_id = StrategyControl->listHead[STRAT_LIST_T2];
+ while (cdb_id >= 0)
+ {
+ buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
+ if (buf->refcount == 0)
+ {
+ strategy_cdb_replace = cdb_id;
+ Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T2);
+ return buf;
+ }
+ cdb_id = StrategyCDB[cdb_id].next;
+ }
+
+ /*
+ * No unpinned T2 buffer found - pardon T1 cache.
+ */
+ cdb_id = StrategyControl->listHead[STRAT_LIST_T1];
+ while (cdb_id >= 0)
+ {
+ buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
+ if (buf->refcount == 0)
+ {
+ strategy_cdb_replace = cdb_id;
+ Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T1);
+ return buf;
+ }
+ cdb_id = StrategyCDB[cdb_id].next;
+ }
+
+ /*
+ * No unpinned buffers at all!!!
+ */
+ elog(ERROR, "StrategyGetBuffer(): Out of unpinned buffers");
+ }
+ }
+ else
+ {
+ /* There is a completely free buffer available - take it */
+
+ /*
+ * Note: This code uses the side effect that a free buffer
+ * can never be pinned or dirty and therefore the call to
+ * StrategyReplaceBuffer() will happen without the bufmgr
+ * releasing the bufmgr-lock in the meantime. That means,
+ * that there will never be any reason to recheck. Otherwise
+ * we would leak shared buffers here!
+ */
+ strategy_cdb_replace = -1;
+ buf = &BufferDescriptors[StrategyControl->listFreeBuffers];
+
+ StrategyControl->listFreeBuffers = buf->bufNext;
+ buf->bufNext = -1;
+
+ /* Buffer of freelist cannot be pinned */
+ Assert(buf->refcount == 0);
+
+ return buf;
+ }
+
+ /* not reached */
+ return NULL;
+}
+
+
+/*
+ * StrategyReplaceBuffer
+ *
+ * Called by the buffer manager to inform us that he possibly flushed
+ * a buffer and is now about to replace the content. Prior to this call,
+ * the cache algorithm still reports the buffer as in the cache. After
+ * this call we report the new block, even if IO might still need to
+ * start.
+ */
+void
+StrategyReplaceBuffer(BufferDesc *buf, Relation rnode, BlockNumber blockNum)
+{
+ BufferStrategyCDB *cdb_found;
+ BufferStrategyCDB *cdb_replace;
+
+ if (strategy_cdb_found >= 0)
+ {
+ /* This was a ghost buffer cache hit (B1 or B2) */
+ cdb_found = &StrategyCDB[strategy_cdb_found];
+
+ /* Assert that the buffer remembered in cdb_found is the one */
+ /* the buffer manager is currently faulting in */
+ Assert(BUFFERTAG_EQUALS(&(cdb_found->buf_tag), rnode, blockNum));
+
+ if (strategy_cdb_replace >= 0)
+ {
+ /* We are satisfying it with an evicted T buffer */
+ cdb_replace = &StrategyCDB[strategy_cdb_replace];
+
+ /* Assert that the buffer remembered in cdb_replace is */
+ /* the one the buffer manager has just evicted */
+ Assert(cdb_replace->list == STRAT_LIST_T1 ||
+ cdb_replace->list == STRAT_LIST_T2);
+ Assert(cdb_replace->buf_id == buf->buf_id);
+ Assert(BUFFERTAGS_EQUAL(&(cdb_replace->buf_tag), &(buf->tag)));
+
+ /* If this was a T1 buffer faulted in by vacuum, just */
+ /* do not cause the CDB end up in the B1 list, so that */
+ /* the vacuum scan does not affect T1_target adjusting */
+ if (strategy_hint_vacuum)
+ {
+ BufTableDelete(&(cdb_replace->buf_tag));
+ STRAT_LIST_REMOVE(cdb_replace);
+ cdb_replace->buf_id = -1;
+ cdb_replace->next = StrategyControl->listUnusedCDB;
+ StrategyControl->listUnusedCDB = strategy_cdb_replace;
+ }
+ else
+ {
+ /* Under normal circumstances move the evicted */
+ /* T list entry to it's corresponding B list */
+ if (cdb_replace->list == STRAT_LIST_T1)
+ {
+ STRAT_LIST_REMOVE(cdb_replace);
+ STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B1);
+ }
+ else
+ {
+ STRAT_LIST_REMOVE(cdb_replace);
+ STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B2);
+ }
+ }
+ /* And clear it's block reference */
+ cdb_replace->buf_id = -1;
+ }
+ else
+ {
+ /* or we satisfy it with an unused buffer */
+ }
+
+ /* Now the found B CDB get's the buffer and is moved to T2 */
+ cdb_found->buf_id = buf->buf_id;
+ STRAT_LIST_REMOVE(cdb_found);
+ STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T2);
+ }
+ else
+ {
+ /* This was a complete cache miss, so we need to create */
+ /* a new CDB. The goal is to keep T1len+B1len <= c */
+
+ if (B1_LENGTH > 0 && (T1_LENGTH + B1_LENGTH) >= Data_Descriptors)
+ {
+ /* So if B1 isn't empty and T1len+B1len >= c we take B1-LRU */
+ cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B1]];
+
+ BufTableDelete(&(cdb_found->buf_tag));
+ STRAT_LIST_REMOVE(cdb_found);
+ }
+ else
+ {
+ /* Otherwise, we try to use a free one */
+ if (StrategyControl->listUnusedCDB >= 0)
+ {
+ cdb_found = &StrategyCDB[StrategyControl->listUnusedCDB];
+ StrategyControl->listUnusedCDB = cdb_found->next;
+ }
+ else
+ {
+ /* If there isn't, we take B2-LRU ... except if */
+ /* T1len+B1len+T2len = c ... oh my */
+ if (B2_LENGTH > 0)
+ cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B2]];
+ else
+ cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B1]];
+
+ BufTableDelete(&(cdb_found->buf_tag));
+ STRAT_LIST_REMOVE(cdb_found);
+ }
+ }
+
+ /* Set the CDB's buf_tag and insert the hash key */
+ INIT_BUFFERTAG(&(cdb_found->buf_tag), rnode, blockNum);
+ BufTableInsert(&(cdb_found->buf_tag), (cdb_found - StrategyCDB));
+
+ if (strategy_cdb_replace >= 0)
+ {
+ /* The buffer was formerly in a T list, move it's CDB
+ * to the corresponding B list */
+ cdb_replace = &StrategyCDB[strategy_cdb_replace];
+
+ Assert(cdb_replace->list == STRAT_LIST_T1 ||
+ cdb_replace->list == STRAT_LIST_T2);
+ Assert(cdb_replace->buf_id == buf->buf_id);
+ Assert(BUFFERTAGS_EQUAL(&(cdb_replace->buf_tag), &(buf->tag)));
+
+ if (cdb_replace->list == STRAT_LIST_T1)
+ {
+ STRAT_LIST_REMOVE(cdb_replace);
+ STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B1);
+ }
+ else
+ {
+ STRAT_LIST_REMOVE(cdb_replace);
+ STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B2);
+ }
+ /* And clear it's block reference */
+ cdb_replace->buf_id = -1;
+ }
+ else
+ {
+ /* or we satisfy it with an unused buffer */
+ }
+
+ /* Assign the buffer id to the new CDB */
+ cdb_found->buf_id = buf->buf_id;
+
+ /*
+ * Specialized VACUUM optimization. If this "complete cache miss"
+ * happened because vacuum needed the page, we want it later on
+ * to be placed at the LRU instead of the MRU position of T1.
+ */
+ if (strategy_hint_vacuum)
+ {
+ if (strategy_vacuum_xid != GetCurrentTransactionId())
+ {
+ strategy_hint_vacuum = false;
+ STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T1);
+ }
+ else
+ STRAT_LRU_INSERT(cdb_found, STRAT_LIST_T1);
+
+ }
+ else
+ STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T1);
+
+ /*
+ * Remember the Xid when this buffer went onto T1 to avoid
+ * a single UPDATE promoting a newcomer straight into T2.
+ */
+ cdb_found->t1_xid = GetCurrentTransactionId();
+ }
+}
+
+
+/*
+ * StrategyInvalidateBuffer
+ *
+ * Called by the buffer manager to inform us that a buffer content
+ * is no longer valid. We simply throw away any eventual existing
+ * buffer hash entry and move the CDB and buffer to the free lists.
+ */
+void
+StrategyInvalidateBuffer(BufferDesc *buf)
+{
+ int cdb_id;
+ BufferStrategyCDB *cdb;
+
+ cdb_id = BufTableLookup(&(buf->tag));
+
+ /* If we have the buffer somewhere in the directory, remove it
+ * and add the CDB to the list of unused CDB's. */
+ if (cdb_id >= 0)
+ {
+ cdb = &StrategyCDB[cdb_id];
+ BufTableDelete(&(cdb->buf_tag));
+ STRAT_LIST_REMOVE(cdb);
+ cdb->buf_id = -1;
+ cdb->next = StrategyControl->listUnusedCDB;
+ StrategyControl->listUnusedCDB = cdb_id;
+ }
+
+ /* Buffer is unreferenced now and should not contain any valid data
+ * so add it to the list of free buffers */
+ buf->bufNext = StrategyControl->listFreeBuffers;
+ StrategyControl->listFreeBuffers = buf->buf_id;
+}
+
+
+void
+StrategyHintVacuum(bool vacuum_active)
+{
+ strategy_hint_vacuum = vacuum_active;
+ strategy_vacuum_xid = GetCurrentTransactionId();
+}
+
+
+int
+StrategyDirtyBufferList(int *buffer_list, int max_buffers)
+{
+ int num_buffer_dirty = 0;
+ int cdb_id_t1;
+ int cdb_id_t2;
+ int buf_id;
+ BufferDesc *buf;
+
+ /*
+ * Traverse the T1 and T2 list LRU to MRU in "parallel"
+ * and add all dirty buffers found in that order to the list.
+ * The ARC strategy keeps all used buffers including pinned ones
+ * in the T1 or T2 list. So we cannot loose any dirty buffers.
+ */
+ cdb_id_t1 = StrategyControl->listHead[STRAT_LIST_T1];
+ cdb_id_t2 = StrategyControl->listHead[STRAT_LIST_T2];
+
+ while ((cdb_id_t1 >= 0 || cdb_id_t2 >= 0) &&
+ num_buffer_dirty < max_buffers)
+ {
+ if (cdb_id_t1 >= 0)
+ {
+ buf_id = StrategyCDB[cdb_id_t1].buf_id;
+ buf = &BufferDescriptors[buf_id];
+
+ if (buf->flags & BM_VALID)
+ {
+ if ((buf->flags & BM_DIRTY) || (buf->cntxDirty))
+ {
+ buffer_list[num_buffer_dirty++] = buf_id;
+ }
+ }
+
+ cdb_id_t1 = StrategyCDB[cdb_id_t1].next;
+ }
+
+ if (cdb_id_t2 >= 0)
+ {
+ buf_id = StrategyCDB[cdb_id_t2].buf_id;
+ buf = &BufferDescriptors[buf_id];
+
+ if (buf->flags & BM_VALID)
+ {
+ if ((buf->flags & BM_DIRTY) || (buf->cntxDirty))
+ {
+ buffer_list[num_buffer_dirty++] = buf_id;
+ }
+ }
+
+ cdb_id_t2 = StrategyCDB[cdb_id_t2].next;
+ }
+ }
+
+ return num_buffer_dirty;
+}
+
+
+/*
+ * StrategyInitialize -- initialize the buffer cache replacement
+ * strategy.
+ *
+ * Assume: All of the buffers are already building a linked list.
+ * Only called by postmaster and only during initialization.
+ */
+void
+StrategyInitialize(bool init)
+{
+ bool found;
+ int i;
+
+ /*
+ * Initialize the shared CDB lookup hashtable
+ */
+ InitBufTable(Data_Descriptors * 2);
+
+ /*
+ * Get or create the shared strategy control block and the CDB's
+ */
+ StrategyControl = (BufferStrategyControl *)
+ ShmemInitStruct("Buffer Strategy Status",
+ sizeof(BufferStrategyControl) +
+ sizeof(BufferStrategyCDB) * (Data_Descriptors * 2 - 1),
+ &found);
+ StrategyCDB = &(StrategyControl->cdb[0]);
+
+ if (!found)
+ {
+ /*
+ * Only done once, usually in postmaster
+ */
+ Assert(init);
+
+ /*
+ * Grab the whole linked list of free buffers for our
+ * strategy
+ */
+ StrategyControl->listFreeBuffers = 0;
+
+ /*
+ * We start off with a target T1 list size of
+ * half the available cache blocks.
+ */
+ StrategyControl->target_T1_size = Data_Descriptors / 2;
+
+ /*
+ * Initialize B1, T1, T2 and B2 lists to be empty
+ */
+ for (i = 0; i < STRAT_NUM_LISTS; i++)
+ {
+ StrategyControl->listHead[i] = -1;
+ StrategyControl->listTail[i] = -1;
+ StrategyControl->listSize[i] = 0;
+ StrategyControl->num_hit[i] = 0;
+ }
+ StrategyControl->num_lookup = 0;
+ StrategyControl->stat_report = 0;
+
+ /*
+ * All CDB's are linked as the listUnusedCDB
+ */
+ for (i = 0; i < Data_Descriptors * 2; i++)
+ {
+ StrategyCDB[i].next = i + 1;
+ StrategyCDB[i].list = STRAT_LIST_UNUSED;
+ CLEAR_BUFFERTAG(&(StrategyCDB[i].buf_tag));
+ StrategyCDB[i].buf_id = -1;
+ }
+ StrategyCDB[Data_Descriptors * 2 - 1].next = -1;
+ StrategyControl->listUnusedCDB = 0;
+ }
+ else
+ {
+ Assert(!init);
+ }
+}
+
+
#undef PinBuffer
/*
if (buf->refcount == 0)
{
- IsInQueue(buf);
-
- /* remove from freelist queue */
- BufferDescriptors[buf->freeNext].freePrev = buf->freePrev;
- BufferDescriptors[buf->freePrev].freeNext = buf->freeNext;
- buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR;
-
/* mark buffer as no longer free */
buf->flags &= ~BM_FREE;
}
- else
- IsNotInQueue(buf);
if (PrivateRefCount[b] == 0)
buf->refcount++;
{
int b = BufferDescriptorGetBuffer(buf) - 1;
- IsNotInQueue(buf);
Assert(buf->refcount > 0);
Assert(PrivateRefCount[b] > 0);
PrivateRefCount[b]--;
if (buf->refcount == 0)
{
/* buffer is now unpinned */
- AddBufferToFreelist(buf);
buf->flags |= BM_FREE;
}
else if ((buf->flags & BM_PIN_COUNT_WAITER) != 0 &&
}
#endif
-/*
- * GetFreeBuffer() -- get the 'next' buffer from the freelist.
- */
-BufferDesc *
-GetFreeBuffer(void)
-{
- BufferDesc *buf;
-
- if (Free_List_Descriptor == SharedFreeList->freeNext)
- {
- /* queue is empty. All buffers in the buffer pool are pinned. */
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_RESOURCES),
- errmsg("out of free buffers")));
- return NULL;
- }
- buf = &(BufferDescriptors[SharedFreeList->freeNext]);
-
- /* remove from freelist queue */
- BufferDescriptors[buf->freeNext].freePrev = buf->freePrev;
- BufferDescriptors[buf->freePrev].freeNext = buf->freeNext;
- buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR;
-
- buf->flags &= ~(BM_FREE);
-
- return buf;
-}
-
-/*
- * InitFreeList -- initialize the dummy buffer descriptor used
- * as a freelist head.
- *
- * Assume: All of the buffers are already linked in a circular
- * queue. Only called by postmaster and only during
- * initialization.
- */
-void
-InitFreeList(bool init)
-{
- SharedFreeList = &(BufferDescriptors[Free_List_Descriptor]);
-
- if (init)
- {
- /* we only do this once, normally in the postmaster */
- SharedFreeList->data = INVALID_OFFSET;
- SharedFreeList->flags = 0;
- SharedFreeList->flags &= ~(BM_VALID | BM_DELETED | BM_FREE);
- SharedFreeList->buf_id = Free_List_Descriptor;
-
- /* insert it into a random spot in the circular queue */
- SharedFreeList->freeNext = BufferDescriptors[0].freeNext;
- SharedFreeList->freePrev = 0;
- BufferDescriptors[SharedFreeList->freeNext].freePrev =
- BufferDescriptors[SharedFreeList->freePrev].freeNext =
- Free_List_Descriptor;
- }
-}
-
/*
* print out the free list and check for breaks.