TransactionDB:ReinitializeTransaction

Summary: Add function to reinitialize a transaction object so that it can be reused.  This is an optimization so users can potentially avoid reallocating transaction objects.

Test Plan: added tests

Reviewers: yhchiang, kradhakrishnan, IslamAbdelRahman, sdong

Reviewed By: sdong

Subscribers: jkedgar, dhruba, leveldb

Differential Revision: https://reviews.facebook.net/D53835
This commit is contained in:
agiardullo 2016-02-02 19:19:17 -08:00
parent 1f5954147b
commit 5ea9aa3c14
8 changed files with 176 additions and 22 deletions

View File

@ -111,14 +111,18 @@ class TransactionDB : public StackableDB {
virtual ~TransactionDB() {} virtual ~TransactionDB() {}
// Starts a new Transaction. Passing set_snapshot=true has the same effect // Starts a new Transaction.
// as calling Transaction::SetSnapshot().
// //
// Caller should delete the returned transaction after calling // Caller is responsible for deleting the returned transaction when no
// Transaction::Commit() or Transaction::Rollback(). // longer needed.
//
// If old_txn is not null, BeginTransaction will reuse this Transaction
// handle instead of allocating a new one. This is an optimization to avoid
// extra allocations when repeatedly creating transactions.
virtual Transaction* BeginTransaction( virtual Transaction* BeginTransaction(
const WriteOptions& write_options, const WriteOptions& write_options,
const TransactionOptions& txn_options = TransactionOptions()) = 0; const TransactionOptions& txn_options = TransactionOptions(),
Transaction* old_txn = nullptr) = 0;
protected: protected:
// To Create an TransactionDB, call Open() // To Create an TransactionDB, call Open()

View File

@ -24,7 +24,10 @@ TransactionBaseImpl::TransactionBaseImpl(DB* db,
start_time_(db_->GetEnv()->NowMicros()), start_time_(db_->GetEnv()->NowMicros()),
write_batch_(cmp_, 0, true) {} write_batch_(cmp_, 0, true) {}
TransactionBaseImpl::~TransactionBaseImpl() {} TransactionBaseImpl::~TransactionBaseImpl() {
// Release snapshot if snapshot is set
SetSnapshotInternal(nullptr);
}
void TransactionBaseImpl::Clear() { void TransactionBaseImpl::Clear() {
save_points_.reset(nullptr); save_points_.reset(nullptr);
@ -35,12 +38,22 @@ void TransactionBaseImpl::Clear() {
num_merges_ = 0; num_merges_ = 0;
} }
void TransactionBaseImpl::Reinitialize(const WriteOptions& write_options) {
Clear();
write_options_ = write_options;
start_time_ = db_->GetEnv()->NowMicros();
}
void TransactionBaseImpl::SetSnapshot() { void TransactionBaseImpl::SetSnapshot() {
assert(dynamic_cast<DBImpl*>(db_) != nullptr); assert(dynamic_cast<DBImpl*>(db_) != nullptr);
auto db_impl = reinterpret_cast<DBImpl*>(db_); auto db_impl = reinterpret_cast<DBImpl*>(db_);
const Snapshot* snapshot = db_impl->GetSnapshotForWriteConflictBoundary(); const Snapshot* snapshot = db_impl->GetSnapshotForWriteConflictBoundary();
SetSnapshotInternal(snapshot);
}
void TransactionBaseImpl::SetSnapshotInternal(const Snapshot* snapshot) {
// Set a custom deleter for the snapshot_ SharedPtr as the snapshot needs to // Set a custom deleter for the snapshot_ SharedPtr as the snapshot needs to
// be released, not deleted when it is no longer referenced. // be released, not deleted when it is no longer referenced.
snapshot_.reset(snapshot, std::bind(&TransactionBaseImpl::ReleaseSnapshot, snapshot_.reset(snapshot, std::bind(&TransactionBaseImpl::ReleaseSnapshot,
@ -493,8 +506,10 @@ WriteBatchBase* TransactionBaseImpl::GetBatchForWrite() {
} }
void TransactionBaseImpl::ReleaseSnapshot(const Snapshot* snapshot, DB* db) { void TransactionBaseImpl::ReleaseSnapshot(const Snapshot* snapshot, DB* db) {
if (snapshot != nullptr) {
db->ReleaseSnapshot(snapshot); db->ReleaseSnapshot(snapshot);
} }
}
void TransactionBaseImpl::UndoGetForUpdate(ColumnFamilyHandle* column_family, void TransactionBaseImpl::UndoGetForUpdate(ColumnFamilyHandle* column_family,
const Slice& key) { const Slice& key) {

View File

@ -32,6 +32,8 @@ class TransactionBaseImpl : public Transaction {
// Remove pending operations queued in this transaction. // Remove pending operations queued in this transaction.
virtual void Clear(); virtual void Clear();
void Reinitialize(const WriteOptions& write_options);
// Called before executing Put, Merge, Delete, and GetForUpdate. If TryLock // Called before executing Put, Merge, Delete, and GetForUpdate. If TryLock
// returns non-OK, the Put/Merge/Delete/GetForUpdate will be failed. // returns non-OK, the Put/Merge/Delete/GetForUpdate will be failed.
// untracked will be true if called from PutUntracked, DeleteUntracked, or // untracked will be true if called from PutUntracked, DeleteUntracked, or
@ -240,7 +242,7 @@ class TransactionBaseImpl : public Transaction {
const Comparator* cmp_; const Comparator* cmp_;
// Stores that time the txn was constructed, in microseconds. // Stores that time the txn was constructed, in microseconds.
const uint64_t start_time_; uint64_t start_time_;
// Stores the current snapshot that was was set by SetSnapshot or null if // Stores the current snapshot that was was set by SetSnapshot or null if
// no snapshot is currently set. // no snapshot is currently set.
@ -306,6 +308,8 @@ class TransactionBaseImpl : public Transaction {
bool read_only, bool untracked = false); bool read_only, bool untracked = false);
WriteBatchBase* GetBatchForWrite(); WriteBatchBase* GetBatchForWrite();
void SetSnapshotInternal(const Snapshot* snapshot);
}; };
} // namespace rocksdb } // namespace rocksdb

View File

@ -31,10 +31,14 @@ TransactionDBImpl::TransactionDBImpl(DB* db,
new TransactionDBMutexFactoryImpl())) {} new TransactionDBMutexFactoryImpl())) {}
Transaction* TransactionDBImpl::BeginTransaction( Transaction* TransactionDBImpl::BeginTransaction(
const WriteOptions& write_options, const TransactionOptions& txn_options) { const WriteOptions& write_options, const TransactionOptions& txn_options,
Transaction* txn = new TransactionImpl(this, write_options, txn_options); Transaction* old_txn) {
if (old_txn != nullptr) {
return txn; ReinitializeTransaction(old_txn, write_options, txn_options);
return old_txn;
} else {
return new TransactionImpl(this, write_options, txn_options);
}
} }
TransactionDBOptions TransactionDBImpl::ValidateTxnDBOptions( TransactionDBOptions TransactionDBImpl::ValidateTxnDBOptions(
@ -173,7 +177,7 @@ void TransactionDBImpl::UnLock(TransactionImpl* txn, uint32_t cfh_id,
Transaction* TransactionDBImpl::BeginInternalTransaction( Transaction* TransactionDBImpl::BeginInternalTransaction(
const WriteOptions& options) { const WriteOptions& options) {
TransactionOptions txn_options; TransactionOptions txn_options;
Transaction* txn = BeginTransaction(options, txn_options); Transaction* txn = BeginTransaction(options, txn_options, nullptr);
assert(dynamic_cast<TransactionImpl*>(txn) != nullptr); assert(dynamic_cast<TransactionImpl*>(txn) != nullptr);
auto txn_impl = reinterpret_cast<TransactionImpl*>(txn); auto txn_impl = reinterpret_cast<TransactionImpl*>(txn);
@ -302,5 +306,14 @@ bool TransactionDBImpl::TryStealingExpiredTransactionLocks(
return tx.TryStealingLocks(); return tx.TryStealingLocks();
} }
void TransactionDBImpl::ReinitializeTransaction(
Transaction* txn, const WriteOptions& write_options,
const TransactionOptions& txn_options) {
assert(dynamic_cast<TransactionImpl*>(txn) != nullptr);
auto txn_impl = reinterpret_cast<TransactionImpl*>(txn);
txn_impl->Reinitialize(write_options, txn_options);
}
} // namespace rocksdb } // namespace rocksdb
#endif // ROCKSDB_LITE #endif // ROCKSDB_LITE

View File

@ -26,7 +26,8 @@ class TransactionDBImpl : public TransactionDB {
~TransactionDBImpl() {} ~TransactionDBImpl() {}
Transaction* BeginTransaction(const WriteOptions& write_options, Transaction* BeginTransaction(const WriteOptions& write_options,
const TransactionOptions& txn_options) override; const TransactionOptions& txn_options,
Transaction* old_txn) override;
using StackableDB::Put; using StackableDB::Put;
virtual Status Put(const WriteOptions& options, virtual Status Put(const WriteOptions& options,
@ -78,6 +79,10 @@ class TransactionDBImpl : public TransactionDB {
bool TryStealingExpiredTransactionLocks(TransactionID tx_id); bool TryStealingExpiredTransactionLocks(TransactionID tx_id);
private: private:
void ReinitializeTransaction(
Transaction* txn, const WriteOptions& write_options,
const TransactionOptions& txn_options = TransactionOptions());
const TransactionDBOptions txn_db_options_; const TransactionDBOptions txn_db_options_;
TransactionLockMgr lock_mgr_; TransactionLockMgr lock_mgr_;

View File

@ -39,21 +39,34 @@ TransactionImpl::TransactionImpl(TransactionDB* txn_db,
const TransactionOptions& txn_options) const TransactionOptions& txn_options)
: TransactionBaseImpl(txn_db->GetBaseDB(), write_options), : TransactionBaseImpl(txn_db->GetBaseDB(), write_options),
txn_db_impl_(nullptr), txn_db_impl_(nullptr),
txn_id_(GenTxnID()), txn_id_(0),
expiration_time_(txn_options.expiration >= 0 expiration_time_(0),
? start_time_ + txn_options.expiration * 1000 lock_timeout_(0),
: 0),
lock_timeout_(txn_options.lock_timeout * 1000),
exec_status_(STARTED) { exec_status_(STARTED) {
txn_db_impl_ = dynamic_cast<TransactionDBImpl*>(txn_db); txn_db_impl_ = dynamic_cast<TransactionDBImpl*>(txn_db);
assert(txn_db_impl_); assert(txn_db_impl_);
Initialize(txn_options);
}
void TransactionImpl::Initialize(const TransactionOptions& txn_options) {
txn_id_ = GenTxnID();
exec_status_ = STARTED;
lock_timeout_ = txn_options.lock_timeout * 1000;
if (lock_timeout_ < 0) { if (lock_timeout_ < 0) {
// Lock timeout not set, use default // Lock timeout not set, use default
lock_timeout_ = lock_timeout_ =
txn_db_impl_->GetTxnDBOptions().transaction_lock_timeout * 1000; txn_db_impl_->GetTxnDBOptions().transaction_lock_timeout * 1000;
} }
if (txn_options.expiration >= 0) {
expiration_time_ = start_time_ + txn_options.expiration * 1000;
} else {
expiration_time_ = 0;
}
if (txn_options.set_snapshot) { if (txn_options.set_snapshot) {
SetSnapshot(); SetSnapshot();
} }
@ -74,6 +87,12 @@ void TransactionImpl::Clear() {
TransactionBaseImpl::Clear(); TransactionBaseImpl::Clear();
} }
void TransactionImpl::Reinitialize(const WriteOptions& write_options,
const TransactionOptions& txn_options) {
TransactionBaseImpl::Reinitialize(write_options);
Initialize(txn_options);
}
bool TransactionImpl::IsExpired() const { bool TransactionImpl::IsExpired() const {
if (expiration_time_ > 0) { if (expiration_time_ > 0) {
if (db_->GetEnv()->NowMicros() >= expiration_time_) { if (db_->GetEnv()->NowMicros() >= expiration_time_) {

View File

@ -38,6 +38,9 @@ class TransactionImpl : public TransactionBaseImpl {
virtual ~TransactionImpl(); virtual ~TransactionImpl();
void Reinitialize(const WriteOptions& write_options,
const TransactionOptions& txn_options);
Status Commit() override; Status Commit() override;
Status CommitBatch(WriteBatch* batch); Status CommitBatch(WriteBatch* batch);
@ -82,11 +85,11 @@ class TransactionImpl : public TransactionBaseImpl {
static std::atomic<TransactionID> txn_id_counter_; static std::atomic<TransactionID> txn_id_counter_;
// Unique ID for this transaction // Unique ID for this transaction
const TransactionID txn_id_; TransactionID txn_id_;
// If non-zero, this transaction should not be committed after this time (in // If non-zero, this transaction should not be committed after this time (in
// microseconds according to Env->NowMicros()) // microseconds according to Env->NowMicros())
const uint64_t expiration_time_; uint64_t expiration_time_;
// Timeout in microseconds when locking a key or -1 if there is no timeout. // Timeout in microseconds when locking a key or -1 if there is no timeout.
int64_t lock_timeout_; int64_t lock_timeout_;
@ -96,6 +99,8 @@ class TransactionImpl : public TransactionBaseImpl {
void Clear() override; void Clear() override;
void Initialize(const TransactionOptions& txn_options);
Status ValidateSnapshot(ColumnFamilyHandle* column_family, const Slice& key, Status ValidateSnapshot(ColumnFamilyHandle* column_family, const Slice& key,
SequenceNumber prev_seqno, SequenceNumber* new_seqno); SequenceNumber prev_seqno, SequenceNumber* new_seqno);

View File

@ -448,7 +448,6 @@ TEST_F(TransactionTest, FlushTest2) {
s = txn->Delete("S"); s = txn->Delete("S");
// Should fail after encountering a write to S in SST file // Should fail after encountering a write to S in SST file
fprintf(stderr, "%" ROCKSDB_PRIszt " %s\n", n, s.ToString().c_str());
ASSERT_TRUE(s.IsBusy()); ASSERT_TRUE(s.IsBusy());
// Write a bunch of keys to db to force a compaction // Write a bunch of keys to db to force a compaction
@ -1210,6 +1209,96 @@ TEST_F(TransactionTest, ExpiredTransaction) {
delete txn2; delete txn2;
} }
TEST_F(TransactionTest, ReinitializeTest) {
WriteOptions write_options;
ReadOptions read_options;
TransactionOptions txn_options;
string value;
Status s;
// Set txn expiration timeout to 0 microseconds (expires instantly)
txn_options.expiration = 0;
Transaction* txn1 = db->BeginTransaction(write_options, txn_options);
// Reinitialize transaction to no long expire
txn_options.expiration = -1;
db->BeginTransaction(write_options, txn_options, txn1);
s = txn1->Put("Z", "z");
ASSERT_OK(s);
// Should commit since not expired
s = txn1->Commit();
ASSERT_OK(s);
db->BeginTransaction(write_options, txn_options, txn1);
s = txn1->Put("Z", "zz");
ASSERT_OK(s);
// Reinitilize txn1 and verify that Z gets unlocked
db->BeginTransaction(write_options, txn_options, txn1);
Transaction* txn2 = db->BeginTransaction(write_options, txn_options, nullptr);
s = txn2->Put("Z", "zzz");
ASSERT_OK(s);
s = txn2->Commit();
ASSERT_OK(s);
delete txn2;
s = db->Get(read_options, "Z", &value);
ASSERT_OK(s);
ASSERT_EQ(value, "zzz");
// Verify snapshots get reinitialized correctly
txn1->SetSnapshot();
s = txn1->Put("Z", "zzzz");
ASSERT_OK(s);
s = txn1->Commit();
ASSERT_OK(s);
s = db->Get(read_options, "Z", &value);
ASSERT_OK(s);
ASSERT_EQ(value, "zzzz");
db->BeginTransaction(write_options, txn_options, txn1);
const Snapshot* snapshot = txn1->GetSnapshot();
ASSERT_TRUE(snapshot);
txn_options.set_snapshot = true;
db->BeginTransaction(write_options, txn_options, txn1);
snapshot = txn1->GetSnapshot();
ASSERT_TRUE(snapshot);
s = txn1->Put("Z", "a");
ASSERT_OK(s);
txn1->Rollback();
s = txn1->Put("Y", "y");
ASSERT_OK(s);
txn_options.set_snapshot = false;
db->BeginTransaction(write_options, txn_options, txn1);
snapshot = txn1->GetSnapshot();
s = txn1->Put("X", "x");
ASSERT_OK(s);
s = txn1->Commit();
ASSERT_OK(s);
s = db->Get(read_options, "Z", &value);
ASSERT_OK(s);
ASSERT_EQ(value, "zzzz");
s = db->Get(read_options, "Y", &value);
ASSERT_TRUE(s.IsNotFound());
delete txn1;
}
TEST_F(TransactionTest, Rollback) { TEST_F(TransactionTest, Rollback) {
WriteOptions write_options; WriteOptions write_options;
ReadOptions read_options; ReadOptions read_options;