Summary: Refactored db_bench transaction stress tests so that they can be called from unit tests as well. Test Plan: run new unit test as well as db_bench Reviewers: yhchiang, IslamAbdelRahman, sdong Reviewed By: IslamAbdelRahman Subscribers: andrewkr, dhruba, leveldb Differential Revision: https://reviews.facebook.net/D55203main
parent
e8e6cf0173
commit
790252805d
@ -0,0 +1,237 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
|
||||||
|
#ifndef __STDC_FORMAT_MACROS |
||||||
|
#define __STDC_FORMAT_MACROS |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "util/transaction_test_util.h" |
||||||
|
|
||||||
|
#include <inttypes.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "rocksdb/db.h" |
||||||
|
#include "rocksdb/utilities/optimistic_transaction_db.h" |
||||||
|
#include "rocksdb/utilities/transaction.h" |
||||||
|
#include "rocksdb/utilities/transaction_db.h" |
||||||
|
#include "util/random.h" |
||||||
|
#include "util/string_util.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
RandomTransactionInserter::RandomTransactionInserter( |
||||||
|
Random64* rand, const WriteOptions& write_options, |
||||||
|
const ReadOptions& read_options, uint64_t num_keys, uint16_t num_sets) |
||||||
|
: rand_(rand), |
||||||
|
write_options_(write_options), |
||||||
|
read_options_(read_options), |
||||||
|
num_keys_(num_keys), |
||||||
|
num_sets_(num_sets) {} |
||||||
|
|
||||||
|
RandomTransactionInserter::~RandomTransactionInserter() { |
||||||
|
if (txn_ != nullptr) { |
||||||
|
delete txn_; |
||||||
|
} |
||||||
|
if (optimistic_txn_ != nullptr) { |
||||||
|
delete optimistic_txn_; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
bool RandomTransactionInserter::TransactionDBInsert( |
||||||
|
TransactionDB* db, const TransactionOptions& txn_options) { |
||||||
|
txn_ = db->BeginTransaction(write_options_, txn_options, txn_); |
||||||
|
|
||||||
|
return DoInsert(nullptr, txn_, false); |
||||||
|
} |
||||||
|
|
||||||
|
bool RandomTransactionInserter::OptimisticTransactionDBInsert( |
||||||
|
OptimisticTransactionDB* db, |
||||||
|
const OptimisticTransactionOptions& txn_options) { |
||||||
|
optimistic_txn_ = |
||||||
|
db->BeginTransaction(write_options_, txn_options, optimistic_txn_); |
||||||
|
|
||||||
|
return DoInsert(nullptr, optimistic_txn_, true); |
||||||
|
} |
||||||
|
|
||||||
|
bool RandomTransactionInserter::DBInsert(DB* db) { |
||||||
|
return DoInsert(db, nullptr, false); |
||||||
|
} |
||||||
|
|
||||||
|
bool RandomTransactionInserter::DoInsert(DB* db, Transaction* txn, |
||||||
|
bool is_optimistic) { |
||||||
|
Status s; |
||||||
|
WriteBatch batch; |
||||||
|
std::string value; |
||||||
|
|
||||||
|
// pick a random number to use to increment a key in each set
|
||||||
|
uint64_t incr = (rand_->Next() % 100) + 1; |
||||||
|
|
||||||
|
bool unexpected_error = false; |
||||||
|
|
||||||
|
// For each set, pick a key at random and increment it
|
||||||
|
for (uint8_t i = 0; i < num_sets_; i++) { |
||||||
|
uint64_t int_value = 0; |
||||||
|
char prefix_buf[5]; |
||||||
|
// prefix_buf needs to be large enough to hold a uint16 in string form
|
||||||
|
|
||||||
|
// key format: [SET#][random#]
|
||||||
|
std::string rand_key = ToString(rand_->Next() % num_keys_); |
||||||
|
Slice base_key(rand_key); |
||||||
|
|
||||||
|
// Pad prefix appropriately so we can iterate over each set
|
||||||
|
snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", i + 1); |
||||||
|
std::string full_key = std::string(prefix_buf) + base_key.ToString(); |
||||||
|
Slice key(full_key); |
||||||
|
|
||||||
|
if (txn != nullptr) { |
||||||
|
s = txn->GetForUpdate(read_options_, key, &value); |
||||||
|
} else { |
||||||
|
s = db->Get(read_options_, key, &value); |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
// Found key, parse its value
|
||||||
|
int_value = std::stoull(value); |
||||||
|
|
||||||
|
if (int_value == 0 || int_value == ULONG_MAX) { |
||||||
|
unexpected_error = true; |
||||||
|
fprintf(stderr, "Get returned unexpected value: %s\n", value.c_str()); |
||||||
|
s = Status::Corruption(); |
||||||
|
} |
||||||
|
} else if (s.IsNotFound()) { |
||||||
|
// Have not yet written to this key, so assume its value is 0
|
||||||
|
int_value = 0; |
||||||
|
s = Status::OK(); |
||||||
|
} else { |
||||||
|
// Optimistic transactions should never return non-ok status here.
|
||||||
|
// Non-optimistic transactions may return write-coflict/timeout errors.
|
||||||
|
if (is_optimistic || !(s.IsBusy() || s.IsTimedOut() || s.IsTryAgain())) { |
||||||
|
fprintf(stderr, "Get returned an unexpected error: %s\n", |
||||||
|
s.ToString().c_str()); |
||||||
|
unexpected_error = true; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
// Increment key
|
||||||
|
std::string sum = ToString(int_value + incr); |
||||||
|
if (txn != nullptr) { |
||||||
|
s = txn->Put(key, sum); |
||||||
|
if (!s.ok()) { |
||||||
|
// Since we did a GetForUpdate, Put should not fail.
|
||||||
|
fprintf(stderr, "Put returned an unexpected error: %s\n", |
||||||
|
s.ToString().c_str()); |
||||||
|
unexpected_error = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
batch.Put(key, sum); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
if (txn != nullptr) { |
||||||
|
s = txn->Commit(); |
||||||
|
|
||||||
|
if (!s.ok()) { |
||||||
|
if (is_optimistic) { |
||||||
|
// Optimistic transactions can have write-conflict errors on commit.
|
||||||
|
// Any other error is unexpected.
|
||||||
|
if (!(s.IsBusy() || s.IsTimedOut() || s.IsTryAgain())) { |
||||||
|
unexpected_error = true; |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Non-optimistic transactions should only fail due to expiration
|
||||||
|
// or write failures. For testing purproses, we do not expect any
|
||||||
|
// write failures.
|
||||||
|
if (!s.IsExpired()) { |
||||||
|
unexpected_error = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (unexpected_error) { |
||||||
|
fprintf(stderr, "Commit returned an unexpected error: %s\n", |
||||||
|
s.ToString().c_str()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
s = db->Write(write_options_, &batch); |
||||||
|
if (!s.ok()) { |
||||||
|
unexpected_error = true; |
||||||
|
fprintf(stderr, "Write returned an unexpected error: %s\n", |
||||||
|
s.ToString().c_str()); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (txn != nullptr) { |
||||||
|
txn->Rollback(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (s.ok()) { |
||||||
|
success_count_++; |
||||||
|
} else { |
||||||
|
failure_count_++; |
||||||
|
} |
||||||
|
|
||||||
|
last_status_ = s; |
||||||
|
|
||||||
|
// return success if we didn't get any unexpected errors
|
||||||
|
return !unexpected_error; |
||||||
|
} |
||||||
|
|
||||||
|
Status RandomTransactionInserter::Verify(DB* db, uint16_t num_sets) { |
||||||
|
uint64_t prev_total = 0; |
||||||
|
|
||||||
|
// For each set of keys with the same prefix, sum all the values
|
||||||
|
for (uint32_t i = 0; i < num_sets; i++) { |
||||||
|
char prefix_buf[5]; |
||||||
|
snprintf(prefix_buf, sizeof(prefix_buf), "%.4u", i + 1); |
||||||
|
uint64_t total = 0; |
||||||
|
|
||||||
|
Iterator* iter = db->NewIterator(ReadOptions()); |
||||||
|
|
||||||
|
for (iter->Seek(Slice(prefix_buf, 4)); iter->Valid(); iter->Next()) { |
||||||
|
Slice key = iter->key(); |
||||||
|
|
||||||
|
// stop when we reach a different prefix
|
||||||
|
if (key.ToString().compare(0, 4, prefix_buf) != 0) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
Slice value = iter->value(); |
||||||
|
uint64_t int_value = std::stoull(value.ToString()); |
||||||
|
if (int_value == 0 || int_value == ULONG_MAX) { |
||||||
|
fprintf(stderr, "Iter returned unexpected value: %s\n", |
||||||
|
value.ToString().c_str()); |
||||||
|
return Status::Corruption(); |
||||||
|
} |
||||||
|
|
||||||
|
total += int_value; |
||||||
|
} |
||||||
|
delete iter; |
||||||
|
|
||||||
|
if (i > 0) { |
||||||
|
if (total != prev_total) { |
||||||
|
fprintf(stderr, |
||||||
|
"RandomTransactionVerify found inconsistent totals. " |
||||||
|
"Set[%" PRIu32 "]: %" PRIu64 ", Set[%" PRIu32 "]: %" PRIu64 |
||||||
|
" \n", |
||||||
|
i - 1, prev_total, i, total); |
||||||
|
return Status::Corruption(); |
||||||
|
} |
||||||
|
} |
||||||
|
prev_total = total; |
||||||
|
} |
||||||
|
|
||||||
|
return Status::OK(); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
@ -0,0 +1,111 @@ |
|||||||
|
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||||
|
// This source code is licensed under the BSD-style license found in the
|
||||||
|
// LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
// of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#ifndef ROCKSDB_LITE |
||||||
|
|
||||||
|
#include "rocksdb/options.h" |
||||||
|
#include "rocksdb/utilities/optimistic_transaction_db.h" |
||||||
|
#include "rocksdb/utilities/transaction_db.h" |
||||||
|
|
||||||
|
namespace rocksdb { |
||||||
|
|
||||||
|
class DB; |
||||||
|
class Random64; |
||||||
|
|
||||||
|
// Utility class for stress testing transactions. Can be used to write many
|
||||||
|
// transactions in parallel and then validate that the data written is logically
|
||||||
|
// consistent. This class assumes the input DB is initially empty.
|
||||||
|
//
|
||||||
|
// Each call to TransactionDBInsert()/OptimisticTransactionDBInsert() will
|
||||||
|
// increment the value of a key in #num_sets sets of keys. Regardless of
|
||||||
|
// whether the transaction succeeds, the total sum of values of keys in each
|
||||||
|
// set is an invariant that should remain equal.
|
||||||
|
//
|
||||||
|
// After calling TransactionDBInsert()/OptimisticTransactionDBInsert() many
|
||||||
|
// times, Verify() can be called to validate that the invariant holds.
|
||||||
|
//
|
||||||
|
// To test writing Transaction in parallel, multiple threads can create a
|
||||||
|
// RandomTransactionInserter with similar arguments using the same DB.
|
||||||
|
class RandomTransactionInserter { |
||||||
|
public: |
||||||
|
// num_keys is the number of keys in each set.
|
||||||
|
// num_sets is the number of sets of keys.
|
||||||
|
explicit RandomTransactionInserter( |
||||||
|
Random64* rand, const WriteOptions& write_options = WriteOptions(), |
||||||
|
const ReadOptions& read_options = ReadOptions(), uint64_t num_keys = 1000, |
||||||
|
uint16_t num_sets = 3); |
||||||
|
|
||||||
|
~RandomTransactionInserter(); |
||||||
|
|
||||||
|
// Increment a key in each set using a Transaction on a TransactionDB.
|
||||||
|
//
|
||||||
|
// Returns true if the transaction succeeded OR if any error encountered was
|
||||||
|
// expected (eg a write-conflict). Error status may be obtained by calling
|
||||||
|
// GetLastStatus();
|
||||||
|
bool TransactionDBInsert( |
||||||
|
TransactionDB* db, |
||||||
|
const TransactionOptions& txn_options = TransactionOptions()); |
||||||
|
|
||||||
|
// Increment a key in each set using a Transaction on an
|
||||||
|
// OptimisticTransactionDB
|
||||||
|
//
|
||||||
|
// Returns true if the transaction succeeded OR if any error encountered was
|
||||||
|
// expected (eg a write-conflict). Error status may be obtained by calling
|
||||||
|
// GetLastStatus();
|
||||||
|
bool OptimisticTransactionDBInsert( |
||||||
|
OptimisticTransactionDB* db, |
||||||
|
const OptimisticTransactionOptions& txn_options = |
||||||
|
OptimisticTransactionOptions()); |
||||||
|
// Increment a key in each set without using a transaction. If this function
|
||||||
|
// is called in parallel, then Verify() may fail.
|
||||||
|
//
|
||||||
|
// Returns true if the write succeeds.
|
||||||
|
// Error status may be obtained by calling GetLastStatus().
|
||||||
|
bool DBInsert(DB* db); |
||||||
|
|
||||||
|
// Returns OK if Invariant is true.
|
||||||
|
static Status Verify(DB* db, uint16_t num_sets); |
||||||
|
|
||||||
|
// Returns the status of the previous Insert operation
|
||||||
|
Status GetLastStatus() { return last_status_; } |
||||||
|
|
||||||
|
// Returns the number of successfully written calls to
|
||||||
|
// TransactionDBInsert/OptimisticTransactionDBInsert/DBInsert
|
||||||
|
uint64_t GetSuccessCount() { return success_count_; } |
||||||
|
|
||||||
|
// Returns the number of calls to
|
||||||
|
// TransactionDBInsert/OptimisticTransactionDBInsert/DBInsert that did not
|
||||||
|
// write any data.
|
||||||
|
uint64_t GetFailureCount() { return failure_count_; } |
||||||
|
|
||||||
|
private: |
||||||
|
// Input options
|
||||||
|
Random64* rand_; |
||||||
|
const WriteOptions write_options_; |
||||||
|
const ReadOptions read_options_; |
||||||
|
const uint64_t num_keys_; |
||||||
|
const uint16_t num_sets_; |
||||||
|
|
||||||
|
// Number of successful insert batches performed
|
||||||
|
uint64_t success_count_ = 0; |
||||||
|
|
||||||
|
// Number of failed insert batches attempted
|
||||||
|
uint64_t failure_count_ = 0; |
||||||
|
|
||||||
|
// Status returned by most recent insert operation
|
||||||
|
Status last_status_; |
||||||
|
|
||||||
|
// optimization: re-use allocated transaction objects.
|
||||||
|
Transaction* txn_ = nullptr; |
||||||
|
Transaction* optimistic_txn_ = nullptr; |
||||||
|
|
||||||
|
bool DoInsert(DB* db, Transaction* txn, bool is_optimistic); |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace rocksdb
|
||||||
|
|
||||||
|
#endif // ROCKSDB_LITE
|
Loading…
Reference in new issue