Added concept of "transaction fingerprints"

The region allocator marks freed regions "Condemned" before actually freeing them.  Condemned regions are associated 
with a transaction; before they can be reused / coalesced the transaction that condemned them must complete.

The region allocator used "TisActiveTransaction(xid)" to determine whether a given transaction had completed.  The 
problem is that Stasis now reuses transaction id's, and condemned regions were never reclaimed in practice.  A transaction
fingerprint is a unique identifier (currently transaction id, rec_lsn) that can be used to distinguish transactions that
did not execute concurrently.

I considered an alternative design based upon end-of-transaction actions.  This would have worked as well, but would 
have been a more invasive change.
This commit is contained in:
Sears Russell 2010-02-18 19:12:46 +00:00
parent 6454d55a42
commit 5ffd1110e9
5 changed files with 54 additions and 19 deletions

View file

@ -59,7 +59,8 @@ static int operate_dealloc_region_unlocked(int xid, const regionAllocArg *dat) {
assert(ret);
t.status = REGION_VACANT;
t.region_xid = xid;
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
assert(!err);
TsetBoundaryTag(xid, firstPage -1, &t);
@ -132,7 +133,8 @@ static void TdeallocBoundaryTag(int xid, pageid_t page) {
int ret = readBoundaryTag(xid, page, &t);
assert(ret);
t.status = REGION_CONDEMNED;
t.region_xid = xid;
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
assert(!err);
TsetBoundaryTag(xid, page, &t);
}
@ -146,7 +148,7 @@ void regionsInit(stasis_log_t *log) {
t.size = PAGEID_T_MAX;
t.prev_size = PAGEID_T_MAX;
t.status = REGION_VACANT;
t.region_xid = INVALID_XID;
TgetTransactionFingerprint(INVALID_XID, &t.region_xid_fp);
t.allocation_manager = 0;
// This does what TallocBoundaryTag(-1, 0, &t); would do, but it
@ -285,7 +287,7 @@ static void TregionAllocHelper(int xid, pageid_t page, pageid_t pageCount, int a
// - It could cause some fragmentation if interleaved transactions are allocating, and some abort.
// - Multiple transactions can allocate space at the end of the page file without blocking each other.
new_tag.status = REGION_VACANT;
new_tag.region_xid = INVALID_XID;
TgetTransactionFingerprint(INVALID_XID, &new_tag.region_xid_fp);
new_tag.allocation_manager = 0;
TallocBoundaryTag(xid, newPageid, &new_tag);
@ -293,7 +295,8 @@ static void TregionAllocHelper(int xid, pageid_t page, pageid_t pageCount, int a
}
t.status = REGION_ZONED;
t.region_xid = xid;
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
assert(!err);
t.allocation_manager = allocationManager;
t.size = pageCount;
@ -303,7 +306,7 @@ static void TregionAllocHelper(int xid, pageid_t page, pageid_t pageCount, int a
static void consolidateRegions(int xid, pageid_t * firstPage, boundary_tag *t) {
if(t->status != REGION_VACANT || TisActiveTransaction(t->region_xid)) { return; }
if(t->status != REGION_VACANT || TisActiveTransaction(&t->region_xid_fp)) { return; }
// (*firstPage)++;
@ -324,7 +327,7 @@ static void consolidateRegions(int xid, pageid_t * firstPage, boundary_tag *t)
// TODO: Truncate page file.
TdeallocBoundaryTag(xid, succ_page);
mustWriteOriginalTag = 1;
} else if(succ_tag.status == REGION_VACANT && (!TisActiveTransaction(succ_tag.region_xid))) {
} else if(succ_tag.status == REGION_VACANT && (!TisActiveTransaction(&succ_tag.region_xid_fp))) {
t->size = t->size + succ_tag.size + 1;
pageid_t succ_succ_page = succ_page + succ_tag.size + 1;
@ -357,7 +360,7 @@ static void consolidateRegions(int xid, pageid_t * firstPage, boundary_tag *t)
int ret = readBoundaryTag(xid, pred_page, &pred_tag);
assert(ret);
if(pred_tag.status == REGION_VACANT && (!TisActiveTransaction(pred_tag.region_xid))) {
if(pred_tag.status == REGION_VACANT && (!TisActiveTransaction(&pred_tag.region_xid_fp))) {
TdeallocBoundaryTag(xid, *firstPage);
@ -452,7 +455,7 @@ pageid_t TregionAlloc(int xid, pageid_t pageCount, int allocationManager) {
// printf(" %d, {%d, %d, %d}\tpageCount=%d\n", pageid, t.size, t.prev_size, t.status, pageCount);
while(t.status != REGION_VACANT || t.size < pageCount || TisActiveTransaction(t.region_xid)) {
while(t.status != REGION_VACANT || t.size < pageCount || TisActiveTransaction(&t.region_xid_fp)) {
// TODO: This while loop and the boundary tag manipulation below should be factored into two submodules.
// printf("t.status = %d, REGION_VACANT = %d, t.size = %d, pageCount = %d\n", t.status, REGION_VACANT, t.size, pageCount);

View file

@ -444,8 +444,27 @@ int TactiveThreadCount(void) {
int* TlistActiveTransactions(int *count) {
return stasis_transaction_table_list_active(stasis_transaction_table, count);
}
int TisActiveTransaction(int xid) {
return stasis_transaction_table_is_active(stasis_transaction_table, xid);
int TgetTransactionFingerprint(int xid, stasis_transaction_fingerprint_t * fp) {
if(stasis_transaction_table_is_active(stasis_transaction_table, xid)) {
lsn_t rec_lsn = stasis_transaction_table_get(stasis_transaction_table, xid)->recLSN;
if(rec_lsn == INVALID_LSN) { // Generate a dummy entry.
Tupdate(xid,0,0,0,OPERATION_NOOP);
}
fp->xid = xid;
rec_lsn = stasis_transaction_table_get(stasis_transaction_table, xid)->recLSN;
fp->rec_lsn = rec_lsn;
return 0;
} else if(xid == INVALID_XID) {
fp->xid = INVALID_XID;
fp->rec_lsn = INVALID_LSN;
} else {
return ENOENT;
}
}
int TisActiveTransaction(stasis_transaction_fingerprint_t * fp) {
// stasis_transaction_table_is_active returns false for INVALID_XID, which is what we want.
return stasis_transaction_table_is_active(stasis_transaction_table, fp->xid)
&& stasis_transaction_table_get(stasis_transaction_table, fp->xid)->recLSN == fp->rec_lsn;
}

View file

@ -16,7 +16,7 @@ typedef struct boundary_tag {
pageid_t size;
pageid_t prev_size;
int status;
int region_xid;
stasis_transaction_fingerprint_t region_xid_fp;
int allocation_manager;
} boundary_tag;

View file

@ -727,13 +727,25 @@ lsn_t TendNestedTopAction(int xid, void * handle);
*/
int* TlistActiveTransactions(int *count);
/**
* A unique (for all time) transaction descriptor.
*
* Unlike xids, fingerprints are not reused.
*
*/
typedef struct stasis_transaction_fingerprint_t {
lsn_t rec_lsn; // The lsn of the first entry in the transaction
int xid; // The transaction id. Lets us look up the rec_lsn in the transaction table.
} stasis_transaction_fingerprint_t;
int TgetTransactionFingerprint(int xid, stasis_transaction_fingerprint_t * fp);
/**
* Checks to see if a transaction is still active.
*
* @param xid The transaction id to be tested.
* @return true if the transaction is still running, false otherwise.
*/
int TisActiveTransaction(int xid);
int TisActiveTransaction(stasis_transaction_fingerprint_t* fp);
/*
* @return the number of currently active transactions.
*/

View file

@ -186,12 +186,6 @@ START_TEST(regions_randomizedTest) {
Tcommit(xid);
Tdeinit();
if((double)max_size/(double)max_ideal_size > 5) {
// max_blowup isn't what we want here; it measures the peak
// percentage of the file that is unused. Instead, we want to
// measure the actual and ideal page file sizes for this run.
printf("WARNING: Excessive blowup ");
}
printf("Max # of regions = %lld, page file size = %5.2fM, ideal page file size = %5.2fM, (blowup = %5.2f)\n",
//peak bytes wasted = %5.2fM, blowup = %3.2f\n",
max_region_count,
@ -200,6 +194,13 @@ START_TEST(regions_randomizedTest) {
(double)max_size/(double)max_ideal_size);
// ((double)max_waste * PAGE_SIZE)/(1024.0*1024.0),
// max_blowup);
if((double)max_size/(double)max_ideal_size > 5) {
// max_blowup isn't what we want here; it measures the peak
// percentage of the file that is unused. Instead, we want to
// measure the actual and ideal page file sizes for this run.
printf("ERROR: Excessive blowup (max allowable is 5)\n");
abort();
}
} END_TEST