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:
parent
6454d55a42
commit
5ffd1110e9
5 changed files with 54 additions and 19 deletions
|
@ -59,7 +59,8 @@ static int operate_dealloc_region_unlocked(int xid, const regionAllocArg *dat) {
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
|
||||||
t.status = REGION_VACANT;
|
t.status = REGION_VACANT;
|
||||||
t.region_xid = xid;
|
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
|
||||||
|
assert(!err);
|
||||||
|
|
||||||
TsetBoundaryTag(xid, firstPage -1, &t);
|
TsetBoundaryTag(xid, firstPage -1, &t);
|
||||||
|
|
||||||
|
@ -132,7 +133,8 @@ static void TdeallocBoundaryTag(int xid, pageid_t page) {
|
||||||
int ret = readBoundaryTag(xid, page, &t);
|
int ret = readBoundaryTag(xid, page, &t);
|
||||||
assert(ret);
|
assert(ret);
|
||||||
t.status = REGION_CONDEMNED;
|
t.status = REGION_CONDEMNED;
|
||||||
t.region_xid = xid;
|
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
|
||||||
|
assert(!err);
|
||||||
TsetBoundaryTag(xid, page, &t);
|
TsetBoundaryTag(xid, page, &t);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -146,7 +148,7 @@ void regionsInit(stasis_log_t *log) {
|
||||||
t.size = PAGEID_T_MAX;
|
t.size = PAGEID_T_MAX;
|
||||||
t.prev_size = PAGEID_T_MAX;
|
t.prev_size = PAGEID_T_MAX;
|
||||||
t.status = REGION_VACANT;
|
t.status = REGION_VACANT;
|
||||||
t.region_xid = INVALID_XID;
|
TgetTransactionFingerprint(INVALID_XID, &t.region_xid_fp);
|
||||||
t.allocation_manager = 0;
|
t.allocation_manager = 0;
|
||||||
|
|
||||||
// This does what TallocBoundaryTag(-1, 0, &t); would do, but it
|
// 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.
|
// - 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.
|
// - Multiple transactions can allocate space at the end of the page file without blocking each other.
|
||||||
new_tag.status = REGION_VACANT;
|
new_tag.status = REGION_VACANT;
|
||||||
new_tag.region_xid = INVALID_XID;
|
TgetTransactionFingerprint(INVALID_XID, &new_tag.region_xid_fp);
|
||||||
new_tag.allocation_manager = 0;
|
new_tag.allocation_manager = 0;
|
||||||
|
|
||||||
TallocBoundaryTag(xid, newPageid, &new_tag);
|
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.status = REGION_ZONED;
|
||||||
t.region_xid = xid;
|
int err = TgetTransactionFingerprint(xid, &t.region_xid_fp);
|
||||||
|
assert(!err);
|
||||||
t.allocation_manager = allocationManager;
|
t.allocation_manager = allocationManager;
|
||||||
t.size = pageCount;
|
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) {
|
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)++;
|
// (*firstPage)++;
|
||||||
|
|
||||||
|
@ -324,7 +327,7 @@ static void consolidateRegions(int xid, pageid_t * firstPage, boundary_tag *t)
|
||||||
// TODO: Truncate page file.
|
// TODO: Truncate page file.
|
||||||
TdeallocBoundaryTag(xid, succ_page);
|
TdeallocBoundaryTag(xid, succ_page);
|
||||||
mustWriteOriginalTag = 1;
|
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;
|
t->size = t->size + succ_tag.size + 1;
|
||||||
pageid_t succ_succ_page = succ_page + 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);
|
int ret = readBoundaryTag(xid, pred_page, &pred_tag);
|
||||||
assert(ret);
|
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);
|
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);
|
// 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.
|
// 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);
|
// printf("t.status = %d, REGION_VACANT = %d, t.size = %d, pageCount = %d\n", t.status, REGION_VACANT, t.size, pageCount);
|
||||||
|
|
|
@ -444,8 +444,27 @@ int TactiveThreadCount(void) {
|
||||||
int* TlistActiveTransactions(int *count) {
|
int* TlistActiveTransactions(int *count) {
|
||||||
return stasis_transaction_table_list_active(stasis_transaction_table, count);
|
return stasis_transaction_table_list_active(stasis_transaction_table, count);
|
||||||
}
|
}
|
||||||
int TisActiveTransaction(int xid) {
|
int TgetTransactionFingerprint(int xid, stasis_transaction_fingerprint_t * fp) {
|
||||||
return stasis_transaction_table_is_active(stasis_transaction_table, xid);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ typedef struct boundary_tag {
|
||||||
pageid_t size;
|
pageid_t size;
|
||||||
pageid_t prev_size;
|
pageid_t prev_size;
|
||||||
int status;
|
int status;
|
||||||
int region_xid;
|
stasis_transaction_fingerprint_t region_xid_fp;
|
||||||
int allocation_manager;
|
int allocation_manager;
|
||||||
} boundary_tag;
|
} boundary_tag;
|
||||||
|
|
||||||
|
|
|
@ -727,13 +727,25 @@ lsn_t TendNestedTopAction(int xid, void * handle);
|
||||||
*/
|
*/
|
||||||
int* TlistActiveTransactions(int *count);
|
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.
|
* Checks to see if a transaction is still active.
|
||||||
*
|
*
|
||||||
* @param xid The transaction id to be tested.
|
* @param xid The transaction id to be tested.
|
||||||
* @return true if the transaction is still running, false otherwise.
|
* @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.
|
* @return the number of currently active transactions.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -186,12 +186,6 @@ START_TEST(regions_randomizedTest) {
|
||||||
Tcommit(xid);
|
Tcommit(xid);
|
||||||
|
|
||||||
Tdeinit();
|
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",
|
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",
|
//peak bytes wasted = %5.2fM, blowup = %3.2f\n",
|
||||||
max_region_count,
|
max_region_count,
|
||||||
|
@ -200,6 +194,13 @@ START_TEST(regions_randomizedTest) {
|
||||||
(double)max_size/(double)max_ideal_size);
|
(double)max_size/(double)max_ideal_size);
|
||||||
// ((double)max_waste * PAGE_SIZE)/(1024.0*1024.0),
|
// ((double)max_waste * PAGE_SIZE)/(1024.0*1024.0),
|
||||||
// max_blowup);
|
// 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
|
} END_TEST
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue