const std::string TileGroupHeader::GetInfo() const { std::ostringstream os; os << "\t-----------------------------------------------------------\n"; os << "\tTILE GROUP HEADER \n"; oid_t active_tuple_slots = GetCurrentNextTupleSlot(); peloton::ItemPointer item; for (oid_t header_itr = 0; header_itr < active_tuple_slots; header_itr++) { txn_id_t txn_id = GetTransactionId(header_itr); cid_t beg_commit_id = GetBeginCommitId(header_itr); cid_t end_commit_id = GetEndCommitId(header_itr); bool insert_commit = GetInsertCommit(header_itr); bool delete_commit = GetDeleteCommit(header_itr); int width = 10; os << "\t txn id : "; if (txn_id == MAX_TXN_ID) os << std::setw(width) << "MAX_TXN_ID"; else os << std::setw(width) << txn_id; os << " beg cid : "; if (beg_commit_id == MAX_CID) os << std::setw(width) << "MAX_CID"; else os << std::setw(width) << beg_commit_id; os << " end cid : "; if (end_commit_id == MAX_CID) os << std::setw(width) << "MAX_CID"; else os << std::setw(width) << end_commit_id; os << " insert commit : "; if (insert_commit == true) os << "O"; else os << "X"; os << " delete commit : "; if (delete_commit == true) os << "O"; else os << "X"; peloton::ItemPointer location = GetNextItemPointer(header_itr); peloton::ItemPointer location2 = GetPrevItemPointer(header_itr); os << " next : " << "[ " << location.block << " , " << location.offset << " ]\n" << " prev : " << "[ " << location2.block << " , " << location2.offset << " ]\n"; } os << "\t-----------------------------------------------------------\n"; return os.str(); }
void PessimisticTxnManager::PerformUpdate(const oid_t &tile_group_id, const oid_t &tuple_id) { auto &manager = catalog::Manager::GetInstance(); auto tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader(); // Set MVCC info assert(tile_group_header->GetTransactionId(tuple_id) == current_txn->GetTransactionId()); assert(tile_group_header->GetBeginCommitId(tuple_id) == MAX_CID); assert(tile_group_header->GetEndCommitId(tuple_id) == MAX_CID); // Add the old tuple into the update set auto old_location = tile_group_header->GetPrevItemPointer(tuple_id); if (old_location.IsNull() == false) { // update an inserted version current_txn->RecordUpdate(old_location.block, old_location.offset); } }
void PessimisticTxnManager::PerformDelete(const oid_t &tile_group_id, const oid_t &tuple_id) { auto &manager = catalog::Manager::GetInstance(); auto tile_group_header = manager.GetTileGroup(tile_group_id)->GetHeader(); assert(tile_group_header->GetTransactionId(tuple_id) == current_txn->GetTransactionId()); assert(tile_group_header->GetBeginCommitId(tuple_id) == MAX_CID); assert(tile_group_header->GetEndCommitId(tuple_id) == MAX_CID); tile_group_header->SetEndCommitId(tuple_id, INVALID_CID); // Add the old tuple into the delete set auto old_location = tile_group_header->GetPrevItemPointer(tuple_id); if (old_location.IsNull() == false) { // delete an inserted version current_txn->RecordDelete(old_location.block, old_location.offset); } else { // if this version is newly inserted. current_txn->RecordDelete(tile_group_id, tuple_id); } }
// Validate that MVCC storage is correct, it assumes an old-to-new chain // Invariants // 1. Transaction id should either be INVALID_TXNID or INITIAL_TXNID // 2. Begin commit id should <= end commit id // 3. Timestamp consistence // 4. Version doubly linked list consistency static void ValidateMVCC_OldToNew(storage::DataTable *table) { auto &catalog_manager = catalog::Manager::GetInstance(); LOG_INFO("Validating MVCC storage"); int tile_group_count = table->GetTileGroupCount(); LOG_INFO("The table has %d tile groups in the table", tile_group_count); for (int tile_group_offset = 0; tile_group_offset < tile_group_count; tile_group_offset++) { LOG_INFO("Validate tile group #%d", tile_group_offset); auto tile_group = table->GetTileGroup(tile_group_offset); auto tile_group_header = tile_group->GetHeader(); size_t tuple_count = tile_group->GetAllocatedTupleCount(); LOG_INFO("Tile group #%d has allocated %lu tuples", tile_group_offset, tuple_count); // 1. Transaction id should either be INVALID_TXNID or INITIAL_TXNID for (oid_t tuple_slot = 0; tuple_slot < tuple_count; tuple_slot++) { txn_id_t txn_id = tile_group_header->GetTransactionId(tuple_slot); EXPECT_TRUE(txn_id == INVALID_TXN_ID || txn_id == INITIAL_TXN_ID) << "Transaction id is not INVALID_TXNID or INITIAL_TXNID"; } LOG_INFO("[OK] All tuples have valid txn id"); // double avg_version_chain_length = 0.0; for (oid_t tuple_slot = 0; tuple_slot < tuple_count; tuple_slot++) { txn_id_t txn_id = tile_group_header->GetTransactionId(tuple_slot); cid_t begin_cid = tile_group_header->GetBeginCommitId(tuple_slot); cid_t end_cid = tile_group_header->GetEndCommitId(tuple_slot); ItemPointer next_location = tile_group_header->GetNextItemPointer(tuple_slot); ItemPointer prev_location = tile_group_header->GetPrevItemPointer(tuple_slot); // 2. Begin commit id should <= end commit id EXPECT_TRUE(begin_cid <= end_cid) << "Tuple begin commit id is less than or equal to end commit id"; // This test assumes a oldest-to-newest version chain if (txn_id != INVALID_TXN_ID) { EXPECT_TRUE(begin_cid != MAX_CID) << "Non invalid txn shouldn't have a MAX_CID begin commit id"; // The version is an oldest version if (prev_location.IsNull()) { if (next_location.IsNull()) { EXPECT_EQ(end_cid, MAX_CID) << "Single version has a non MAX_CID end commit time"; } else { cid_t prev_end_cid = end_cid; ItemPointer prev_location(tile_group->GetTileGroupId(), tuple_slot); while (!next_location.IsNull()) { auto next_tile_group = catalog_manager.GetTileGroup(next_location.block); auto next_tile_group_header = next_tile_group->GetHeader(); txn_id_t next_txn_id = next_tile_group_header->GetTransactionId( next_location.offset); if (next_txn_id == INVALID_TXN_ID) { // If a version in the version chain has a INVALID_TXN_ID, it // must be at the tail // of the chain. It is either because we have deleted a tuple // (so append a invalid tuple), // or because this new version is aborted. EXPECT_TRUE( next_tile_group_header->GetNextItemPointer( next_location.offset).IsNull()) << "Invalid version in a version chain and is not delete"; } cid_t next_begin_cid = next_tile_group_header->GetBeginCommitId( next_location.offset); cid_t next_end_cid = next_tile_group_header->GetEndCommitId(next_location.offset); // 3. Timestamp consistence if (next_begin_cid == MAX_CID) { // It must be an aborted version, it should be at the end of the // chain EXPECT_TRUE( next_tile_group_header->GetNextItemPointer( next_location.offset).IsNull()) << "Version with MAX_CID begin cid is not version tail"; } else { EXPECT_EQ(prev_end_cid, next_begin_cid) << "Prev end commit id should equal net begin commit id"; ItemPointer next_prev_location = next_tile_group_header->GetPrevItemPointer( next_location.offset); // 4. Version doubly linked list consistency EXPECT_TRUE(next_prev_location.offset == prev_location.offset && next_prev_location.block == prev_location.block) << "Next version's prev version does not match"; } prev_location = next_location; prev_end_cid = next_end_cid; next_location = next_tile_group_header->GetNextItemPointer( next_location.offset); } // Now prev_location is at the tail of the version chain ItemPointer last_location = prev_location; auto last_tile_group = catalog_manager.GetTileGroup(last_location.block); auto last_tile_group_header = last_tile_group->GetHeader(); // txn_id_t last_txn_id = // last_tile_group_header->GetTransactionId(last_location.offset); cid_t last_end_cid = last_tile_group_header->GetEndCommitId(last_location.offset); EXPECT_TRUE( last_tile_group_header->GetNextItemPointer(last_location.offset) .IsNull()) << "Last version has a next pointer"; EXPECT_EQ(last_end_cid, MAX_CID) << "Last version doesn't end with MAX_CID"; } } } else { EXPECT_TRUE(tile_group_header->GetNextItemPointer(tuple_slot).IsNull()) << "Invalid tuple must not have next item pointer"; } } LOG_INFO("[OK] oldest-to-newest version chain validated"); } }
void TileGroupHeader::PrintVisibility(txn_id_t txn_id, cid_t at_cid) { oid_t active_tuple_slots = GetNextTupleSlot(); std::stringstream os; os << "\t-----------------------------------------------------------\n"; for (oid_t header_itr = 0; header_itr < active_tuple_slots; header_itr++) { bool own = (txn_id == GetTransactionId(header_itr)); bool activated = (at_cid >= GetBeginCommitId(header_itr)); bool invalidated = (at_cid >= GetEndCommitId(header_itr)); txn_id_t txn_id = GetTransactionId(header_itr); cid_t beg_commit_id = GetBeginCommitId(header_itr); cid_t end_commit_id = GetEndCommitId(header_itr); bool insert_commit = GetInsertCommit(header_itr); bool delete_commit = GetDeleteCommit(header_itr); int width = 10; os << "\tslot :: " << std::setw(width) << header_itr; os << " txn id : "; if (txn_id == MAX_TXN_ID) os << std::setw(width) << "MAX_TXN_ID"; else os << std::setw(width) << txn_id; os << " beg cid : "; if (beg_commit_id == MAX_CID) os << std::setw(width) << "MAX_CID"; else os << std::setw(width) << beg_commit_id; os << " end cid : "; if (end_commit_id == MAX_CID) os << std::setw(width) << "MAX_CID"; else os << std::setw(width) << end_commit_id; os << " insert commit : "; if (insert_commit == true) os << "O"; else os << "X"; os << " delete commit : "; if (delete_commit == true) os << "O"; else os << "X"; peloton::ItemPointer location = GetPrevItemPointer(header_itr); os << " prev : " << "[ " << location.block << " , " << location.offset << " ]"; //<< os << " own : " << own; os << " activated : " << activated; os << " invalidated : " << invalidated << " "; // Visible iff past Insert || Own Insert if ((!own && activated && !invalidated) || (own && !activated && !invalidated)) os << "\t\t[ true ]\n"; else os << "\t\t[ false ]\n"; } os << "\t-----------------------------------------------------------\n"; std::cout << os.str().c_str(); }
void TimestampOrderingTransactionManager::PerformUpdate( TransactionContext *const current_txn, const ItemPointer &location, const ItemPointer &new_location) { PL_ASSERT(current_txn->GetIsolationLevel() != IsolationLevelType::READ_ONLY); ItemPointer old_location = location; LOG_TRACE("Performing Update old tuple %u %u", old_location.block, old_location.offset); LOG_TRACE("Performing Update new tuple %u %u", new_location.block, new_location.offset); auto &manager = catalog::Manager::GetInstance(); auto tile_group_header = manager.GetTileGroup(old_location.block)->GetHeader(); auto new_tile_group_header = manager.GetTileGroup(new_location.block)->GetHeader(); auto transaction_id = current_txn->GetTransactionId(); // if we can perform update, then we must have already locked the older // version. PL_ASSERT(tile_group_header->GetTransactionId(old_location.offset) == transaction_id); PL_ASSERT(tile_group_header->GetPrevItemPointer(old_location.offset) .IsNull() == true); // check whether the new version is empty. PL_ASSERT(new_tile_group_header->GetTransactionId(new_location.offset) == INVALID_TXN_ID); PL_ASSERT(new_tile_group_header->GetBeginCommitId(new_location.offset) == MAX_CID); PL_ASSERT(new_tile_group_header->GetEndCommitId(new_location.offset) == MAX_CID); // if the executor doesn't call PerformUpdate after AcquireOwnership, // no one will possibly release the write lock acquired by this txn. // Set double linked list tile_group_header->SetPrevItemPointer(old_location.offset, new_location); new_tile_group_header->SetNextItemPointer(new_location.offset, old_location); new_tile_group_header->SetTransactionId(new_location.offset, transaction_id); // we should guarantee that the newer version is all set before linking the // newer version to older version. COMPILER_MEMORY_FENCE; InitTupleReserved(new_tile_group_header, new_location.offset); // we must be updating the latest version. // Set the header information for the new version ItemPointer *index_entry_ptr = tile_group_header->GetIndirection(old_location.offset); // if there's no primary index on a table, then index_entry_ptr == nullptr. if (index_entry_ptr != nullptr) { new_tile_group_header->SetIndirection(new_location.offset, index_entry_ptr); // Set the index header in an atomic way. // We do it atomically because we don't want any one to see a half-done // pointer. // In case of contention, no one can update this pointer when we are // updating it // because we are holding the write lock. This update should success in // its first trial. UNUSED_ATTRIBUTE auto res = AtomicUpdateItemPointer(index_entry_ptr, new_location); PL_ASSERT(res == true); } // Add the old tuple into the update set current_txn->RecordUpdate(old_location); // Increment table update op stats if (static_cast<StatsType>(settings::SettingsManager::GetInt(settings::SettingId::stats_mode)) != StatsType::INVALID) { stats::BackendStatsContext::GetInstance()->IncrementTableUpdates( new_location.block); } }