Check wide columns in TestIterate (#10818)

Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/10818

Test Plan: Tested using some simple blackbox crash test runs in the various modes (non-batched, batched, CF consistency).

Reviewed By: riversand963

Differential Revision: D40349527

Pulled By: ltamasi

fbshipit-source-id: 2918bc26adbbeac314beaa958aafe770b01e5cc6
main
Levi Tamasi 2 years ago committed by Facebook GitHub Bot
parent 1ee747d795
commit eae3a686ee
  1. 5
      db_stress_tool/batched_ops_stress.cc
  2. 161
      db_stress_tool/db_stress_test_base.cc
  3. 2
      db_stress_tool/db_stress_test_base.h

@ -377,6 +377,11 @@ class BatchedOpsStressTest : public StressTest {
return Status::OK(); return Status::OK();
} }
Slice GetValueBaseSlice(Slice slice) override {
slice.remove_prefix(1);
return slice;
}
void VerifyDb(ThreadState* /* thread */) const override {} void VerifyDb(ThreadState* /* thread */) const override {}
void ContinuouslyVerifyDb(ThreadState* /* thread */) const override {} void ContinuouslyVerifyDb(ThreadState* /* thread */) const override {}

@ -1133,24 +1133,26 @@ Status StressTest::TestIterate(ThreadState* thread,
const ReadOptions& read_opts, const ReadOptions& read_opts,
const std::vector<int>& rand_column_families, const std::vector<int>& rand_column_families,
const std::vector<int64_t>& rand_keys) { const std::vector<int64_t>& rand_keys) {
Status s; assert(!rand_column_families.empty());
const Snapshot* snapshot = db_->GetSnapshot(); assert(!rand_keys.empty());
ReadOptions readoptionscopy = read_opts;
readoptionscopy.snapshot = snapshot; ManagedSnapshot snapshot_guard(db_);
ReadOptions ro = read_opts;
ro.snapshot = snapshot_guard.snapshot();
std::string read_ts_str; std::string read_ts_str;
Slice read_ts_slice; Slice read_ts_slice;
MaybeUseOlderTimestampForRangeScan(thread, read_ts_str, read_ts_slice, MaybeUseOlderTimestampForRangeScan(thread, read_ts_str, read_ts_slice, ro);
readoptionscopy);
bool expect_total_order = false; bool expect_total_order = false;
if (thread->rand.OneIn(16)) { if (thread->rand.OneIn(16)) {
// When prefix extractor is used, it's useful to cover total order seek. // When prefix extractor is used, it's useful to cover total order seek.
readoptionscopy.total_order_seek = true; ro.total_order_seek = true;
expect_total_order = true; expect_total_order = true;
} else if (thread->rand.OneIn(4)) { } else if (thread->rand.OneIn(4)) {
readoptionscopy.total_order_seek = false; ro.total_order_seek = false;
readoptionscopy.auto_prefix_mode = true; ro.auto_prefix_mode = true;
expect_total_order = true; expect_total_order = true;
} else if (options_.prefix_extractor.get() == nullptr) { } else if (options_.prefix_extractor.get() == nullptr) {
expect_total_order = true; expect_total_order = true;
@ -1159,101 +1161,106 @@ Status StressTest::TestIterate(ThreadState* thread,
std::string upper_bound_str; std::string upper_bound_str;
Slice upper_bound; Slice upper_bound;
if (thread->rand.OneIn(16)) { if (thread->rand.OneIn(16)) {
// in 1/16 chance, set a iterator upper bound // With a 1/16 chance, set an iterator upper bound.
int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread); // Note: upper_bound can be smaller than the seek key.
const int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread);
upper_bound_str = Key(rand_upper_key); upper_bound_str = Key(rand_upper_key);
upper_bound = Slice(upper_bound_str); upper_bound = Slice(upper_bound_str);
// uppder_bound can be smaller than seek key, but the query itself ro.iterate_upper_bound = &upper_bound;
// should not crash either.
readoptionscopy.iterate_upper_bound = &upper_bound;
} }
std::string lower_bound_str; std::string lower_bound_str;
Slice lower_bound; Slice lower_bound;
if (thread->rand.OneIn(16)) { if (thread->rand.OneIn(16)) {
// in 1/16 chance, enable iterator lower bound // With a 1/16 chance, enable iterator lower bound.
int64_t rand_lower_key = GenerateOneKey(thread, FLAGS_ops_per_thread); // Note: lower_bound can be greater than the seek key.
const int64_t rand_lower_key = GenerateOneKey(thread, FLAGS_ops_per_thread);
lower_bound_str = Key(rand_lower_key); lower_bound_str = Key(rand_lower_key);
lower_bound = Slice(lower_bound_str); lower_bound = Slice(lower_bound_str);
// uppder_bound can be smaller than seek key, but the query itself ro.iterate_lower_bound = &lower_bound;
// should not crash either.
readoptionscopy.iterate_lower_bound = &lower_bound;
} }
auto cfh = column_families_[rand_column_families[0]]; ColumnFamilyHandle* const cfh = column_families_[rand_column_families[0]];
std::unique_ptr<Iterator> iter(db_->NewIterator(readoptionscopy, cfh)); assert(cfh);
std::vector<std::string> key_str; std::unique_ptr<Iterator> iter(db_->NewIterator(ro, cfh));
std::vector<std::string> key_strs;
if (thread->rand.OneIn(16)) { if (thread->rand.OneIn(16)) {
// Generate keys close to lower or upper bound of SST files. // Generate keys close to lower or upper bound of SST files.
key_str = GetWhiteBoxKeys(thread, db_, cfh, rand_keys.size()); key_strs = GetWhiteBoxKeys(thread, db_, cfh, rand_keys.size());
} }
if (key_str.empty()) { if (key_strs.empty()) {
// If key string is not geneerated using white block keys, // Use the random keys passed in.
// Use randomized key passe in.
for (int64_t rkey : rand_keys) { for (int64_t rkey : rand_keys) {
key_str.push_back(Key(rkey)); key_strs.push_back(Key(rkey));
} }
} }
std::string op_logs; std::string op_logs;
const size_t kOpLogsLimit = 10000; constexpr size_t kOpLogsLimit = 10000;
for (const std::string& skey : key_str) { for (const std::string& key_str : key_strs) {
if (op_logs.size() > kOpLogsLimit) { if (op_logs.size() > kOpLogsLimit) {
// Shouldn't take too much memory for the history log. Clear it. // Shouldn't take too much memory for the history log. Clear it.
op_logs = "(cleared...)\n"; op_logs = "(cleared...)\n";
} }
Slice key = skey; if (ro.iterate_upper_bound != nullptr && thread->rand.OneIn(2)) {
// With a 1/2 chance, change the upper bound.
if (readoptionscopy.iterate_upper_bound != nullptr && // It is possible that it is changed before first use, but there is no
thread->rand.OneIn(2)) {
// 1/2 chance, change the upper bound.
// It is possible that it is changed without first use, but there is no
// problem with that. // problem with that.
int64_t rand_upper_key = GenerateOneKey(thread, FLAGS_ops_per_thread); const int64_t rand_upper_key =
GenerateOneKey(thread, FLAGS_ops_per_thread);
upper_bound_str = Key(rand_upper_key); upper_bound_str = Key(rand_upper_key);
upper_bound = Slice(upper_bound_str); upper_bound = Slice(upper_bound_str);
} else if (readoptionscopy.iterate_lower_bound != nullptr && }
thread->rand.OneIn(4)) { if (ro.iterate_lower_bound != nullptr && thread->rand.OneIn(4)) {
// 1/4 chance, change the lower bound. // With a 1/4 chance, change the lower bound.
// It is possible that it is changed without first use, but there is no // It is possible that it is changed before first use, but there is no
// problem with that. // problem with that.
int64_t rand_lower_key = GenerateOneKey(thread, FLAGS_ops_per_thread); const int64_t rand_lower_key =
GenerateOneKey(thread, FLAGS_ops_per_thread);
lower_bound_str = Key(rand_lower_key); lower_bound_str = Key(rand_lower_key);
lower_bound = Slice(lower_bound_str); lower_bound = Slice(lower_bound_str);
} }
// Record some options to op_logs; // Record some options to op_logs
op_logs += "total_order_seek: "; op_logs += "total_order_seek: ";
op_logs += (readoptionscopy.total_order_seek ? "1 " : "0 "); op_logs += (ro.total_order_seek ? "1 " : "0 ");
op_logs += "auto_prefix_mode: "; op_logs += "auto_prefix_mode: ";
op_logs += (readoptionscopy.auto_prefix_mode ? "1 " : "0 "); op_logs += (ro.auto_prefix_mode ? "1 " : "0 ");
if (readoptionscopy.iterate_upper_bound != nullptr) { if (ro.iterate_upper_bound != nullptr) {
op_logs += "ub: " + upper_bound.ToString(true) + " "; op_logs += "ub: " + upper_bound.ToString(true) + " ";
} }
if (readoptionscopy.iterate_lower_bound != nullptr) { if (ro.iterate_lower_bound != nullptr) {
op_logs += "lb: " + lower_bound.ToString(true) + " "; op_logs += "lb: " + lower_bound.ToString(true) + " ";
} }
// Set up an iterator and does the same without bounds and with total // Set up an iterator, perform the same operations without bounds and with
// order seek and compare the results. This is to identify bugs related // total order seek, and compare the results. This is to identify bugs
// to bounds, prefix extractor or reseeking. Sometimes we are comparing // related to bounds, prefix extractor, or reseeking. Sometimes we are
// iterators with the same set-up, and it doesn't hurt to check them // comparing iterators with the same set-up, and it doesn't hurt to check
// to be equal. // them to be equal.
//
// This `ReadOptions` is for validation purposes. Ignore // This `ReadOptions` is for validation purposes. Ignore
// `FLAGS_rate_limit_user_ops` to avoid slowing any validation. // `FLAGS_rate_limit_user_ops` to avoid slowing any validation.
ReadOptions cmp_ro; ReadOptions cmp_ro;
cmp_ro.timestamp = readoptionscopy.timestamp; cmp_ro.timestamp = ro.timestamp;
cmp_ro.iter_start_ts = readoptionscopy.iter_start_ts; cmp_ro.iter_start_ts = ro.iter_start_ts;
cmp_ro.snapshot = snapshot; cmp_ro.snapshot = snapshot_guard.snapshot();
cmp_ro.total_order_seek = true; cmp_ro.total_order_seek = true;
ColumnFamilyHandle* cmp_cfh =
ColumnFamilyHandle* const cmp_cfh =
GetControlCfh(thread, rand_column_families[0]); GetControlCfh(thread, rand_column_families[0]);
assert(cmp_cfh);
std::unique_ptr<Iterator> cmp_iter(db_->NewIterator(cmp_ro, cmp_cfh)); std::unique_ptr<Iterator> cmp_iter(db_->NewIterator(cmp_ro, cmp_cfh));
bool diverged = false; bool diverged = false;
bool support_seek_first_or_last = expect_total_order; Slice key(key_str);
const bool support_seek_first_or_last = expect_total_order;
LastIterateOp last_op; LastIterateOp last_op;
if (support_seek_first_or_last && thread->rand.OneIn(100)) { if (support_seek_first_or_last && thread->rand.OneIn(100)) {
@ -1277,12 +1284,13 @@ Status StressTest::TestIterate(ThreadState* thread,
last_op = kLastOpSeek; last_op = kLastOpSeek;
op_logs += "S " + key.ToString(true) + " "; op_logs += "S " + key.ToString(true) + " ";
} }
VerifyIterator(thread, cmp_cfh, readoptionscopy, iter.get(), cmp_iter.get(),
last_op, key, op_logs, &diverged);
bool no_reverse = VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op,
key, op_logs, &diverged);
const bool no_reverse =
(FLAGS_memtablerep == "prefix_hash" && !expect_total_order); (FLAGS_memtablerep == "prefix_hash" && !expect_total_order);
for (uint64_t i = 0; i < FLAGS_num_iterations && iter->Valid(); i++) { for (uint64_t i = 0; i < FLAGS_num_iterations && iter->Valid(); ++i) {
if (no_reverse || thread->rand.OneIn(2)) { if (no_reverse || thread->rand.OneIn(2)) {
iter->Next(); iter->Next();
if (!diverged) { if (!diverged) {
@ -1298,25 +1306,19 @@ Status StressTest::TestIterate(ThreadState* thread,
} }
op_logs += "P"; op_logs += "P";
} }
last_op = kLastOpNextOrPrev; last_op = kLastOpNextOrPrev;
VerifyIterator(thread, cmp_cfh, readoptionscopy, iter.get(),
cmp_iter.get(), last_op, key, op_logs, &diverged); VerifyIterator(thread, cmp_cfh, ro, iter.get(), cmp_iter.get(), last_op,
key, op_logs, &diverged);
} }
if (s.ok()) {
thread->stats.AddIterations(1); thread->stats.AddIterations(1);
} else {
fprintf(stderr, "TestIterate error: %s\n", s.ToString().c_str());
thread->stats.AddErrors(1);
break;
}
op_logs += "; "; op_logs += "; ";
} }
db_->ReleaseSnapshot(snapshot); return Status::OK();
return s;
} }
#ifndef ROCKSDB_LITE #ifndef ROCKSDB_LITE
@ -1352,6 +1354,8 @@ void StressTest::VerifyIterator(ThreadState* thread,
Iterator* cmp_iter, LastIterateOp op, Iterator* cmp_iter, LastIterateOp op,
const Slice& seek_key, const Slice& seek_key,
const std::string& op_logs, bool* diverged) { const std::string& op_logs, bool* diverged) {
assert(diverged);
if (*diverged) { if (*diverged) {
return; return;
} }
@ -1481,6 +1485,21 @@ void StressTest::VerifyIterator(ThreadState* thread,
} }
} }
} }
if (!*diverged && iter->Valid()) {
const Slice value_base_slice = GetValueBaseSlice(iter->value());
const WideColumns expected_columns = GenerateExpectedWideColumns(
GetValueBase(value_base_slice), iter->value());
if (iter->columns() != expected_columns) {
fprintf(stderr, "Value and columns inconsistent for iterator: %s\n",
DebugString(iter->value(), iter->columns(), expected_columns)
.c_str());
*diverged = true;
}
}
if (*diverged) { if (*diverged) {
fprintf(stderr, "Control CF %s\n", cmp_cfh->GetName().c_str()); fprintf(stderr, "Control CF %s\n", cmp_cfh->GetName().c_str());
thread->stats.AddErrors(1); thread->stats.AddErrors(1);

@ -139,6 +139,8 @@ class StressTest {
return column_families_[column_family_id]; return column_families_[column_family_id];
} }
virtual Slice GetValueBaseSlice(Slice slice) { return slice; }
#ifndef ROCKSDB_LITE #ifndef ROCKSDB_LITE
// Generated a list of keys that close to boundaries of SST keys. // Generated a list of keys that close to boundaries of SST keys.
// If there isn't any SST file in the DB, return empty list. // If there isn't any SST file in the DB, return empty list.

Loading…
Cancel
Save